1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 6066 6067 6068 6069 6070 6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090 6091 6092 6093 6094 6095 6096 6097 6098 6099 6100 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 6119 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137 6138 6139 6140 6141 6142 6143 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187 6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 6269 6270 6271 6272 6273 6274 6275 6276 6277 6278 6279 6280 6281 6282 6283 6284 6285 6286 6287 6288 6289 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309 6310 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 6365 6366 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455 6456 6457 6458 6459 6460 6461 6462 6463 6464 6465 6466 6467 6468 6469 6470 6471 6472 6473 6474 6475 6476 6477 6478 6479 6480 6481 6482 6483 6484 6485 6486 6487 6488 6489 6490 6491 6492 6493 6494 6495 6496 6497 6498 6499 6500 6501 6502 6503 6504 6505 6506 6507 6508 6509 6510 6511 6512 6513 6514 6515 6516 6517 6518 6519 6520 6521 6522 6523 6524 6525 6526 6527 6528 6529 6530 6531 6532 6533 6534 6535 6536 6537 6538 6539 6540 6541 6542 6543 6544 6545 6546 6547 6548 6549 6550 6551 6552 6553 6554 6555 6556 6557 6558 6559 6560 6561 6562 6563 6564 6565 6566 6567 6568 6569 6570 6571 6572 6573 6574 6575 6576 6577 6578 6579 6580 6581 6582 6583 6584 6585 6586 6587 6588 6589 6590 6591 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 6663 6664 6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680 6681 6682 6683 6684 6685 6686 6687 6688 6689 6690 6691 6692 6693 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 6747 6748 6749 6750 6751 6752 6753 6754 6755 6756 6757 6758 6759 6760 6761 6762 6763 6764 6765 6766 6767 6768 6769 6770 6771 6772 6773 6774 6775 6776 6777 6778 6779 6780 6781 6782 6783 6784 6785 6786 6787 6788 6789 6790 6791 6792 6793 6794 6795 6796 6797 6798 6799 6800 6801 6802 6803 6804 6805 6806 6807 6808 6809 6810 6811 6812 6813 6814 6815 6816 6817 6818 6819 6820 6821 6822 6823 6824 6825 6826 6827 6828 6829 6830 6831 6832 6833 6834 6835 6836 6837 6838 6839 6840 6841 6842 6843 6844 6845 6846 6847 6848 6849 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 6864 6865 6866 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 6878 6879 6880 6881 6882 6883 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916 6917 6918 6919 6920 6921 6922 6923 6924 6925 6926 6927 6928 6929 6930 6931 6932 6933 6934 6935 6936 6937 6938 6939 6940 6941 6942 6943 6944 6945 6946 6947 6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 6964 6965 6966 6967 6968 6969 6970 6971 6972 6973 6974 6975 6976 6977 6978 6979 6980 6981 6982 6983 6984 6985 6986 6987 6988 6989 6990 6991 6992 6993 6994 6995 6996 6997 6998 6999 7000 7001 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014 7015 7016 7017 7018 7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 7029 7030 7031 7032
|
#!/usr/bin/env python3
"""
A system for processing Python via markup embeded in text.
"""
__project__ = "EmPy"
__program__ = "empy"
__module__ = "em"
__version__ = "4.2.1"
__url__ = "http://www.alcyone.com/software/empy/"
__author__ = "Erik Max Francis <max@alcyone.com>"
__contact__ = "software@alcyone.com"
__copyright__ = "Copyright (C) 2002-2026 Erik Max Francis"
__license__ = "BSD"
#
# imports
#
import codecs
import copy
import getopt
import os
import platform
import re
import sys
import unicodedata
#
# compatibility
#
# Initializes the following global names based on Python 2.x vs. 3.x:
#
# - major Detected major Python version
# - minor Detected minor Python version
# - compat List of Python backward-compatibility features applied
# - narrow Was Python built with narrow Unicode (UTF-16 natively)?
# - modules Is EmPy module support feasible?
# - nativeStr The native str type (str in Python 3.x; str in Python 2.x)
# - str (_unicode) The str type (str in Python 3.x; unicode in Python 2.x)
# - bytes (_str) The bytes type (bytes in Python 3.x; str in Python 2.x)
# - strType The str type (Python 3.x) or the bytes and str types (2.x)
# - chr The chr function (unichr in Python 2.x)
# - input The input function (raw_input in Python 2.x)
# - evalFunc The eval function
# - execFunc The exec function
# - binaryOpen The codecs.open function, or open (Python 3.14+)
# - BaseException The base exception class for all exceptions
# - FileNotFoundError FileNotFoundError (= IOError in Python < 3.3)
# - StringIO The StringIO class
# - isIdentifier Is this string a valid Python identifier?
# - uliteral Return a version-specific wide Unicode literal ('\U...')
# - toString Convert an arbitrary object to a Unicode-compatible string
# The major version of Python (2 or 3).
major = sys.version_info[0]
# The minor version of Python.
minor = sys.version_info[1]
# A list of Python backward-compatibility features which were applied.
compat = []
# The native str type/function.
nativeStr = str
# The eval function.
evalFunc = eval
if major == 2:
# We're using Python 2.x! Make sure there are Python 3.x-like names for
# the basic types and functions; hereafter, use str for unicode and bytes
# for str, respectively.
bytes = _str = str
str = _unicode = unicode
strType = (bytes, str)
chr = unichr
input = raw_input
# In Python 2.x, binaryOpen defers to codecs.open.
binaryOpen = codecs.open
# In Python 2.x, StringIO is contained in the cStringIO module.
try:
from cStringIO import StringIO
except ImportError:
# If cStringIO is not present for some reason, try to use the slower
# StringIO module.
from StringIO import StringIO
if minor < 5:
# Starting with Python 2.5, a new BaseException class serves as the
# base class of all exceptions; prior to that, it was just Exception.
# So create a name for it if necessary.
BaseException = Exception
compat.append('BaseException')
# Python 2.x did not have a FileNotFoundError.
FileNotFoundError = IOError
compat.append('FileNotFoundError')
# In Python 2.x, exec is a statement; in Python 3.x, it's a function. Make
# a new function that will simulate the Python 3.x form but work in Python
# 2.x.
def execFunc(code, globals=None, locals=None):
if globals is None:
exec("""exec code""")
else:
if locals is None:
exec("""exec code in globals""")
else:
exec("""exec code in globals, locals""")
def toString(value):
"""Convert value to a (Unicode) string."""
if isinstance(value, _unicode):
# Good to go.
return value
elif isinstance(value, _str):
# It's already a str (bytes), convert it to a unicode (str).
return _unicode(value)
else:
# In Python 2.x, __str__ returns a str, not a unicode. Convert the
# object to a str (bytes), then convert it to a unicode (str).
return _unicode(_str(value))
def isIdentifier(string, first=True):
"""Is this string a valid identifier? If first is true, make
sure the first character is a valid starting identifier character."""
for char in string:
if first:
if not (char.isalpha() or char == '_'):
return False
first = False
else:
if not (char.isalpha() or char.isdigit() or char == '_'):
return False
return True
def uliteral(i):
"""Return a wide Unicode string literal."""
return r"u'\U%08x'" % i
elif major >= 3:
# We're using Python 3.x! Add Python 2.x-like names for the basic types
# and functions. The name duplication is so that there will always be a
# definition of both str and types in the globals (as opposed to the
# builtins).
bytes = _str = bytes
str = _unicode = str
strType = str
chr = chr
input = input
# In Python 3.x, the module containing StringIO is io.
from io import StringIO
# codecs.open is deprecated starting with Python 3.14. Use open instead.
if minor >= 14:
binaryOpen = open
compat.append('!codecs.open')
else:
binaryOpen = codecs.open
# Python 3.x prior to 3.3 did not have a FileNotFoundError.
if minor < 3:
FileNotFoundError = IOError
compat.append('FileNotFoundError')
# The callable builtin was removed from Python 3.0 and reinstated in Python
# 3.2, but we need it.
if minor < 2:
def callable(object):
return getattr(object, '__call__', None) is not None
compat.append('callable')
# In Python 3.x, exec is a function, but attempting to reference it as such
# in Python 2.x generates an error. Since this needs to also compile in
# Python 2.x, defer the evaluation past the parsing phase.
try:
execFunc = evalFunc('exec')
except NameError:
execFunc = evalFunc('__builtins__.exec')
def toString(value):
"""Convert value to a (Unicode) string."""
return str(value)
def isIdentifier(string, first=True):
"""Is this string a valid identifier? If first is true, make
sure the first character is a valid starting identifier character."""
if first:
return string.isidentifier()
else:
return ('_' + string).isidentifier()
def uliteral(i):
"""Return a wide Unicode string literal."""
return r"'\U%08x'" % i
# Was this Python interpreter built with narrow Unicode? That is, does it use
# a UTF-16 encoding (with surrgoate pairs) vs. UTF-32 internally?
if hasattr(sys, 'maxunicode'):
narrow = sys.maxunicode < 0x10000
else:
narrow = len(evalFunc(uliteral(0x10000))) > 1
if narrow:
# Narrow Python builds will raise a ValueError when calling chr (unichr) on
# a code point value outside of the Basic Multilingual Plane (U+0000
# .. U+FFFF). See if it needs to be replaced.
_chr = chr
if major == 2:
if minor >= 6:
# Versions 2.6 and up can use this struct/decode trick.
compat.append('chr/decode')
def chr(i, _chr=_chr):
if i < 0x10000:
return _chr(i)
else:
import struct
try:
return struct.pack('i', i).decode('utf-32')
except UnicodeDecodeError:
raise ValueError("chr() arg not in range")
else:
# Earlier versions (2.5 and below) need to evaluate a literal.
compat.append('chr/uliteral')
def chr(i, _chr=_chr):
if i < 0x10000:
return _chr(i)
else:
try:
return evalFunc(uliteral(i))
except (SyntaxError, UnicodeDecodeError):
raise ValueError("chr() arg not in range")
compat.append('narrow')
# Is EmPy module support feasible on this interpreter?
modules = True
if (major, minor) < (3, 4):
# importlib architecture didn't exist before Python 3.4.
modules = False
if ('python_implementation' in platform.__dict__ and
platform.python_implementation() == 'IronPython'):
# importlib architecture doesn't work right in IronPython.
modules = False
if not modules:
compat.append('!modules')
#
# constants
#
# Character information.
UNDERSCORE_CHAR = '_'
DOT_CHAR = '.'
BACKSLASH_CHAR = '\\'
CARET_CHAR = '^'
STROKE_CHAR = '|'
OCTOTHORPE_CHAR = '#'
PLUS_CHAR = '+'
MINUS_CHAR = '-'
ASTERISK_CHAR = '*'
QUESTION_CHAR = '?'
EXCLAMATION_CHAR = '!'
PERCENT_CHAR = '%'
OPEN_PARENTHESIS_CHAR = '('
OPEN_BRACE_CHAR = '{'
OPEN_BRACKET_CHAR = '['
OPEN_ANGLE_CHAR = '<'
BACKQUOTE_CHAR = '`'
DOLLAR_CHAR = '$'
COLON_CHAR = ':'
WHITESPACE_CHARS = ' \t\v\f\r\n'
LITERAL_CHARS = '()[]{}<>\\\'\"?'
CARRIAGE_RETURN_CHAR = '\r'
NEWLINE_CHAR = '\n'
OPENING_CHARS = '([{<'
DUPLICATIVE_CHARS = '([{'
PHRASE_OPENING_CHARS = '(['
CLOSING_CHARS = ')]}>'
QUOTE_CHARS = '\'\"'
ENDING_CHAR_MAP = {'(': ')', '[': ']', '{': '}', '<': '>'}
# Environment variable names.
OPTIONS_ENV = 'EMPY_OPTIONS'
CONFIG_ENV = 'EMPY_CONFIG'
PREFIX_ENV = 'EMPY_PREFIX'
PSEUDO_ENV = 'EMPY_PSEUDO'
FLATTEN_ENV = 'EMPY_FLATTEN'
RAW_ERRORS_ENV = 'EMPY_RAW_ERRORS'
INTERACTIVE_ENV = 'EMPY_INTERACTIVE'
DELETE_ON_ERROR_ENV = 'EMPY_DELETE_ON_ERROR'
NO_PROXY_ENV = 'EMPY_NO_PROXY'
BUFFERING_ENV = 'EMPY_BUFFERING'
BINARY_ENV = 'EMPY_BINARY'
ENCODING_ENV = 'EMPY_ENCODING'
INPUT_ENCODING_ENV = 'EMPY_INPUT_ENCODING'
OUTPUT_ENCODING_ENV = 'EMPY_OUTPUT_ENCODING'
ERRORS_ENV = 'EMPY_ERRORS'
INPUT_ERRORS_ENV = 'EMPY_INPUT_ERRORS'
OUTPUT_ERRORS_ENV = 'EMPY_OUTPUT_ERRORS'
# A regular expression string suffix which will match singificators; prepend
# the interpreter prefix to make a full regular expression string. A
# successful match will yield six groups, arranged in two clusters of three.
# Each cluster contains the following three named groups in index order:
#
# - string: A `!` to represent a stringized significator, or blank
# - key: The significator key
# - value: The significator value; can be blank
#
# The value should be stripped before being tested. If it is blank and if the
# significator is not stringized, then the resulting significator value will be
# None.
#
# The first cluster (with group names ending in 2: string2, key2, value2)
# matches multiline significators; the second (with group names ending in 1:
# string1, key1, value1) matches singleline ones. Only one of these clusters
# will be set.
#
# The regular expression should be compiled with the following flags:
# re.MULTILINE|re.DOTALL|re.VERBOSE. To get a compiled regular expression
# object for a given config, call `config.significatorRe()`.
SIGNIFICATOR_RE_STRING_SUFFIX = r"""
# Flags: re.MULTILINE|re.DOTALL|re.VERBOSE
% # Opening `%`
(?: # Start non-grouping choice: Multiline
% # A second `%`
(?P<string2>!?) # An optional `!` [string2]
[ \t\v\f\r]* # Zero or more non-newline whitespace
(?P<key2>\S+) # One or more non-whitespace [key2]
\s* # Zero or more whitespace
(?P<value2>[^%]*) # Zero or more non-`%` characters [value2]
\s* # Zero or more whitespace
%% # Closing `%%`
| # Next non-grouping choice: Singleline
(?P<string1>!?) # An optional `!` [string1]
[ \t\v\f\r]* # Zero or more non-newline whitespace
(?P<key1>\S+) # One or more non-whitespace [key1]
[ \t\v\f\r]* # Zero or more non-newline whitespace
(?P<value1>[^\n]*) # Zero or more non-newline characters [value1]
[ \t\v\f\r]* # Zero or more non-newline whitespace
) # End choice
$ # End string
"""
#
# Error ...
#
class Error(Exception):
def __init__(self, *args, **kwargs):
# super does not work here in Python 2.4.
Exception.__init__(self, *args)
self.__dict__.update(kwargs)
class ConsistencyError(Error): pass
class ProxyError(ConsistencyError): pass
class DiversionError(Error): pass
class FilterError(Error): pass
class CoreError(Error): pass
class ExtensionError(Error): pass
class StackUnderflowError(Error, IndexError): pass
class UnknownEmojiError(Error, KeyError): pass
class StringError(Error): pass
class InvocationError(Error): pass
class ConfigurationError(Error): pass
class CompatibilityError(ConfigurationError): pass
class ConfigurationFileNotFoundError(ConfigurationError, FileNotFoundError): pass
class ParseError(Error): pass
class TransientParseError(ParseError): pass
#
# Flow ...
#
class Flow(Exception): pass
class ContinueFlow(Flow): pass
class BreakFlow(Flow): pass
#
# Root
#
class Root(object):
"""The root class of all EmPy class hierarchies. It defines a default
__repr__ method which will work appropriately whether or not the subclass
defines a __str__ method. Very old versions of Python 2.x won't
print the proper __str__ form, but so be it."""
def __repr__(self):
if (hasattr(self, '__str__') and hasattr(type(self), '__str__') and
getattr(type(self), '__str__') is not getattr(Root, '__str__')):
return '%s(%s)' % (self.__class__.__name__, toString(self))
else:
return '<%s @ 0x%x>' % (self.__class__.__name__, id(self))
#
# EmojiModuleInfo
#
class EmojiModuleInfo(Root):
"""The abstraction of an emoji module that may or may not be available
for usage."""
def __init__(self, name, attribute, format, capitalization, delimiters,
*extra):
self.name = name
self.attribute = attribute
self.format = format
self.capitalization = capitalization
self.delimiters = delimiters
self.extra = extra
self.ok = False
self.initialize()
def __str__(self):
return self.name
def initialize(self):
"""Attempt to initialize this emoji module. Set ok to true if
successful."""
try:
self.module = __import__(self.name)
self.function = getattr(self.module, self.attribute, None)
except ImportError:
self.module = self.function = None
if self.function is not None:
self.ok = True
def substitute(self, text):
"""Substitute text using this module. Return None if
unsuccessful."""
assert self.ok
wrapped = self.format % text
try:
result = self.function(wrapped)
except KeyError:
return None
if wrapped == result:
return None
else:
return result
Module = EmojiModuleInfo # DEPRECATED
#
# Configuration
#
class Configuration(Root):
"""The configuration encapsulates all the defaults and parameterized
behavior of an interpreter. When created, an interpreter is assigned
a configuration; multiple configurations can be shared between different
interpreters. To override the defaults of an interpreter, create a
Configuration instance and then modify its attributes."""
# Constants.
version = __version__
bangpath = '#!'
unknownScriptName = '<->'
significatorReStringSuffix = SIGNIFICATOR_RE_STRING_SUFFIX
fullBuffering = -1
noBuffering = 0
lineBuffering = 1
unwantedGlobalsKeys = [None, '__builtins__'] # None = pseudomodule name
unflattenableGlobalsKeys = ['globals']
priorityVariables = ['checkVariables']
ignoredConstructorArguments = []
emojiModuleInfos = [
# module name, attribute name, wrapping format, capitaliztion, delimiters
('emoji', 'emojize', ':%s:', 'lowercase', 'underscores'),
('emojis', 'encode', ':%s:', 'lowercase', 'underscores'),
('emoji_data_python', 'replace_colons', ':%s:', 'lowercase', 'underscores'),
('unicodedata', 'lookup', '%s', 'both', 'spaces'),
]
# Defaults.
defaultName = 'default'
defaultPrefix = '@'
defaultPseudomoduleName = 'empy'
defaultModuleExtension = '.em'
defaultRoot = '<root>'
defaultBuffering = 16384
defaultContextFormat = '%(name)s:%(line)d:%(column)d'
defaultNormalizationForm = 'NFKC'
defaultErrors = 'strict'
defaultConfigVariableName = '_'
defaultStdout = sys.stdout
defaultSuccessCode = 0
defaultFailureCode = 1
defaultUnknownCode = 2
defaultSkipCode = 111
defaultSignificatorDelimiters = ('__', '__')
defaultEmptySignificator = None
defaultAutoValidateIcons = True
defaultEmojiModuleNames = [
'emoji',
'emojis',
'emoji_data_python',
'unicodedata',
]
defaultNoEmojiModuleNames = [
'unicodedata',
]
# Statics.
baseException = BaseException
topLevelErrors = (ConfigurationError,)
fallThroughErrors = (SyntaxError,)
proxyWrapper = None
useContextFormatMethod = None
emojiModules = None
iconsSignature = None
verboseFile = sys.stderr
factory = None
ignorableErrorAttributes = [
# Python atttributes
'args', 'message', 'add_note', 'characters_written', 'with_traceback',
# Jython attributes
'addSuppressed', 'cause', 'class', 'equals', 'fillInStackTrace',
'getCause', 'getClass', 'getLocalizedMessage', 'getMessage',
'getStackTrace', 'getSuppressed', 'hashCode', 'initCause',
'localizedMessage', 'notify', 'notifyAll', 'printStackTrace',
'setStackTrace', 'stackTrace', 'suppressed', 'toString', 'wait',
]
tokens = None # list of token factories; intialized below
_initialized = False # overridden in instances
tag = None # overridden in instances
# Dictionaries.
controls = {
# C0 (ASCII, ISO 646, ECMA-6)
'NUL': (0x00, "null"),
'SOH': (0x01, "start of heading, transmission control one"),
'TC1': (0x01, "start of heading, transmission control one"),
'STX': (0x02, "start of text, transmission control two"),
'TC2': (0x02, "start of text, transmission control two"),
'ETX': (0x03, "end of text, transmission control three"),
'TC3': (0x03, "end of text, transmission control three"),
'EOT': (0x04, "end of transmission, transmission control four"),
'TC4': (0x04, "end of transmission, transmission control four"),
'ENQ': (0x05, "enquiry, transmission control five"),
'TC5': (0x05, "enquiry, transmission control five"),
'ACK': (0x06, "acknowledge, transmission control six"),
'TC6': (0x06, "acknowledge, transmission control six"),
'BEL': (0x07, "bell; alert"),
'BS': (0x08, "backspace, format effector zero"),
'FE0': (0x08, "backspace, format effector zero"),
'HT': (0x09, "horizontal tabulation, format effector one; tab"),
'FE1': (0x09, "horizontal tabulation, format effector one; tab"),
'LF': (0x0a, "linefeed, format effector two; newline (Unix)"),
'NL': (0x0a, "linefeed, format effector two; newline (Unix)"),
'FE2': (0x0a, "linefeed, format effector two; newline (Unix)"),
'VT': (0x0b, "line tabulation, format effector three; vertical tab"),
'LT': (0x0b, "line tabulation, format effector three; vertical tab"),
'FE3': (0x0b, "line tabulation, format effector three; vertical tab"),
'FF': (0x0c, "form feed, format effector four"),
'FE4': (0x0c, "form feed, format effector four"),
'CR': (0x0d, "carriage return, format effector five; enter"),
'FE5': (0x0d, "carriage return, format effector five; enter"),
'SO': (0x0e, "shift out, locking-shift one"),
'LS1': (0x0e, "shift out, locking-shift one"),
'SI': (0x0f, "shift in, locking-shirt zero"),
'LS0': (0x0f, "shift in, locking-shirt zero"),
'DLE': (0x10, "data link escape; transmission control seven"),
'TC7': (0x10, "data link escape; transmission control seven"),
'XON': (0x11, "device control one; xon"),
'DC1': (0x11, "device control one; xon"),
'DC2': (0x12, "device control two"),
'XOFF': (0x13, "device control three; xoff"),
'DC3': (0x13, "device control three; xoff"),
'STOP': (0x14, "device control four; stop"),
'DC4': (0x14, "device control four; stop"),
'NAK': (0x15, "negative acknowledge, transmission control eight"),
'TC8': (0x15, "negative acknowledge, transmission control eight"),
'SYN': (0x16, "synchronous idle, transmission control nine"),
'TC9': (0x16, "synchronous idle, transmission control nine"),
'ETB': (0x17, "end of transmission block, transmission control ten"),
'TC10': (0x17, "end of transmission block, transmission control ten"),
'CAN': (0x18, "cancel"),
'EM': (0x19, "end of medium"),
'SUB': (0x1a, "substitute; end of file (DOS)"),
'ESC': (0x1b, "escape"),
'FS': (0x1c, "file separator, information separator four"),
'IS4': (0x1c, "file separator, information separator four"),
'GS': (0x1d, "group separator, information separator three"),
'IS3': (0x1d, "group separator, information separator three"),
'RS': (0x1e, "record separator, information separator two"),
'IS2': (0x1e, "record separator, information separator two"),
'US': (0x1f, "unit separator, information separator one"),
'IS1': (0x1f, "unit separator, information separator one"),
'SP': (0x20, "space"),
'DEL': (0x7f, "delete"),
# C1 (ANSI X3.64, ISO 6429, ECMA-48)
'PAD': (0x80, "padding character"),
'HOP': (0x81, "high octet preset"),
'BPH': (0x82, "break permitted here"),
'NBH': (0x83, "no break here"),
'IND': (0x84, "index"),
'NEL': (0x85, "next line"),
'SSA': (0x86, "start of selected area"),
'ESA': (0x87, "end of selected area"),
'HTS': (0x88, "horizontal/character tabulation set"),
'HTJ': (0x89, "horizontal/character tabulation with justification"),
'VTS': (0x8a, "vertical/line tabulation set"),
'PLD': (0x8b, "partial line down/forward"),
'PLU': (0x8c, "partial line up/backward"),
'RI': (0x8d, "reverse index, reverse line feed"),
'SS2': (0x8e, "single shift two"),
'SS3': (0x8f, "single shift three"),
'DCS': (0x90, "device control string"),
'PU1': (0x91, "private use one"),
'PU2': (0x92, "private use two"),
'STS': (0x93, "set transmission state"),
'CHC': (0x94, "cancel character"),
'MW': (0x95, "message waiting"),
'SPA': (0x96, "start of protected/guarded area"),
'EPA': (0x97, "end of protected/guarded area"),
'SOS': (0x98, "start of string"),
'SGCI': (0x99, "single graphic character introducer, unassigned"),
'SCI': (0x9a, "single character introducer"),
'CSI': (0x9b, "control sequence introducer"),
'ST': (0x9c, "string terminator"),
'OSC': (0x9d, "operating system command"),
'PM': (0x9e, "privacy message"),
'APC': (0x9f, "application program command"),
# ISO 8859
'NBSP': (0xa0, "no-break space"),
'SHY': (0xad, "soft hyphen, discretionary hyphen"),
# Unicode, general punctuation
'NQSP': (0x2000, "en quad"),
'MQSP': (0x2001, "em quad; mutton quad"),
'ENSP': (0x2002, "en space; nut"),
'EMSP': (0x2003, "em space; mutton"),
'3MSP': (0x2004, "three-per-em space; thick space"),
'4MSP': (0x2005, "four-per-em space; mid space"),
'6MSP': (0x2006, "six-per-em space"),
'FSP': (0x2007, "figure space"),
'PSP': (0x2008, "punctuation space"),
'THSP': (0x2009, "thin space"),
'HSP': (0x200a, "hair space"),
'ZWSP': (0x200b, "zero width space"),
'ZWNJ': (0x200c, "zero width non-joiner"),
'ZWJ': (0x200d, "zero width joiner"),
'LRM': (0x200e, "left-to-right mark"),
'RLM': (0x200f, "right-to-left mark"),
'NBHY': (0x2011, "non-breaking hyphen"),
'LS': (0x2028, "line separator"),
'LSEP': (0x2028, "line separator"),
'PS': (0x2029, "paragraph separator"),
'PSEP': (0x2029, "paragraph separator"),
'LRE': (0x202a, "left-to-right encoding"),
'RLE': (0x202b, "right-to-left encoding"),
'PDF': (0x202c, "pop directional formatting"),
'LRO': (0x202d, "left-to-right override"),
'RLO': (0x202e, "right-to-left override"),
'NNBSP': (0x202f, "narrow no-break space"),
'MMSP': (0x205f, "medium mathematical space"),
'WJ': (0x2060, "word joiner"),
'FA': (0x2061, "function application (`f()`)"),
'IT': (0x2062, "invisible times (`x`)"),
'IS': (0x2063, "invisible separator (`,`)"),
'IP': (0x2064, "invisible plus (`+`)"),
'LRI': (0x2066, "left-to-right isolate"),
'RLI': (0x2067, "right-to-left isolate"),
'FSI': (0x2068, "first strong isolate"),
'PDI': (0x2069, "pop directional isolate"),
'ISS': (0x206a, "inhibit symmetric swapping"),
'ASS': (0x206b, "activate symmetric swapping"),
'IAFS': (0x206c, "inhibit arabic form shaping"),
'AAFS': (0x206d, "activate arabic form shaping"),
'NADS': (0x206e, "national digit shapes"),
'NODS': (0x206f, "nominal digit shapes"),
# Geometric shapes (some circles)
'WC': (0x25cb, "white circle"),
'DC': (0x25cc, "dotted circle"),
'CWVF': (0x25cc, "circle with vertical fill"),
'BE': (0x25cc, "bullseye"),
# Unicode, CJK symbols and punctuation
'IDSP': (0x3000, "ideographic space"),
'IIM': (0x3005, "ideographic iteration mark"),
'ICM': (0x3006, "ideographic closing mark"),
'INZ': (0x3007, "ideographic number zero"),
'VIIM': (0x303b, "vertical ideographic iteration mark"),
'MASU': (0x303c, "masu mark"),
'PAM': (0x303d, "part alternation mark"),
'IVI': (0x303e, "ideographic variation indicator"),
'IHFSP': (0x303f, "ideograhic half fill space"),
# Combining diacritical marks
'CGJ': (0x034f, "combining grapheme joiner"),
# Arabic leter forms
'ANS': (0x0600, "Arabic number sign"),
'ASN': (0x0601, "Arabic sign sanah"),
'AFM': (0x0602, "Arabic footnote marker"),
'ASF': (0x0603, "Arabic sign safha"),
'ASM': (0x0604, "Arabic sign samvat"),
'ANMA': (0x0605, "Arabic number mark above"),
'ALM': (0x061c, "Arabic letter mark"),
# Unicode, variation selectors
'VS1': (0xfe00, "variation selector 1"),
'VS2': (0xfe01, "variation selector 2"),
'VS3': (0xfe02, "variation selector 3"),
'VS4': (0xfe03, "variation selector 4"),
'VS5': (0xfe04, "variation selector 5"),
'VS6': (0xfe05, "variation selector 6"),
'VS7': (0xfe06, "variation selector 7"),
'VS8': (0xfe07, "variation selector 8"),
'VS9': (0xfe08, "variation selector 9"),
'VS10': (0xfe09, "variation selector 10"),
'VS11': (0xfe0a, "variation selector 11"),
'VS12': (0xfe0b, "variation selector 12"),
'VS13': (0xfe0c, "variation selector 13"),
'VS14': (0xfe0d, "variation selector 14"),
'VS15': (0xfe0e, "variation selector 15; text display"),
'TEXT': (0xfe0e, "variation selector 15; text display"),
'VS16': (0xfe0f, "variation selector 16; emoji display"),
'EMOJI': (0xfe0f, "variation selector 16; emoji display"),
# Unicode, Arabic presentation forms
'ZWNBSP': (0xfeff, "zero width no-break space; byte order mark"),
'BOM': (0xfeff, "zero width no-break space; byte order mark"),
# Unicode, specials
'IAA': (0xfff9, "interlinear annotation anchor"),
'IAS': (0xfffa, "interlinear annotation separator"),
'IAT': (0xfffb, "interlinear annotation terminator"),
'ORC': (0xfffc, "object replacement character"),
'RC': (0xfffd, "replacement character"),
# Egyptian hieroglyph format controls
'EHVJ': (0x13430, "Egyptian hieroglyph vertical joiner"),
'EHHJ': (0x13431, "Egyptian hieroglyph horizontal joiner"),
'EHITS': (0x13432, "Egyptian hieroglyph insert at top start"),
'EHIBS': (0x13433, "Egyptian hieroglyph insert at bottom start"),
'EHITE': (0x13434, "Egyptian hieroglyph insert at top end"),
'EHIBE': (0x13435, "Egyptian hieroglyph insert at bottom end"),
'EHOM': (0x13436, "Egyptian hieroglyph overlay middle"),
'EHBS': (0x13437, "Egyptian hieroglyph begin segment"),
'EHES': (0x13437, "Egyptian hieroglyph end segment"),
# Shorthand format controls
'SFLO': (0x1bca0, "shorthand format letter overlap"),
'SFCO': (0x1bca1, "shorthand format continuing overlap"),
'SFDS': (0x1bca2, "shorthand format down step"),
'SFUS': (0x1bca3, "shorthand format up step"),
# step
'TAG': (0xe0001, "language tag"),
}
diacritics = {
'`': (0x0300, "grave"),
"'": (0x0301, "acute"),
'^': (0x0302, "circumflex accent"),
'~': (0x0303, "tilde"),
'-': (0x0304, "macron"),
'_': (0x0305, "overline"),
'(': (0x0306, "breve"),
'.': (0x0307, "dot"),
':': (0x0308, "diaeresis"),
'?': (0x0309, "hook above"),
'o': (0x030a, "ring above"),
'"': (0x030b, "double acute accent"),
'v': (0x030c, "caron"),
's': (0x030d, "vertical line above"),
'S': (0x030e, "double vertical line above"),
'{': (0x030f, "double grave accent"),
'@': (0x0310, "candrabinu"),
')': (0x0311, "inverted breve"),
'1': (0x0312, "turned comma above"),
'2': (0x0313, "comma above"),
'3': (0x0314, "reversed comma above"),
'4': (0x0315, "comma above right"),
']': (0x0316, "grave accent below"),
'[': (0x0317, "acute accent below"),
'<': (0x0318, "left tack below"),
'>': (0x0319, "right tack below"),
'A': (0x031a, "left angle above"),
'h': (0x031b, "horn"),
'r': (0x031c, "left half ring below"),
'u': (0x031d, "up tack below"),
'd': (0x031e, "down tack below"),
'+': (0x031f, "plus sign below"),
'm': (0x0320, "minus sign below"),
'P': (0x0321, "palatalized hook below"),
'R': (0x0322, "retroflex hook below"),
'D': (0x0323, "dot below"),
'E': (0x0324, "diaeresis below"),
'O': (0x0325, "ring below"),
'c': (0x0326, "comma below"),
',': (0x0327, "cedilla"),
'K': (0x0328, "ogonek"),
'V': (0x0329, "vertical line below"),
'$': (0x032a, "bridge below"),
'W': (0x032b, "inverted double arch below"),
'H': (0x032c, "caron below"),
'C': (0x032d, "circumflex accent below"),
'B': (0x032e, "breve below"),
'N': (0x032f, "inverted breve below"),
'T': (0x0330, "tilde below"),
'M': (0x0331, "macron below"),
'l': (0x0332, "low line"),
'L': (0x0333, "double low line"),
'&': (0x0334, "tilde overlay"),
'!': (0x0335, "short stroke overlay"),
'|': (0x0336, "long stroke overlay"),
'%': (0x0337, "short solidays overlay"),
'/': (0x0338, "long solidus overlay"),
'g': (0x0339, "right half ring below"),
'*': (0x033a, "inverted bridge below"),
'#': (0x033b, "square below"),
'G': (0x033c, "seagull below"),
'x': (0x033d, "x above"),
';': (0x033e, "vertical tilde"),
'=': (0x033f, "double overline"),
}
icons = {
'!': ([0x2757, 0xfe0f], "exclamation mark"),
'#': (0x1f6d1, "octagonal sign"),
'$': (0x1f4b2, "heavy dollar sign"),
'%%': (0x1f3b4, "flower playing cards"),
'%': None,
'%c': ([0x2663, 0xfe0f], "club suit"),
'%d': ([0x2666, 0xfe0f], "diamond suit"),
'%e': (0x1f9e7, "red gift envelope"),
'%h': ([0x2665, 0xfe0f], "heart suit"),
'%j': (0x1f0cf, "joker"),
'%r': (0x1f004, "Mahjong red dragon"),
'%s': ([0x2660, 0xfe0f], "spade suit"),
'&!': ([0x1f396, 0xfe0f], "military medal"),
'&$': (0x1f3c6, "trophy"),
'&': None,
'&0': (0x1f3c5, "sports medal"),
'&1': (0x1f947, "first place medal"),
'&2': (0x1f948, "second place medal"),
'&3': (0x1f949, "third place medal"),
'*': ([0x2a, 0xfe0f], "asterisk"),
'+': (0x1f53a, "red triangle pointed up"),
',': None,
',+': (0x1f44d, "thumbs up"),
',-': (0x1f44e, "thumbs down"),
',a': ([0x261d, 0xfe0f], "point above"),
',d': (0x1f447, "point down"),
',f': (0x1f44a, "oncoming fist"),
',l': (0x1f448, "point left"),
',o': (0x1faf5, "point out"),
',r': (0x1f449, "point right"),
',s': (0x1f91d, "handshake"),
',u': (0x1f446, "point up"),
'-': (0x1f53b, "red triangle pointed down"),
'.': None,
'.d': ([0x2b07, 0xfe0f], "down arrow"),
'.l': ([0x2b05, 0xfe0f], "left arrow"),
'.r': ([0x27a1, 0xfe0f], "right arrow"),
'.u': ([0x2b06, 0xfe0f], "up arrow"),
'/': ([0x2714, 0xfe0f], "check mark"),
':$': (0x1f911, "money-mouth face"),
':': None,
':(': (0x1f61e, "disappointed face"),
':)': (0x1f600, "grinning face"),
':*': (0x1f618, "face blowing a kiss"),
':/': (0x1f60f, "smirking face"),
':0': (0x1f636, "face without mouth"),
':1': (0x1f914, "thinking face"),
':2': (0x1f92b, "shushing face"),
':3': (0x1f617, "kissing face"),
':4': (0x1f605, "grinning face with sweat"),
':5': (0x1f972, "smiling face with tear"),
':6': (0x1f602, "face with tears of joy"),
':7': (0x1f917, "smiling face with open hands"),
':8': (0x1f910, "zipper-mouth face"),
':9': (0x1f923, "rolling on the floor laughing"),
':<': None,
':<3': ([0x2764, 0xfe0f], "red heart"),
':D': (0x1f601, "beaming face with smiling eyes"),
':O': (0x1f62f, "hushed face"),
':P': (0x1f61b, "face with tongue"),
':S': (0x1fae1, "saluting face"),
':T': (0x1f62b, "tired face"),
':Y': (0x1f971, "yawning face"),
':Z': (0x1f634, "sleeping face"),
':[': (0x1f641, "frowning face"),
':\\': (0x1f615, "confused face"),
':]': ([0x263a, 0xfe0f], "smiling face"),
':|': (0x1f610, "neutral face"),
';': None,
';)': (0x1f609, "winking face"),
'<': (0x23ea, "black left-pointing double triangle"),
'=': None,
'=*': ([0x2716, 0xfe0f], "heavy multiplication sign"),
'=+': ([0x2795, 0xfe0f], "heavy plus sign"),
'=-': ([0x2796, 0xfe0f], "heavy minus sign"),
'=/': ([0x2797, 0xfe0f], "heavy division sign"),
'>': (0x23e9, "black right-pointing double triangle"),
'?': ([0x2753, 0xfe0f], "question mark"),
'B': None,
'B)': (0x1f60e, "smiling face with sunglasses"),
'E': (0x2130, "script capital E"),
'F': (0x2131, "script capital F"),
'M': (0x2133, "script capital M"),
'\"': None,
'\"(': (0x201c, "left double quotation mark"),
'\")': (0x201d, "right double quotation mark"),
'\"\"': (0x22, "quotation mark"),
'\'': None,
'\'(': (0x2018, "left single quotation mark"),
'\')': (0x2019, "right single quotation mark"),
'\'/': (0xb4, "acute accent"),
'\'\'': (0x27, "apostrophe"),
'\'\\': (0x60, "grave accent"),
'\\': ([0x274c, 0xfe0f], "cross mark"),
'^': ([0x26a0, 0xfe0f], "warning sign"),
'{!!': None,
'{!!}': ([0x203c, 0xfe0f], "double exclamation mark"),
'{!': None,
'{!?': None,
'{!?}': ([0x2049, 0xfe0f], "exclamation question mark"),
'{': None,
'{(': None,
'{()': None,
'{()}': (0x1f534, "red circle"),
'{[': None,
'{[]': None,
'{[]}': (0x1f7e5, "red square"),
'{{': None,
'{{}': None,
'{{}}': ([0x2b55, 0xfe0f], "hollow red circle"),
'|': (0x1f346, "aubergine"),
'~': ([0x3030, 0xfe0f], "wavy dash"),
}
emojis = {}
def __init__(self, **kwargs):
self._initialized = False
# Meta variables.
self._names = []
self._specs = {}
self._initials = {}
self._descriptions = {}
self._nones = {}
self._functions = {}
# Initialize.
self.initialize()
# Mark initialized.
self._initialized = True
# Update with any keyword arguments, if specified.
self.update(**kwargs)
def __setattr__(self, name, value):
self.set(name, value)
def __contains__(self, name):
return name in self.__dict__
def __bool__(self): return self._initialized # 3.x
def __nonzero__(self): return self._initialized # 2.x
def __iter__(self):
return iter(self._names)
def __str__(self):
results = []
for name in self._names:
results.append("%s=%r" % (name, self.get(name)))
return ', '.join(results)
# Initialization.
def initialize(self):
"""Setup the declarations and definitions for the defined
attributes."""
self.define('name', strType, self.defaultName, "The name of this configuration (optional)")
self.define('notes', None, None, "Notes for this configuration (optional)")
self.define('prefix', strType, self.defaultPrefix, "The prefix", none=True, env=PREFIX_ENV)
self.define('pseudomoduleName', strType, self.defaultPseudomoduleName, "The pseudomodule name", env=PSEUDO_ENV)
self.define('verbose', bool, False, "Verbose processing (for debugging)?")
self.define('rawErrors', bool, None, "Print Python stacktraces on error?", env=RAW_ERRORS_ENV)
self.define('verboseErrors', bool, True, "Show attributes in error messages?")
self.define('exitOnError', bool, True, "Exit after an error?")
self.define('ignoreErrors', bool, False, "Ignore errors?")
self.define('contextFormat', strType, self.defaultContextFormat, "Context format")
self.define('goInteractive', bool, None, "Go interactive after done processing?", env=INTERACTIVE_ENV)
self.define('deleteOnError', bool, None, "Delete output file on error?", env=DELETE_ON_ERROR_ENV)
self.define('doFlatten', bool, None, "Flatten pseudomodule members at start?", env=FLATTEN_ENV)
self.define('useProxy', bool, None, "Install a stdout proxy?", env=NO_PROXY_ENV, invert=True)
self.define('relativePath', bool, False, "Add EmPy script path to sys.path?")
self.define('buffering', int, self.defaultBuffering, "Specify buffering strategy for files:\n0 (none), 1 (line), -1 (full), or N", env=BUFFERING_ENV, func=self.setBuffering)
self.define('replaceNewlines', bool, False, "Replace newlines with spaces in expressions?")
self.define('ignoreBangpaths', bool, True, "Treat bangpaths as comments?")
self.define('noneSymbol', strType, None, "String to write when expanding None", none=True)
self.define('missingConfigIsError', bool, True, "Is a missing configuration file an error?")
self.define('pauseAtEnd', bool, False, "Prompt at the end of processing?")
self.define('startingLine', int, 1, "Line number to start with")
self.define('startingColumn', int, 1, "Column number to start with")
self.define('significatorDelimiters', tuple, self.defaultSignificatorDelimiters, "Significator variable delimiters")
self.define('emptySignificator', object, self.defaultEmptySignificator, "Value to use for empty significators", none=True)
self.define('autoValidateIcons', bool, self.defaultAutoValidateIcons, "Automatically validate icons before each use?")
self.define('emojiModuleNames', list, self.defaultEmojiModuleNames, "List of emoji modules to try to use\n", none=True)
self.define('emojiNotFoundIsError', bool, True, "Is an unknown emoji an error?")
self.define('useBinary', bool, False, "Open files as binary (Python 2.x Unicode)?", env=BINARY_ENV)
defaultEncoding = self.environment(ENCODING_ENV, self.getDefaultEncoding())
self.define('inputEncoding', strType, defaultEncoding, "Set input Unicode encoding", none=True, env=INPUT_ENCODING_ENV, helpFunction=lambda x: x == 'utf_8' and 'utf-8' or x)
self.define('outputEncoding', strType, defaultEncoding, "Set output Unicode encoding", none=True, env=OUTPUT_ENCODING_ENV, helpFunction=lambda x: x == 'utf_8' and 'utf-8' or x)
defaultErrors = self.environment(ERRORS_ENV, self.defaultErrors)
self.define('inputErrors', strType, defaultErrors, "Set input Unicode error handler", none=True, env=INPUT_ERRORS_ENV)
self.define('outputErrors', strType, defaultErrors, "Set output Unicode error handler", none=True, env=OUTPUT_ERRORS_ENV)
self.define('normalizationForm', strType, self.defaultNormalizationForm, "Specify Unicode normalization form", none=True)
self.define('autoPlayDiversions', bool, True, "Auto-play diversions on exit?")
self.define('expandUserConstructions', bool, True, "Expand ~ and ~user constructions")
self.define('configVariableName', strType, self.defaultConfigVariableName, "Configuration variable name while loading")
self.define('successCode', int, self.defaultSuccessCode, "Exit code to return on script success")
self.define('failureCode', int, self.defaultFailureCode, "Exit code to return on script failure")
self.define('unknownCode', int, self.defaultUnknownCode, "Exit code to return on bad configuration")
self.define('skipCode', int, self.defaultSkipCode, "Exit code to return on requirements failure (testing")
self.define('checkVariables', bool, True, "Check configuration variables on assignment?")
self.define('pathSeparator', strType, sys.platform.startswith('win') and ';' or ':', "Path separator for configuration file paths")
self.define('supportModules', bool, True, "Support EmPy modules?")
self.define('moduleExtension', strType, self.defaultModuleExtension, "Filename extension for EmPy modules")
self.define('moduleFinderIndex', int, 0, "Index of module finder in meta path")
self.define('enableImportOutput', bool, True, "Disable output during import?")
self.define('duplicativeFirsts', list, list(DUPLICATIVE_CHARS), "List of duplicative first characters")
self.define('openFunc', None, None, "The open function to use (None for automatic)")
# Redefine static configuration variables so they're in the help.
self.define('controls', dict, self.controls, "Controls dictionary")
self.define('diacritics', dict, self.diacritics, "Diacritics dictionary")
self.define('icons', dict, self.icons, "Icons dictionary")
self.define('emojis', dict, self.emojis, "Emojis dictionary")
# If the encoding or error handling has changed, enable binary
# implicitly, just as if it had been specified on the command line.
if (self.inputEncoding != defaultEncoding or
self.outputEncoding != defaultEncoding or
self.inputErrors != defaultErrors or
self.outputErrors != defaultErrors):
self.enableBinary(major, minor)
def isInitialized(self):
"""Is this configuration initialized and ready for use?"""
return self._initialized
def check(self, inputFilename, outputFilename):
"""Do a sanity check on the configuration settings."""
if self.prefix == '' or self.prefix == 'none' or self.prefix == 'None':
self.prefix = None
if self.buffering is None:
self.buffering = self.defaultBuffering
if isinstance(self.prefix, strType) and len(self.prefix) != 1:
raise ConfigurationError("prefix must be single-character string")
if not self.pseudomoduleName:
raise ConfigurationError("pseudomodule name must be non-empty string")
if self.deleteOnError and outputFilename is None:
raise ConfigurationError("-d only makes sense with -o, -a, -O or -A arguments")
if self.hasNoBuffering() and not self.hasBinary():
raise ConfigurationError("no buffering requires file open in binary mode; try adding -u option")
# Access.
def declare(self, name, specs, initial, description,
none=False, helpFunction=None):
"""Declare the configuration attribute."""
self._names.append(name)
self._specs[name] = specs
self._initials[name] = initial
self._descriptions[name] = description
self._nones[name] = none
self._functions[name] = helpFunction
def define(self, name, specs, value, description, none=False,
env=None, func=None, blank=None, invert=False, helpFunction=None):
"""Define a configuration attribute with the given name, type
specification, initial value, and description. If none is true, None
is a legal value. Also, allow an optional corresponding environment
variable, and, if present, an optional blank variable to set the
value to if the environment variable is defined but is blank. If
both env and func are present, call the function to set the
environment variable. Finally, if invert is true, invert the
(bool) environment variable. Additionally, if the type specification
is or contains toString, convert the value to a proper string."""
if isinstance(specs, tuple):
isString = str in specs or bytes in specs
else:
isString = str is specs or bytes is specs
if value is not None and specs is not None:
assert isinstance(value, specs), (value, specs)
if env is not None:
if func is not None:
if self.hasEnvironment(env):
value = func(self.environment(env, value))
elif isString:
value = self.environment(env, value, blank)
elif specs is bool:
value = self.hasEnvironment(env)
if invert:
value = not value
elif not isinstance(specs, tuple):
value = specs(self.environment(env, value, blank))
else:
assert False, "environment attribute type must be bool or str: `%s`" % name
if isString and value is not None:
value = toString(value)
self.declare(name, specs, value, description, none, helpFunction)
self.set(name, value)
def has(self, name):
"""Is this name a valid configuration attribute?"""
return name in self.__dict__ or name in self.__class__.__dict__
def get(self, name, default=None):
"""Get this (valid, existing) configuration attribute."""
return getattr(self, name, default)
def set(self, name, value):
"""Set the (valid) configuration attribute, checking its type
if necessary."""
if self._initialized and self.checkVariables:
if not name.startswith('_'):
# If this attribute is not already present, it's invalid.
if not self.has(name):
raise ConfigurationError("unknown configuration attribute: `%s`" % name)
# If there's a registered type for this attribute, check it.
specs = self._specs.get(name)
if specs is not None:
if value is None:
if not self._nones[name]:
raise ConfigurationError("configuration value cannot be None: `%s`: `%s`; `%s`" % (name, value, specs))
elif not isinstance(value, specs):
raise ConfigurationError("configuration value has invalid type: `%s`: `%s`; `%s`" % (name, value, specs))
self.__dict__[name] = value
def update(self, **kwargs):
"""Update the configuration by keyword arguments."""
for name, value in kwargs.items():
if not self.has(name):
raise ConfigurationError("unknown configuration attribute: `%s`" % name)
self.set(name, value)
def clone(self, deep=False):
"""Create a distinct copy of this configuration. Make it deep if
desired."""
if deep:
copyMethod = copy.deepcopy
else:
copyMethod = copy.copy
return copyMethod(self)
# Configuration file loading.
def run(self, statements):
"""Run some configuration variable assignment statements."""
locals = {self.configVariableName: self}
execFunc(statements, globals(), locals)
keys = list(locals.keys())
# Put the priority variables first.
for priority in self.priorityVariables:
if priority in locals:
keys.remove(priority)
keys.insert(0, priority)
for key in keys:
if not key.startswith('_') and key != self.configVariableName:
setattr(self, key, locals[key])
return True
def load(self, filename, required=None):
"""Update the configuration by loading from a resource
file. If required is true, raise an exception; if false,
ignore; if None, use the default in this configuration.
Return whether or not the load succeeded."""
if required is None:
required = self.missingConfigIsError
try:
file = self.open(filename, 'r')
except IOError:
if required:
raise ConfigurationFileNotFoundError("cannot open configuration file: %s" % filename, filename)
return False
try:
contents = file.read()
return self.run(contents)
finally:
file.close()
def path(self, path, required=None):
"""Attempt to load several resource paths, either as a
list of paths or a delimited string of paths. If required
is true, raise an exception; if false, ignore; if None,
use the default in this configuration."""
if isinstance(path, list):
filenames = path
else:
filenames = path.split(self.pathSeparator)
for filename in filenames:
self.load(filename, required)
# Environment.
def hasEnvironment(self, name):
"""Is the current environment variable defined in the environment?
The value does not matter."""
return name in os.environ
def environment(self, name, default=None, blank=None):
"""Get the value of the given environment variable, or default
if it is not set. If a variable is set to an empty value and
blank is non-None, return blank instead."""
if name in os.environ:
value = os.environ[name]
if not value and blank is not None:
return blank
else:
return value
else:
return default
# Convenience.
def recode(self, result, encoding=None):
"""Convert a lookup table entry into a string. A value can be a
string itself, an integer corresponding to a code point, or a
2-tuple, the first value of which is one of the above (the
second is a description). If the encoding is provided, use that
to convert bytes objects; otherwise, use the output encoding."""
assert result is not None
# First, if it's a tuple, then use the first element; the remaining
# elements are a description.
if isinstance(result, tuple):
result = result[0]
# Check the type of the value:
if isinstance(result, list):
# If it's a list, then it's a sequence of some the above.
# Turn them all into strings and then concatenate them.
fragments = []
for elem in result:
fragments.append(self.recode(elem))
result = ''.join(fragments)
elif isinstance(result, str):
# It's already a string, so do nothing.
pass
elif isinstance(result, bytes):
# If it's a bytes, decode it.
if encoding is None:
encoding = self.outputEncoding
result = result.decode(encoding)
elif isinstance(result, int):
# If it's an int, then it's a character code.
result = chr(result)
elif callable(result):
# If it's callable, then call it.
result = self.recode(result())
else:
# Otherwise, it's something convertible to a string.
result = str(result)
return result
def escaped(self, ord, prefix='\\'):
"""Write a valid Python string escape sequence for the given
character ordinal."""
if ord <= 0xff:
return '%sx%02x' % (prefix, ord)
elif ord <= 0xffff:
return '%su%04x' % (prefix, ord)
else:
return '%sU%08x' % (prefix, ord)
def hasDefaultPrefix(self):
"""Is this configuration's prefix the default or
non-existent?"""
return self.prefix is None or self.prefix == self.defaultPrefix
# Buffering.
def hasFullBuffering(self): return self.buffering <= self.fullBuffering
def hasNoBuffering(self): return self.buffering == self.noBuffering
def hasLineBuffering(self): return self.buffering == self.lineBuffering
def hasFixedBuffering(self):
return self.buffering is not None and self.buffering > self.lineBuffering
def setBuffering(self, name):
"""Set the buffering by name or value."""
if isinstance(name, int):
self.buffering = name
elif name == 'none':
self.buffering = self.noBuffering
elif name == 'line':
self.buffering = self.lineBuffering
elif name == 'full':
self.buffering = self.fullBuffering
elif name is None or name == 'default' or name == '':
self.buffering = self.defaultBuffering
else:
try:
self.buffering = int(name)
except ValueError:
raise ConfigurationError("invalid buffering name: `%s`" % name)
if self.buffering < self.fullBuffering:
self.buffering = self.fullBuffering
return self.buffering
# Token factories.
def createFactory(self, tokens=None):
"""Create a token factory and return it."""
if tokens is None:
tokens = self.tokens
return Factory(tokens)
def adjustFactory(self):
"""Adjust the factory to take into account a non-default
prefix."""
self.factory.adjust(self)
def getFactory(self, tokens=None, force=False):
"""Get a factory (creating one if one has not yet been
created). If force is true, always create a new one."""
if self.factory is None or force:
self.factory = self.createFactory(tokens)
self.adjustFactory()
return self.factory
def resetFactory(self):
"""Clear the current factory."""
self.factory = None
def createExtensionToken(self, first, name, last=None):
if last is None:
last = ENDING_CHAR_MAP.get(first[0], first[0])
newType = type('ExtensionToken:' + first[:1], (ExtensionToken,),
{'first': first, 'last': last, 'name': name})
return newType
# Binary/Unicode.
def hasBinary(self):
"""Is binary/Unicode file open support enabled?"""
return self.useBinary
def enableBinary(self, major=None, minor=None):
"""Enable binary/Unicode file open support. This is needed in
Python 2.x for Unicode support. If major/minor is present, only
enable it implicitly if this is in fact Python 2.x."""
if major is None or major == 2:
self.useBinary = True
def disableBinary(self):
"""Disable binary/Unicode support for this configuration."""
self.useBinary = False
# File I/O.
def isDefaultEncodingErrors(self, encoding=None, errors=None, asInput=True):
"""Are both of the encoding/errors combinations the default?
If either passed value is None the value in this configuration
is what is checked. Check for input if asInput is true;
otherwise check output."""
if encoding is None:
if asInput:
encoding = self.inputEncoding
else:
encoding = self.outputEncoding
if encoding is not None and encoding != self.getDefaultEncoding():
return False
if errors is None:
if asInput:
errors = self.inputErrors
else:
errors = self.outputErrors
if errors is not None and errors != self.defaultErrors:
return False
return True
def getDefaultEncoding(self, default='unknown'):
"""What is the default encoding?"""
try:
return sys.getdefaultencoding()
except AttributeError:
return default
def determineOpenFunc(self, filename,
mode=None, buffering=-1, encoding=None, errors=None):
"""Determine which openFunc to use if it has not already been
specified and return it."""
if self.openFunc is None:
if self.useBinary:
# Use binary mode, so call binaryOpen.
self.openFunc = binaryOpen
else:
if major >= 3:
# If it's Python 3.x, just use open.
self.openFunc = open
else:
# For Python 2.x, open doesn't take encoding and error
# handler arguments. Check to make sure non-default
# encodings and error handlers haven't been chosen, because
# we can't comply.
if not self.isDefaultEncodingErrors(encoding, errors):
raise ConfigurationError("cannot comply with non-default Unicode encoding/errors selected in Python 2.x; use -u option: `%s`/`%s`" % (encoding, errors))
self.openFunc = open
assert self.openFunc is not None
return self.openFunc
def isModeBinary(self, mode):
"""Does this mode represent binary mode?"""
return 'b' in mode
def open(self, filename,
mode=None, buffering=-1, encoding=None, errors=None,
expand=None):
"""Open a new file, handling whether or not binary
(Unicode) should be employed. Raise if the selection
cannot be complied with. Arguments:
- filename: The filename to open (required);
- mode: The file open mode, None for read;
- buffering: The buffering setting (int);
- encoding: The encoding to use, None for default;
- errors: The error handler to use, None for default;
- expand: Expand user constructions? (~ and ~user)"""
if expand is None:
expand = self.expandUserConstructions
if expand:
filename = os.path.expanduser(filename)
if mode is None:
# Default to read.
mode = 'r'
if self.useBinary and not self.isModeBinary(mode):
# Make it binary if it needs to be.
mode += 'b'
# Figure out the encoding and error handler.
if 'w' in mode or 'a' in mode:
if encoding is None:
encoding = self.outputEncoding
if errors is None:
errors = self.outputErrors
else:
if encoding is None:
encoding = self.inputEncoding
if errors is None:
errors = self.inputErrors
func = self.determineOpenFunc(
filename, mode, buffering, encoding, errors)
try:
return func(filename,
mode=mode,
buffering=buffering,
encoding=encoding,
errors=errors)
except TypeError:
# Some older versions of the open functions (e.g., Python 2.x's
# open) do not accept the Unicode encoding and errors arguments.
# Try again.
return func(filename,
mode=mode,
buffering=buffering)
def reconfigure(self, file,
buffering=-1, encoding=None, errors=None):
"""Reconfigure an existing file (e.g., sys.stdout)) with the same
arguments as open."""
try:
if buffering == 1:
file.reconfigure(encoding=encoding,
errors=errors,
line_buffering=True)
else:
file.reconfigure(encoding=encoding,
errors=errors)
except (AssertionError, AttributeError):
raise InvocationError("non-default Unicode output encoding/errors selected with %s; use -o/-a option instead: %s/%s" % (file.name, encoding, errors))
# Significators.
def significatorReString(self):
"""Return a string that can be compiled into a regular
expression representing a significator. If multi is true,
it will match multiline significators."""
return self.prefix + self.significatorReStringSuffix
def significatorRe(self, flags=re.MULTILINE|re.DOTALL,
baseFlags=re.VERBOSE):
"""Return a regular expression object with the given
flags that is suitable for parsing significators."""
return re.compile(self.significatorReString(), flags|baseFlags)
def significatorFor(self, key):
"""Return the significator name for this key."""
prefix, suffix = self.significatorDelimiters
return prefix + toString(key) + suffix
# Contexts.
def setContextFormat(self, rawFormat):
"""Set the context format, auto-detecting which
mechanism to use."""
useFormatMethod = None
format = rawFormat
if format.startswith('format:'):
useFormatMethod = True
format = format.split(':', 1)[1]
elif format.startswith('operator:'):
useFormatMethod = False
format = format.split(':', 1)[1]
elif format.startswith('variable:'):
useFormatMethod = False
format = format.split(':', 1)[1]
format = format.replace('$NAME', '%(name)s')
format = format.replace('$LINE', '%(line)d')
format = format.replace('$COLUMN', '%(column)d')
format = format.replace('$CHARS', '%(chars)d')
else:
useFormatMethod = '%' not in format
self.contextFormat = format
self.useContextFormatMethod = useFormatMethod
def renderContext(self, context):
"""Render the context as a string according to this
configuration."""
if self.useContextFormatMethod is None:
self.setContextFormat(self.contextFormat)
return context.render(self.contextFormat, self.useContextFormatMethod)
# Icons.
def calculateIconsSignature(self, icons=None):
"""Calculate a signature of the icons dict. If the value
changes, it's likely (but not certain) that the underlying
dict has changed. The signature will always differ from
None."""
if icons is None:
icons = self.icons
length = len(icons)
try:
# Include the size of the dictionary, if possible. If it's not
# available, that's okay.
sizeof = sys.getsizeof(icons)
except (AttributeError, TypeError):
sizeof = -1
return length, sizeof
def signIcons(self, icons=None):
"""Sign the icons dict."""
self.iconsSignature = self.calculateIconsSignature(icons)
return self.iconsSignature
def transmogrifyIcons(self, icons=None):
"""Process the icons and make sure any keys' prefixes are
backfilled with Nones. Call this method after modifying
icons."""
if icons is None:
icons = self.icons
additions = {}
for parent in icons.keys():
key = parent
while len(key) > 1:
key = key[:-1]
if key in icons:
value = icons[key]
if value is None:
continue
else:
raise ConfigurationError("icon `%s` makes icon `%s` inaccessible" % (key, parent))
else:
additions[key] = None
icons.update(additions)
return icons
def validateIcons(self, icons=None):
"""If the icons have not been transmogrified yet, do so
and store their signature for future reference."""
if icons is None:
icons = self.icons
if icons is None:
raise ConfigurationError("icons not configured")
if not self.autoValidateIcons:
return icons
if self.iconsSignature is None:
self.transmogrifyIcons(icons)
self.signIcons(icons)
return icons
# Emojis.
def initializeEmojiModules(self, moduleNames=None):
"""Initialize the emoji modules. If moduleNames is not
specified, check the defaults. Idempotent."""
if self.emojiModules is None:
okNames = []
# Use the config default if not specified.
if moduleNames is None:
moduleNames = self.emojiModuleNames
# If it's still blank, specify no modules.
if moduleNames is None:
moduleNames = []
# Create a map of module names for fast lookup. (This would be a
# set, but sets aren't available in early versions of Python 2.x.)
nameMap = {}
for moduleName in moduleNames:
nameMap[moduleName] = None
# Now iterate over each potential module.
self.emojiModules = {}
for info in self.emojiModuleInfos:
moduleName = info[0]
if moduleName in nameMap:
module = EmojiModuleInfo(*info)
if module.ok:
okNames.append(moduleName)
self.emojiModules[moduleName] = module
# Finally, replace the requested module names with the ones
# actually present.
self.emojiModuleNames = okNames
if not self.emojiModules and not self.emojis:
raise ConfigurationError("no emoji lookup methods available; install modules and set emoji modules or set emojis dictionary in configuration")
def substituteEmoji(self, text):
"""Substitute emojis from the provided string. Return
the resulting substitution or None."""
text = text.replace('\n', ' ')
self.initializeEmojiModules()
for moduleName in self.emojiModuleNames:
module = self.emojiModules[moduleName]
result = module.substitute(text)
if result is not None:
return result
# Exit codes and errors.
def isSuccessCode(self, code):
"""Does this exit code indicate success?"""
return code == self.successCode
def isExitError(self, error):
"""Is this error a SystemExit?"""
return isinstance(error, SystemExit)
def errorToExitCode(self, error):
"""Determine the exit code (can be a string) from the
error. If the error is None, then it is success."""
if error is None:
return self.successCode
isExit = self.isExitError(error)
if isExit:
if len(error.args) == 0:
return self.successCode
else:
# This can be a string which is okay.
return error.args[0]
else:
return self.failureCode
def isNotAnError(self, error):
"""Is this error None (no error) or does it indicate a
successful exit?"""
if error is None:
return True
else:
return self.isSuccessCode(self.errorToExitCode(error))
def formatError(self, error, prefix=None, suffix=None):
"""Format an error into a string for printing."""
parts = []
if prefix is not None:
parts.append(prefix)
parts.append(error.__class__.__name__)
if self.verboseErrors:
# Find the error's arguments. This needs special treatment due to
# spurious Java exceptions leaking through under Jython.
args = getattr(error, 'args', None)
if args is None:
# It might be an unwrapped Java exception. Check for
# getMessage.
method = getattr(error, 'getMessage', None)
if method is not None:
args = (method(),)
else:
# Otherwise, not sure what this is; treat it as having no
# arguments.
args = ()
# Check for arguments.
if len(args) > 0:
parts.append(": ")
parts.append(", ".join([toString(x) for x in args]))
# Check for keyword arguments.
pairs = []
for attrib in dir(error):
if (attrib not in self.ignorableErrorAttributes and
not attrib.startswith('_')):
value = getattr(error, attrib, None)
if value is not None:
pairs.append((attrib, value))
if pairs:
parts.append("; ")
pairs.sort()
kwargs = []
for key, value in pairs:
kwargs.append("%s=%s" % (key, value))
parts.append(', '.join(kwargs))
# Fold the arguments together.
if suffix is not None:
parts.append(suffix)
return ''.join(parts)
# Proxy.
@staticmethod
def proxy(object=sys):
"""Find the proxy for this Python interpreter session, or
None."""
return getattr(object, '_EmPy_proxy', None)
@staticmethod
def evocare(increment=0, ignore=True):
"""Try to call the EmPy special method on the proxy with the
given increment argument and return the resulting count value.
If the magic method is not present (no proxy installed) and
ignore is true (default), return None; otherwise, raise. Exodus
is four as one!"""
method = getattr(Configuration.proxy(), '_EmPy_evocare', None)
if method is not None:
try:
return method(increment)
except:
raise ProxyError("proxy evocare method should not raise")
else:
if ignore:
return None
else:
raise ProxyError("proxy evocare method not found")
def installProxy(self, output):
"""Install a proxy if necessary around the given output,
wrapped to be uncloseable. Return the wrapped object (not the
proxy)."""
assert output is not None
# Invoke the special method ...
count = self.evocare(+1)
proxy = self.proxy()
if count is not None:
# ... and if it's present, we've already created it.
new = False
else:
if proxy is None:
# If not, setup the proxy, and increment the reference count.
proxy = sys._EmPy_proxy = ProxyFile(output, self.proxyWrapper)
if self.useProxy:
# Replace sys.stdout with the proxy.
sys.stdout = proxy
self.evocare(+1)
else:
# ... but if the count showed no proxy but there is one,
# something went wrong.
raise ProxyError("proxy conflict; no proxy registered but one found")
new = True
if not self.useProxy:
output = UncloseableFile(output)
return output
def uninstallProxy(self):
"""Uninstall a proxy if necessary."""
# Try decrementing the reference count; if it hits zero, it will
# automatically remove itself and restore sys.stdout.
try:
proxy = self.proxy()
done = not self.evocare(-1)
if done:
del sys._EmPy_proxy
except AttributeError:
if self.proxy() is not None:
raise ProxyError("proxy lost")
def checkProxy(self, abandonedIsError=True):
"""Check whether a proxy is installed. Returns the
current reference count (positive means one is
installed), None (for no proxy installed), or 0 if the
proxy has been abandoned. Thus, true means a proxy is
installed, false means one isn't. If abandonIsError
is true, raise instead of returning 0 on abandonment."""
if not self.useProxy:
return False
count = self.evocare(0)
if count is not None:
if count == 0 and abandonedIsError:
raise ProxyError("stdout proxy abandoned; proxy present but with zero reference count: %r" % sys.stdout)
return count
else:
return None
# Meta path finder (for module support).
@staticmethod
def finder(object=sys):
"""Find the meta path finder for this Python interpreter
session, if there is one."""
return getattr(object, '_EmPy_finder', None)
def createFinder(self):
"""Create a new finder object, ready for installation."""
# Use the importlib architecture to set up an EmPy path finder.
import importlib
import importlib.abc
import importlib.util
#
# Loader
#
class Loader(importlib.abc.Loader):
def __init__(self, filename):
self.filename = filename
def create_module(self, spec):
return None # default
def exec_module(self, module):
interp = sys.stdout._EmPy_current()
assert interp
interp.import_(self.filename, module)
#
# Finder
#
class Finder(importlib.abc.PathEntryFinder):
_EmPy_next = 1
def __init__(self):
self._EmPy_tag = self._EmPy_next
self.__class__._EmPy_next += 1
def __str__(self):
return '%s [tag %d]' % (
self.__class__.__name__, self._EmPy_tag)
def find_spec(self, fullname, path, target=None):
# If the proxy is not installed, skip.
method = getattr(sys.stdout, '_EmPy_current', None)
if not method:
return None
interp = method()
# If there's no active interpreter, also skip.
if not interp:
return None
# If this interpreter has modules disable, also skip.
if not interp.config.supportModules:
return None
if not path:
path = sys.path
import os
name = fullname.replace('.', os.sep)
for dirname in path:
filename = (os.path.join(dirname, name) +
interp.config.moduleExtension)
if os.path.isfile(filename):
return importlib.util.spec_from_file_location(
fullname, filename, loader=Loader(filename))
return None
return Finder()
def installFinder(self, index=None, dryRun=False):
"""Install EmPy module support, if possible. Mark a flag the
first time this is called so it's only installed once, if ever.
Idempotent."""
if Configuration.finder():
# A finder had already been installed; abort.
return None
if not self.supportModules or not self.moduleExtension:
# This configuration does not want to support mnodules; abort.
return None
if not modules:
# Modules are not supported by the underlying interpreter; abort.
return None
if dryRun:
# This is a dry run for displaying details; abort.
return None
# Create the finder.
finder = self.createFinder()
# Register it with this configuration.
self.tag = finder._EmPy_tag
# And install it.
if index is None:
index = self.moduleFinderIndex
if index < 0:
sys.meta_path.append(finder)
else:
sys.meta_path.insert(index, finder)
# Register it with the sys module.
sys._EmPy_finder = finder
return finder
def uninstallFinder(self, tag=None):
"""Uninstall any module meta path finder for EmPy support,
either by tag (if not None), or all. Idempotent."""
if sys.meta_path is not None:
newMetaPath = []
for finder in sys.meta_path:
finderTag = getattr(finder, '_EmPy_tag', None)
if tag is None:
# Delete any custom finder.
if finderTag is not None:
continue
else:
# Delete only the matching finder.
if finderTag == tag:
continue
# If we're still here, we're keeping this finder.
newMetaPath.append(finder)
sys.meta_path = newMetaPath
# Debugging.
def printTraceback(self, file=sys.stderr):
import types, traceback
tb = None
depth = 0
while True:
try:
frame = sys._getframe(depth)
depth += 1
except ValueError:
break
tb = types.TracebackType(tb, frame, frame.f_lasti, frame.f_lineno)
traceback.print_tb(tb, file=file)
#
# Version
#
class Version(Root):
"""An enumerated type representing version detail levels."""
NONE, VERSION, INFO, BASIC, PYTHON, SYSTEM, PLATFORM, RELEASE, ALL = range(9)
DATA = BASIC
#
# Stack
#
class Stack(Root):
"""A simple stack that is implemented as a sequence."""
def __init__(self, seq=None):
if seq is None:
seq = []
self.data = seq
def __bool__(self): return len(self.data) != 0 # 3.x
def __nonzero__(self): return len(self.data) != 0 # 2.x
def __len__(self): return len(self.data)
def __getitem__(self, index): return self.data[-(index + 1)]
def __str__(self):
return '[%s]' % ', '.join([repr(x) for x in self.data])
def top(self):
"""Access the top element on the stack."""
if self.data:
return self.data[-1]
else:
raise StackUnderflowError("stack is empty for top")
def pop(self):
"""Pop the top element off the stack and return it."""
if self.data:
return self.data.pop()
else:
raise StackUnderflowError("stack is empty for pop")
def push(self, object):
"""Push an element onto the top of the stack."""
self.data.append(object)
def replace(self, object):
"""Replace the top element of the stack with another one."""
if self.data:
self.data[-1] = object
else:
raise StackUnderflowError("stack is empty for replace")
def filter(self, function):
"""Filter the elements of the stack through the function."""
self.data = list(filter(function, self.data))
def purge(self, function=None):
"""Purge the stack, calling an optional function on each element
first from top to bottom."""
if function is None:
self.data = []
else:
while self.data:
element = self.data.pop()
function(element)
def clone(self):
"""Create a duplicate of this stack."""
return self.__class__(self.data[:])
#
# File ...
#
class File(Root):
"""An abstract filelike object."""
def __enter__(self):
pass
def __exit__(self, *exc):
self.close()
def write(self, data): raise NotImplementedError
def writelines(self, lines): raise NotImplementedError
def flush(self): raise NotImplementedError
def close(self): raise NotImplementedError
class NullFile(File):
"""A simple class that supports all the file-like object methods
but simply does nothing at all."""
def write(self, data): pass
def writelines(self, lines): pass
def flush(self): pass
def close(self): pass
class DelegatingFile(File):
"""A simple class which wraps around a delegate file-like object
and lets everything through."""
def __init__(self, delegate):
self.delegate = delegate
def __repr__(self):
return '<%s : %r @ 0x%x>' % (
self.__class__.__name__,
self.delegate,
id(self))
def write(self, data):
self.delegate.write(data)
def writelines(self, lines):
self.delegate.writelines(lines)
def flush(self):
self.delegate.flush()
def close(self):
self.delegate.close()
self.unlink()
def unlink(self):
"""Unlink from the delegate."""
self.delegate = None
class UncloseableFile(DelegatingFile):
"""A delegating file class that lets through everything except
close calls, which it turns into a flush."""
def close(self):
self.flush()
class ProxyFile(File):
"""The proxy file object that is intended to take the place of
sys.stdout. The proxy can manage a stack of interpreters (and
their file object streams) it is writing to, and an underlying
bottom file object."""
def __init__(self, bottom, wrapper=None):
"""Create a new proxy file object with bottom as the
underlying stream. If wrapper is not None (and is an instance
of (a subclass of) a DelegatingFile), we should wrap and
unwrap the bottom file with it for protection."""
assert bottom is not None
self._EmPy_original = sys.stdout
self._EmPy_count = 0
self._EmPy_stack = Stack()
self._EmPy_bottom = bottom
self._EmPy_wrapper = wrapper
self._EmPy_wrap()
def __del__(self):
self.finalize()
def __str__(self):
return '%s [count %d, depth %d]'% (
self.__class__.__name__,
self._EmPy_count,
len(self._EmPy_stack))
def __repr__(self):
return '<%s [count %d, depth %d] : %r @ 0x%x>' % (
self.__class__.__name__,
self._EmPy_count,
len(self._EmPy_stack),
self._EmPy_bottom,
id(self))
def __getattr__(self, name):
return getattr(self._EmPy_top(), name)
# Finalizer.
def finalize(self): pass
# File methods.
def write(self, data):
self._EmPy_top().write(data)
def writelines(self, lines):
self._EmPy_top().writelines(lines)
def flush(self):
top = self._EmPy_top()
assert top is not None
top.flush()
def close(self):
"""Close the current file. If the current file is the bottom,
then flush it (don't close it) and dispose of it."""
top = self.top()
assert top is not None
if top is self._EmPy_bottom:
# If it's the bottom stream, flush it, don't close it, and mark
# this proxy done.
top.flush()
self._EmPy_bottom = None
else:
top.close()
# Stack management.
def _EmPy_enabled(self):
"""Is the top interpreter enabled?"""
if self._EmPy_stack:
return self._EmPy_stack.top().enabled
else:
return True
def _EmPy_current(self):
"""Get the current interpreter, or None."""
if self._EmPy_stack:
return self._EmPy_stack.top()
else:
return None
def _EmPy_top(self):
"""Get the current stream (not interpreter) to write to."""
if self._EmPy_stack:
return self._EmPy_stack.top().top()
else:
return self._EmPy_bottom
def _EmPy_push(self, interpreter):
self._EmPy_stack.push(interpreter)
def _EmPy_pop(self, interpreter):
top = self._EmPy_stack.pop()
if interpreter.error is not None and interpreter is not top:
# Only check if an error is not in progress; otherwise, when there
# are interpreters and subinterpreters and full dispatchers,
# interpreters can get popped out of order.
raise ConsistencyError("interpreter popped off of proxy stack out of order")
def _EmPy_clear(self, interpreter):
self._EmPy_stack.filter(lambda x, i=interpreter: x is not i)
# Bottom file protection.
def _EmPy_shouldWrap(self):
"""Should the bottom file be wrapped and unwrapped?"""
return self._EmPy_wrapper is not None
def _EmPy_wrap(self):
"""Wrap the bottom file in a delegate."""
if self._EmPy_shouldWrap():
assert issubclass(self._EmPy_wrapper, DelegatingFile), self._EmPy_wrapper
self._EmPy_bottom = self._EmPy_wrapper(self._EmPy_bottom)
def _EmPy_unwrap(self):
"""Unwrap the bottom file from the delegate, and unlink the
delegate."""
if self._EmPy_shouldWrap():
wrapped = self._EmPy_bottom
self._EmPy_bottom = wrapped.delegate
wrapped.unlink()
# Special.
def _EmPy_evocare(self, increment):
"""Do the EmPy magic: Increment or decrement the reference
count. Either way, return the current reference count.
Beware of Exodus!"""
if increment > 0:
self._EmPy_count += increment
elif increment < 0:
self._EmPy_count += increment # note: adding a negative number
if self._EmPy_count <= 0:
assert self._EmPy_original is not None
self._EmPy_unwrap()
sys.stdout = self._EmPy_original
return self._EmPy_count
#
# Diversion
#
class Diversion(File):
"""The representation of an active diversion. Diversions act as
(writable) file objects, and then can be recalled either as pure
strings or (readable) file objects."""
def __init__(self, name):
self.name = name
self.file = StringIO()
def __str__(self):
return self.name
# These methods define the writable file-like interface for the diversion.
def write(self, data):
self.file.write(data)
def writelines(self, lines):
for line in lines:
self.write(line)
def flush(self):
self.file.flush()
def close(self):
self.file.close()
# These methods are specific to diversions.
def preferFile(self):
"""Would this particular diversion prefer to be treated as a
file (true) or a string (false)? This allows future
optimization of diversions into actual files if they get
overly large."""
return False
def asString(self):
"""Return the diversion as a string."""
return self.file.getvalue()
def asFile(self):
"""Return the diversion as a file."""
return StringIO(self.file.getvalue())
def spool(self, sink, chunkSize=Configuration.defaultBuffering):
"""Spool the diversion to the given sink."""
if self.preferFile():
# Either write it a chunk at a time ...
input = self.asFile()
while True:
chunk = input.read(chunkSize)
if not chunk:
break
sink.write(chunk)
else:
# ... or write it all at once.
sink.write(self.asString())
#
# Stream
#
class Stream(File):
"""A wrapper around an (output) file object which supports
diversions and filtering."""
def __init__(self, interp, file, diversions):
assert file is not None
self.interp = interp
self.file = file
self.current = None
self.diversions = diversions
self.sink = file
self.done = False
def __repr__(self):
return '<%s : %r @ 0x%x>' % (
self.__class__.__name__,
self.file,
id(self))
def __getattr__(self, name):
return getattr(self.sink, name)
# File methods.
def write(self, data):
if self.current is None:
if self.interp.enabled:
self.sink.write(data)
else:
self.diversions[self.current].write(data)
def writelines(self, lines):
if self.interp.enabled:
for line in lines:
self.write(line)
def flush(self):
self.sink.flush()
def close(self):
self.flush()
if not self.done:
self.sink.close()
self.done = True
# Filters.
def count(self):
"""Count the number of filters."""
thisFilter = self.sink
result = 0
while thisFilter is not None and thisFilter is not self.file:
thisFilter = thisFilter.follow()
result += 1
return result
def last(self):
"""Find the last filter in the current filter chain, or None if
there are no filters installed."""
if self.sink is None:
return None
thisFilter, lastFilter = self.sink, None
while thisFilter is not None and thisFilter is not self.file:
lastFilter = thisFilter
thisFilter = thisFilter.follow()
return lastFilter
def install(self, filters=None):
"""Install a list of filters as a chain, replacing the current
chain."""
self.sink.flush()
if filters is None:
filters = []
if len(filters) == 0:
# An empty sequence means no filter.
self.sink = self.file
else:
# If there's more than one filter provided, chain them together.
lastFilter = None
for filter in filters:
if lastFilter is not None:
lastFilter.attach(filter)
lastFilter = filter
lastFilter.attach(self.file)
self.sink = filters[0]
def prepend(self, filter):
"""Attach a solitary filter (no sequences allowed here)
at the beginning of the current filter chain."""
self.sink.flush()
firstFilter = self.sink
if firstFilter is None:
# Just install it from scratch if there is no active filter.
self.install([filter])
else:
# Attach this filter to the current one, and set this as the main
# filter.
filter.attach(firstFilter)
self.sink = filter
def append(self, filter):
"""Attach a solitary filter (no sequences allowed here) at the
end of the current filter chain."""
self.sink.flush()
lastFilter = self.last()
if lastFilter is None:
# Just install it from scratch if there is no active filter.
self.install([filter])
else:
# Attach the last filter to this one, and this one to the file.
lastFilter.attach(filter)
filter.attach(self.file)
# Diversions.
def names(self):
"""Return a sorted sequence of diversion names."""
keys = list(self.diversions.keys())
keys.sort()
return keys
def has(self, name):
"""Does this stream have a diversion with the given name?"""
return name in self.diversions
def revert(self):
"""Reset any current diversions."""
self.current = None
def create(self, name):
"""Create a diversion if one does not already exist, but do not
divert to it yet. Return the diversion."""
if name is None:
raise DiversionError("diversion name must be non-None")
diversion = None
if not self.has(name):
diversion = Diversion(name)
self.diversions[name] = diversion
return diversion
def retrieve(self, name, *defaults):
"""Retrieve the given diversion. If an additional argument is
provided, return that instead of raising on a nonexistent
diversion."""
if name is None:
raise DiversionError("diversion name must be non-None")
if self.has(name):
return self.diversions[name]
else:
if defaults:
return defaults[0]
else:
raise DiversionError("nonexistent diversion: `%s`" % name)
def divert(self, name):
"""Start diverting."""
if name is None:
raise DiversionError("diversion name must be non-None")
self.create(name)
self.current = name
def undivert(self, name, dropAfterwards=False):
"""Undivert a particular diversion."""
if name is None:
raise DiversionError("diversion name must be non-None")
if self.has(name):
if self.interp.enabled:
diversion = self.diversions[name]
diversion.spool(self.sink)
if dropAfterwards:
self.drop(name)
else:
raise DiversionError("nonexistent diversion: `%s`" % name)
def drop(self, name):
"""Drop the specified diversion."""
if name is None:
raise DiversionError("diversion name must be non-None")
if self.has(name):
del self.diversions[name]
if self.current == name:
self.current = None
def undivertAll(self, dropAfterwards=True):
"""Undivert all pending diversions."""
if self.diversions:
self.revert() # revert before undiverting!
for name in self.names():
self.undivert(name, dropAfterwards)
def dropAll(self):
"""Eliminate all existing diversions."""
if self.diversions:
self.diversions.clear()
self.current = None
#
# Context
#
class Context(Root):
"""A simple interpreter context, storing only the current data.
It is not intended to be modified."""
format = Configuration.defaultContextFormat
def __init__(self, name, line, column, chars=0,
startingLine=None, startingColumn=None):
self.name = name
self.line = line
self.column = column
self.chars = chars
if startingLine is None:
startingLine = line
self.startingLine = startingLine
if startingColumn is None:
startingColumn = column
self.startingColumn = startingColumn
self.pendingLines = 0
self.pendingColumns = 0
self.pendingChars = 0
def __str__(self):
return self.render(self.format)
def reset(self):
"""Reset the context to the start."""
self.line = self.startingLine
self.column = self.startingColumn
self.chars = 0
def track(self, string, start, end):
"""Track the information for the substring in [start, end) in
the context and mark it pending."""
assert end >= start, (start, end)
if end > start:
length = end - start
self.pendingLines += string.count(NEWLINE_CHAR, start, end)
loc = string.rfind(NEWLINE_CHAR, start, end)
if loc == -1:
self.pendingColumns += length
else:
self.pendingColumns = self.startingColumn + end - loc - 1
self.pendingChars += length
def accumulate(self):
"""Accumulate the pending information and incorporate it into
the total."""
self.line += self.pendingLines
if self.pendingLines > 0:
self.column = self.pendingColumns # replaced, not added
else:
self.column += self.pendingColumns
self.chars += self.pendingChars
self.pendingLines = 0
self.pendingColumns = 0
self.pendingChars = 0
def save(self, strict=False):
"""Take a snapshot of the current context."""
if strict and self.pendingChars == 0:
raise ConsistencyError("context %s has pending chars" % toString(self))
return Context(self.name, self.line, self.column, self.chars)
def restore(self, other, strict=False):
"""Restore from another context."""
if strict and self.pendingChars == 0:
raise ConsistencyError("context %s has pending chars" % toString(self))
self.name = other.name
self.line = other.line
self.column = other.column
self.chars = other.chars
def render(self, format, useFormatMethod=None):
"""Render the context with the given format. If
useFormatMethod is true, use the format method; if false, use
the % operator. If useFormatMethod is None, try to trivially
auto-detect given the format."""
record = {
'name': self.name,
'line': self.line,
'column': self.column,
'chars': self.chars,
}
if useFormatMethod is None:
# If it contains a percent sign, it looks like it's not using the
# format method.
useFormatMethod = '%' not in format
if useFormatMethod:
return format.format(**record)
else:
return format % record
def identify(self):
return self.name, self.line, self.column, self.chars
#
# Token ...
#
class Token(Root):
"""An element resulting from parsing."""
def __init__(self, current):
self.current = current
def __str__(self):
return self.string()
def string(self):
raise NotImplementedError
def run(self, interp, locals):
raise NotImplementedError
class TextToken(Token):
"""A chunk of text not containing markups."""
def __init__(self, current, data):
super(TextToken, self).__init__(current)
self.data = data
def string(self):
return self.data
def run(self, interp, locals):
interp.write(self.data)
class ExpansionToken(Token):
"""A token that involves an expansion."""
last = None # subclasses should always define a first class attribute
def __init__(self, current, config, first):
super(ExpansionToken, self).__init__(current)
self.config = config
# Only record a local copy of first/last if it's ambiguous.
if self.first is None:
self.first = first
elif len(self.first) != 1:
first = first[:1]
self.first = first
self.last = ENDING_CHAR_MAP.get(first, first)
def scan(self, scanner):
pass
def run(self, interp, locals):
pass
class CommentToken(ExpansionToken):
"""The abstract base class for the comment tokens."""
pass
class LineCommentToken(CommentToken):
"""A line comment markup: ``@# ... NL``"""
first = OCTOTHORPE_CHAR
def scan(self, scanner):
loc = scanner.find(NEWLINE_CHAR)
if loc >= 0:
self.comment = scanner.chop(loc, 1)
else:
raise TransientParseError("comment expects newline")
def string(self):
return '%s%s%s\n' % (self.config.prefix, self.first, self.comment)
def run(self, interp, locals):
interp.invoke('preLineComment', comment=self.comment)
class InlineCommentToken(CommentToken):
"""An inline comment markup: ``@* ... *``"""
first = ASTERISK_CHAR
last = ASTERISK_CHAR
def scan(self, scanner):
# Find the run of starting characters to match.
self.count = 1
start = scanner.last(self.first)
self.count += start
scanner.advance(start)
# Then match them with the same number of closing characters.
loc = scanner.find(self.last * self.count)
if loc >= 0:
self.comment = scanner.chop(loc, self.count)
else:
raise TransientParseError("inline comment expects asterisk")
def string(self):
return '%s%s%s%s' % (
self.config.prefix,
self.first * self.count,
self.comment,
self.last * self.count)
def run(self, interp, locals):
interp.invoke('preInlineComment', comment=self.comment)
class LiteralToken(ExpansionToken):
"""The abstract base class of the literal tokens. If used as a
concrete token class, it will expand to the first character."""
def string(self):
return '%s%s' % (self.config.prefix, self.first)
def run(self, interp, locals):
interp.write(self.first)
class WhitespaceToken(LiteralToken):
"""A whitespace markup: ``@ WS``"""
first = '<whitespace>'
def string(self):
return '%s%s' % (self.config.prefix, self.first)
def run(self, interp, locals):
interp.invoke('preWhitespace', whitespace=self.first)
class SwitchToken(CommentToken):
"""Base class for the enable/disable tokens."""
def scan(self, scanner):
loc = scanner.find(NEWLINE_CHAR)
if loc >= 0:
self.comment = scanner.chop(loc, 1)
else:
raise TransientParseError("switch expects newline")
def string(self):
return '%s%s%s\n' % (self.config.prefix, self.first, self.comment)
class EnableToken(SwitchToken):
"""An enable output markup: ``@+ ... NL``"""
first = PLUS_CHAR
def run(self, interp, locals):
interp.invoke('preEnable', comment=self.comment)
interp.enable()
class DisableToken(SwitchToken):
"""An disable output markup: ``@- ... NL``"""
first = MINUS_CHAR
def run(self, interp, locals):
interp.invoke('preDisable', comment=self.comment)
interp.disable()
class PrefixToken(LiteralToken):
"""A prefix markup: ``@@``"""
first = None # prefix
def string(self):
return self.config.prefix * 2
def run(self, interp, locals):
if interp.invoke('prePrefix'):
return
interp.write(self.config.prefix)
class StringToken(LiteralToken):
"""A string token markup: ``@'...'``, ``@'''...'''``, ``@"..."``,
``@\"\"\"...\"\"\"``"""
first = list(QUOTE_CHARS)
def scan(self, scanner):
scanner.retreat()
assert scanner[0] == self.first, (scanner[0], self.first)
i = scanner.quote()
self.literal = scanner.chop(i)
def string(self):
return '%s%s' % (self.config.prefix, self.literal)
def run(self, interp, locals):
if interp.invoke('preString', string=self.literal):
return
interp.literal(self.literal, locals)
interp.invoke('postString')
class BackquoteToken(LiteralToken):
"""A backquote markup: ``@`...```"""
first = BACKQUOTE_CHAR
def scan(self, scanner):
# Find the run of starting characters to match.
self.count = 1
start = scanner.last(self.first)
self.count += start
scanner.advance(start)
# Then match them with the same number of closing characters.
loc = scanner.find(self.first * self.count)
if loc >= 0:
self.literal = scanner.chop(loc, self.count)
else:
raise TransientParseError("backquote markup expects %d backquotes" % self.count)
def string(self):
return '%s%s%s%s' % (
self.config.prefix,
self.first * self.count,
self.literal,
self.first * self.count)
def run(self, interp, locals):
if interp.invoke('preBackquote', literal=self.literal):
return
interp.write(self.literal)
interp.invoke('postBackquote', result=self.literal)
class SimpleToken(ExpansionToken):
"""An abstract base class for simple tokens which consist of nothing but
the prefix and expand to either the results a function call in the
interpreter globals (if args is not specified or is a tuple) or the value
of a variable name named function (if args is None)."""
def string(self):
return self.config.prefix + self.first
def run(self, interp, locals):
args = getattr(self, 'args', ())
if args is None:
result = interp.lookup(self.function)
else:
callable = interp.lookup(self.function)
result = callable(*args)
interp.write(result)
class ExecutionToken(ExpansionToken):
"""The abstract base class for execution tokens (expressions,
statements, controls, etc.)"""
pass
class ExpressionToken(ExecutionToken):
"""An expression markup: ``@(...)``"""
first = OPEN_PARENTHESIS_CHAR
last = ENDING_CHAR_MAP[first]
def scan(self, scanner):
start = 0
end = scanner.complex(self.first, self.last)
try:
except_ = scanner.next('$', start, end, True)
except ParseError:
except_ = end
# Build up a list of relevant indices (separating start/if/then/else
# clauses)
indices = [start - 1] # start, if, then, [if, then]..., [else]
while True:
try:
first = scanner.next('?', start, except_, True)
indices.append(first)
try:
last = scanner.next('!', first, except_, True)
indices.append(last)
except ParseError:
last = except_
break
except ParseError:
first = last = except_
break
start = last + 1
indices.append(except_)
code = scanner.chop(end, 1)
# Now build up the ifThenCodes pair chain.
self.ifThenCodes = []
prev = None
pair = None
for index in indices:
if prev is not None:
# pair can either be None or a 2-list, possibly with None as
# the second element.
if pair is None:
pair = [code[prev + 1:index], None]
else:
pair[1] = code[prev + 1:index]
self.ifThenCodes.append(pair)
pair = None
prev = index
# If there's a half-constructed pair not yet added to the chain, add it
# now.
if pair is not None:
self.ifThenCodes.append(pair)
self.exceptCode = code[except_ + 1:end]
def string(self):
fragments = []
sep = None
for ifCode, thenCode in self.ifThenCodes:
if sep:
fragments.append(sep)
fragments.append(ifCode)
if thenCode is not None:
fragments.append('?')
fragments.append(thenCode)
sep = '!'
if self.exceptCode:
fragments.append('$' + self.exceptCode)
return '%s%s%s%s' % (
self.config.prefix, self.first,
''.join(fragments), self.last)
def run(self, interp, locals):
# ifThenCodes is a list of 2-lists. A list of one sublist whose second
# subelement is None is a simple expression to evaluate; no
# if-then-else logic. A list of more than one sublist is a chained
# if-then-if-then-...-else clause with each sublist containing an if
# and a then subelement. If the second subelement is None, then the
# first element is not an if but an else.
if interp.invoke('preExpression', pairs=self.ifThenCodes,
except_=self.exceptCode, locals=locals):
return
result = None
try:
for ifCode, thenCode in self.ifThenCodes:
# An if or an else clause; evaluate it.
result = interp.evaluate(ifCode, locals)
if thenCode is None:
# If there's no then clause, then this is an isolated if or
# a final else, so we're done.
break
else:
if result:
# With a then clause with a true if clause, evaluate
# it.
result = interp.evaluate(thenCode, locals)
# If it's true, we're done.
if result:
break
else:
# Otherwise, go again. If there no remaining pairs,
# return None (no expansion).
result = None
except self.config.fallThroughErrors:
# Don't catch these errors; let them fall through.
raise
except:
if self.exceptCode:
result = interp.evaluate(self.exceptCode, locals)
else:
raise
interp.serialize(result)
interp.invoke('postExpression', result=result)
class SimpleExpressionToken(ExecutionToken):
"""A simple expression markup: ``@x``, ``@x.y``, ``@x(y)``, ``@x[y]``,
``@f{...}``"""
first = '<identifier>'
def top(self):
return self.subtokens[-1]
def new(self):
self.subtokens.append([])
def append(self, token):
self.subtokens[-1].append(token)
def scan(self, scanner, begin='{', end='}'):
i = scanner.simple()
self.code = self.first + scanner.chop(i)
# Now scan ahead for functional expressions.
self.subtokens = []
scanner.acquire()
try:
while True:
if not scanner:
raise TransientParseError("need more context for end of simple expression")
if scanner.read() != begin:
break
self.new()
count = None
while True:
peek = scanner.read()
if peek == begin:
# The start of an argument.
if count is None:
count = scanner.last(begin)
scanner.chop(count)
current = self.config.renderContext(scanner.context)
scanner.currents.replace(current)
elif peek == end and count is not None:
# The possible end of an argument.
if scanner.read(0, count) == end * count:
scanner.chop(count)
current = self.config.renderContext(scanner.context)
scanner.currents.replace(current)
break
token = scanner.one([end * count])
self.append(token)
finally:
scanner.release()
def string(self):
results = ['%s%s' % (self.config.prefix, self.code)]
for tokens in self.subtokens:
results.append('{%s}' % ''.join(map(str, tokens)))
return ''.join(results)
def run(self, interp, locals):
if interp.invoke('preSimple', code=self.code,
subtokens=self.subtokens, locals=locals):
return
result = None
if self.subtokens:
result = interp.functional(self.code, self.subtokens, locals)
else:
result = interp.evaluate(self.code, locals)
interp.serialize(result)
interp.invoke('postSimple', result=result)
class InPlaceToken(ExecutionToken):
"""An in-place markup: ``@$...$...$``"""
first = DOLLAR_CHAR
def scan(self, scanner):
i = scanner.next(self.first)
j = scanner.next(self.first, i + 1)
self.code = scanner.chop(i, j - i + 1)
def string(self):
return '%s%s%s%s%s' % (
self.config.prefix, self.first, self.code, self.first, self.first)
def run(self, interp, locals):
if interp.invoke('preInPlace', code=self.code, locals=locals):
return
result = None
interp.write("%s%s%s%s" % (
self.config.prefix, self.first,
self.code, self.first))
try:
result = interp.evaluate(self.code, locals)
interp.serialize(result)
finally:
interp.write(self.first)
interp.invoke('postInPlace', result=result)
class StatementToken(ExecutionToken):
"""A statement markup: ``@{...}``"""
first = OPEN_BRACE_CHAR
last = ENDING_CHAR_MAP[first]
def scan(self, scanner):
i = scanner.complex(self.first, self.last)
self.code = scanner.chop(i, 1)
def string(self):
return '%s%s%s%s' % (
self.config.prefix, self.first, self.code, self.last)
def run(self, interp, locals):
if interp.invoke('preStatement', code=self.code, locals=locals):
return
interp.execute(self.code, locals)
interp.invoke('postStatement')
class ControlToken(ExecutionToken):
"""A control markup: ``@[...]``"""
first = OPEN_BRACKET_CHAR
last = ENDING_CHAR_MAP[first]
class Chain(Root):
"""A chain of tokens with a starting token and the rest
that follow."""
def __init__(self, head, tail):
self.head = head
self.tail = tail
def __str__(self):
return '(%s, %s)' % (self.head, self.tail)
def getType(self):
return self.head.type
def hasType(self, name):
return self.head.type == name
PRIMARY = ['if', 'for', 'while', 'dowhile', 'try', 'with', 'match', 'defined', 'def']
SECONDARY = ['elif', 'else', 'except', 'finally', 'case']
TERTIARY = ['continue', 'break']
GREEDY = [
'if', 'elif', 'for', 'while', 'dowhile', 'with', 'match', 'defined', 'def', 'end'
]
CLEAN = ['try', 'else', 'except', 'finally', 'case', 'continue', 'break', 'end']
END = ['end']
ALLOWED = {
'if': ['elif', 'else'],
'for': ['else'],
'while': ['else'],
'dowhile': ['else'],
'try': ['except', 'else', 'finally'],
'with': [],
'match': ['case', 'else'],
'defined': ['else'],
'def': None,
'continue': None,
'break': None,
}
IN_RE = re.compile(r"\bin\b")
AS_RE = re.compile(r"\bas\b")
runPrefix = 'run_'
elseCase = '_'
def scan(self, scanner):
scanner.acquire()
try:
i = scanner.complex(self.first, self.last)
self.contents = scanner.chop(i, 1)
fields = self.contents.strip().split(None, 1)
if len(fields) > 1:
self.type, self.rest = fields
# If this is a "clean" control, remove anything that looks like
# a comment.
if self.type in self.CLEAN and '#' in self.rest:
self.rest = self.rest.split('#', 1)[0].strip()
else:
self.type = fields[0]
self.rest = None
self.subtokens = []
if self.type in self.GREEDY and self.rest is None:
raise ParseError("control `%s` needs arguments" % self.type)
if self.type in self.PRIMARY:
self.subscan(scanner, self.type)
self.kind = 'primary'
elif self.type in self.SECONDARY:
self.kind = 'secondary'
elif self.type in self.TERTIARY:
self.kind = 'tertiary'
elif self.type in self.END:
self.kind = 'end'
else:
raise ParseError("unknown control markup: `%s`" % self.type)
finally:
scanner.release()
def subscan(self, scanner, primary):
"""Do a subscan for contained tokens."""
while True:
token = scanner.one()
if token is None:
raise TransientParseError("control `%s` needs more tokens" % primary)
if isinstance(token, ControlToken) and token.type in self.END:
if token.rest != primary:
raise ParseError("control must end with `end %s`" % primary)
break
self.subtokens.append(token)
def build(self, allowed):
"""Process the list of subtokens and divide it up into a list
of chains, returning that list. Allowed specifies a list of
the only secondary markup types which are allowed."""
result = []
current = []
result.append(self.Chain(self, current))
for subtoken in self.subtokens:
if (isinstance(subtoken, ControlToken) and
subtoken.kind == 'secondary'):
if subtoken.type not in allowed:
raise ParseError("control unexpected secondary: `%s`" % subtoken.type)
current = []
result.append(self.Chain(subtoken, current))
else:
current.append(subtoken)
return result
def subrun(self, tokens, interp, locals):
"""Execute a list of tokens."""
interp.runSeveral(tokens, locals)
def substring(self):
return ''.join([toString(x) for x in self.subtokens])
def string(self):
if self.kind == 'primary':
return ('%s[%s]%s%s[end %s]' %
(self.config.prefix, self.contents, self.substring(),
self.config.prefix, self.type))
else:
return '%s[%s]' % (self.config.prefix, self.contents)
def run(self, interp, locals):
if interp.invoke('preControl', type=self.type,
rest=self.rest, locals=locals):
return
try:
allowed = self.ALLOWED[self.type]
except KeyError:
raise ParseError("control `%s` cannot be at this level" % self.type)
if allowed is not None:
chains = self.build(allowed)
else:
chains = None
try:
method = getattr(self, self.runPrefix + self.type)
except AttributeError:
raise ConsistencyError("unknown handler for control type `%s`" % self.type)
method(chains, interp, locals)
interp.invoke('postControl')
# Type handlers.
def run_if(self, chains, interp, locals):
# @[if E]...@[end if]
# @[if E]...@[else]...@[end if]
# @[if E]...@[elif E2]...@[end if]
# @[if E]...@[elif E2]...@[else]...@[end if]
# @[if E]...@[elif E2]... ... @[else]...@[end if]
if chains[-1].hasType('else'):
elseChain = chains.pop()
else:
elseChain = None
first = True
for chain in chains:
if first:
if not chain.hasType('if'):
raise ParseError("control `if` expected: `%s`" % chain.head.type)
first = False
else:
if not chain.hasType('elif'):
raise ParseError("control `elif` expected: `%s`" % chain.head.type)
if interp.evaluate(chain.head.rest, locals):
self.subrun(chain.tail, interp, locals)
break
else:
if elseChain:
self.subrun(elseChain.tail, interp, locals)
def run_for(self, chains, interp, locals):
# @[for N in E]...@[end for]
# @[for N in E]...@[else]...@[end for]
sides = self.IN_RE.split(self.rest, 1)
if len(sides) != 2:
raise ParseError("control `for` expected `for x in ...`")
iterator, iterableCode = sides
forChain = chains[0]
assert forChain.hasType('for'), forChain.getType()
elseChain = None
if chains[-1].hasType('else'):
elseChain = chains.pop()
if len(chains) != 1:
raise ParseError("control `for` expects at most one `else`")
iterable = interp.evaluate(iterableCode, locals)
for element in iterable:
try:
interp.assign(iterator, element, locals)
self.subrun(forChain.tail, interp, locals)
except ContinueFlow:
continue
except BreakFlow:
break
else:
if elseChain:
self.subrun(elseChain.tail, interp, locals)
def run_while(self, chains, interp, locals):
# @[while E]...@[end while]
# @[while E]...@[else]...@[end while]
testCode = self.rest
whileChain = chains[0]
assert whileChain.hasType('while'), whileChain.getType()
elseChain = None
if chains[-1].hasType('else'):
elseChain = chains.pop()
if len(chains) != 1:
raise ParseError("control `while` expects at most one `else`")
exitedNormally = False
while True:
try:
if not interp.evaluate(testCode, locals):
exitedNormally = True
break
self.subrun(whileChain.tail, interp, locals)
except ContinueFlow:
continue
except BreakFlow:
break
if exitedNormally and elseChain:
self.subrun(elseChain.tail, interp, locals)
def run_dowhile(self, chains, interp, locals):
# @[dowhile E]...@[end dowhile]
# @[dowhile E]...@[else]...@[end dowhile]
testCode = self.rest
doWhileChain = chains[0]
assert doWhileChain.hasType('dowhile'), doWhileChain.getType()
elseChain = None
if chains[-1].hasType('else'):
elseChain = chains.pop()
if len(chains) != 1:
raise ParseError("control `dowhile` expects at most one `else`")
exitedNormally = False
while True:
try:
self.subrun(doWhileChain.tail, interp, locals)
if not interp.evaluate(testCode, locals):
exitedNormally = True
break
except ContinueFlow:
continue
except BreakFlow:
break
if exitedNormally and elseChain:
self.subrun(elseChain.tail, interp, locals)
def run_try(self, chains, interp, locals):
# @[try]...@[except]...@[end try]
# @[try]...@[except C]...@[end try]
# @[try]...@[except C as N]...@[end try]
# @[try]...@[except C, N]...@[end try]
# @[try]...@[except (C1, C2, ...) as N]...@[end try]
# @[try]...@[except C1]...@[except C2]...@[end try]
# @[try]...@[except C1]...@[except C2]... ... @[end try]
# @[try]...@[finally]...@[end try]
# @[try]...@[except ...]...@[finally]...@[end try]
# @[try]...@[except ...]...@[else]...@[end try]
# @[try]...@[except ...]...@[else]...@[finally]...@[end try]
if len(chains) == 1:
raise ParseError("control `try` expects at least one `except` or `finally`")
tryChain = None
exceptChains = []
elseChain = None
finallyChain = None
# Process the chains and verify them.
for chain in chains:
if chain.hasType('try'):
if exceptChains or elseChain or finallyChain:
raise ParseError("control `try` must be first")
tryChain = chain
elif chain.hasType('except'):
if elseChain:
raise ParseError("control `try` cannot have `except` following `else`")
elif finallyChain:
raise ParseError("control `try` cannot have `except` following `finally`")
exceptChains.append(chain)
elif chain.hasType('else'):
if not exceptChains:
raise ParseError("control `try` cannot have `else` with no preceding `except`")
elif elseChain:
raise ParseError("control `try` cannot have more than one `else`")
elif finallyChain:
raise ParseError("control `try` cannot have `else` following `finally`")
elseChain = chain
elif chain.hasType('finally'):
if finallyChain:
raise ParseError("control `try` cannot have more than one `finally`")
finallyChain = chain
else:
assert False, chain
try:
try:
self.subrun(tryChain.tail, interp, locals)
if elseChain:
self.subrun(elseChain.tail, interp, locals)
except Flow:
raise
except self.config.baseException:
type, error, traceback = sys.exc_info()
for chain in exceptChains:
exception, variable = interp.clause(chain.head.rest)
if isinstance(error, exception):
if variable is not None:
interp.assign(variable, error, locals)
self.subrun(chain.tail, interp, locals)
break
else:
raise
finally:
if finallyChain:
self.subrun(finallyChain.tail, interp, locals)
def run_with(self, chains, interp, locals):
# @[with E as N]...@[end with]
# @[with N]...@[end with]
# @[with E]...@[end with]
fields = self.AS_RE.split(self.rest, 1)
if len(fields) == 1:
expression = fields[0].strip()
variable = None
else: # len(fields) == 2
expression, variable = fields
variable = variable.strip()
manager = interp.evaluate(expression, locals)
resource = manager
if variable is not None:
interp.assign(variable, resource, locals)
if len(chains) != 1:
raise ParseError("control `with` must be simple")
withChain = chains[0]
assert withChain.hasType('with'), withChain.getType()
# As per Python's compound statement reference documentation.
enter = manager.__enter__
exit = manager.__exit__
resource = enter()
oops = False
try:
try:
self.subrun(withChain.tail, interp, locals)
except:
oops = True
if not exit(*sys.exc_info()):
raise
finally:
if not oops:
exit(None, None, None)
def run_match(self, chains, interp, locals):
# @[match E]...@[case C]...@[end match]
# @[match E]...@[case C1]...@[case C2]...@[end match]
# @[match E]...@[case C1]...@[case C2]...@[else]...@[end match]
expression = self.rest.strip()
if not expression:
raise ParseError("control `match` expects an expression")
matchChain = chains[0]
self.subrun(matchChain.tail, interp, locals)
assert matchChain.hasType('match'), matchChain.getType()
if chains[-1].hasType('else'):
elseChain = chains[-1]
chains = chains[:-1]
else:
elseChain = None
if len(chains) + bool(elseChain) <= 1:
raise ParseError("control `match` expects at least one `case` or `else`")
cases = []
for chain in chains[1:]:
if not chain.hasType('case'):
raise ParseError("contol `match` expects only `case` and at most one `else`")
cases.append(Core.Case(chain.head.rest, chain.tail))
if elseChain:
cases.append(Core.Case(self.elseCase, elseChain.tail))
interp.core.match(expression, cases, locals)
def run_defined(self, chains, interp, locals):
# @[defined N]...@[end defined]
# @[defined N]...@[else]...@[end defined]
testName = self.rest.strip()
if not testName:
raise ParseError("control `defined` expects an argument")
definedChain = chains[0]
assert definedChain.hasType('defined'), definedChain.getType()
if len(chains) > 3:
raise ParseError("control `defined` expects at most one `else`")
if len(chains) == 2:
elseChain = chains[1]
else:
elseChain = None
if interp.defined(testName, locals):
self.subrun(definedChain.tail, interp, locals)
elif elseChain:
self.subrun(elseChain.tail, interp, locals)
def run_def(self, chains, interp, locals):
# @[def F(...)]...@[end def]
assert chains is None, chains
signature = self.rest
definition = self.substring()
interp.core.define(signature, definition, locals)
def run_continue(self, chains, interp, locals):
# @[continue]
assert chains is None, chains
raise ContinueFlow("control `continue` encountered without loop control")
def run_break(self, chains, interp, locals):
# @[break]
assert chains is None, chains
raise BreakFlow("control `break` encountered without loop control")
class CodedToken(ExpansionToken):
"""The abstract base class for a token that supports codings."""
def recode(self, result):
return self.config.recode(result)
class EscapeToken(CodedToken):
"""An escape markup: ``@\\...``"""
first = BACKSLASH_CHAR
def scan(self, scanner):
try:
code = scanner.chop(1)
result = None
if code in LITERAL_CHARS: # literals
result = code
elif code == '0': # NUL, null
result = 0x00
elif code == 'a': # BEL, bell
result = 0x07
elif code == 'b': # BS, backspace
result = 0x08
elif code == 'B': # freeform binary code
binaryCode = scanner.enclosure()
result = int(binaryCode, 2)
elif code == 'd': # three-digit decimal code
decimalCode = scanner.chop(3)
result = int(decimalCode, 10)
elif code == 'D': # freeform decimal code
decimalCode = scanner.enclosure()
result = int(decimalCode, 10)
elif code == 'e': # ESC, escape
result = 0x1b
elif code == 'f': # FF, form feed
result = 0x0c
elif code == 'h': # DEL, delete
result = 0x7f
elif code == 'k': # ACK, acknowledge
result = 0x06
elif code == 'K': # NAK, negative acknowledge
result = 0x15
elif code == 'n': # LF, linefeed; newline
result = 0x0a
elif code == 'N': # Unicode character name
if unicodedata is None:
raise ConfigurationError("unicodedata module not available; cannot use @\\N{...} markup")
name = scanner.enclosure()
try:
name = name.replace('\n', ' ')
result = unicodedata.lookup(name)
except AttributeError:
raise ConfigurationError("unicodedata.lookup function not available; cannot use @\\N{...} markup")
except KeyError:
raise ParseError("unknown Unicode character name: `%s`" % name)
elif code == 'o': # three-digit octal code
octalCode = scanner.chop(3)
result = int(octalCode, 8)
elif code == 'O': # freeform octal code
octalCode = scanner.enclosure()
result = int(octalCode, 8)
elif code == 'q': # four-digit quaternary code
quaternaryCode = scanner.chop(4)
result = int(quaternaryCode, 4)
elif code == 'Q': # freeform quaternary code
quaternaryCode = scanner.enclosure()
result = int(quaternaryCode, 4)
elif code == 'r': # CR, carriage return
result = 0x0d
elif code == 's' or code in WHITESPACE_CHARS: # SP, space
result = ' '
elif code == 'S': # NBSP, no-break space
result = 0xa0
elif code == 't': # HT, horizontal tab
result = 0x09
elif code == 'u': # 16-bit (four-digit) hexadecimal Unicode
hexCode = scanner.chop(4)
result = int(hexCode, 16)
elif code == 'U': # 32-bit (eight-digit) hexadecimal Unicode
hexCode = scanner.chop(8)
result = int(hexCode, 16)
elif code == 'v': # VT, vertical tab
result = 0x0b
elif code == 'V': # variation selector
name = scanner.enclosure()
try:
selector = int(name)
except ValueError:
raise ParseError("variation selector must be int: `%s`" % name)
if selector < 1 or selector > 256:
raise ParseError("variation selector must be between 1 and 256 inclusive: %d" % selector)
if selector <= 16:
result = 0xfe00 + selector - 1
else:
result = 0xe0100 + (selector - 17)
elif code == 'w': # variation selector 15; text display
result = 0xfe0e
elif code == 'W': # variation selector 16; emoji display
result = 0xfe0f
elif code == 'x': # 8-bit (two-digit) hexadecimal code
hexCode = scanner.chop(2)
result = int(hexCode, 16)
elif code == 'X': # freeform hexadecimal code
hexCode = scanner.enclosure()
result = int(hexCode, 16)
elif code == 'y': # SUB, substitution
result = 0x1a
elif code == 'Y': # RC, replacement character
result = 0xfffd
elif code == 'z': # EOT, end of transmission
result = 0x04
elif code == 'Z': # ZWNBSP/BOM, zero-width no-break space/byte order mark
result = 0xfeff
elif code == ',': # THSP, thin space
result = 0x2009
elif code == '^': # control character
controlCode = scanner.chop(1).upper()
if controlCode == '{':
if self.config.controls is None:
raise ConfigurationError("controls not configured")
name = scanner.grab('}')
try:
result = self.config.controls[name.upper()]
except KeyError:
raise ParseError("unknown control character name: `%s`" % name)
elif controlCode >= '@' and controlCode <= '`':
result = ord(controlCode) - ord('@')
elif controlCode == '?':
result = 0x7f
else:
raise ParseError("invalid escape control code")
else:
raise ParseError("unrecognized escape code: `%s`" % code)
self.code = self.recode(result)
except ValueError:
raise ParseError("invalid numeric escape code")
def string(self):
"""Return a general hexadecimal escape sequence rather than
the exact one that was input."""
return self.config.escaped(ord(self.code),
self.config.prefix + self.first)
def run(self, interp, locals):
if interp.invoke('preEscape', code=self.code):
return
interp.serialize(self.code)
interp.invoke('postEscape')
class DiacriticToken(CodedToken):
"""A diacritic markup: ``@^...``"""
first = CARET_CHAR
def scan(self, scanner):
if self.config.diacritics is None:
raise ConfigurationError("diacritics not configured")
character = scanner.chop(1)
diacritics = scanner.chop(1)
if diacritics == '{':
diacritics = scanner.grab('}')
codes = []
try:
for diacritic in diacritics:
code = self.config.diacritics[diacritic]
code = self.recode(code)
codes.append(code)
except KeyError:
raise ParseError("unknown diacritical mark: `%s`" % diacritic)
self.character = character
self.codes = codes
self.diacritics = diacritics
def string(self):
if len(self.diacritics) > 1:
return '%s%s%s{%s}' % (
self.config.prefix, self.first, self.character, self.diacritics)
else:
return '%s%s%s%s' % (
self.config.prefix, self.first, self.character, self.diacritics)
def run(self, interp, locals):
combiners = ''.join([interp.core.serialize(x) for x in self.codes])
result = self.character + combiners
if interp.invoke('preDiacritic', code=result):
return
try:
if self.config.normalizationForm:
result = unicodedata.normalize(
self.config.normalizationForm, result)
except AttributeError:
raise ConfigurationError("unicodedata.normalize function not available; cannot use normalization form (must be blank or None)")
except ValueError:
pass
interp.serialize(result)
interp.invoke('postDiacritic')
class IconToken(CodedToken):
"""An icon markup: ``@|...``"""
first = STROKE_CHAR
def scan(self, scanner):
self.config.validateIcons()
key = ''
while True:
key += scanner.chop(1)
try:
result = self.config.icons[key]
except KeyError:
raise ParseError("unknown icon sequence: `%s`" % key)
if result is None:
continue
else:
break
self.key = key
self.code = self.recode(result)
def string(self):
return '%s%s%s' % (self.config.prefix, self.first, self.key)
def run(self, interp, locals):
if interp.invoke('preIcon', code=self.code):
return
interp.serialize(self.code)
interp.invoke('postIcon')
class EmojiToken(CodedToken):
"""An emoji markup: ``@:...:``"""
first = COLON_CHAR
def scan(self, scanner):
i = scanner.next(self.first)
self.name = scanner.chop(i, 1)
if not self.name:
raise ParseError("emoji cannot be blank")
def string(self):
return '%s%s%s%s' % (
self.config.prefix, self.first, self.name, self.first)
def run(self, interp, locals):
if interp.invoke('preEmoji', name=self.name):
return
if (self.config.emojis is not None and
self.name in self.config.emojis):
code = self.config.emojis[self.name]
else:
code = self.config.substituteEmoji(self.name)
if code is None:
if self.config.emojiNotFoundIsError:
raise UnknownEmojiError("emoji not found: `%s`" % self.name)
else:
code = '%s%s%s' % (self.first, self.name, self.first)
code = self.recode(code)
interp.serialize(code)
interp.invoke('postEmoji')
class SignificatorToken(ExpansionToken):
"""A significator markup: ``@%... ... NL``, ``@%!... ... NL``,
``@%%... ... %% NL``, ``@%%!... ... %% NL``"""
first = PERCENT_CHAR
def ending(self, multiline):
if multiline:
return (self.first * 2) + NEWLINE_CHAR
else:
return NEWLINE_CHAR
def scan(self, scanner):
self.multiline = self.stringized = False
peek = scanner.read()
if peek == self.first:
self.multiline = True
scanner.advance(1)
peek = scanner.read()
if peek == '!':
self.stringized = True
scanner.advance(1)
loc = scanner.find(self.ending(self.multiline))
if loc >= 0:
contents = scanner.chop(loc, len(self.ending(self.multiline)))
if not contents:
raise ParseError("significator must have nonblank key")
contents = contents.strip()
# Work around a subtle CPython-Jython difference by stripping the
# string before splitting it: 'a '.split(None, 1) has two elements
# in Jython 2.1).
fields = contents.strip().split(None, 1)
self.key = fields[0]
if len(fields) > 1:
self.value = fields[1].strip()
else:
self.value = ''
if not self.value and not self.stringized:
self.value = self.config.emptySignificator
else:
if self.multiline:
raise TransientParseError("significator expects %s and then newline" % (self.first * 2))
else:
raise TransientParseError("significator expects newline")
def string(self):
if self.value is None:
return '%s%s%s%s\n' % (
self.config.prefix, self.first,
['', '!'][self.stringized],
self.key)
else:
if self.multiline:
return '%s%s%s%s %s%s\n' % (
self.config.prefix, self.first * 2,
['', '!'][self.stringized],
self.key, self.value,
self.first * 2)
else:
return '%s%s%s%s %s\n' % (
self.config.prefix, self.first,
['', '!'][self.stringized],
self.key, self.value)
def run(self, interp, locals):
if interp.invoke('preSignificator', key=self.key,
value=self.value, stringized=self.stringized):
return
value = self.value
if not self.stringized:
if value is not None and value != self.config.emptySignificator:
value = interp.evaluate(value, locals, replace=False)
interp.significate(self.key, value, locals)
interp.invoke('postSignificator')
class ContextToken(ExpansionToken):
"""A base class for the context tokens."""
pass
class ContextNameToken(ContextToken):
"""A context name change markup: ``@?...``"""
first = QUESTION_CHAR
def scan(self, scanner):
loc = scanner.find(NEWLINE_CHAR)
if loc >= 0:
self.name = scanner.chop(loc, 1).strip()
else:
raise TransientParseError("context name expects newline")
def string(self):
return '%s%s%s\n' % (self.config.prefix, self.first, self.name)
def run(self, interp, locals):
if interp.invoke('preContextName', name=self.name):
return
context = interp.getContext()
context.name = self.name
interp.invoke('postContextName', context=context)
class ContextLineToken(ContextToken):
"""A context line change markup: ``@!...``"""
first = EXCLAMATION_CHAR
def scan(self, scanner):
loc = scanner.find(NEWLINE_CHAR)
if loc >= 0:
try:
self.line = int(scanner.chop(loc, 1))
except ValueError:
raise ParseError("context line requires integer")
else:
raise TransientParseError("context line expects newline")
def string(self):
return '%s%s%d\n' % (self.config.prefix, self.first, self.line)
def run(self, interp, locals):
if interp.invoke('preContextLine', line=self.line):
return
context = interp.getContext()
context.line = self.line
interp.invoke('postContextLine', context=context)
class ExtensionToken(ExpansionToken):
"""An extension markup, used for all customizable markup:
``@((...))``, ``@[[...]]``, ``@{{...}}``, ``@<...>``, etc."""
def scan(self, scanner):
# Find the run of starting characters to match.
self.depth = 1
start = scanner.last(self.first)
self.depth += start
scanner.advance(start)
# Then match them with the same number of closing characters.
loc = scanner.find(self.last * self.depth)
if loc >= 0:
self.contents = scanner.chop(loc, self.depth)
else:
raise TransientParseError("custom markup (%s) expects %d closing characters (%s)" % (self.first, self.depth, self.last))
def string(self):
return '%s%s%s%s' % (
self.config.prefix,
self.first * self.depth,
self.contents,
self.last * self.depth)
def run(self, interp, locals):
if interp.callback is not None:
# Legacy custom callback behavior.
if interp.invoke('preCustom', contents=self.contents):
return
result = interp.callExtension(
self.name, self.contents, self.depth, locals)
interp.invoke('postCustom', result=result)
else:
# New extension behavior.
if interp.invoke('preExtension', name=self.name,
contents=self.contents, depth=self.depth,
locals=locals):
return
result = interp.callExtension(
self.name, self.contents, self.depth, locals)
interp.invoke('postExtension', result=result)
Configuration.tokens = [
LineCommentToken,
InlineCommentToken,
WhitespaceToken,
DisableToken,
EnableToken,
PrefixToken,
StringToken,
BackquoteToken,
ExpressionToken,
SimpleExpressionToken,
InPlaceToken,
StatementToken,
ControlToken,
EscapeToken,
DiacriticToken,
IconToken,
EmojiToken,
SignificatorToken,
ContextNameToken,
ContextLineToken,
]
#
# Factory
#
class Factory(Root):
"""Turn a first character sequence into a token class. Token
classes have a first attribute which is either None to indicate
whatever the current prefix is; a string in angle brackets to
indicate a special test; or a character sequence; or a list of
character sequences. Token classes are then retrieved by lookup
table, or special test. Initialize this meta-factory with a list of
factory classes and it will automatically setup the lookup tables
based on their first attributes."""
addenda = {
')': "; the `@)` markup has been removed, just use `)` instead",
']': "; the `@]` markup has been removed, just use `]` instead",
'}': "; the `@}` markup has been removed, just use `}` instead",
'((': "; extension markup `@((...))` invoked with no installed extension",
'[[': "; extension markup `@[[...]]` invoked with no installed extension",
'{{': "; extension markup `@{{...}}` invoked with no installed extension",
'<': "; extension markup `@<...>` invoked with no installed extension or callback",
}
def __init__(self, tokens):
self.byChar = {}
self.identifier = None
self.whitespace = None
for token in tokens:
self.addToken(token)
assert self.identifier is not None
assert self.whitespace is not None
def __contains__(self, first):
return first in self.byChar
def __getitem__(self, first):
return self.byChar[first]
def __call__(self, first):
if first in self.byChar:
return self.byChar[first]
else:
if first.isspace():
return self.whitespace
elif isIdentifier(first):
return self.identifier
return None
def addToken(self, token, protect=False):
"""Add another token class to the factory."""
first = token.first
if first is None:
# A prefix.
if None in self.byChar and protect:
raise ConfigurationError("will not replace prefix token; set protect to true")
assert None not in self.byChar
self.byChar[None] = token
elif (isinstance(first, strType) and
first.startswith('<') and first.endswith('>')):
# A special case.
if first == '<identifier>':
assert self.identifier is None, self.identifier
self.identifier = token
elif first == '<whitespace>':
assert self.whitespace is None, self.whitespace
self.whitespace = token
else:
raise ConsistencyError("unknown special token case: `%s`" % first)
else:
# A character sequence or list of them.
if not isinstance(first, list):
first = [first]
for char in first:
if char in self.byChar and protect:
raise ConfigurationError("will not replace token with first `%s`; set protect to true" % char)
self.byChar[char] = token
def removeToken(self, first):
"""Remove token(s) from the mapping by first, which can be a string
first or a list of strings firsts."""
if first.startswith('<') and first.endswith('>'):
if first == '<identifier>':
self.identifier = None
elif first == '<whitespace>':
self.whitespace = None
else:
raise ConsistencyError("unknown special token case: `%s`" % first)
else:
del self.byChar[first]
def removeTokens(self, firsts):
for first in firsts:
self.removeToken(first)
def adjust(self, config):
"""Adjust this factory to swap the markup for a non-default
prefix, if necessary."""
if not config.hasDefaultPrefix() and config.prefix in self:
oldFactory = self[config.prefix]
self.byChar[config.defaultPrefix] = oldFactory
oldFactory._first = oldFactory.first
oldFactory.first = None # config.defaultPrefix
def addendum(self, first):
"""An optional addendum about unsupported markup sequence (for
compatibility or future notes)."""
return self.addenda.get(first, '')
#
# Scanner
#
class Scanner(Root):
"""A scanner holds a buffer for lookahead parsing and has the
ability to scan for special symbols and indicators in that
buffer."""
def __init__(self, config, context, currents, data=''):
self.config = config
self.context = context
self.currents = currents
self.head = 0
self.pointer = 0
self.buffer = data
self.lock = 0
self.factory = config.getFactory()
def __bool__(self):
return self.head + self.pointer < len(self.buffer) # 3.x
def __nonzero__(self):
return self.head + self.pointer < len(self.buffer) # 2.x
def __len__(self): return len(self.buffer) - self.pointer - self.head
if major >= 3:
def __getitem__(self, index):
if isinstance(index, slice):
assert index.step is None or index.step == 1, index.step
return self.__getslice__(index.start, index.stop)
else:
return self.buffer[self.head + self.pointer + index]
else:
def __getitem__(self, index):
return self.buffer[self.head + self.pointer + index]
def __getslice__(self, start, stop):
if start is None:
start = 0
if stop is None:
stop = len(self)
if stop > len(self):
stop = len(self)
return self.buffer[self.head + self.pointer + start:
self.head + self.pointer + stop]
# Meta.
def advance(self, count=1):
"""Advance the pointer count characters."""
self.pointer += count
def retreat(self, count=1):
self.pointer -= count
if self.pointer < 0:
raise ParseError("cannot retreat back over synced out chars")
def set(self, data):
"""Start the scanner digesting a new batch of data; start the pointer
over from scratch."""
self.head = 0
self.pointer = 0
self.buffer = data
def feed(self, data):
"""Feed some more data to the scanner."""
self.rectify()
if self.buffer:
self.buffer += data
else:
self.buffer = data
def acquire(self):
"""Lock the scanner so it doesn't destroy data on sync."""
self.lock += 1
def release(self):
"""Unlock the scanner."""
self.lock -= 1
def track(self):
"""Accumulate the moved pointer into the context."""
if self.pointer > 0:
self.context.track(self.buffer,
self.head, self.head + self.pointer)
def accumulate(self):
"""Update the accumulated context into the actual context."""
self.context.accumulate()
def rectify(self):
"""Reset the read head and trim down the buffer."""
if self.head + self.pointer > 0:
self.buffer = self.buffer[self.head + self.pointer:]
self.head = 0
self.pointer = 0
def sync(self):
"""Sync up the buffer with the read head."""
if self.lock == 0 and self.pointer > 0:
self.track()
self.head += self.pointer
self.pointer = 0
def unsync(self):
"""Undo changes; reset the read head."""
if self.lock == 0:
self.pointer = 0
def rest(self):
"""Get the remainder of the buffer."""
return self[:]
# Active.
def chop(self, count=None, slop=0):
"""Chop the first count + slop characters off the front, and
return the first count, advancing the pointer past them. If
count is not specified, then return everything."""
if count is None:
assert slop == 0, slop
count = len(self)
if count > len(self):
raise TransientParseError("not enough data to read")
result = self[:count]
self.advance(count + slop)
return result
def enclosure(self, begin='{', end='}'):
"""Consume and return the next enclosure (text wrapped
in the given delimiters). The delimiters can be repeated."""
count = self.last(begin)
self.advance(count)
if count == 0:
raise ParseError("enclosure must start with %s" % begin)
loc = self.find(end * count)
if loc < 0:
raise TransientParseError("enclosure must end with %s" % (end * count))
return self.chop(loc, count)
def read(self, start=0, count=1):
"""Read count chars starting from start; raise a transient
error if there aren't enough characters remaining."""
if len(self) < start + count:
raise TransientParseError("need more data to read")
else:
return self[start:start + count]
def find(self, sub, start=0, end=None):
"""Find the next occurrence of the substring, or return -1."""
if end is None:
end = len(self)
return self.rest().find(sub, start, end)
def trivial(self, sub, start=0, end=None):
"""Find the first occurrence of the substring where the
previous character is _not_ the escape character `\\`)."""
head = start
while True:
i = self.find(sub, head, end)
if i == -1:
raise TransientParseError("substring not found: `%s`" % sub)
if i > 0 and self[i - 1] == BACKSLASH_CHAR:
head = i + len(sub)
continue
return i
def last(self, chars, start=0, end=None):
"""Find the first character that is _not_ one of the specified
characters."""
if end is None:
end = len(self)
i = start
while i < end:
if self[i] not in chars:
return i
i += 1
else:
raise TransientParseError("expecting other than %s" % chars)
def grab(self, sub, start=0, end=None):
"""Find the next occurrence of the substring and chop the
intervening characters, disposing the substring found."""
i = self.find(sub, start, end)
if i >= 0:
return self.chop(i, len(sub))
else:
raise TransientParseError("delimiter not found: `%s`" % sub)
def next(self, target, start=0, end=None, mandatory=False):
"""Scan for the next occurrence of one of the characters in
the target string; optionally, make the scan mandatory."""
if mandatory:
assert end is not None
quote = None
if end is None:
end = len(self)
i = start
while i < end:
newQuote = self.check(i, quote)
if newQuote:
if newQuote == quote:
quote = None
else:
quote = newQuote
i += len(newQuote)
else:
c = self[i]
if quote:
if c == '\\':
i += 1
else:
if c in target:
return i
i += 1
else:
if mandatory:
raise ParseError("expecting %s, not found" % target)
else:
raise TransientParseError("expecting ending character")
def check(self, start=0, archetype=None):
"""Scan for the next single or triple quote, optionally with
the specified archetype. Return the found quote or None."""
quote = None
i = start
if len(self) - i <= 1:
raise TransientParseError("need to scan for rest of quote")
if self[i] in QUOTE_CHARS:
quote = self[i]
if self[i + 1] == quote:
if len(self) - i <= 2:
raise TransientParseError("need more context to complete quote")
if self[i + 2] == quote:
quote *= 3
if quote is not None:
if archetype is None:
return quote
else:
if archetype == quote:
return archetype
elif len(archetype) < len(quote) and archetype[0] == quote[0]:
return archetype
else:
return None
else:
return None
def quote(self, start=0, end=None, mandatory=False):
"""Scan for the end of the next quote."""
assert self[start] in QUOTE_CHARS, self[start]
quote = self.check(start)
if end is None:
end = len(self)
i = start + len(quote)
while i < end:
newQuote = self.check(i, quote)
if newQuote:
i += len(newQuote)
if newQuote == quote:
return i
else:
c = self[i]
if c == '\\':
i += 1
i += 1
else:
if mandatory:
raise ParseError("expecting end of string literal")
else:
raise TransientParseError("expecting end of string literal")
def nested(self, enter, exit, start=0, end=None):
"""Scan from start for an ending sequence, respecting entries and exits
only."""
depth = 0
if end is None:
end = len(self)
i = start
while i < end:
c = self[i]
if c == enter:
depth += 1
elif c == exit:
depth -= 1
if depth < 0:
return i
i += 1
else:
raise TransientParseError("expecting end of complex expression")
def complex(self, enter, exit, comment=OCTOTHORPE_CHAR,
start=0, end=None, skip=None):
"""Scan from start for an ending sequence, respecting quotes,
entries and exits."""
quote = None
depth = 0
if end is None:
end = len(self)
lastNonQuote = None
commented = False
i = start
while i < end:
if commented:
c = self[i]
if c == '\n':
commented = False
elif c == exit:
return i
i += 1
else:
newQuote = self.check(i, quote)
if newQuote:
if newQuote == quote:
quote = None
else:
quote = newQuote
i += len(newQuote)
else:
c = self[i]
if quote:
if c == '\\':
i += 1
else:
if skip is None or lastNonQuote != skip:
if c == enter:
depth += 1
elif c == exit:
depth -= 1
if depth < 0:
return i
if c == comment and depth == 0:
commented = True
lastNonQuote = c
i += 1
else:
raise TransientParseError("expecting end of complex expression")
def word(self, start=0, additional='._'):
"""Scan from start for a simple word."""
length = len(self)
i = start
while i < length:
if not (self[i].isalnum() or self[i] in additional):
return i
i += 1
else:
raise TransientParseError("expecting end of word")
def phrase(self, start=0):
"""Scan from start for a phrase (e.g., 'word', 'f(a, b, c)',
'a[i]', or combinations like 'x[i](a)'."""
# Find the word.
i = self.word(start)
while i < len(self) and self[i] in PHRASE_OPENING_CHARS:
enter = self[i]
exit = ENDING_CHAR_MAP[enter]
i = self.complex(enter, exit, None, i + 1) + 1
return i
def simple(self, start=0):
"""Scan from start for a simple expression, which consists of
one more phrases separated by dots. Return a tuple giving the
end of the expression and a list of tuple pairs consisting of
the simple expression extensions found, if any."""
i = self.phrase(start)
length = len(self)
while i < length and self[i] == DOT_CHAR:
i = self.phrase(i)
# Make sure we don't end with a trailing dot.
while i > 0 and self[i - 1] == DOT_CHAR:
i -= 1
return i
def one(self, firebreaks=None):
"""Parse, scan, and return one token, or None if the scanner
is empty. If the firebreaks argument is supplied, chop up
text tokens before a character in that string."""
if not self:
return None
if not self.config.prefix:
loc = -1
else:
loc = self.find(self.config.prefix)
if loc < 0:
# If there's no prefix in the buffer, then set the location to the
# end so the whole thing gets processed.
loc = len(self)
if loc == 0:
# If there's a prefix at the beginning of the buffer, process
# an expansion.
prefix = self.chop(1)
assert prefix == self.config.prefix, prefix
try:
first = self.chop(1)
if first == self.config.prefix:
first = None
elif first in self.config.duplicativeFirsts:
# If the first character is duplicative, there might be
# more han one. Check if there is a second; if so, use
# that as the first.
if self.read() == first:
first *= 2
tokenClass = self.factory(first)
if tokenClass is None:
raise ParseError("unknown markup sequence: `%s%s`%s" % (self.config.prefix, first, self.factory.addendum(first)))
current = self.config.renderContext(self.context)
self.currents.replace(current)
token = tokenClass(current, self.config, first)
token.scan(self)
except TransientParseError:
# If a transient parse error occurs, reset the buffer pointer
# so we can (conceivably) try again later.
self.unsync()
raise
else:
# Process everything up to loc as a text token, unless there are
# intervening firebreaks before loc.
if firebreaks:
for firebreak in firebreaks:
i = self.find(firebreak, 0, loc)
if i >= 0 and i < loc:
loc = i
data = self.chop(loc)
current = self.config.renderContext(self.context)
self.currents.replace(current)
token = TextToken(current, data)
self.sync()
return token
def all(self):
"""Yield a sequence of all tokens."""
while True:
token = self.one()
if token:
yield token
else:
break
#
# Command ...
#
class Command(Root):
"""A generic high-level processing command."""
def __init__(self, noun):
self.noun = noun
def __str__(self):
return self.noun
def cleanup(self):
pass
def process(self, interp, n):
"""Run the command."""
raise NotImplementedError
class ImportCommand(Command):
"""Import a Python module."""
def process(self, interp, n):
name = '<import:%s>' % n
context = interp.newContext(name)
interp.pushContext(context)
# Expand shortcuts.
self.noun = self.noun.replace('+', ' ')
self.noun = self.noun.replace('=', ' as ')
method = interp.string
if ':' in self.noun:
first, second = self.noun.split(':', 1)
target = '%s{from %s import %s}' % (
interp.config.prefix, first, second)
else:
target = '%s{import %s}' % (interp.config.prefix, self.noun)
interp.protect(name, method, target)
class DefineCommand(Command):
"""Define a Python variable."""
def process(self, interp, n):
name = '<define:%s>' % n
context = interp.newContext(name)
interp.pushContext(context)
if '=' in self.noun:
interp.execute(self.noun)
else:
interp.atomic(self.noun.strip(), None)
interp.popContext()
class StringCommand(Command):
"""Define a Python string variable."""
def process(self, interp, n):
if '=' in self.noun:
key, value = self.noun.split('=', 1)
key = key.strip()
value = value.strip()
else:
key = self.noun.strip()
value = ''
interp.atomic(key, value)
class DocumentCommand(Command):
"""Read and execute an EmPy document."""
def process(self, interp, n):
name = self.noun
method = interp.file
self.target = None
self.target = interp.config.open(self.noun, 'r')
interp.protect(name, method, self.target)
def cleanup(self):
if self.target is not None:
self.target.close()
class ExecuteCommand(Command):
"""Execute a Python statement."""
def process(self, interp, n):
name = '<execute:%s>' % n
context = interp.newContext(name)
interp.pushContext(context)
interp.execute(self.noun)
interp.popContext()
ExecCommand = ExecuteCommand # DEPRECATED
class FileCommand(Command):
"""Load an execute a Python file."""
def process(self, interp, n):
name = '<file:%s>' % n
context = interp.newContext(name)
interp.pushContext(context)
try:
file = interp.config.open(self.noun, 'r')
try:
data = file.read()
finally:
file.close()
interp.execute(data)
finally:
interp.popContext()
class ExpandCommand(Command):
"""Execute a Python statement."""
def process(self, interp, n):
name = '<expand:%s>' % n
context = interp.newContext(name)
interp.pushContext(context)
try:
interp.string(self.noun)
finally:
interp.popContext()
#
# Plugin
#
class Plugin(Root):
"""A plugin is an object associated with an interpreter that has a
back-reference to it."""
def __init__(self, interp=None):
if interp is not None:
self.attach(interp)
def attach(self, interp):
"""Attach this plugin to an interpreter. This needs to be a
separate step since some methods require access to the
interpreter."""
self.interp = interp
def detach(self, interp=None):
"""Detach this plugin from an interpreter, or any interpreter if
not specified. This breaks any cyclical links between the
interpreter and the plugin."""
if interp is not None and interp is not self.interp:
raise em.ConsistencyError("plugin not associated with this interpeter")
self.interp = None
def push(self):
self.interp.push()
def pop(self):
self.interp.pop()
# DEPRECATED:
def register(self, interp):
self.attach(interp)
def deregister(self, interp=None):
self.detach(interp)
#
# Core
#
class Core(Plugin):
"""A core encapsulates the functionality of the underlying language
(Python by default). To create an object where this these are
native methods to that class, derive a class from Core but do not
call its constructor."""
casesVariable = '_EmPy_pairs'
class Case:
def __init__(self, expression, tokens):
self.expression = expression
self.tokens = tokens
def __getitem__(self, index):
if index == 0:
return self.expression
elif index == 1:
return self.tokens
else:
raise IndexError
def __len__(self):
return 2
def __iter__(self):
yield self.expression
yield self.tokens
def __init__(self, **kwargs):
evaluate = extract(kwargs, 'evaluate', None)
if evaluate is not None:
self.evaluate = evaluate
execute = extract(kwargs, 'execute', None)
if execute is not None:
self.execute = execute
serialize = extract(kwargs, 'serialize', None)
if serialize is not None:
self.serialize = serialize
define = extract(kwargs, 'define', None)
if define is not None:
self.define = define
match = extract(kwargs, 'match', None)
if match is not None:
self.match = match
interp = extract(kwargs, 'interp', None)
if interp is not None:
interp.insertCore(self)
def quote(self, code):
"""Find the right quote for this code."""
if '"""' in code and "'''" in code:
raise CoreError("cannot find proper quotes for code; code cannot contain both \'\'\' and \"\"\": %r" % code, code=code)
if '"""' in code:
return "'''"
else:
return '"""'
# Implementation (override these)
def evaluate(self, code, globals, locals=None):
"""Evaluate an expression and return it."""
if locals is None:
return evalFunc(code, globals)
else:
return evalFunc(code, globals, locals)
def execute(self, code, globals, locals=None):
"""Execute a statement(s); return value ignored."""
if locals is None:
execFunc(code, globals)
else:
execFunc(code, globals, locals)
def serialize(self, thing):
"""Return the string representation of an object."""
return toString(thing)
def define(self, signature, definition, locals=None):
"""Implement @[def ...] markup; return value ignored."""
quote = self.quote(definition)
quoted = quote + definition + quote
code = ('def %s:\n'
'\tr%s\n'
'\treturn %s.expand(r%s, locals())\n' %
(signature, quoted,
self.interp.config.pseudomoduleName, quoted))
self.execute(code, self.interp.globals, locals)
def match(self, expression, cases, locals=None):
"""Implement @[match ...] markup; return value ignored."""
if sys.version_info < (3, 10):
raise CompatibilityError("`match` control requires `match` control structure (Python 3.10 and later)")
if locals is None:
locals = {}
if cases and isinstance(cases[0], tuple):
# Old-style form is that cases is a list of tuples. Transform them
# into Cases.
cases = [Core.Case(*x) for x in cases]
locals[self.casesVariable] = cases
lines = []
lines.append('match %s:\n' % expression)
for i, case in enumerate(cases):
lines.append('\tcase %s:\n' % case.expression)
lines.append('\t\t%s.runSeveral(%s[%d].tokens, locals())\n' % (
self.interp.config.pseudomoduleName, self.casesVariable, i))
self.execute(''.join(lines), self.interp.globals, locals)
#
# Extension
#
class Extension(Plugin):
"""An extension plugin that the interpreter will defer to for
non-standard markup."""
mapping = {
'((': 'parentheses',
'[[': 'square_brackets',
'{{': 'curly_braces',
'<': 'angle_brackets',
}
def __init__(self, mapping=None, **kwargs):
if mapping is None:
# The default class attribute will suffice.
pass
elif isinstance(mapping, dict):
# A dict should be used directly.
self.mapping = mapping
elif isinstance(mapping, (list, set)):
# A list of 2-tuples should be updated on top of the default.
self.mapping = self.mapping.copy()
self.mapping.update(mapping)
else:
raise ExtensionError("unknown mapping type (must be dict, list or None): %r" % mapping)
for name, method in kwargs.items():
self.__dict__[name] = method
#
# Interpreter
#
class Interpreter(Root):
"""An interpreter can process chunks of EmPy code."""
# Compatibility.
version = __version__
compat = compat
# Constants.
ASSIGN_TOKEN_RE = re.compile(r"[_a-zA-Z][_a-zA-Z0-9]*|\(|\)|,")
AS_RE = re.compile(r"\bas\b")
# Construction, initialization, destruction.
def __init__(self, **kwargs):
"""Accept keyword arguments only, so users will never have to
worry about the ordering of arguments."""
self.ok = None # is the interpreter initialized?
self.shuttingDown = False # is the interpreter shutting down?
config = extract(kwargs, 'config', None)
if config is None:
config = Configuration()
core = extract(kwargs, 'core', None)
if core is None:
# Specifying the ...Func callbacks separately from the core is now
# DEPRECATED.
core = Core(
evaluate=extract(kwargs, 'evalFunc', None),
execute=extract(kwargs, 'execFunc', None),
serialize=extract(kwargs, 'serializerFunc', None),
define=extract(kwargs, 'definerFunc', None),
match=extract(kwargs, 'matcherFunc', None),
)
extension = extract(kwargs, 'extension', None)
args = (
config,
core,
extension,
extract(kwargs, 'ident', None),
extract(kwargs, 'globals', None),
extract(kwargs, 'output', None),
extract(kwargs, 'executable', '?'),
extract(kwargs, 'argv', None),
extract(kwargs, 'filespec', None),
extract(kwargs, 'hooks', None),
extract(kwargs, 'finalizers', None),
extract(kwargs, 'filters', None),
extract(kwargs, 'callback', None),
extract(kwargs, 'dispatcher', True),
extract(kwargs, 'handler', None),
extract(kwargs, 'input', sys.stdin),
extract(kwargs, 'root', None),
extract(kwargs, 'origin', False),
extract(kwargs, 'immediately', True),
)
if kwargs:
# Any remaining keyword arguments are a mistake: either simple
# typos, or an old-style specification of local variables in an
# `expand` call.
badKeys = []
for key in kwargs.keys():
if key not in config.ignoredConstructorArguments:
badKeys.append(key)
if badKeys:
badKeys.sort()
raise CompatibilityError("unrecognized Interpreter constructor keyword arguments; when calling expand, use locals dictionary instead of keywords: %s" % badKeys, keys=badKeys)
self._initialize(*args)
def __del__(self):
self.shutdown()
def __repr__(self):
details = []
if self.ident:
details.append(' "%s"' % self.ident)
if self.config and self.config.name:
details.append(' ("%s")' % self.config.name)
return '<%s pseudomodule/interpreter object%s @ 0x%x>' % (
self.config.pseudomoduleName, ''.join(details), id(self))
def __bool__(self): return self.ok # 3.x
def __nonzero__(self): return self.ok # 2.x
def __enter__(self):
self.check()
return self
def __exit__(self, *exc):
self.shutdown()
def _initialize(self, config=None, core=None, extension=None,
ident=None, globals=None, output=None,
executable=None, argv=None, filespec=None,
hooks=None, finalizers=None, filters=None, callback=None,
dispatcher=True, handler=None, input=sys.stdin, root=None,
origin=False, immediately=True):
"""Initialize the interpreter with the given arguments (all of
which have defaults). The number and order of arguments here
is subject to change."""
self.ident = ident
self.error = None # last error that occurred or None
# Set up the configuration.
if config is None:
config = Configuration()
self.config = config
self.filespec = filespec
self.globals = globals
# Handle the executable and arguments.
self.executable = executable
if argv is None:
argv = [None]
if argv[0] is None:
argv[0] = config.unknownScriptName
self.argv = argv
# The interpreter stacks.
self.enabled = True
self.contexts = Stack()
self.streams = Stack()
self.currents = Stack()
# Initialize hooks.
if hooks is None:
hooks = []
self.hooks = []
self.hooksEnabled = None
for hook in hooks:
self.addHook(hook)
# Initialize finalizers:
if finalizers is None:
finalizers = []
self.finalizers = []
self.setFinalizers(finalizers)
# Initialize dispatcher.
if dispatcher is True:
dispatcher = self.dispatch
elif dispatcher is False:
dispatcher = self.reraise
elif dispatcher is None:
raise ConfigurationError("dispatcher cannot be None")
self.dispatcher = dispatcher
# Initialize handler.
self.handler = None
if handler is not None:
self.setHandler(handler)
# Install a proxy stdout if one hasn't been already.
self.output = self.bottom(output)
self.install(self.output)
# Setup the execution core.
self.insertCore(core)
# Setup any extension.
self.extension = None
if extension is not None:
self.installExtension(extension)
# Initialize callback.
self.callback = None
if callback is not None:
self.registerCallback(callback)
# Setup the input file.
self.input = input
# Setup the root context.
if root is None:
root = self.config.defaultRoot
self.root = root
# Is this a top-level interpreter?
self.origin = origin
# Now declare that we've started up.
self.ok = True
self.invoke('atStartup')
# Reset the state.
self.reset(True)
# Initialize filters (needs to be done after stacks are up).
if filters:
self.setFilterChain(filters)
# Declare the interpreter ready.
if immediately and not self.shuttingDown:
self.ready()
def _deinitialize(self):
"""Deinitialize by detaching all plugins. Called at the end of
shutdown."""
self.clearHooks()
self._deregisterCallback()
self.clearFinalizers()
self.resetHandler()
self.uninstallExtension()
self.ejectCore()
def reset(self, clearStacks=False):
"""Completely reset the interpreter state. If clearStacks is
true, wipe the call stacks. If immediately is true, declare
the interpreter ready."""
self.ok = False
self.error = None
self.enabled = True
# None is a special sentinel meaning "false until added."
self.hooksEnabled = len(self.hooks) > 0 and True or None
# Set up a diversions dictionary.
self.diversions = {}
# Significators.
self.significators = {}
# Now set up the globals.
self.fixGlobals()
self.globalsHistory = Stack()
# Reset the command counter.
self.command = 0
# Now, clear the state of all the stacks.
if clearStacks:
self.clear()
self.current = None
# Done. Now declare that we've started up.
self.ok = True
def ready(self):
"""Declare the interpreter ready for normal operations."""
self.invoke('atReady')
def finalize(self):
"""Execute any remaining final routines."""
if self.finalizers:
self.push()
self.invoke('atFinalize')
try:
# Pop them off one at a time so they get executed in reverse
# order and we remove them as they're executed in case
# something bad happens.
while self.finalizers:
finalizer = self.finalizers.pop()
if self.invoke('beforeFinalizer', finalizer=finalizer):
continue
finalizer()
self.invoke('afterFinalizer')
self.detach(finalizer)
finally:
self.pop()
def install(self, output):
"""Given the desired output files, install any global
apparatus."""
self.installProxy(output)
if self.config.evocare() == 1:
self.installFinder()
def uninstall(self):
"""Uninstall any global apparatus. The apparatus should be
installed."""
self.uninstallProxy()
if self.config.evocare() is None:
self.uninstallFinder()
def succeeded(self):
"""Did the interpreter succeed? That is, is the logged
error not an error?"""
return self.config.isNotAnError(self.error)
def pause(self):
"""Pause (at the end of processing)."""
try:
self.input.readline()
except EOFError:
pass
def shutdown(self):
"""Declare this interpreting session over; close all the
stream file objects, and if this is the last interpreter,
uninstall the proxy and/or finder. This method is idempotent."""
if self.ok and not self.shuttingDown:
self.shuttingDown = True
# Finally, if we're supposed to go interactive afterwards, do it.
if self.config.goInteractive:
self.interact()
# Wrap things up.
self.ok = False
succeeded = self.succeeded()
try:
self.finalize()
self.invoke('atShutdown')
while self.streams:
stream = self.streams.pop()
if self.streams:
stream.close()
else:
# Don't close the bottom stream; auto-play diversions
# and just flush it.
if self.config.autoPlayDiversions and succeeded:
stream.undivertAll()
stream.flush()
self.clear()
finally:
self.uninstall()
if self.origin and self.evocare() is not None:
raise ProxyError("proxy persists; did you not call shutdown?")
# Deinitialize (detach all plugins).
self._deinitialize()
# Do a final flush of all the streams.
self.flushAll()
# Finally, pause if desired.
if self.config.pauseAtEnd:
self.pause()
def check(self):
"""Check the verify this interpreter is still alive."""
if not self.ok:
raise ConsistencyError("interpreter has already been shutdown")
def failed(self):
"""Has this interpreter had an error (which we should not
ignore)?"""
return self.error and self.config.exitOnError
# Installation delegates.
def proxy(self, *args, **kwargs):
return self.config.proxy(*args, **kwargs)
def evocare(self, *args, **kwargs):
return self.config.evocare(*args, **kwargs)
def installProxy(self, *args, **kwargs):
before = self.config.evocare()
output = self.config.installProxy(*args, **kwargs)
proxy = self.config.proxy()
if proxy is not None:
self.invoke('atInstallProxy', proxy=proxy, new=(before is None))
return output
def uninstallProxy(self):
proxy = self.config.proxy()
self.config.uninstallProxy()
after = self.config.evocare()
self.invoke('atUninstallProxy', proxy=proxy, done=(after is None))
def checkProxy(self, *args, **kwargs):
return self.config.checkProxy(*args, **kwargs)
def installFinder(self, *args, **kwargs):
self.config.installFinder(*args, **kwargs)
finder = self.config.finder()
self.invoke('atInstallFinder', finder=finder)
def uninstallFinder(self):
finder = self.config.finder()
self.invoke('atUninstallFinder', finder=finder)
self.config.uninstallFinder()
# Writeable file-like methods.
def enable(self, value=True):
self.enabled = value
def disable(self, value=False):
self.enabled = value
def write(self, data):
stream = self.top()
assert stream is not None
stream.write(data)
def writelines(self, lines):
stream = self.top()
assert stream is not None
stream.writelines(lines)
def flush(self):
stream = self.top()
assert stream is not None
stream.flush()
def flushAll(self):
for stream in self.streams:
stream.flush()
def close(self):
self.shutdown()
def serialize(self, thing):
"""Output the string version of an object, or a special token if
it is None."""
if thing is None:
if self.config.noneSymbol is not None:
self.write(self.config.noneSymbol)
else:
self.write(self.core.serialize(thing))
def bottom(self, output):
"""Get the underlying bottom file."""
# If there's no output, check the bottom file in the proxy first.
if output is None:
output = getattr(self.config.proxy(), '_EmPy_bottom', None)
# Otherwise, default to the config's stdout.
if output is None:
output = self.config.defaultStdout
return output
# Stream stack-related activity.
def top(self):
"""Get the top stream."""
return self.streams.top()
def push(self):
if self.config.useProxy and self.ok:
try:
sys.stdout._EmPy_push(self)
except AttributeError:
raise ProxyError("proxy lost; cannot push stream")
def pop(self):
if self.config.useProxy and self.ok:
try:
sys.stdout._EmPy_pop(self)
except AttributeError:
raise ProxyError("proxy lost; cannot pop stream")
def clear(self):
self.streams.purge()
self.streams.push(Stream(self, self.output, self.diversions))
self.contexts.purge()
context = self.newContext(self.root)
self.contexts.push(context)
self.currents.purge()
self.currents.push(self.config.renderContext(context))
# Entry-level processing.
def include(self, fileOrFilename, locals=None, name=None):
"""Do an include pass on a file or filename."""
close = False
if isinstance(fileOrFilename, strType):
# Either it's a string representing a filename ...
filename = fileOrFilename
if not name:
name = filename
file = self.config.open(filename, 'r')
close = True
else:
# ... or a file object.
file = fileOrFilename
if not name:
name = '<%s>' % toString(file.__class__.__name__)
try:
if self.invoke('beforeInclude', file=file, locals=locals, name=name):
return
if name:
context = self.newContext(name)
self.pushContext(context)
self.file(file, locals)
if name:
self.popContext()
finally:
if close:
file.close()
self.invoke('afterInclude')
def expand(self, data, locals=None, name='<expand>', dispatcher=False):
"""Do an explicit expansion on a subordinate stream in a
new context. If dispatch is true, dispatch any exception
through the interpreter; otherwise just reraise."""
if dispatcher is None:
dispatcher = self.dispatcher
elif dispatcher is True:
dispatcher = self.dispatch
elif dispatcher is False:
dispatcher = self.reraise
outFile = StringIO()
stream = Stream(self, outFile, self.diversions)
if self.invoke('beforeExpand', string=data, locals=locals, name=name,
dispatcher=dispatcher):
return
self.push()
self.streams.push(stream)
try:
if name:
context = self.newContext(name)
self.pushContext(context)
try:
self.string(data, locals, dispatcher)
finally:
if name:
self.popContext()
try:
stream.flush()
result = outFile.getvalue()
except ValueError:
# Premature termination will result in the file being closed;
# ignore it.
result = None
self.invoke('afterExpand', result=result)
return result
finally:
self.streams.pop()
self.pop()
# High-level processing.
def go(self, inputFilename, inputMode,
preprocessing=None, postprocessing=None):
"""Execute an interpreter stack at a high level."""
# Execute any preprocessing commands.
if preprocessing is None:
preprocessing = []
if postprocessing is None:
postprocessing = []
self.processAll(preprocessing)
# Ready!
if not self.shuttingDown:
self.ready()
# Now process the primary file.
method = self.file
if inputFilename is None:
# We'll be using stdin, so check the encoding.
if not self.config.isDefaultEncodingErrors(asInput=True):
self.config.reconfigure(sys.stdin,
self.config.buffering,
self.config.inputEncoding,
self.config.inputErrors)
self.config.goInteractive = True
else:
if inputFilename == '-':
file = sys.stdin
name = '<stdin>'
else:
file = self.config.open(inputFilename, inputMode, self.config.buffering)
name = inputFilename
if self.config.relativePath:
dirname = os.path.split(inputFilename)[0]
sys.path.insert(0, dirname)
try:
self.protect(name, method, file)
finally:
if file is not sys.stdin:
file.close()
# Finally, execute any postprocessing commands.
self.processAll(postprocessing)
def protect(self, name, callable, *args, **kwargs):
"""Wrap around an application of a callable in a new
context (named name)."""
if name:
context = self.newContext(name)
self.pushContext(context)
try:
if kwargs is None:
kwargs = {}
callable(*args, **kwargs)
finally:
if name and self.contexts:
self.popContext()
def interact(self):
"""Perform interaction."""
self.invoke('atInteract')
self.protect('<interact>', self.fileLines, self.input)
def file(self, file, locals=None, dispatcher=None):
"""Parse a file according to the current buffering strategy."""
config = self.config
if config.hasNoBuffering() or config.hasLineBuffering():
self.fileLines(file, locals, dispatcher)
elif config.hasFullBuffering():
self.fileFull(file, locals, dispatcher)
else: # if self.hasFixedBuffering()
self.fileChunks(file, config.buffering, locals, dispatcher)
def fileLines(self, file, locals=None, dispatcher=None):
"""Parse the entire contents of a file-like object, line by line."""
if self.invoke('beforeFileLines', file=file, locals=locals,
dispatcher=dispatcher):
return
scanner = Scanner(self.config, self.getContext(), self.currents)
done = False
first = True
while not done and not self.failed():
line = file.readline()
if first:
if self.config.ignoreBangpaths and self.config.prefix:
if line.startswith(self.config.bangpath):
line = self.config.prefix + line
first = False
if line:
scanner.feed(line)
else:
done = True
while not self.safe(scanner, done, locals, dispatcher):
pass
self.invoke('afterFileLines')
def fileChunks(self, file, bufferSize=None, locals=None, dispatcher=None):
"""Parse the entire contents of a file-like object, in
buffered chunks."""
if bufferSize is None:
bufferSize = self.config.buffering
assert bufferSize > 0, bufferSize
if self.invoke('beforeFileChunks', file=file, bufferSize=bufferSize,
locals=locals, dispatcher=dispatcher):
return
scanner = Scanner(self.config, self.getContext(), self.currents)
done = False
first = True
while not done and not self.failed():
chunk = file.read(bufferSize)
if first:
if self.config.ignoreBangpaths and self.config.prefix:
if chunk.startswith(self.config.bangpath):
chunk = self.config.prefix + chunk
first = False
if chunk:
scanner.feed(chunk)
else:
done = True
while not self.safe(scanner, done, locals, dispatcher):
pass
self.invoke('afterFileChunks')
def fileFull(self, file, locals=None, dispatcher=None):
"""Parse the entire contents of a file-like object, in one big
chunk."""
if self.invoke('beforeFileFull', file=file, locals=locals,
dispatcher=dispatcher):
return
scanner = Scanner(self.config, self.getContext(), self.currents)
data = file.read()
if self.config.ignoreBangpaths and self.config.prefix:
if data.startswith(self.config.bangpath):
data = self.config.prefix + data
scanner.feed(data)
while not self.safe(scanner, True, locals, dispatcher):
pass
self.invoke('afterFileFull')
def string(self, string, locals=None, dispatcher=None):
"""Parse a string. Cleans up after itself."""
if self.invoke('beforeString', string=string, locals=locals,
dispatcher=dispatcher):
return
scanner = Scanner(self.config, self.getContext(), self.currents, string)
while not self.safe(scanner, True, locals, dispatcher):
pass
self.invoke('afterString')
def safe(self, scanner, final=False, locals=None, dispatcher=None):
"""Do a protected parse. Catch transient parse errors; if
final is true, then make a final pass with a terminator,
otherwise ignore the transient parse error (more data is
pending). Return true if the scanner is exhausted or if an
error has occurred."""
if dispatcher is None:
dispatcher = self.dispatcher
try:
return self.parse(scanner, locals)
except TransientParseError:
if final:
buffer = scanner.rest()
# If the buffer ends with a prefix, it's a real parse
# error.
if buffer and buffer.endswith(self.config.prefix):
raise
# Try tacking on a dummy terminator to take into account
# greedy tokens.
scanner.feed(self.config.prefix + NEWLINE_CHAR)
try:
# Any error thrown from here is a real parse error.
self.parse(scanner, locals)
except:
if dispatcher():
return True
return True
except:
if dispatcher():
return True
def import_(self, filename, module, locals=None, dispatcher=None):
"""Import an EmPy module."""
if self.invoke('beforeImport', filename=filename, module=module,
locals=locals, dispatcher=dispatcher):
return
globals = self.globals
self.globals = vars(module)
self.fixGlobals()
switch = self.enabled
if not self.config.enableImportOutput:
self.disable()
context = self.newContext(filename)
self.pushContext(context)
try:
self.push()
file = self.config.open(filename, 'r')
try:
self.file(file, locals, dispatcher)
finally:
file.close()
self.pop()
self.globals = globals
self.enabled = switch
self.invoke('afterImport')
finally:
self.popContext()
def parse(self, scanner, locals=None):
"""Parse and run as much from this scanner as possible. Return
true if the scanner ran out of tokens."""
self.invoke('atParse', scanner=scanner, locals=locals)
while True:
token = scanner.one()
if token is None:
break
self.invoke('atToken', token=token)
self.run(token, locals)
scanner.accumulate()
return True
def process(self, command):
"""Process a command."""
if isinstance(command, list):
# Backward compatibility: If process is called with a list, defer
# to the explicit list version.
self.processAll(command)
return
self.command += 1
if self.invoke('beforeProcess', command=command, n=self.command):
return
try:
command.process(self, self.command)
finally:
command.cleanup()
self.invoke('afterProcess')
def processAll(self, commands):
"""Process a sequence of commands."""
for command in commands:
self.process(command)
# Medium-level processing.
def tokens(self, tokens, locals=None):
"""Do an explicit result on a sequence of tokens.
Cleans up after itself."""
outFile = StringIO()
stream = Stream(self, outFile, self.diversions)
if self.invoke('beforeTokens', tokens=tokens, locals=locals):
return
self.streams.push(stream)
try:
self.runSeveral(tokens, locals)
stream.flush()
result = outFile.getvalue()
self.invoke('afterTokens', result=result)
return result
finally:
self.streams.pop()
def quote(self, data):
"""Quote the given string so that if it were expanded it would
evaluate to the original."""
if self.invoke('beforeQuote', string=data):
return
scanner = Scanner(self.config, self.getContext(), self.currents, data)
result = []
i = 0
try:
j = scanner.next(self.config.prefix, i)
result.append(data[i:j])
result.append(self.config.prefix * 2)
i = j + 1
except TransientParseError:
pass
result.append(data[i:])
result = ''.join(result)
self.invoke('afterQuote', result=result)
return result
def escape(self, data, more=''):
"""Escape a string so that nonprintable or non-ASCII characters
are replaced with compatible EmPy expansions. Also treat
characters in more as escapes."""
if self.invoke('beforeEscape', string=data, more=more):
return
result = []
for char in data:
if char in more:
result.append(self.config.prefix + '\\' + char)
elif char < ' ' or char > '~':
result.append(self.config.escaped(ord(char),
self.config.prefix + '\\'))
else:
result.append(char)
result = ''.join(result)
self.invoke('afterEscape', result=result)
return result
def tokenize(self, name):
"""Take an lvalue string and return a name or a (possibly recursive)
list of names."""
result = []
stack = [result]
for garbage in self.ASSIGN_TOKEN_RE.split(name):
garbage = garbage.strip()
if garbage:
raise ParseError("unexpected assignment token: `%s`" % garbage)
tokens = self.ASSIGN_TOKEN_RE.findall(name)
# While processing, put a None token at the start of any list in which
# commas actually appear.
for token in tokens:
if token == '(':
stack.append([])
elif token == ')':
top = stack.pop()
if len(top) == 1:
top = top[0] # no None token means that it's not a 1-tuple
elif top[0] is None:
del top[0] # remove the None token for real tuples
stack[-1].append(top)
elif token == ',':
if len(stack[-1]) == 1:
stack[-1].insert(0, None)
else:
stack[-1].append(token)
# If it's a 1-tuple at the top level, turn it into a real subsequence.
if result and result[0] is None:
result = [result[1:]]
if len(result) == 1:
return result[0]
else:
return result
def atomic(self, name, value, locals=None):
"""Do an atomic assignment."""
if self.invoke('beforeAtomic', name=name, value=value, locals=locals):
return
if locals is None:
self.globals[name] = value
else:
locals[name] = value
self.invoke('afterAtomic')
def multi(self, names, values, locals=None):
"""Do a (potentially recursive) assignment."""
if self.invoke('beforeMulti', names=names, values=values, locals=locals):
return
values = tuple(values) # to force an exception if not a sequence
if len(names) != len(values):
raise ValueError("unpack tuple of wrong size")
for name, value in zip(names, values):
if isinstance(name, strType):
self.atomic(name, value, locals)
else:
self.multi(name, value, locals)
self.invoke('afterMulti')
def assign(self, name, value, locals=None):
"""Do a potentially complex (including tuple unpacking) assignment."""
left = self.tokenize(name)
# The return value of tokenize can either be a string or a list of
# (lists of) strings.
if isinstance(left, strType):
self.atomic(left, value, locals)
else:
self.multi(left, value, locals)
def significate(self, key, value=None, locals=None):
"""Declare a significator."""
if self.invoke('beforeSignificate', key=key, value=value, locals=locals):
return
name = self.config.significatorFor(key)
self.atomic(name, value, locals)
self.significators[key] = value
self.invoke('afterSignificate')
def clause(self, catch, locals=None):
"""Given the string representation of an except clause, turn
it into a 2-tuple consisting of the class name or tuple of
names, and either a variable name or None. If the
representation is None, then it's all exceptions and no name."""
if self.invoke('beforeClause', catch=catch, locals=locals):
return
done = False
if catch is None:
exceptionCode, variable = None, None
done = True
if not done:
match = self.AS_RE.search(catch)
if match:
exceptionCode, variable = self.AS_RE.split(catch.strip(), 1)
exceptionCode = exceptionCode.strip()
variable = variable.strip()
else:
comma = catch.rfind(',')
if comma >= 0:
exceptionCode, variable = catch[:comma], catch[comma + 1:]
exceptionCode = exceptionCode.strip()
variable = variable.strip()
else:
exceptionCode, variable = catch.strip(), None
if not exceptionCode:
exception = self.config.baseException
else:
exception = self.evaluate(exceptionCode, locals)
self.invoke('afterClause', exception=exception, variable=variable)
return exception, variable
def dictionary(self, code, locals=None):
"""Given a string representing a key-value argument list, turn
it into a dictionary."""
code = code.strip()
self.push()
try:
if self.invoke('beforeDictionary', code=code, locals=locals):
return
if code.strip():
result = self.evaluate('{%s}' % code, locals)
else:
result = {}
self.invoke('afterDictionary', result=result)
return result
finally:
self.pop()
def literal(self, text, locals=None):
"""Process a string literal."""
if self.invoke('beforeLiteral', text=text, locals=locals):
return
result = self.evaluate(text, locals, replace=False)
self.serialize(result)
self.invoke('afterLiteral', result=result)
def functional(self, code, tokensLists, locals=None):
"""Handle a functional expression like @f{x} and return the
result. code is the Python code to evaluate tokensLists is a
list of list of tokens."""
self.push()
try:
if self.invoke('beforeFunctional', code=code, lists=tokensLists,
locals=locals):
return
function = self.evaluate(code, locals)
arguments = []
for tokensSublist in tokensLists:
arguments.append(self.tokens(tokensSublist, locals))
result = function(*tuple(arguments))
self.invoke('afterFunctional', result=result)
return result
finally:
self.pop()
# Low-level evaluation.
def run(self, token, locals=None):
"""Run a token, tracking the current context."""
token.run(self, locals)
def runSeveral(self, tokens, locals=None):
"""Run a sequence of tokens."""
for token in tokens:
self.run(token, locals)
def defined(self, name, locals=None):
"""Return a Boolean indicating whether or not the name is
defined either in the locals or the globals."""
if self.invoke('beforeDefined', name=name, locals=locals):
return
result = False
if locals is not None and name in locals:
result = True
elif name in self.globals:
result = True
self.invoke('afterDefined', result=result)
return result
def lookup(self, variable, locals=None):
"""Lookup the value of a variable."""
if locals is not None and variable in locals:
return locals[variable]
else:
return self.globals[variable]
def evaluate(self, expression, locals=None, replace=True):
"""Evaluate an expression. If replace is true, replace
newlines in the expression with spaces if that config
variable is set; otherwise, don't do it regardless."""
self.push()
try:
if self.invoke('beforeEvaluate',
expression=expression, locals=locals, replace=replace):
return
if replace and self.config.replaceNewlines:
expression = expression.replace('\n', ' ')
if locals is not None:
result = self.core.evaluate(expression, self.globals, locals)
else:
result = self.core.evaluate(expression, self.globals)
self.invoke('afterEvaluate', result=result)
return result
finally:
self.pop()
def execute(self, statements, locals=None):
"""Execute a statement(s)."""
# If there are any carriage returns (as opposed to linefeeds/newlines)
# in the statements code, then remove them. Even on Windows platforms,
# this will work in the Python interpreter.
if CARRIAGE_RETURN_CHAR in statements:
statements = statements.replace(CARRIAGE_RETURN_CHAR, '')
# If there are no newlines in the statements code, then strip any
# leading or trailing whitespace.
if statements.find(NEWLINE_CHAR) < 0:
statements = statements.strip()
self.push()
try:
if self.invoke('beforeExecute',
statements=statements, locals=locals):
return
self.core.execute(statements, self.globals, locals)
self.invoke('afterExecute')
finally:
self.pop()
def single(self, source, locals=None):
"""Execute an expression or statement, just as if it were
entered into the Python interactive interpreter."""
self.push()
try:
if self.invoke('beforeSingle',
source=source, locals=locals):
return
code = compile(source, '<single>', 'single')
result = self.core.execute(code, self.globals, locals)
self.invoke('afterSingle', result=result)
return result
finally:
self.pop()
#
# Pseudomodule routines.
#
# Identification.
def identify(self):
"""Identify the topmost context with a tuple of the name and
counters."""
return self.getContext().identify()
# Contexts.
def getContext(self):
"""Get the top context."""
return self.contexts.top()
def newContext(self, name='<unnamed>', line=None, column=None, chars=None):
"""Create and return a new context."""
if isinstance(name, tuple):
# If name is a tuple, then use it as an argument.
return self.newContext(*name)
elif isinstance(name, Context):
# If it's a Context, create a fresh clone of it.
context = Context('<null>', 0, 0,
startingLine=self.config.startingLine,
startingColumn=self.config.startingColumn)
context.restore(name)
return context
else:
# Otherwise, build it up from scratch.
if line is None:
line = self.config.startingLine
if column is None:
column = self.config.startingColumn
if chars is None:
chars = 0
return Context(name, line, column, chars)
def pushContext(self, context):
"""Push a new context on the stack."""
self.invoke('pushContext', context=context)
self.contexts.push(context)
self.currents.push(self.config.renderContext(context))
def popContext(self):
"""Pop the top context."""
context = self.contexts.pop()
self.currents.pop()
self.invoke('popContext', context=context)
def setContext(self, context):
"""Replace the top context."""
self.contexts.replace(context)
self.currents.replace(self.config.renderContext(context))
self.invoke('setContext', context=context)
def setContextName(self, name):
"""Set the name of the topmost context."""
context = self.getContext()
context.name = name
self.currents.replace(self.config.renderContext(context))
self.invoke('setContext', context=context)
def setContextLine(self, line):
"""Set the line number of the topmost context."""
context = self.getContext()
context.line = line
self.currents.replace(self.config.renderContext(context))
self.invoke('setContext', context=context)
def setContextColumn(self, column):
"""Set the column number of the topmost context."""
context = self.getContext()
context.column = column
self.currents.replace(self.config.renderContext(context))
self.invoke('setContext', context=context)
def setContextData(self, name=None, line=None, column=None, chars=None):
"""Set any of the name, line, or column of the topmost context."""
context = self.getContext()
if name is not None:
context.name = name
if line is not None:
context.line = line
if column is not None:
context.column = column
if chars is not None:
context.chars = chars
self.currents.replace(self.config.renderContext(context))
self.invoke('setContext', context=context)
def restoreContext(self, oldContext, strict=False):
"""Restore from an old context."""
context = self.getContext()
context.restore(oldContext, strict)
self.currents.replace(self.config.renderContext(context))
self.invoke('restoreContext', context=context)
# Plugins.
def attach(self, plugin):
"""Attach the plugin to this interpreter."""
if hasattr(plugin, 'attach'):
plugin.attach(self)
def detach(self, plugin):
"""Detach the plugin from this interpreter."""
if hasattr(plugin, 'detach'):
plugin.detach(self)
# Finalizers.
def clearFinalizers(self):
"""Clear all finalizers."""
for finalizer in self.finalizers:
self.detach(finalizer)
self.finalizers = []
def appendFinalizer(self, finalizer):
"""Register a function to be called at exit."""
self.attach(finalizer)
self.finalizers.append(finalizer)
def prependFinalizer(self, finalizer):
"""Register a function to be called at exit."""
self.attach(finalizer)
self.finalizers.insert(0, finalizer)
def setFinalizers(self, finalizers):
self.clearFinalizers()
for finalizer in finalizers:
self.attach(finalizer)
self.finalizers = finalizers
atExit = appendFinalizer # DEPRECATED
# Globals.
def fixGlobals(self):
"""Reset the globals, stamping in the pseudomodule."""
if self.globals is None:
self.globals = {}
# Make sure that there is no collision between two interpreters'
# globals.
if self.config.pseudomoduleName in self.globals:
if self.globals[self.config.pseudomoduleName] is not self:
raise ConsistencyError("interpreter pseudomodule collision in globals")
# And finally, flatten the namespaces if that option has been set.
if self.config.doFlatten:
self.flattenGlobals()
self.globals[self.config.pseudomoduleName] = self
def unfixGlobals(self):
"""Remove the pseudomodule (if present) from the globals."""
for unwantedKey in self.config.unwantedGlobalsKeys:
# None is a special sentinel that must be replaced with the name of
# the pseudomodule.
if unwantedKey is None:
unwantedKey = self.config.pseudomoduleName
if unwantedKey in self.globals:
del self.globals[unwantedKey]
def getGlobals(self):
"""Retrieve the globals."""
return self.globals
def setGlobals(self, globals):
"""Set the globals to the specified dictionary."""
self.globals = globals
self.fixGlobals()
def updateGlobals(self, otherGlobals):
"""Merge another mapping object into this interpreter's globals."""
self.globals.update(otherGlobals)
self.fixGlobals()
def clearGlobals(self):
"""Clear out the globals with a brand new dictionary."""
self.globals = {}
self.fixGlobals()
def pushGlobals(self, globals):
"""Push a globals dictionary onto the history stack."""
self.globalsHistory.push(globals)
def popGlobals(self):
"""Pop a globals dictinoary off the history stack and return
it."""
return self.globalsHistory.pop()
def saveGlobals(self, deep=True):
"""Save a copy of the globals off onto the history stack."""
if deep:
copyMethod = copy.deepcopy
else:
copyMethod = copy.copy
self.unfixGlobals()
self.globalsHistory.push(copyMethod(self.globals))
self.fixGlobals()
def restoreGlobals(self, destructive=True):
"""Restore the most recently saved copy of the globals."""
if destructive:
fetchMethod = self.globalsHistory.pop
else:
fetchMethod = self.globalsHistory.top
self.unfixGlobals()
self.globals = fetchMethod()
self.fixGlobals()
def flattenGlobals(self, skipKeys=None):
"""Flatten the contents of the pseudo-module into the globals
namespace."""
flattened = {}
if skipKeys is None:
skipKeys = self.config.unflattenableGlobalsKeys
# The pseudomodule is really a class instance, so we need to fumble
# using getattr instead of simply fumbling through the instance's
# __dict__.
for key in self.__dict__.keys():
if key not in skipKeys and not key.startswith('_'):
flattened[key] = getattr(self, key)
for key in self.__class__.__dict__.keys():
if key not in skipKeys and not key.startswith('_'):
flattened[key] = getattr(self, key)
# Stomp everything into the globals namespace.
self.globals.update(flattened)
# Prefix.
def getPrefix(self):
"""Get the current prefix."""
return self.config.prefix
def setPrefix(self, prefix):
"""Set the prefix."""
assert (prefix is None or
(isinstance(prefix, strType) and len(prefix) == 1)), prefix
self.config.prefix = prefix
# Diversions.
def stopDiverting(self):
"""Stop any diverting."""
self.top().revert()
def createDiversion(self, name):
"""Create a diversion (but do not divert to it) if it does not
already exist."""
self.top().create(name)
def retrieveDiversion(self, name, *defaults):
"""Retrieve the diversion object associated with the name."""
return self.top().retrieve(name, *defaults)
def startDiversion(self, name):
"""Start diverting to the given diversion name."""
self.top().divert(name)
def playDiversion(self, name, drop=True):
"""Play the given diversion and then drop it."""
self.top().undivert(name, drop)
def replayDiversion(self, name, drop=False):
"""Replay the diversion without dropping it."""
self.top().undivert(name, drop)
def dropDiversion(self, name):
"""Eliminate the given diversion."""
self.top().drop(name)
def playAllDiversions(self):
"""Play all existing diversions and then drop them."""
self.top().undivertAll(True)
def replayAllDiversions(self):
"""Replay all existing diversions without dropping them."""
self.top().undivertAll(False)
def dropAllDiversions(self):
"""Drop all existing diversions."""
self.top().dropAll()
def getCurrentDiversionName(self):
"""Get the name of the current diversion."""
return self.top().current
def getAllDiversionNames(self):
"""Get the names of all existing diversions."""
return self.top().names()
def isExistingDiversionName(self, name):
"""Does a diversion with this name currently exist?"""
return self.top().has(name)
# Filters.
def resetFilter(self):
"""Reset the filter stream so that it does no filtering."""
self.top().install(None)
def getFilter(self):
"""Get the top-level filter."""
filter = self.top().sink
if filter is self.top().file:
return None
else:
return filter
getFirstFilter = getFilter
def getLastFilter(self):
"""Get the last filter in the current chain."""
return self.top().last()
def getFilterCount(self):
"""Get the number of chained filters; 0 means no active
filters."""
return self.top().count()
def setFilter(self, *filters):
"""Set the filter."""
self.top().install(filters)
def prependFilter(self, filter):
"""Attach a single filter to the end of the current filter chain."""
self.top().prepend(filter)
def appendFilter(self, filter):
"""Attach a single filter to the end of the current filter chain."""
self.top().append(filter)
attachFilter = appendFilter # DEPRECATED
def setFilterChain(self, filters):
"""Set the filter."""
self.top().install(filters)
# Core-related activity.
def hasCore(self):
"""Does this interpreter have a core inserted?"""
return self.core is not None
def getCore(self):
"""Get this interpreter's core or None."""
return self.core
def insertCore(self, core=None):
"""Insert and attach the execution core."""
if core is None:
core = Core()
self.attach(core)
self.core = core
def ejectCore(self):
"""Clear the execution core, breaking a potential cyclical link."""
if self.core is not None:
self.detach(self.core)
self.core = None
def resetCore(self):
"""Reset the execution core to the default core."""
self.ejectCore()
self.insertCore()
# Extension-related activity.
def hasExtension(self):
"""Does this interpreter have an extension installed?"""
return self.extension is not None
def installExtension(self, extension):
"""Install an extension."""
if self.extension is not None:
raise ExtensionError("cannot replace an installed extension")
self.extension = extension
self.attach(extension)
# Now make sure there are token classes for them.
factory = self.config.getFactory()
for first, name in extension.mapping.items():
factory.addToken(self.config.createExtensionToken(first, name))
def uninstallExtension(self):
"""Uninstall any extension. This should only be done by the
interpreter itself at shutdown time."""
if self.extension is not None:
self.detach(self.extension)
self.extension = None
def callExtension(self, name, contents, depth, locals):
if self.extension is None:
raise ExtensionError("no extension installed")
method = getattr(self.extension, name, None)
if method is None:
raise ExtensionError("extension name `%s` not defined; define method on extension object" % name)
if self.callback is not None:
# Legacy callback behavior: only pass the contents argument.
result = method(contents)
else:
result = method(contents, depth, locals)
self.serialize(result)
return result
# Hooks.
def invokeHook(self, _name, **kwargs):
"""Invoke the hook(s) associated with the hook name, should they
exist. Stop and return on the first hook which returns a true
result."""
if self.config.verbose:
self.config.verboseFile.write("%s: %r\n" % (_name, kwargs))
if self.hooksEnabled:
for hook in self.hooks:
try:
method = getattr(hook, _name)
result = method(**kwargs)
if result:
return result
finally:
pass
return None
invoke = invokeHook
def areHooksEnabled(self):
"""Return whether or not hooks are presently enabled."""
if self.hooksEnabled is None:
# None is a special value indicate that hooks are enabled but none
# have been added yet. It is equivalent to true for testing but
# can be optimized away upon invocation.
return True
else:
return self.hooksEnabled
def enableHooks(self):
"""Enable hooks."""
self.hooksEnabled = True
def disableHooks(self):
"""Disable hooks."""
self.hooksEnabled = False
def getHooks(self):
"""Get the current hooks."""
return self.hooks
def addHook(self, hook, prepend=False):
"""Add a new hook; optionally insert it rather than appending it."""
self.attach(hook)
if self.hooksEnabled is None:
self.hooksEnabled = True
if prepend:
self.hooks.insert(0, hook)
else:
self.hooks.append(hook)
def appendHook(self, hook):
"""Append the given hook."""
self.addHook(hook, False)
def prependHook(self, hook):
"""Prepend the given hook."""
self.addHook(hook, True)
def removeHook(self, hook):
"""Remove a preexisting hook."""
self.detach(hook)
self.hooks.remove(hook)
def clearHooks(self):
"""Clear all hooks."""
for hook in self.hooks:
self.detach(hook)
self.hooks = []
self.hooksEnabled = None
# Callbacks (DEPRECATED).
def hasCallback(self):
"""Is there a custom callback registered?"""
return self.callback is not None
def getCallback(self):
"""Get the custom markup callback registered with this
interpreter, or None."""
return self.callback
def registerCallback(self, callback, extensionFactory=Extension):
"""Register a custom markup callback with this interpreter."""
if self.callback:
raise ExtensionError("old-style callbacks cannot be reregistered; use extensions instead")
if self.hasExtension():
raise ExtensionError("old-style callbacks cannot be registered over existing extension; add `angle_brackets` method to extension instead")
self.callback = callback
self.attach(callback)
extension = extensionFactory(angle_brackets=callback)
self.installExtension(extension)
def deregisterCallback(self):
"""Remove any previously registered custom markup callback
with this interpreter."""
raise ExtensionError("old-style callbacks cannot be deregistered; use extensions instead")
def _deregisterCallback(self):
"""Remove any previously registered custom markup callback
with this interpreter. Internal use only."""
if self.callback is not None:
self.detach(self.callback)
self.callback = None
def invokeCallback(self, contents):
"""Call the custom markup callback."""
if self.invoke('beforeCallback', contents=contents):
return
if self.callback is None:
raise ConfigurationError("custom markup `@<...>` invoked with no defined callback")
else:
result = self.callback(contents)
self.invoke('afterCallback', result=result)
return result
# Error handling.
def getExitCode(self):
"""Get the exit code corresponding for the current error (if
any)."""
return self.config.errorToExitCode(self.error)
def exit(self, exitCode=None):
"""Exit. If exitCode is None, use the exit code from the
current error (which may itself be None for no error)."""
if exitCode is None:
exitCode = self.getExitCode()
# If we are supposed to delete the file on error, do it.
if exitCode != self.config.successCode and self.config.deleteOnError:
if self.filespec is not None:
os.remove(self.filespec[0])
def reraise(self, *args):
"""Reraise an exception."""
raise
def dispatch(self, triple=None):
"""Dispatch an exception."""
if self.config.ignoreErrors:
return False
if triple is None:
triple = sys.exc_info()
type, error, traceback = triple
# If error is None, then this is a old-style string exception.
if error is None:
error = StringError(type)
# If it's a keyboard interrupt, quit immediately.
if isinstance(type, KeyboardInterrupt):
fatal = True
else:
fatal = False
# Now handle the exception.
self.handle((type, error, traceback), fatal)
return self.error is not None and self.config.exitOnError
def handle(self, info, fatal=False):
"""Handle an actual error that occurred."""
self.invoke('atHandle', info=info, fatal=fatal, contexts=self.currents)
type, self.error, traceback = info
if self.config.isExitError(self.error):
# No Python exception, but we're going to exit.
fatal = True
else:
useDefault = True
if self.handler is not None:
# Call the customer handler.
useDefault = self.handler(type, self.error, traceback)
if useDefault and self.error is not None:
# Call the default handler if there's still an error.
self.defaultHandler(type, self.error, traceback)
if self.config.rawErrors:
raise
if self.error is not None and (fatal or self.config.exitOnError):
self.shutdown()
self.exit()
def defaultHandler(self, type, error, traceback):
"""Report an error."""
first = True
self.flush()
sys.stderr.write('\n')
for current in self.currents:
if current is None:
current = self.config.renderContext(self.getContext())
if first:
if error is not None:
description = "error: %s" % self.config.formatError(error)
else:
description = "error"
else:
description = "from this context"
first = False
sys.stderr.write('%s: %s\n' % (current, description))
sys.stderr.flush()
def getHandler(self):
"""Get the current handler, or None for the default."""
return self.handler
def setHandler(self, handler, exitOnError=False):
"""Set the current handler. Additionally, specify whether
errors should exit (defaults to false with a custom
handler)."""
if self.handler is not None:
self.detach(self.handler)
self.attach(handler)
self.handler = handler
if exitOnError is not None:
self.config.exitOnError = exitOnError
def resetHandler(self, exitOnError=None):
"""Reset the current handler to the default."""
if self.handler is not None:
self.detach(self.handler)
self.handler = None
if exitOnError is not None:
self.config.exitOnError = exitOnError
def invokeHandler(self, *args):
"""Manually invoke the error handler with the given
exception info 3-tuple or three arguments."""
if len(args) == 1:
self.handler(*args[0])
else:
self.handler(*args)
# Emojis.
def initializeEmojiModules(self, moduleNames=None):
"""Determine which emoji module to use. If moduleNames is not
specified, use the defaults."""
return self.config.initializeEmojiModules(moduleNames)
def getEmojiModule(self, moduleName):
"""Return an abstracted emoji module by name or return
None."""
return self.config.emojiModules.get(moduleName)
def getEmojiModuleNames(self):
"""Return the emoji module names in usage in their proper
order."""
return self.config.emojiModuleNames
def substituteEmoji(self, text):
"""Substitute an emoji text or return None."""
return self.config.substituteEmoji(text)
#
# functions
#
def extract(dict, key, default):
"""Retrieve the value of the given key in this dictionary, but delete
it first. If the key is not present, use the given default."""
if key in dict:
value = dict.get(key)
del dict[key]
else:
value = default
return value
def details(level, config=None, prelim="Welcome to ", postlim=".\n",
file=sys.stdout):
"""Write some details, using the details subsystem if available."""
if config is None:
config = Configuration()
config.installFinder(dryRun=True)
try:
write = file.write
details = None
if level > Version.VERSION:
try:
import emlib
details = emlib.Details(config)
except ImportError:
raise ConfigurationError("missing emlib module; details subsystem not available")
if details is not None:
try:
details.show(level, prelim, postlim, file)
except TypeError:
raise
else:
write("%s%s version %s%s" % (prelim, __project__, __version__, postlim))
sys.stdout.flush()
finally:
config.uninstallFinder()
def expand(data,
_globals=None, _argv=None, _prefix=None, _pseudo=None, _options=None,
**kwargs):
"""Do a self-contained expansion of the given source data,
creating and shutting down an interpreter dedicated to the task.
Expects the same keyword arguments as the Interpreter constructor.
Additionally, 'name' will identify the expansion filename and
'locals', if present, represents the locals dictionary to use.
The sys.stdout object is saved off and then replaced before this
function returns. Any exception that occurs will be raised to the
caller."""
# For backward compatibility. These arguments (starting with an
# underscore) are now DEPRECATED.
if _globals is not None:
if 'globals' in kwargs:
raise CompatibilityError("keyword arguments contain extra `globals` key; use keyword arguments")
kwargs['globals'] = _globals
if _argv is not None:
if 'argv' in kwargs:
raise CompatibilityError("keyword arguments contain extra `argv` key; use keyword arguments")
kwargs['argv'] = _argv
if _prefix is not None:
raise CompatibilityError("_prefix argument to expand no longer supported; use prefix configuration variable")
if _pseudo is not None:
raise CompatibilityError("_pseudo argument to expand no longer supported; use pseudomoduleName configuration variable")
if _options is not None:
raise CompatibilityError("options dictionary is no longer supported; use configurations")
# Keyword argument compatibility checks.
for key in ['filters', 'handler', 'input', 'output']:
if kwargs.get(key):
raise ConfigurationError("argument does not make sense with an ephemeral interpreter; use a non-ephemeral interpreter instead: `%s`" % key, key=key)
# Set up the changed defaults.
if 'dispatcher' not in kwargs:
kwargs['dispatcher'] = False
# And then the local variables.
name = extract(kwargs, 'name', '<expand>')
locals = extract(kwargs, 'locals', None)
if isinstance(locals, dict) and len(locals) == 0:
# If there were no keyword arguments specified, don't use a locals
# dictionary at all.
locals = None
interp = None
result = None
try:
interp = Interpreter(**kwargs)
result = interp.expand(data, locals, name, dispatcher=None)
finally:
if interp:
interp.shutdown()
interp.unfixGlobals() # remove pseudomodule to prevent clashes
return result
def invoke(args, **kwargs):
"""Run a standalone instance of an EmPy interpreter with the given
command line arguments. See the Interpreter constructor for the
keyword arguments."""
# Get the defaults.
config = extract(kwargs, 'config', None)
errors = extract(kwargs, 'errors', ())
globals = extract(kwargs, 'globals', {})
hooks = extract(kwargs, 'hooks', [])
output = extract(kwargs, 'output', None)
for key in ['filespec', 'immediately']:
if key in kwargs:
raise ConfigurationError("argument cannot be specified with invoke: %s" % key, key=key)
# Initialize the options.
if config is None:
config = Configuration()
if errors is None:
errors = config.topLevelErrors
# Let's go!
try:
interp = None
inputFilename = None
inputMode = 'r'
outputFilename = None
outputMode = None
nullFile = False
preprocessing = []
postprocessing = []
preinitializers = []
postinitializers = []
configStatements = []
configPaths = []
immediately = False
level = Version.NONE
topics = None
# Note any configuration files from the environment.
configPath = config.environment(CONFIG_ENV)
if configPath is not None:
configPaths.append(configPath)
# Get any extra arguments from the environment.
extraArguments = config.environment(OPTIONS_ENV)
if extraArguments is not None:
extraArguments = extraArguments.split()
args = extraArguments + args
# Parse the arguments.
try:
SHORTS = 'VWZh?H:vp:qm:fkersidnc:Co:a:O:A:b:NLBP:Q:I:D:S:E:F:G:K:X:Y:wlux:y:z:gj'
LONGS = ['version', 'info', 'details', 'help', 'topic=', 'topics=', 'extended-help=', 'verbose', 'prefix=', 'no-prefix', 'no-output', 'pseudomodule=', 'module=', 'flatten', 'keep-going', 'ignore-errors', 'raw-errors', 'brief-errors', 'verbose-errors', 'interactive', 'delete-on-error', 'no-proxy', 'no-override-stdout', 'config=', 'configuration=', 'config-file=', 'configuration-file=', 'config-variable=', 'configuration-variable=', 'ignore-missing-config', 'output=' 'append=', 'output-binary=', 'append-binary=', 'output-mode=', 'input-mode=', 'buffering=', 'default-buffering', 'no-buffering', 'line-buffering', 'full-buffering', 'preprocess=', 'postprocess=', 'import=', 'define=', 'string=', 'execute=', 'file=', 'postfile=', 'postexecute=', 'expand=', 'postexpand=', 'preinitializer=', 'postinitializer=', 'pause-at-end', 'relative-path', 'replace-newlines', 'no-replace-newlines', 'ignore-bangpaths', 'no-ignore-bangpaths', 'expand-user', 'no-expand-user', 'auto-validate-icons', 'no-auto-validate-icons', 'none-symbol=', 'no-none-symbol', 'starting-line=', 'starting-column=', 'emoji-modules=', 'no-emoji-modules', 'disable-emoji-modules', 'ignore-emoji-not-found', 'binary', 'input-binary', 'unicode', 'encoding=', 'unicode-encoding=', 'input-encoding=', 'unicode-input-encoding=', 'output-encoding=', 'unicode-output-encoding=', 'errors=', 'unicode-errors=', 'input-errors=', 'unicode-input-errors=', 'output-errors=', 'unicode-output-errors=', 'normalization-form=', 'unicode-normalization-form=', 'auto-play-diversions', 'no-auto-play-diversions', 'check-variables', 'no-check-variables', 'path-separator=', 'enable-modules', 'disable-modules', 'module-extension=', 'module-finder-index=', 'enable-import-output', 'disable-import-output', 'context-format=', 'success-code=', 'failure-code=', 'unknown-code=', 'null-hook', 'requirements=']
pairs, argv = getopt.getopt(args, SHORTS, LONGS)
except getopt.GetoptError:
type, error, traceback = sys.exc_info()
if error.args[1] in ['H', 'topic', 'topics', 'extended-help']:
# A missing argument with -H should be interpreted as -H all.
pairs = []
topics = 'all'
else:
raise InvocationError(*error.args)
for option, argument in pairs:
if option in ['-V', '--version']:
level += 1
elif option in ['-W', '--info']:
if level < Version.INFO:
level = Version.INFO
else:
level += 1
elif option in ['-Z', '--details']:
level = Version.ALL
elif option in ['-h', '-?', '--help']:
if not topics:
topics = 'default'
elif topics == 'default':
topics = 'more'
else:
topics = 'all'
elif option in ['-H', '--topic', '--topics', '--extended-help']:
topics = argument
if ',' in topics:
topics = topics.split(',')
else:
topics = [topics]
elif option in ['-v', '--verbose']:
config.verbose = True
elif option in ['-p', '--prefix']:
config.prefix = argument
elif option in ['--no-prefix']:
config.prefix = None
elif option in ['-q', '--no-output']:
nullFile = True
elif option in ['-m', '--pseudomodule', '--module']:
config.pseudomoduleName = argument
elif option in ['-f', '--flatten']:
config.doFlatten = True
elif option in ['-k', '--keep-going']:
config.exitOnError = False
elif option in ['-e', '--ignore-errors']:
config.ignoreErrors = True
config.exitOnError = False
elif option in ['-r', '--raw-errors']:
config.rawErrors = True
elif option in ['-s', '--brief-errors']:
config.verboseErrors = False
elif option in ['--verbose-errors']:
config.verboseErrors = True
elif option in ['-i', '--interactive']:
config.goInteractive = True
elif option in ['-d', '--delete-on-error']:
config.deleteOnError = True
elif option in ['-n', '--no-proxy', '--no-override-stdout']:
config.useProxy = False
elif option in ['--config', '--configuration']:
configStatements.append(argument)
elif option in ['-c', '--config-file', '--configuration-file']:
configPaths.append(argument)
elif option in ['--config-variable', '--configuration-variable']:
config.configVariableName = argument
elif option in ['-C', '--ignore-missing-config']:
config.missingConfigIsError = False
elif option in ['-o', '--output']:
outputFilename = argument
outputMode = 'w'
elif option in ['-a', '--append']:
outputFilename = argument
outputMode = 'a'
elif option in ['-O', '--output-binary']:
outputFilename = argument
outputMode = 'wb'
elif option in ['-A', '--append-binary']:
outputFilename = argument
outputMode = 'ab'
elif option in ['--output-mode']:
outputMode = argument
elif option in ['--input-mode']:
inputMode = argument
elif option in ['-b', '--buffering']:
config.setBuffering(argument)
elif option in ['--default-buffering']:
config.setBuffering(config.defaultBuffering)
elif option in ['-N', '--no-buffering']:
config.setBuffering(config.noBuffering)
elif option in ['-L', '--line-buffering']:
config.setBuffering(config.lineBuffering)
elif option in ['-B', '--full-buffering']:
config.setBuffering(config.fullBuffering)
elif option in ['-I', '--import']:
for module in argument.split(','):
module = module.strip()
preprocessing.append(ImportCommand(module))
elif option in ['-D', '--define']:
preprocessing.append(DefineCommand(argument))
elif option in ['-S', '--string']:
preprocessing.append(StringCommand(argument))
elif option in ['-P', '--preprocess']:
preprocessing.append(DocumentCommand(argument))
elif option in ['-Q', '--postprocess']:
postprocessing.append(DocumentCommand(argument))
elif option in ['-E', '--execute']:
preprocessing.append(ExecuteCommand(argument))
elif option in ['-F', '--file']:
preprocessing.append(FileCommand(argument))
elif option in ['-G', '--postfile']:
postprocessing.append(FileCommand(argument))
elif option in ['-K', '--postexecute']:
postprocessing.append(ExecuteCommand(argument))
elif option in ['-X', '--expand']:
preprocessing.append(ExpandCommand(argument))
elif option in ['-Y', '--postexpand']:
postprocessing.append(ExpandCommand(argument))
elif option in ['--preinitializer']:
preinitializers.append(argument)
elif option in ['--postinitializer']:
postinitializers.append(argument)
elif option in ['-w', '--pause-at-end']:
config.pauseAtEnd = True
elif option in ['-l', '--relative-path']:
config.relativePath = True
elif option in ['--replace-newlines']:
config.replaceNewlines = True
elif option in ['--no-replace-newlines']:
config.replaceNewlines = False
elif option in ['--ignore-bangpaths']:
config.ignoreBangpaths = True
elif option in ['--no-ignore-bangpaths']:
config.ignoreBangpaths = False
elif option in ['--expand-user']:
config.expandUserConstructions = True
elif option in ['--no-expand-user']:
config.expandUserConstructions = False
elif option in ['--auto-validate-icons']:
config.autoValidateIcons = True
elif option in ['--no-auto-validate-icons']:
config.autoValidateIcons = False
elif option in ['--none-symbol']:
config.noneSymbol = argument
elif option in ['--no-none-symbol']:
config.noneSymbol = None
elif option in ['--starting-line']:
config.startingLine = int(argument)
elif option in ['--starting-column']:
config.startingColumn = int(argument)
elif option in ['--emoji-modules']:
moduleNames = [x.strip() for x in argument.split(',')]
if moduleNames == ['None'] or moduleNames == ['']:
moduleNames = None
config.emojiModuleNames = moduleNames
elif option in ['--no-emoji-modules']:
config.emojiModuleNames = config.defaultNoEmojiModuleNames
elif option in ['--disable-emoji-modules']:
config.emojiModuleNames = None
elif option in ['--ignore-emoji-not-found']:
config.emojiNotFoundIsError = False
elif option in ['-u', '--binary', '--input-binary', '--unicode']:
config.enableBinary()
elif option in ['-x', '--encoding', '--unicode-encoding']:
config.enableBinary(major, minor)
config.inputEncoding = config.outputEncoding = argument
elif option in ['--input-encoding', '--unicode-input-encoding']:
config.enableBinary(major, minor)
config.inputEncoding = argument
elif option in ['--output-encoding', '--unicode-output-encoding']:
config.enableBinary(major, minor)
config.outputEncoding = argument
elif option in ['-y', '--errors', '--unicode-errors']:
config.enableBinary(major, minor)
config.inputErrors = config.outputErrors = argument
elif option in ['--input-errors', '--unicode-input-errors']:
config.enableBinary(major, minor)
config.inputErrors = argument
elif option in ['--output-errors', '--unicode-output-errors']:
config.enableBinary(major, minor)
config.outputErrors = argument
elif option in ['-z', '--normalization-form', '--unicode-normalization-form']:
if argument == 'none' or argument == 'None':
argument = ''
config.normalizationForm = argument
elif option in ['--auto-play-diversions']:
config.autoPlayDiversions = True
elif option in ['--no-auto-play-diversions']:
config.autoPlayDiversions = False
elif option in ['--check-variables']:
config.checkVariables = True
elif option in ['--no-check-variables']:
config.checkVariables = False
elif option in ['--path-separator']:
config.pathSeparator = argument
elif option in ['--enable-modules']:
config.supportModules = True
elif option in ['-g', '--disable-modules']:
config.supportModules = False
config.moduleExtension = argument
elif option in ['--module-extension']:
config.moduleExtension = argument
elif option in ['--module-finder-index']:
config.moduleFinderIndex = int(argument)
elif option in ['--enable-import-output']:
config.enableImportOutput = True
elif option in ['-j', '--disable-import-output']:
config.enableImportOutput = False
elif option in ['--context-format']:
config.setContextFormat(argument)
Context.format = config.contextFormat
elif option in ['--success-code']:
config.successCode = int(argument)
elif option in ['--failure-code']:
config.failureCode = int(argument)
elif option in ['--unknown-code']:
config.unknownCode = int(argument)
elif option in ['--null-hook']:
try:
import emlib
hooks.append(emlib.Hook())
except ImportError:
raise InvocationError("missing emlib module; --null-hook not available")
elif option in ['--requirements']:
try:
import emlib
det = emlib.Details()
if not det.checkRequirements(argument):
sys.exit(config.skipCode)
except ImportError:
raise InvocationError("missing emlib module; cannot check requirements")
else:
assert False, "unhandled option: %s" % option
# Show the details and exit if desired.
if level > 0:
details(level, config)
return config.successCode
# Load any configuration files.
for configStatement in configStatements:
config.run(configStatement)
for configPath in configPaths:
config.path(configPath)
# Show the help if desired.
if topics:
try:
import emhelp
usage = emhelp.Usage(config)
usage.hello()
usage.show(topics)
except ImportError:
raise InvocationError("missing emhelp subsystem module; no help available")
return config.successCode
# Set up the main script filename and the arguments.
if not argv:
argv.append(None)
else:
inputFilename = argv[0]
# Do sanity checks on the configuration.
config.check(inputFilename, outputFilename)
# Now initialize the output file.
if nullFile:
output = NullFile()
filespec = None
elif outputFilename is not None:
if output is not None:
raise InvocationError("cannot specify more than one output")
filespec = outputFilename, outputMode, config.buffering
output = config.open(*filespec)
else:
# So this is stdout. Check the encoding.
if not config.isDefaultEncodingErrors(asInput=False):
config.reconfigure(sys.stdout,
config.buffering,
config.outputEncoding,
config.outputErrors)
filespec = None
# Run any pre-initializers.
for initializer in preinitializers:
file = config.open(initializer, 'r')
try:
data = file.read()
execFunc(data, globals, locals())
finally:
file.close()
# Get ready!
kwargs['argv'] = argv
kwargs['config'] = config
kwargs['filespec'] = filespec
kwargs['globals'] = globals
kwargs['hooks'] = hooks
kwargs['immediately'] = immediately
kwargs['output'] = output
try:
# Create the interpreter.
interp = Interpreter(**kwargs)
# Check it.
interp.check()
# Run it.
interp.go(
inputFilename, inputMode, preprocessing, postprocessing)
exitCode = interp.getExitCode()
finally:
# Finally, handle any cleanup.
if interp is not None:
interp.shutdown()
# Run any post-initializers.
for initializer in postinitializers:
try:
file = config.open(initializer, 'r')
data = file.read()
execFunc(data, globals, locals())
finally:
file.close()
except SystemExit:
type, error, traceback = sys.exc_info()
if len(error.args) > 0:
exitCode = error.args[0] # okay even if a string
else:
exitCode = config.successCode
except KeyboardInterrupt:
if config.rawErrors:
raise
type, error, traceback = sys.exc_info()
sys.stderr.write(config.formatError(error, "ERROR: ", "\n"))
exitCode = config.failureCode
except errors:
if config.rawErrors:
raise
type, error, traceback = sys.exc_info()
if not interp:
sys.stderr.write(config.formatError(error, "ERROR: ", "\n"))
exitCode = config.unknownCode
except:
if config.rawErrors:
raise
type, error, traceback = sys.exc_info()
if not interp:
sys.stderr.write(config.formatError(error, "ERROR: ", "\n"))
exitCode = config.errorToExitCode(error)
# Old versions of Python 3.x don't flush sys.__stdout__ when redirecting
# stdout for some reason.
if sys.__stdout__ is not None:
sys.__stdout__.flush()
return exitCode
#
# main
#
def main():
exitCode = invoke(sys.argv[1:],
executable=sys.argv[0], errors=None, origin=True)
sys.exit(exitCode)
if __name__ == '__main__': main()
|