mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Compare commits
3072 Commits
0.3.1
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23813ddbcd | ||
|
|
1ce37bf2d4 | ||
|
|
0e66439212 | ||
|
|
8018ba689f | ||
|
|
26f817a55b | ||
|
|
938d42184f | ||
|
|
1385c4e472 | ||
|
|
fc637a155d | ||
|
|
7d54ba502e | ||
|
|
e80a14ba12 | ||
|
|
30ae48b24b | ||
|
|
1a959cf7f3 | ||
|
|
ca412fce5d | ||
|
|
f95b7468f7 | ||
|
|
1cb24c5ebb | ||
|
|
ce79cd4853 | ||
|
|
dcb273ba68 | ||
|
|
dd8d250ba3 | ||
|
|
3db172c025 | ||
|
|
fb531ecce3 | ||
|
|
9701c46d86 | ||
|
|
021c78655e | ||
|
|
411230a46c | ||
|
|
b69644196a | ||
|
|
a6fee92683 | ||
|
|
f4696ea890 | ||
|
|
0237a1d520 | ||
|
|
6bbe24a725 | ||
|
|
f9e4988cf5 | ||
|
|
a5775e0c07 | ||
|
|
bc3d00e879 | ||
|
|
52305c0c72 | ||
|
|
7011942e4e | ||
|
|
438925401f | ||
|
|
2fabf38d8f | ||
|
|
d7d936575c | ||
|
|
b5ff41c219 | ||
|
|
e1d9a1123e | ||
|
|
b9efe10537 | ||
|
|
d3272e752b | ||
|
|
f3b83efcee | ||
|
|
1a013930a7 | ||
|
|
c513b923df | ||
|
|
cf3b6397b2 | ||
|
|
2a163609cd | ||
|
|
4eb9534646 | ||
|
|
ab5bc43359 | ||
|
|
06f73f2ae1 | ||
|
|
c845861c4f | ||
|
|
a136f9047b | ||
|
|
948924a14b | ||
|
|
ae6c16093e | ||
|
|
a6f13b98fb | ||
|
|
4daa526dc1 | ||
|
|
d073964aaa | ||
|
|
ee006af13e | ||
|
|
de06fc0923 | ||
|
|
ae3804fb21 | ||
|
|
d51069bc7f | ||
|
|
0063a6d18b | ||
|
|
cea21f953d | ||
|
|
62b081b893 | ||
|
|
26f5bbf077 | ||
|
|
ccf1508a06 | ||
|
|
c5143aa82f | ||
|
|
0a2461a704 | ||
|
|
276a0e6c42 | ||
|
|
d8dee81157 | ||
|
|
e8d7437d91 | ||
|
|
53941295a2 | ||
|
|
1fe4485c10 | ||
|
|
50f235aded | ||
|
|
b902ffdcf8 | ||
|
|
9c0557820b | ||
|
|
150b9103a8 | ||
|
|
48ad238349 | ||
|
|
67e66bdc75 | ||
|
|
e16f1aa657 | ||
|
|
6e56d935cf | ||
|
|
20cb8843e0 | ||
|
|
a4579a98b2 | ||
|
|
883ce9d273 | ||
|
|
723766ef69 | ||
|
|
3ed8c91fef | ||
|
|
a1a87dc8ca | ||
|
|
ea2a3d9d84 | ||
|
|
9c188b4da9 | ||
|
|
ab72b292bd | ||
|
|
f4f035013d | ||
|
|
c77c56d99c | ||
|
|
b432d4cbdc | ||
|
|
5a5230a400 | ||
|
|
8c988711dd | ||
|
|
f2ef252724 | ||
|
|
3a56f37505 | ||
|
|
4b9416a558 | ||
|
|
2d8f8ab818 | ||
|
|
a364f86fd5 | ||
|
|
7273d76cf2 | ||
|
|
54589bf255 | ||
|
|
0871bc8a2d | ||
|
|
f26016049d | ||
|
|
451bdcc9da | ||
|
|
8253253fc7 | ||
|
|
56269f704a | ||
|
|
078e23de27 | ||
|
|
ddfcb25957 | ||
|
|
e467b67ef7 | ||
|
|
460b1d39ed | ||
|
|
11e991fb95 | ||
|
|
ef375bec26 | ||
|
|
4059a032a7 | ||
|
|
e8711edd2d | ||
|
|
f8e0eeb579 | ||
|
|
32e6f8dd81 | ||
|
|
e36cd994e8 | ||
|
|
7ebffd063b | ||
|
|
5ef91c22de | ||
|
|
79395de9f3 | ||
|
|
2b19ba79a8 | ||
|
|
9d77b25858 | ||
|
|
d09179a7ee | ||
|
|
8fe23b8a15 | ||
|
|
ff05dae11c | ||
|
|
1da29ff003 | ||
|
|
a702e9ccc7 | ||
|
|
9ee27526bc | ||
|
|
fb37b5ecc6 | ||
|
|
3bfa5ab8c0 | ||
|
|
a0632ae2c5 | ||
|
|
f3c2198ff0 | ||
|
|
2bcbe96e6d | ||
|
|
f5357692e8 | ||
|
|
e0689bad7c | ||
|
|
1ac55db966 | ||
|
|
a7c9b98b5a | ||
|
|
2bb7dd8cf9 | ||
|
|
f6c382c595 | ||
|
|
72679af4b2 | ||
|
|
27db8e5847 | ||
|
|
68be554684 | ||
|
|
2e5077fe12 | ||
|
|
673db1d71a | ||
|
|
3cfb0fe7bc | ||
|
|
9e066c4f50 | ||
|
|
3bce5566fa | ||
|
|
3b67d0f5d2 | ||
|
|
77070eb6ca | ||
|
|
d6abdc4b79 | ||
|
|
fc00fc22d2 | ||
|
|
865e75dd99 | ||
|
|
9f019a5b86 | ||
|
|
ad6cc8f0a2 | ||
|
|
4a46e84eb9 | ||
|
|
949a620811 | ||
|
|
315865a6f7 | ||
|
|
c79aefecee | ||
|
|
3fbfbf53c2 | ||
|
|
3bd79e6b5b | ||
|
|
8e8b70fb33 | ||
|
|
29530049fe | ||
|
|
5b0177d20a | ||
|
|
479b2dc997 | ||
|
|
cb0cffbd7c | ||
|
|
cc829b2756 | ||
|
|
dfe99f647d | ||
|
|
b3098c3058 | ||
|
|
8f19dff19d | ||
|
|
320355f633 | ||
|
|
108461f637 | ||
|
|
bf7bb1bf3b | ||
|
|
09c27bb440 | ||
|
|
6dfc68b225 | ||
|
|
17376ace28 | ||
|
|
9eacdfca48 | ||
|
|
68a0bc030b | ||
|
|
edcf3002de | ||
|
|
67630ffbfe | ||
|
|
6ed17c3cb5 | ||
|
|
002fc1122e | ||
|
|
ae5c9119c9 | ||
|
|
4aa73ddab2 | ||
|
|
7576d68060 | ||
|
|
3bab7a9086 | ||
|
|
e10a27b1ae | ||
|
|
3b364608d9 | ||
|
|
1c4361803d | ||
|
|
22d4f43ad4 | ||
|
|
02a2b19839 | ||
|
|
ae7ff9c481 | ||
|
|
d877c30417 | ||
|
|
8bd4c5137e | ||
|
|
dc12bff0f4 | ||
|
|
e1dd3d43d5 | ||
|
|
a5cebc3242 | ||
|
|
ad5a55c1e3 | ||
|
|
e4d35b08ea | ||
|
|
02932384d6 | ||
|
|
0325fd429e | ||
|
|
4db0aca471 | ||
|
|
83002d7369 | ||
|
|
90cc888f4b | ||
|
|
cf23884656 | ||
|
|
c3c9292c8b | ||
|
|
ee5e9d0001 | ||
|
|
181e4e7124 | ||
|
|
eb99a8ecf1 | ||
|
|
acc167fc44 | ||
|
|
c2910c06f3 | ||
|
|
ac3e067230 | ||
|
|
be7841f9c1 | ||
|
|
3e66fb508a | ||
|
|
f2ad84a489 | ||
|
|
926d69b50a | ||
|
|
8d1c68c9a0 | ||
|
|
d9fff99718 | ||
|
|
0f25d145fd | ||
|
|
7fd0da92d3 | ||
|
|
c027ffc2ba | ||
|
|
192ba371e4 | ||
|
|
d25195ed0e | ||
|
|
9004089fd1 | ||
|
|
f6e2fcffe7 | ||
|
|
bb77ac6284 | ||
|
|
c2141a765f | ||
|
|
dd1cbac692 | ||
|
|
c98d26e508 | ||
|
|
5e408d65f4 | ||
|
|
73b5b69bd8 | ||
|
|
e79df4a6fb | ||
|
|
32e16fe7da | ||
|
|
88be7bb16a | ||
|
|
f90a5cf650 | ||
|
|
6c2c8421d7 | ||
|
|
6e895fbac4 | ||
|
|
543fcc841c | ||
|
|
6c91c5bb2a | ||
|
|
3c297d478a | ||
|
|
6ed2f15b67 | ||
|
|
6b67067e9a | ||
|
|
04ffa3891c | ||
|
|
ba2b619c0c | ||
|
|
c8ddbd2326 | ||
|
|
3ebcab70c0 | ||
|
|
51e7200d11 | ||
|
|
d7a319d967 | ||
|
|
330b18f2fe | ||
|
|
82e8b200db | ||
|
|
3f718f9942 | ||
|
|
363d19839f | ||
|
|
68aece59c9 | ||
|
|
b3d6d2f247 | ||
|
|
e6d9ea6bfe | ||
|
|
59382f385a | ||
|
|
9db00f741c | ||
|
|
e5f2d2d3b9 | ||
|
|
7fb743b1be | ||
|
|
6c498fc4a7 | ||
|
|
b8f7ae4265 | ||
|
|
1d42ee565f | ||
|
|
9794ab7fdf | ||
|
|
dc81c740cf | ||
|
|
f10f441854 | ||
|
|
1fa676fd07 | ||
|
|
5648a3346f | ||
|
|
02c454bdb4 | ||
|
|
049d44b1e0 | ||
|
|
f6b6b18b62 | ||
|
|
7f8cdddbbf | ||
|
|
625e5bf012 | ||
|
|
a2afaf0f13 | ||
|
|
956267c49e | ||
|
|
be43bb6dbf | ||
|
|
6ab1f806ba | ||
|
|
f0e23aacc2 | ||
|
|
a5f48eaaa1 | ||
|
|
b427f31164 | ||
|
|
7df0801db3 | ||
|
|
3a793ce716 | ||
|
|
adafaf222b | ||
|
|
f6371de4a1 | ||
|
|
fb1218d6ba | ||
|
|
3f8a0b12eb | ||
|
|
2e5c2be7fa | ||
|
|
0d67fd69e2 | ||
|
|
952be48944 | ||
|
|
0913563bbe | ||
|
|
1ab76d012b | ||
|
|
f0acc67855 | ||
|
|
9ebdf10c11 | ||
|
|
7bb2fb0755 | ||
|
|
43ef2eabbe | ||
|
|
dc0c814671 | ||
|
|
a7ea01a135 | ||
|
|
cbfb313de2 | ||
|
|
adb169e65b | ||
|
|
5081f76faf | ||
|
|
f2e055f7d6 | ||
|
|
f2f20175b3 | ||
|
|
a693a0c8aa | ||
|
|
71380bead9 | ||
|
|
5a45d41df0 | ||
|
|
9b0c668f74 | ||
|
|
dc65255fd2 | ||
|
|
a9fd4bf41f | ||
|
|
5058090a3f | ||
|
|
b929a50647 | ||
|
|
f8862e4eed | ||
|
|
18c6c16e2a | ||
|
|
d5921d16af | ||
|
|
9140ef583a | ||
|
|
af41d11faf | ||
|
|
175f12b664 | ||
|
|
b18b71b2db | ||
|
|
fdb49d83c5 | ||
|
|
37707081f8 | ||
|
|
764e4de061 | ||
|
|
b842a6c6c6 | ||
|
|
9669118d3c | ||
|
|
67eedddcd7 | ||
|
|
57ca1d59a6 | ||
|
|
9a0410dab4 | ||
|
|
b80c2bd5ec | ||
|
|
a5b9f0e80b | ||
|
|
caf8d55da5 | ||
|
|
c79baa3317 | ||
|
|
f0bf8100c9 | ||
|
|
1f1be5e29e | ||
|
|
4f1cf6d401 | ||
|
|
3e1aa7cbe6 | ||
|
|
b9f9ba145e | ||
|
|
3d91197b38 | ||
|
|
2827eca293 | ||
|
|
aac207003f | ||
|
|
f82b8d8eb7 | ||
|
|
8d61a2217b | ||
|
|
640cbd7c4a | ||
|
|
aa12accdac | ||
|
|
fd2117355e | ||
|
|
36025386f3 | ||
|
|
330aa08488 | ||
|
|
a2c3e65b81 | ||
|
|
8108b6a153 | ||
|
|
63a1c0e95c | ||
|
|
c98939a7c1 | ||
|
|
9f1429d95f | ||
|
|
73218f42d5 | ||
|
|
f197699e3c | ||
|
|
898da7f58c | ||
|
|
0ee07e3d0a | ||
|
|
49048504f6 | ||
|
|
e3997ad1b8 | ||
|
|
da01e617de | ||
|
|
891538d924 | ||
|
|
3096d77ec5 | ||
|
|
9e2d03428c | ||
|
|
d1e9763ff3 | ||
|
|
2b1b0ba805 | ||
|
|
e09b66dd86 | ||
|
|
a24ee58961 | ||
|
|
1721f62804 | ||
|
|
8f71ff4b47 | ||
|
|
4bbabe5810 | ||
|
|
7544628268 | ||
|
|
7e637e8cbd | ||
|
|
7e5e026941 | ||
|
|
27aed85599 | ||
|
|
d201c48e1c | ||
|
|
9cf7bcd64a | ||
|
|
31edcfa97e | ||
|
|
eac45727d2 | ||
|
|
28acbc66f9 | ||
|
|
a49ce5bf34 | ||
|
|
7b5ac61026 | ||
|
|
ad66d9acd0 | ||
|
|
00dd9a5ed1 | ||
|
|
d5a90e5c1f | ||
|
|
72f397c6df | ||
|
|
e009cc0c3b | ||
|
|
eed618d858 | ||
|
|
87fc4540c4 | ||
|
|
a09afab912 | ||
|
|
3d9688402a | ||
|
|
b61dfdc534 | ||
|
|
6d7d74cc0b | ||
|
|
3f49f42702 | ||
|
|
5afa3493a1 | ||
|
|
1adda8a73d | ||
|
|
344b7a5abd | ||
|
|
d9c4c95369 | ||
|
|
403c2be01d | ||
|
|
5cc9eab2dd | ||
|
|
b275a90cf9 | ||
|
|
43851c21b9 | ||
|
|
611b122ed7 | ||
|
|
1a4964b741 | ||
|
|
106f1c9f37 | ||
|
|
c45f69977b | ||
|
|
2703f94c3e | ||
|
|
9900c761ca | ||
|
|
959b088d25 | ||
|
|
1c39fdb7f9 | ||
|
|
3706c5376e | ||
|
|
e6bcd64066 | ||
|
|
2ebd7026e4 | ||
|
|
6826557884 | ||
|
|
1f6b4c6bf1 | ||
|
|
b9cbd5133b | ||
|
|
902985def7 | ||
|
|
90c5464901 | ||
|
|
da440dbbbe | ||
|
|
1a9b10ece5 | ||
|
|
dd632363c8 | ||
|
|
f7556b00c1 | ||
|
|
fab1c0cc01 | ||
|
|
5dd88ee5ae | ||
|
|
2722bc06de | ||
|
|
3dae07cd60 | ||
|
|
fddd7cb690 | ||
|
|
410721740d | ||
|
|
e3ac1bf8dc | ||
|
|
3a8fb76014 | ||
|
|
a91127c91a | ||
|
|
af0c2526a7 | ||
|
|
f42ffd61a1 | ||
|
|
3f92c3ad1c | ||
|
|
9282a870db | ||
|
|
7a6dbd6624 | ||
|
|
6c3dd2885d | ||
|
|
c9cfb3d606 | ||
|
|
e1ecb87f32 | ||
|
|
ea5a6cd9c0 | ||
|
|
6b5c5a9e92 | ||
|
|
211649d148 | ||
|
|
4ebc3112d9 | ||
|
|
6db7910ca4 | ||
|
|
8d3bc4cb54 | ||
|
|
20c6505bb9 | ||
|
|
372280ede4 | ||
|
|
82432be962 | ||
|
|
40c84f51c8 | ||
|
|
5408627594 | ||
|
|
fb6520e5cc | ||
|
|
e9b45a1419 | ||
|
|
2acf76bbaf | ||
|
|
dc95db7ae3 | ||
|
|
20ae3ccda2 | ||
|
|
f1d0fc31c5 | ||
|
|
56c3a37266 | ||
|
|
8c016157f4 | ||
|
|
907ce4d895 | ||
|
|
2180f535d8 | ||
|
|
3c62b5679f | ||
|
|
dfcb07cd93 | ||
|
|
2d676e7f4d | ||
|
|
3e9f825e1d | ||
|
|
4abe4c5bf0 | ||
|
|
a1203ae207 | ||
|
|
5b6a479a1d | ||
|
|
38f742aa8d | ||
|
|
1b9dc4ef14 | ||
|
|
4141e74e14 | ||
|
|
2ef77f82e1 | ||
|
|
8be5230a9b | ||
|
|
247044a805 | ||
|
|
68cf736a9f | ||
|
|
53fa525fc9 | ||
|
|
b11b8e66ce | ||
|
|
79e17cb1cf | ||
|
|
2f181efed8 | ||
|
|
9a5de28b79 | ||
|
|
31480243f0 | ||
|
|
f2765982cd | ||
|
|
62edc4722a | ||
|
|
0768cf80d3 | ||
|
|
f868f81db6 | ||
|
|
6b768ff702 | ||
|
|
2ea5960998 | ||
|
|
2a61237341 | ||
|
|
87f5c7dd29 | ||
|
|
cda9f8247f | ||
|
|
d04490ee25 | ||
|
|
b2b337588d | ||
|
|
c57f4decd5 | ||
|
|
423b6ca59b | ||
|
|
caca67cf27 | ||
|
|
5f1d5d2815 | ||
|
|
f27490c92b | ||
|
|
d2d2822bb9 | ||
|
|
47c2acd40e | ||
|
|
2b06a0b172 | ||
|
|
f1ddb4fc5e | ||
|
|
97790a88b2 | ||
|
|
03b7c4ebb8 | ||
|
|
f49af3fd48 | ||
|
|
9ddd07a656 | ||
|
|
9a297aad2b | ||
|
|
005860cc0b | ||
|
|
0c14ad195e | ||
|
|
d1ea35962b | ||
|
|
40fc7c210d | ||
|
|
430eb5ffe4 | ||
|
|
e7d2b57dcc | ||
|
|
21d549caf6 | ||
|
|
ed032d31fb | ||
|
|
4483c31c65 | ||
|
|
be6025ab03 | ||
|
|
7d63f65f21 | ||
|
|
073adbd8da | ||
|
|
7c0981b9ce | ||
|
|
a733c8baa7 | ||
|
|
83e3785a1c | ||
|
|
f4eaee1a62 | ||
|
|
021286e9ab | ||
|
|
7c05bef5ff | ||
|
|
b58a910026 | ||
|
|
0f61464c54 | ||
|
|
a14f856af4 | ||
|
|
bdfe2cf923 | ||
|
|
61b3b4f42b | ||
|
|
f26752c5ef | ||
|
|
04b1febd3b | ||
|
|
bb36783206 | ||
|
|
93865e6523 | ||
|
|
8530439202 | ||
|
|
9a20be7b9e | ||
|
|
fcd1bb6e9b | ||
|
|
e9ab35fdd7 | ||
|
|
ca5a3a7c88 | ||
|
|
c578ac0b21 | ||
|
|
331029e043 | ||
|
|
3f20619ee0 | ||
|
|
da745ba48e | ||
|
|
5c631e5129 | ||
|
|
7e4727001d | ||
|
|
85eca218af | ||
|
|
d248a04cae | ||
|
|
a854ef2a2b | ||
|
|
45d93f4b20 | ||
|
|
0355885651 | ||
|
|
e645761142 | ||
|
|
ae8d8c7926 | ||
|
|
55737ede63 | ||
|
|
bf2b993c93 | ||
|
|
5c5aff8fd9 | ||
|
|
5b0fb03fc4 | ||
|
|
d81db8afc5 | ||
|
|
13a89be61b | ||
|
|
2266ba7f47 | ||
|
|
6c12152aeb | ||
|
|
82e9b5d9e1 | ||
|
|
fa4f84ce3f | ||
|
|
ead2d98557 | ||
|
|
ce8952b1f5 | ||
|
|
cbcc19751e | ||
|
|
46abff6880 | ||
|
|
45d81296e4 | ||
|
|
0046c627f1 | ||
|
|
fbab8fc5be | ||
|
|
e669944efe | ||
|
|
f680f8a181 | ||
|
|
abfb51efc9 | ||
|
|
28bda1d34f | ||
|
|
89889acc08 | ||
|
|
4aeec7f2dc | ||
|
|
ab32c785ea | ||
|
|
d9020c5841 | ||
|
|
2bb9173caf | ||
|
|
ed5bffeec6 | ||
|
|
fc1c27896b | ||
|
|
a27d812286 | ||
|
|
04cf5dacca | ||
|
|
3b91466f62 | ||
|
|
7620c28482 | ||
|
|
9ba155418b | ||
|
|
6f07745600 | ||
|
|
73941b2255 | ||
|
|
375b5472ed | ||
|
|
86134e1ca1 | ||
|
|
d1c73ac5b8 | ||
|
|
5ab631db9d | ||
|
|
62766fd4a9 | ||
|
|
9a0511be0b | ||
|
|
5c29074596 | ||
|
|
745efbd8e8 | ||
|
|
68ad332833 | ||
|
|
a98c6469d1 | ||
|
|
798f9e80cd | ||
|
|
c55a9ff728 | ||
|
|
5a5ddbbed4 | ||
|
|
624ea3b13d | ||
|
|
3504993f28 | ||
|
|
42628a53b9 | ||
|
|
4c5f6332e8 | ||
|
|
ff5411110c | ||
|
|
c058add095 | ||
|
|
e9001ec3e5 | ||
|
|
bc108dfe86 | ||
|
|
1d106c3411 | ||
|
|
2f810ae0f6 | ||
|
|
9b9c74973e | ||
|
|
be0c3ca3ca | ||
|
|
2f2a7da185 | ||
|
|
98021543d8 | ||
|
|
16066cc0fe | ||
|
|
4e8a945277 | ||
|
|
62ca60ad82 | ||
|
|
3cbd08f63c | ||
|
|
53ddc7ef48 | ||
|
|
77eade37f1 | ||
|
|
33fca0d77b | ||
|
|
257b0c08a7 | ||
|
|
2ca3275097 | ||
|
|
7b33208455 | ||
|
|
25619aad1d | ||
|
|
7727acc6a7 | ||
|
|
7eb18210ca | ||
|
|
1f6e47ce9c | ||
|
|
43e2df31e0 | ||
|
|
5f08a01cf4 | ||
|
|
903a5f28a0 | ||
|
|
9a12a8d532 | ||
|
|
c71f4ada21 | ||
|
|
0a6a6f8ddb | ||
|
|
3865fdbf5b | ||
|
|
baba1f9447 | ||
|
|
c315033091 | ||
|
|
b1cddc4e0c | ||
|
|
3a81f94aa8 | ||
|
|
7b89d8252e | ||
|
|
888e0eeccb | ||
|
|
69fc75b22d | ||
|
|
f443226e46 | ||
|
|
5a11f33751 | ||
|
|
d0b5a5af50 | ||
|
|
ca390dc5fb | ||
|
|
a6b3a5b6bc | ||
|
|
5eadae8c2e | ||
|
|
58a0f74c76 | ||
|
|
707135f851 | ||
|
|
d23eb4af6c | ||
|
|
2b084457ef | ||
|
|
333ce69374 | ||
|
|
e68fac8655 | ||
|
|
367aff93f0 | ||
|
|
ae8c0b3a93 | ||
|
|
ccd377cc47 | ||
|
|
e4938cf61e | ||
|
|
130c771538 | ||
|
|
dd6cf74fd7 | ||
|
|
3e3165bc42 | ||
|
|
c29b96d09e | ||
|
|
4d0f52adc9 | ||
|
|
919a14623d | ||
|
|
5a56a76fad | ||
|
|
28299e3136 | ||
|
|
be29462087 | ||
|
|
7b7c7a1b6e | ||
|
|
be39a67f27 | ||
|
|
bef46f17d5 | ||
|
|
11ddf6e065 | ||
|
|
9fad2c0b1d | ||
|
|
48d574f90f | ||
|
|
3b0be19b10 | ||
|
|
1731984393 | ||
|
|
d6adfb0b59 | ||
|
|
b05e74b78e | ||
|
|
2833cd8896 | ||
|
|
a4a9b29687 | ||
|
|
cc5f9edd43 | ||
|
|
85d4c42a6b | ||
|
|
dd84a19b5b | ||
|
|
5861393ad8 | ||
|
|
8676ba9128 | ||
|
|
c4de2a7239 | ||
|
|
cc4a7bbbe5 | ||
|
|
f3cf86c8c2 | ||
|
|
cdc7842bc4 | ||
|
|
f5a071d52d | ||
|
|
5c726e1c55 | ||
|
|
32684f9d45 | ||
|
|
754fc85fb8 | ||
|
|
78c5a2ec28 | ||
|
|
81acb9b7c1 | ||
|
|
5a1b3355c2 | ||
|
|
e0eb6d5e8e | ||
|
|
4092346f95 | ||
|
|
0e48ca95f2 | ||
|
|
211823e498 | ||
|
|
72570c521c | ||
|
|
2447d99b12 | ||
|
|
e51d6f0df7 | ||
|
|
4f577e5f99 | ||
|
|
221dfc2e02 | ||
|
|
c5472e2a4e | ||
|
|
f7fe9c53cb | ||
|
|
f8d8e25de4 | ||
|
|
89fb3494f4 | ||
|
|
8b27eaf8f1 | ||
|
|
f1511c4be0 | ||
|
|
eed53cbe9d | ||
|
|
fa68faa2e5 | ||
|
|
b7589a752b | ||
|
|
c6562efce5 | ||
|
|
fd5e91dbff | ||
|
|
36cffabeda | ||
|
|
ff02c266ac | ||
|
|
3be78c6545 | ||
|
|
df9fb69de5 | ||
|
|
09cf49204b | ||
|
|
4c2537010d | ||
|
|
684001ffa2 | ||
|
|
1fdd4fd35c | ||
|
|
4567c689ce | ||
|
|
20a93c54c4 | ||
|
|
fa1e75a809 | ||
|
|
9c329bd62f | ||
|
|
4ef1b48155 | ||
|
|
3c7b25d0fa | ||
|
|
739f92e872 | ||
|
|
f35d0fde0b | ||
|
|
abc7d6dfe6 | ||
|
|
93099e35e7 | ||
|
|
a47572c89e | ||
|
|
804a7e42fc | ||
|
|
c98215ab3a | ||
|
|
457d328294 | ||
|
|
8cf8cb561a | ||
|
|
56de2cd76c | ||
|
|
9f250a0f6e | ||
|
|
1df878e5d3 | ||
|
|
000025e09c | ||
|
|
3ec7b5c375 | ||
|
|
f784a562f8 | ||
|
|
dc7cd26c3c | ||
|
|
0244657770 | ||
|
|
f478ace1a4 | ||
|
|
490a17f641 | ||
|
|
39a5d39c7c | ||
|
|
649a2bf4de | ||
|
|
eb07113d91 | ||
|
|
7dc76c8df1 | ||
|
|
f096bb7dd0 | ||
|
|
a67079909e | ||
|
|
ccfa938f67 | ||
|
|
b48f72d0ad | ||
|
|
6950baf687 | ||
|
|
f9ca638936 | ||
|
|
7b866f66e9 | ||
|
|
2981010a90 | ||
|
|
d19d523c8d | ||
|
|
ea8e25cee5 | ||
|
|
6a02392b57 | ||
|
|
f5eadae767 | ||
|
|
5cc589980c | ||
|
|
add253912e | ||
|
|
edca2dddf1 | ||
|
|
9611b88788 | ||
|
|
6ced4409ac | ||
|
|
dfc5de701b | ||
|
|
eddb8f271a | ||
|
|
41ea698f6a | ||
|
|
80d7850866 | ||
|
|
3d33bea285 | ||
|
|
24e51ee98c | ||
|
|
8cbf870238 | ||
|
|
8e70048891 | ||
|
|
8b50737914 | ||
|
|
38d54fa0cd | ||
|
|
36a53ddf66 | ||
|
|
4b7f1f8751 | ||
|
|
e81a0fc765 | ||
|
|
b5785e2777 | ||
|
|
ccccaaf06f | ||
|
|
d07d52224e | ||
|
|
f0f3c9c8cf | ||
|
|
d4d010ec21 | ||
|
|
8fa0c66bc4 | ||
|
|
705dd7dddd | ||
|
|
b7643c08cf | ||
|
|
6b4605280e | ||
|
|
b87386f4fc | ||
|
|
e3c533a53c | ||
|
|
ee6ff22947 | ||
|
|
5ca3c2894b | ||
|
|
3d9480acf9 | ||
|
|
530e4db6ec | ||
|
|
1f338ce201 | ||
|
|
d71ffa6c47 | ||
|
|
0832828953 | ||
|
|
79453c3e6d | ||
|
|
243beb692b | ||
|
|
c76fb0145b | ||
|
|
72b4ef31a2 | ||
|
|
7b1db4868a | ||
|
|
8552e42d94 | ||
|
|
8e06707fc6 | ||
|
|
4897e0322d | ||
|
|
09b528168e | ||
|
|
4f366d7937 | ||
|
|
3047a63941 | ||
|
|
b4edd547dc | ||
|
|
e1db19c892 | ||
|
|
5530855436 | ||
|
|
f805fa7c8b | ||
|
|
10c393f063 | ||
|
|
2a611c992d | ||
|
|
64088bb624 | ||
|
|
2567355a99 | ||
|
|
914634e4df | ||
|
|
fbbd020e07 | ||
|
|
1ead81f11e | ||
|
|
5610a7c2f4 | ||
|
|
9afd031e1d | ||
|
|
563abea251 | ||
|
|
4e801b6ff8 | ||
|
|
6115ec703d | ||
|
|
89b31f72d6 | ||
|
|
d75ae19888 | ||
|
|
4c9ef79fa5 | ||
|
|
d504b28389 | ||
|
|
5138581e12 | ||
|
|
5caa9009b7 | ||
|
|
20737e3bde | ||
|
|
61825382ed | ||
|
|
2a29fa6e8f | ||
|
|
04f12d2fc9 | ||
|
|
b500957da1 | ||
|
|
2d7c54796f | ||
|
|
ccf6395c51 | ||
|
|
5ea5d7864e | ||
|
|
68256fb877 | ||
|
|
f8be47d758 | ||
|
|
a1b34eda22 | ||
|
|
7918d42815 | ||
|
|
fb6a5bbce2 | ||
|
|
d2be8c0f79 | ||
|
|
5c280d070d | ||
|
|
cf21e2da10 | ||
|
|
c1a327d38b | ||
|
|
1adf9258fc | ||
|
|
8578114a5e | ||
|
|
15555d42e7 | ||
|
|
5dfeee25bb | ||
|
|
11a8655d0d | ||
|
|
9a40f9adec | ||
|
|
b722ece6af | ||
|
|
f7d8a554cf | ||
|
|
c30deb21f7 | ||
|
|
e8698aa19f | ||
|
|
93d83c1ce9 | ||
|
|
516ced9a57 | ||
|
|
e94459d194 | ||
|
|
ec0f3b4da8 | ||
|
|
12db02eaf0 | ||
|
|
1f290dccfe | ||
|
|
16c7439c35 | ||
|
|
9e61458772 | ||
|
|
16bb1040b4 | ||
|
|
2a2086014c | ||
|
|
4a897e57df | ||
|
|
e3edba398e | ||
|
|
29e7f97ef9 | ||
|
|
36df9eb288 | ||
|
|
799ce31270 | ||
|
|
adf9aea274 | ||
|
|
fe39ec184a | ||
|
|
dd422a8c26 | ||
|
|
f960a0fbad | ||
|
|
2e4517971f | ||
|
|
334936045d | ||
|
|
9ec6d6c261 | ||
|
|
b9f693004a | ||
|
|
abfa19fc42 | ||
|
|
38283becaa | ||
|
|
ad29b43ca6 | ||
|
|
a5309f636e | ||
|
|
0c85306068 | ||
|
|
e9582d1c4e | ||
|
|
4b1ff4b003 | ||
|
|
23a26d2d13 | ||
|
|
1499607df3 | ||
|
|
0dd14d0bdf | ||
|
|
a9687f01ee | ||
|
|
8b99358339 | ||
|
|
9c72e32874 | ||
|
|
578bc625fc | ||
|
|
a058c4f73d | ||
|
|
d50cc5e842 | ||
|
|
5124f8aca7 | ||
|
|
9d7387798c | ||
|
|
fb1514b6e8 | ||
|
|
18a264882b | ||
|
|
743780f868 | ||
|
|
718e74d5d4 | ||
|
|
8f0c6e9508 | ||
|
|
18114a8b6e | ||
|
|
9924e2978a | ||
|
|
190cecd7c8 | ||
|
|
96a78a9f43 | ||
|
|
e02e813bf8 | ||
|
|
979b22f8d9 | ||
|
|
29133131fb | ||
|
|
33921c2ac0 | ||
|
|
8939cb059f | ||
|
|
44e1b22f4d | ||
|
|
87cf6848a5 | ||
|
|
4ab7c38c90 | ||
|
|
be43458579 | ||
|
|
d96c1f0a22 | ||
|
|
7f64acdd59 | ||
|
|
8459e9b053 | ||
|
|
8303c7a48b | ||
|
|
cc8d9a2c53 | ||
|
|
542055580b | ||
|
|
b6d08fe371 | ||
|
|
a144882948 | ||
|
|
6e41e97468 | ||
|
|
426019efc4 | ||
|
|
151a11ec36 | ||
|
|
ba6d7b5404 | ||
|
|
34180d8248 | ||
|
|
0d1115464a | ||
|
|
bc35aa8f01 | ||
|
|
a9c98d2666 | ||
|
|
fa59402def | ||
|
|
a9995a7d61 | ||
|
|
c904a91bef | ||
|
|
5242dd42e1 | ||
|
|
6a6e3755c7 | ||
|
|
c91f27c2fa | ||
|
|
d1e81225bc | ||
|
|
c4f23295bf | ||
|
|
a41d4c5029 | ||
|
|
d55a2261fa | ||
|
|
ad6e965ad3 | ||
|
|
cafde31502 | ||
|
|
e616f250af | ||
|
|
5c341efdf0 | ||
|
|
ccd3d4f964 | ||
|
|
c06cf56c60 | ||
|
|
36ff461d65 | ||
|
|
6bfdfb1bea | ||
|
|
2edab987fd | ||
|
|
f594b0a400 | ||
|
|
65d54e8d51 | ||
|
|
fde808e663 | ||
|
|
b521f76ca4 | ||
|
|
91b128a466 | ||
|
|
23cf16a283 | ||
|
|
ac5f77a9a8 | ||
|
|
d79b41ba28 | ||
|
|
f73df6a102 | ||
|
|
3bd061ef5a | ||
|
|
28ed23f7ca | ||
|
|
b6ebbfd365 | ||
|
|
4babbf3f97 | ||
|
|
c4e3a804cc | ||
|
|
bb39d2e1ce | ||
|
|
1a54267aa7 | ||
|
|
6f1edf3b26 | ||
|
|
177837de6b | ||
|
|
48d63a6dae | ||
|
|
c16f6f8b68 | ||
|
|
20ad988585 | ||
|
|
570d50c67f | ||
|
|
c974b77127 | ||
|
|
dc2d235e59 | ||
|
|
d1c19bef38 | ||
|
|
d2a9937ad6 | ||
|
|
4daac232a4 | ||
|
|
470bd5990b | ||
|
|
cf83008d33 | ||
|
|
ea352ccdae | ||
|
|
f777416870 | ||
|
|
5dabad6702 | ||
|
|
23a89663e0 | ||
|
|
c8b4d6308f | ||
|
|
d54cf8f12e | ||
|
|
32b57785c3 | ||
|
|
d2d8eeea2f | ||
|
|
07fc6ee3c7 | ||
|
|
c29d2d9a1c | ||
|
|
bbe5412aea | ||
|
|
87289fd904 | ||
|
|
bff70f957f | ||
|
|
c00ccf7a6c | ||
|
|
ae260c13fa | ||
|
|
47f54f19c5 | ||
|
|
f817ab8d07 | ||
|
|
3d283bb91f | ||
|
|
75137f7cdd | ||
|
|
6b870d62ad | ||
|
|
234bdda40d | ||
|
|
8127000080 | ||
|
|
ea6ab124f4 | ||
|
|
6d29b7f684 | ||
|
|
9596ae3b76 | ||
|
|
1d5857bf5a | ||
|
|
4f14738a74 | ||
|
|
f9686296a9 | ||
|
|
40d418e2fc | ||
|
|
008ec892c8 | ||
|
|
951a97e32d | ||
|
|
a49ab1911c | ||
|
|
b993312365 | ||
|
|
ba462e1223 | ||
|
|
1e1b423fc0 | ||
|
|
ef077319c8 | ||
|
|
956f471013 | ||
|
|
19db8d0b9f | ||
|
|
7ccab4a4c8 | ||
|
|
c3212e7cd0 | ||
|
|
cc23a67493 | ||
|
|
b880c33a8b | ||
|
|
6e09d1b123 | ||
|
|
258ac74384 | ||
|
|
e948314a3e | ||
|
|
afea16569b | ||
|
|
2e62cac72b | ||
|
|
c0af6eb5c0 | ||
|
|
2da6c34547 | ||
|
|
6de6a92717 | ||
|
|
cdb7b0d5ca | ||
|
|
906113f499 | ||
|
|
d4c268c834 | ||
|
|
535638e1e7 | ||
|
|
2fac506b35 | ||
|
|
00ea4636a1 | ||
|
|
5b7db1d2d2 | ||
|
|
0258e8d10a | ||
|
|
d7c259c8c9 | ||
|
|
144dc7e5e2 | ||
|
|
e2ee2067f8 | ||
|
|
684e880689 | ||
|
|
0919b2cb3d | ||
|
|
f260677dbd | ||
|
|
bcc5cf30ac | ||
|
|
afdf8cefe8 | ||
|
|
648223ade2 | ||
|
|
ffc4622896 | ||
|
|
a037bda44b | ||
|
|
0dcc975304 | ||
|
|
5662fa0751 | ||
|
|
f709386ead | ||
|
|
e9c6617687 | ||
|
|
5e0fb7a6ee | ||
|
|
2c417d5bb1 | ||
|
|
79c428e465 | ||
|
|
e56705455a | ||
|
|
c045593e4e | ||
|
|
9f0cd323b6 | ||
|
|
674d7dbb3a | ||
|
|
c0f3a09c2b | ||
|
|
cb2be65a8b | ||
|
|
9c29b0c411 | ||
|
|
dd6e947122 | ||
|
|
ac1dcf7d4b | ||
|
|
f939a06aa9 | ||
|
|
8d07c45483 | ||
|
|
27d70fd58e | ||
|
|
f71fe9bf3a | ||
|
|
023b3b261d | ||
|
|
477e20a7a9 | ||
|
|
d6aab01424 | ||
|
|
182157291a | ||
|
|
9e7faba7f3 | ||
|
|
00cdb307e3 | ||
|
|
ff49bfe3a7 | ||
|
|
69c19f7cd1 | ||
|
|
63bbb3c804 | ||
|
|
1876ac88e0 | ||
|
|
0da5931353 | ||
|
|
400696c0fd | ||
|
|
5bf13e8642 | ||
|
|
ec34befc3c | ||
|
|
cdadde55ef | ||
|
|
b90530cb87 | ||
|
|
169a422ade | ||
|
|
fda12b2fce | ||
|
|
20a58cbe3e | ||
|
|
0a39c66817 | ||
|
|
2f034130b7 | ||
|
|
f7b2660882 | ||
|
|
c6499797ea | ||
|
|
15efc4a808 | ||
|
|
babc3c634f | ||
|
|
100b870175 | ||
|
|
db347b344d | ||
|
|
95624ce818 | ||
|
|
4541cbab72 | ||
|
|
453ff6dc98 | ||
|
|
92a5cf0ac8 | ||
|
|
02537b56fd | ||
|
|
7004502951 | ||
|
|
52d23158ed | ||
|
|
7f46112984 | ||
|
|
359920f749 | ||
|
|
a8e93bd8b1 | ||
|
|
1079161270 | ||
|
|
7ec1f33b60 | ||
|
|
26a8ef9370 | ||
|
|
1022eeebdd | ||
|
|
f26ec68657 | ||
|
|
ab6114d5a2 | ||
|
|
019e754055 | ||
|
|
d767b5fce6 | ||
|
|
89dbd42da2 | ||
|
|
d46a3b4ca9 | ||
|
|
4ef6120c01 | ||
|
|
d1b55f584e | ||
|
|
491d230dec | ||
|
|
946075fe12 | ||
|
|
10d7498c14 | ||
|
|
b805008510 | ||
|
|
784e203b93 | ||
|
|
6f490d2481 | ||
|
|
d8e582ef6e | ||
|
|
1132f66325 | ||
|
|
fce2d7824f | ||
|
|
188d438e00 | ||
|
|
24b6f48841 | ||
|
|
5e732c5e2a | ||
|
|
5997507216 | ||
|
|
68ebb61f7b | ||
|
|
7258a4ae92 | ||
|
|
80929f44d4 | ||
|
|
abd1daac83 | ||
|
|
9a9426ee73 | ||
|
|
963261c40f | ||
|
|
085f517a3f | ||
|
|
b83d155235 | ||
|
|
fe40dd37c0 | ||
|
|
d90792258d | ||
|
|
153b0da60d | ||
|
|
dd09f41dcc | ||
|
|
0b9c90fcc4 | ||
|
|
97861c6809 | ||
|
|
25bf682006 | ||
|
|
0546bb0410 | ||
|
|
c6f7c6e273 | ||
|
|
5e4c3b07fb | ||
|
|
fdf93c5a19 | ||
|
|
380500c92d | ||
|
|
db4a2d5a9f | ||
|
|
294eb2c904 | ||
|
|
80e9172de0 | ||
|
|
60bec8a561 | ||
|
|
3e629287da | ||
|
|
f6d1fe4aa1 | ||
|
|
deb517d346 | ||
|
|
17e810e6ed | ||
|
|
2bb027ac6a | ||
|
|
553be2af33 | ||
|
|
331c0bd613 | ||
|
|
539f79b0b7 | ||
|
|
b220bddd06 | ||
|
|
771d563171 | ||
|
|
04ee6b8adc | ||
|
|
023f5efc43 | ||
|
|
fe3d273bfd | ||
|
|
bb8f759725 | ||
|
|
1a437fc818 | ||
|
|
111ced08e4 | ||
|
|
714d1ce58b | ||
|
|
988b8b865e | ||
|
|
7ea22806d4 | ||
|
|
07ba834381 | ||
|
|
d04a50e835 | ||
|
|
379e285e03 | ||
|
|
c6307d3a52 | ||
|
|
6f9320bb11 | ||
|
|
a8b0ffbfcb | ||
|
|
4d537c7b09 | ||
|
|
7675e10236 | ||
|
|
c771427c38 | ||
|
|
ba8749b792 | ||
|
|
cdd47b6a59 | ||
|
|
10cbfa9361 | ||
|
|
743d6b83d3 | ||
|
|
b1f158e5a8 | ||
|
|
31ee8b593f | ||
|
|
0c174624b4 | ||
|
|
f6d562e2ee | ||
|
|
050faa312a | ||
|
|
adb7a91ec6 | ||
|
|
5df67028ff | ||
|
|
9338509a71 | ||
|
|
980e76ea1f | ||
|
|
cb138c8f37 | ||
|
|
7cd55802f6 | ||
|
|
ec906679b9 | ||
|
|
8265279411 | ||
|
|
922b644116 | ||
|
|
528d6573e0 | ||
|
|
bb0a201502 | ||
|
|
6af0af2b3c | ||
|
|
a1b41ad2ad | ||
|
|
568f24cc84 | ||
|
|
51701ab2a4 | ||
|
|
276a4bdc80 | ||
|
|
62c67546ac | ||
|
|
9183661d88 | ||
|
|
75bcd24918 | ||
|
|
3daabbd2be | ||
|
|
0446bc71a0 | ||
|
|
8e9d591369 | ||
|
|
e4eb0a7116 | ||
|
|
4ca2da4987 | ||
|
|
d0003f85c5 | ||
|
|
95abec4c21 | ||
|
|
d8fa1b4366 | ||
|
|
850a0a7847 | ||
|
|
aaa86584e5 | ||
|
|
2e0b76556a | ||
|
|
fcca0feb70 | ||
|
|
e363b14e81 | ||
|
|
e2fda95f9a | ||
|
|
c3a5f9d5b2 | ||
|
|
0cc43e192c | ||
|
|
d257d95362 | ||
|
|
d2166dd93a | ||
|
|
44c3179ae0 | ||
|
|
4c1c704bb1 | ||
|
|
601b9d8984 | ||
|
|
5b7f93f6b6 | ||
|
|
aa99c05231 | ||
|
|
364ddaab54 | ||
|
|
253414f168 | ||
|
|
7cc4f43779 | ||
|
|
5a02051d68 | ||
|
|
4eb11ba940 | ||
|
|
edca32a194 | ||
|
|
93274d3888 | ||
|
|
f548b3d71a | ||
|
|
0dd73170e5 | ||
|
|
6a3643cdde | ||
|
|
93b26bf595 | ||
|
|
4b823ebaf5 | ||
|
|
131c296bb4 | ||
|
|
0b806b9131 | ||
|
|
8c73f3cb30 | ||
|
|
9216500355 | ||
|
|
b4d09e081d | ||
|
|
617cdc9724 | ||
|
|
0a9d41d3dd | ||
|
|
7eceb145b1 | ||
|
|
6595d50d18 | ||
|
|
9071147db5 | ||
|
|
4963cc659f | ||
|
|
26d64b9fda | ||
|
|
1251fbf0ba | ||
|
|
3e39990503 | ||
|
|
4f1d191ca9 | ||
|
|
2ac1b04914 | ||
|
|
448658e49d | ||
|
|
77add04d3d | ||
|
|
f35791ec64 | ||
|
|
e0e29260f5 | ||
|
|
0cc8f63849 | ||
|
|
185f360fea | ||
|
|
f8d4d991f1 | ||
|
|
483e4a2bab | ||
|
|
c0bdb9a3e5 | ||
|
|
771650a012 | ||
|
|
691d2816f9 | ||
|
|
eedc70dfae | ||
|
|
da41a0cb20 | ||
|
|
f842fbe25d | ||
|
|
b0c5bbc589 | ||
|
|
d86bec3252 | ||
|
|
180c68e76b | ||
|
|
af7bbfa104 | ||
|
|
4c670ba5a2 | ||
|
|
dbbd921a53 | ||
|
|
ac2729511f | ||
|
|
37fe0cfae2 | ||
|
|
2c2b13784e | ||
|
|
a9364cbc52 | ||
|
|
2f76ef654b | ||
|
|
5c7f6a2afa | ||
|
|
2ee48bc5aa | ||
|
|
efce325cbf | ||
|
|
9b56aa5b60 | ||
|
|
125ade5f55 | ||
|
|
11a59bb405 | ||
|
|
8cbaf3c2ef | ||
|
|
43bd4940ea | ||
|
|
0ba57b0632 | ||
|
|
40a43f3210 | ||
|
|
aed3a60e17 | ||
|
|
47df6dd270 | ||
|
|
20376451eb | ||
|
|
512c84dbc0 | ||
|
|
9c8aef3c05 | ||
|
|
bc02b2318c | ||
|
|
32d2406fa8 | ||
|
|
fee1a4b097 | ||
|
|
3ccff6d7c9 | ||
|
|
726a053ca2 | ||
|
|
9e9bfa56c2 | ||
|
|
2fb2a7eda8 | ||
|
|
122fac9c53 | ||
|
|
ce1bbc7938 | ||
|
|
2df879c5ee | ||
|
|
1b5deea53a | ||
|
|
4eecd15a2a | ||
|
|
d7dba07304 | ||
|
|
d690b2d65e | ||
|
|
22974cff20 | ||
|
|
a7f2351244 | ||
|
|
58c804309b | ||
|
|
274e8b4b6b | ||
|
|
90ff571493 | ||
|
|
e0cceaf31c | ||
|
|
3bcd071e4d | ||
|
|
5ef8d48e5a | ||
|
|
e01f26c1f6 | ||
|
|
94ccfb8b18 | ||
|
|
0f28d4e199 | ||
|
|
0cad3cb8b8 | ||
|
|
479bb7af1a | ||
|
|
ff4f7a0e94 | ||
|
|
ddc5ba0b98 | ||
|
|
c82da8b887 | ||
|
|
40c29f689f | ||
|
|
4d27603cb5 | ||
|
|
b472e88caf | ||
|
|
f0c3e7d51f | ||
|
|
52a854a57a | ||
|
|
c8b1cb8cab | ||
|
|
25a3d6dde1 | ||
|
|
6e842cf110 | ||
|
|
f1fd1e9121 | ||
|
|
41cdc5cd5b | ||
|
|
876d3f5e80 | ||
|
|
fe6a60ade7 | ||
|
|
d119c47d2e | ||
|
|
6f41a9491b | ||
|
|
1740b083b8 | ||
|
|
1dceb7533d | ||
|
|
301ff32a59 | ||
|
|
2823053614 | ||
|
|
a7a823daaa | ||
|
|
700b34adeb | ||
|
|
bb72418d9e | ||
|
|
2bbb3d07c6 | ||
|
|
ef2c0a6d9d | ||
|
|
97167ab1cc | ||
|
|
b76906a8bf | ||
|
|
7d2f284ee6 | ||
|
|
2da102fab8 | ||
|
|
e5720232e1 | ||
|
|
aff85c87ce | ||
|
|
b7f55decbd | ||
|
|
82a8f67c71 | ||
|
|
d7a885cea8 | ||
|
|
6a064aad3b | ||
|
|
4ab6a45405 | ||
|
|
00440b179a | ||
|
|
130bb82a43 | ||
|
|
9b5eefbb7b | ||
|
|
0717b53123 | ||
|
|
ed785e3d86 | ||
|
|
3a9f020234 | ||
|
|
050db4725f | ||
|
|
9301ae2746 | ||
|
|
2b8fac3af3 | ||
|
|
252fa816d6 | ||
|
|
4b5e73577a | ||
|
|
d3d63ea2d3 | ||
|
|
6f266651c0 | ||
|
|
e0479fe197 | ||
|
|
e0b19c833c | ||
|
|
8f7b1343bc | ||
|
|
3836958eaa | ||
|
|
005014a3f9 | ||
|
|
e0185aefb1 | ||
|
|
dfed341ec8 | ||
|
|
5242ff5243 | ||
|
|
e8dd5826e6 | ||
|
|
f8a78e6ab6 | ||
|
|
941a7bb784 | ||
|
|
1ccd99ea20 | ||
|
|
eb50246b41 | ||
|
|
7c7b824aa9 | ||
|
|
e4ce70bbda | ||
|
|
96e08a425e | ||
|
|
6616961d08 | ||
|
|
abea6bd114 | ||
|
|
ca76cb7bb0 | ||
|
|
11c9b0e783 | ||
|
|
8ce5f49908 | ||
|
|
6df3753229 | ||
|
|
892116c009 | ||
|
|
6dd5e36d02 | ||
|
|
3d86b26caf | ||
|
|
4eb49491a3 | ||
|
|
c490a357fd | ||
|
|
273c5a349b | ||
|
|
60fb4384e1 | ||
|
|
faeed2cdcc | ||
|
|
133bdf655e | ||
|
|
314a61562c | ||
|
|
d75f272c8b | ||
|
|
ef22bf4774 | ||
|
|
65e08c02b6 | ||
|
|
33689c1d0f | ||
|
|
522793850a | ||
|
|
076d692b42 | ||
|
|
346481d95e | ||
|
|
96038e48c5 | ||
|
|
37cc6b44f9 | ||
|
|
dc93614f5a | ||
|
|
566b6f438b | ||
|
|
a78b569d92 | ||
|
|
353a9f6104 | ||
|
|
51b628642c | ||
|
|
fdfede7545 | ||
|
|
0793bd3d08 | ||
|
|
f5b44f505a | ||
|
|
24bff8d5bf | ||
|
|
eafa0c0149 | ||
|
|
db01a1d653 | ||
|
|
2fe140fdb9 | ||
|
|
8d901a7300 | ||
|
|
280caea579 | ||
|
|
7594ef5121 | ||
|
|
ea1e60e9ca | ||
|
|
299f1ea0aa | ||
|
|
f4363a6b27 | ||
|
|
02cc257b42 | ||
|
|
833f7343c8 | ||
|
|
c934265304 | ||
|
|
e9a57d172b | ||
|
|
380fa39eba | ||
|
|
4cb3b9d8f0 | ||
|
|
ef871d227e | ||
|
|
746e71af87 | ||
|
|
a0ace054f3 | ||
|
|
609dbb1439 | ||
|
|
3a702ac772 | ||
|
|
c5deb740ac | ||
|
|
a3d1b5e7ac | ||
|
|
2a2caa1897 | ||
|
|
c06701abc8 | ||
|
|
b214362ca1 | ||
|
|
e3890f9b4a | ||
|
|
13a875f609 | ||
|
|
7a516c44c9 | ||
|
|
3d4aaaa6c1 | ||
|
|
81d89d1d90 | ||
|
|
2e5257d098 | ||
|
|
6cb763a332 | ||
|
|
49e6931c7e | ||
|
|
9eeea925c7 | ||
|
|
bb57724d3b | ||
|
|
f9e2f9d273 | ||
|
|
e91bc11b0a | ||
|
|
4d7a289971 | ||
|
|
7e47212886 | ||
|
|
10f14fdedd | ||
|
|
1bb3439711 | ||
|
|
69601a18a4 | ||
|
|
00bfea83bf | ||
|
|
f3b7f7fd47 | ||
|
|
29bb8b47cd | ||
|
|
12f08d6ed5 | ||
|
|
f2d5594288 | ||
|
|
1d95a04ac8 | ||
|
|
1dcded40b8 | ||
|
|
7445b27083 | ||
|
|
f9a1f2bda8 | ||
|
|
8d505dc0e5 | ||
|
|
80af69e625 | ||
|
|
717265a59a | ||
|
|
daaae22077 | ||
|
|
1642959b92 | ||
|
|
1c07d3545e | ||
|
|
ae6deb7437 | ||
|
|
def6974bf6 | ||
|
|
e6d2ba06b1 | ||
|
|
a09ed1bf97 | ||
|
|
55c309669b | ||
|
|
d1e75c0ee0 | ||
|
|
98d8ebcc1d | ||
|
|
b76c0a20a8 | ||
|
|
d170b0281f | ||
|
|
12811eb40c | ||
|
|
8765d86355 | ||
|
|
40da80714c | ||
|
|
2769c08a0d | ||
|
|
0a826731cb | ||
|
|
59b80e30ed | ||
|
|
0d128ccc28 | ||
|
|
3427215611 | ||
|
|
99168e076c | ||
|
|
9500f6609b | ||
|
|
55c71aa0ee | ||
|
|
dc9475c26b | ||
|
|
34fe0f2770 | ||
|
|
3aa6bf8696 | ||
|
|
e831124a21 | ||
|
|
1827af100b | ||
|
|
17cc559dd3 | ||
|
|
472c12cad7 | ||
|
|
8d2b215ccb | ||
|
|
8aafbf2bf7 | ||
|
|
cadac344a7 | ||
|
|
c76e72571e | ||
|
|
1f56a0fe0c | ||
|
|
bedcb225e7 | ||
|
|
4a559f3ea4 | ||
|
|
2789ff7a9f | ||
|
|
4c833fc714 | ||
|
|
404836f187 | ||
|
|
32fd6bec49 | ||
|
|
e4fb263788 | ||
|
|
a8e7633f58 | ||
|
|
f436c51ef2 | ||
|
|
08762a35c2 | ||
|
|
5d253d1ec0 | ||
|
|
f297888317 | ||
|
|
440b8de763 | ||
|
|
2a1faf4265 | ||
|
|
3909b18eac | ||
|
|
6ff7b3ed27 | ||
|
|
a400386774 | ||
|
|
9e225f52e2 | ||
|
|
aa9fc7fede | ||
|
|
2d3ef86231 | ||
|
|
6fe05987f0 | ||
|
|
9291178b01 | ||
|
|
8c157990cf | ||
|
|
e835ce9275 | ||
|
|
0cd6eb7b13 | ||
|
|
53feed5920 | ||
|
|
95f5a5b240 | ||
|
|
91f0d70c0c | ||
|
|
f2224de8d7 | ||
|
|
d4be424bf6 | ||
|
|
de0bc732c9 | ||
|
|
c2bfdf30bd | ||
|
|
83a0deac25 | ||
|
|
b38cdaa30e | ||
|
|
a3425b435e | ||
|
|
bf80d2715d | ||
|
|
09bde28281 | ||
|
|
42cc59a90c | ||
|
|
6408228b3e | ||
|
|
4fbf617c06 | ||
|
|
985961b013 | ||
|
|
8df2df3ed7 | ||
|
|
7becac9a10 | ||
|
|
cd613edc71 | ||
|
|
e367145a4a | ||
|
|
03380dc4ec | ||
|
|
906aebf5a1 | ||
|
|
cf963b74c8 | ||
|
|
ead7e0c39c | ||
|
|
85bafc057a | ||
|
|
ab885d37de | ||
|
|
dd65baf617 | ||
|
|
4af869b121 | ||
|
|
75838e567e | ||
|
|
8977c39b60 | ||
|
|
bdf3b3654e | ||
|
|
ce5e1bd455 | ||
|
|
6cc103bad3 | ||
|
|
e1b22f19e6 | ||
|
|
ec564ac190 | ||
|
|
b47337780b | ||
|
|
bdd036ea6b | ||
|
|
fd27c2d78f | ||
|
|
8bfe927de5 | ||
|
|
68d65a7280 | ||
|
|
f929a6eef8 | ||
|
|
4bec6bb6f9 | ||
|
|
eee360822c | ||
|
|
f0526b9e9a | ||
|
|
eea6cc49cb | ||
|
|
cff37af5f6 | ||
|
|
5ae75a679b | ||
|
|
52dd8292c4 | ||
|
|
0eddee500a | ||
|
|
546d35b8c1 | ||
|
|
346519bb6b | ||
|
|
32184103c3 | ||
|
|
2e2924801c | ||
|
|
1f8ef0aa36 | ||
|
|
b19312b403 | ||
|
|
d3e2fa47cc | ||
|
|
3e2ada0586 | ||
|
|
b446dac359 | ||
|
|
a445a22868 | ||
|
|
020ff3058c | ||
|
|
e10dc94992 | ||
|
|
e58cf84c71 | ||
|
|
a4df238b3d | ||
|
|
1e706d911e | ||
|
|
6ccf3b5104 | ||
|
|
7c8df94f4e | ||
|
|
a8cfa36c8a | ||
|
|
d3afd465f3 | ||
|
|
b3b97cac7c | ||
|
|
01a5f94e7b | ||
|
|
bd0aaf6f4f | ||
|
|
37c47fca6b | ||
|
|
4cf5697e06 | ||
|
|
00ae4a1751 | ||
|
|
580f6e3f34 | ||
|
|
cd012f9b2e | ||
|
|
e22091ef60 | ||
|
|
6bf1ab65c0 | ||
|
|
ca1c4c1f71 | ||
|
|
b98f06214d | ||
|
|
9fcc471c94 | ||
|
|
7b9f0d9657 | ||
|
|
e79a1a1a66 | ||
|
|
6bd1d90b6a | ||
|
|
6ed0b4f0ac | ||
|
|
dac236dac0 | ||
|
|
5e1fc93f50 | ||
|
|
1464d5ca43 | ||
|
|
489289f54a | ||
|
|
f274164dcd | ||
|
|
2f4ca509c8 | ||
|
|
f91ffe34d4 | ||
|
|
4ce6827916 | ||
|
|
39aa6f9524 | ||
|
|
80599603e5 | ||
|
|
879813f763 | ||
|
|
feb5066be2 | ||
|
|
45a1940fc6 | ||
|
|
ed62c8ef58 | ||
|
|
183cebe8a1 | ||
|
|
5079ee8fe3 | ||
|
|
2271f74642 | ||
|
|
219b4e316b | ||
|
|
81c13347a4 | ||
|
|
a7d417cf63 | ||
|
|
d1ff2cde77 | ||
|
|
476b75de49 | ||
|
|
3600b6652d | ||
|
|
44327a8ee4 | ||
|
|
7b36c9e8e0 | ||
|
|
75daf6f5ec | ||
|
|
a37f4ec38e | ||
|
|
04a4d1e02f | ||
|
|
f8199d7099 | ||
|
|
ee747134b5 | ||
|
|
527111bc98 | ||
|
|
7edc3bba5d | ||
|
|
012799f560 | ||
|
|
fd6c548766 | ||
|
|
ce00640b22 | ||
|
|
9b2ad34a08 | ||
|
|
5b20c458af | ||
|
|
8d9002800e | ||
|
|
75ecd72428 | ||
|
|
27ab62de48 | ||
|
|
a7d7f81ca7 | ||
|
|
b704f42158 | ||
|
|
7f4d308efc | ||
|
|
4a6e8fb29e | ||
|
|
57b4b4ae45 | ||
|
|
1856415196 | ||
|
|
72a90da13b | ||
|
|
c9bf8df19c | ||
|
|
ae39b132e4 | ||
|
|
21f24cdcc7 | ||
|
|
40acd55290 | ||
|
|
2063c1e0bc | ||
|
|
bcdf37bef1 | ||
|
|
61ddd98b89 | ||
|
|
a5a1173c3f | ||
|
|
151f0746a3 | ||
|
|
aaecdd1747 | ||
|
|
7eb0fe4984 | ||
|
|
8443b2c97e | ||
|
|
92acf339a1 | ||
|
|
49dbbbd5b9 | ||
|
|
aae6bf566f | ||
|
|
6dbc8f0cfa | ||
|
|
5122aed738 | ||
|
|
ebdc033c59 | ||
|
|
7ebb0f0c5c | ||
|
|
72cf6c36d5 | ||
|
|
be9e44aafb | ||
|
|
cbde5ce321 | ||
|
|
c2a7393191 | ||
|
|
a4e60f569e | ||
|
|
7c3bc5ed8d | ||
|
|
9d1477699c | ||
|
|
c4e77287d1 | ||
|
|
3d7e521acd | ||
|
|
4f0b940b16 | ||
|
|
309b2ad32d | ||
|
|
014622ac34 | ||
|
|
c763d67ef4 | ||
|
|
215c5c6d7b | ||
|
|
00205aad14 | ||
|
|
c35cb89f91 | ||
|
|
7c7e55ffc4 | ||
|
|
2785bd8224 | ||
|
|
4a352344b6 | ||
|
|
7e6e0f66c8 | ||
|
|
03d6f4634f | ||
|
|
987216bcb8 | ||
|
|
79abbf0b29 | ||
|
|
4bf0bac52d | ||
|
|
286a5b5b8f | ||
|
|
fc0a34a5a5 | ||
|
|
b44229f7ca | ||
|
|
9760249a75 | ||
|
|
ec7588841f | ||
|
|
df523cb58c | ||
|
|
a84452ab45 | ||
|
|
73e1c3816e | ||
|
|
d919fe516e | ||
|
|
2342006b37 | ||
|
|
5925f1483c | ||
|
|
fdd2ac3b30 | ||
|
|
569bee103f | ||
|
|
8b3bf558fc | ||
|
|
09fa97d1b9 | ||
|
|
898fe85f40 | ||
|
|
6660170bf8 | ||
|
|
7bfa5d9ced | ||
|
|
46c61a65a6 | ||
|
|
ab1105a61d | ||
|
|
6c186e3893 | ||
|
|
70b93898d4 | ||
|
|
100b043fb6 | ||
|
|
9e439667da | ||
|
|
a4c93dfbbf | ||
|
|
0412dfdb3b | ||
|
|
9d2dd17455 | ||
|
|
be559ae453 | ||
|
|
f3916950bf | ||
|
|
adc2b0dbbe | ||
|
|
9aa1f18998 | ||
|
|
246fab63f7 | ||
|
|
aef4de4ab8 | ||
|
|
65bdfc3d4e | ||
|
|
272b36daa5 | ||
|
|
30cc772454 | ||
|
|
e6d9a9aef0 | ||
|
|
78d2e5bdf1 | ||
|
|
550497eb79 | ||
|
|
8e21db11d6 | ||
|
|
d36a2cffde | ||
|
|
21300f689d | ||
|
|
b3c2aa6b51 | ||
|
|
db71554dbc | ||
|
|
dbacb07246 | ||
|
|
835918afa1 | ||
|
|
1e3d4fe218 | ||
|
|
901324f21e | ||
|
|
30dd5bedc6 | ||
|
|
e227956a58 | ||
|
|
f49d20df22 | ||
|
|
9ccf4c1872 | ||
|
|
093ba251ab | ||
|
|
6f8050526f | ||
|
|
c11a72a4f1 | ||
|
|
9bbfdf5067 | ||
|
|
ef6c6ad117 | ||
|
|
152d10bfea | ||
|
|
d11604995a | ||
|
|
d22956ebc1 | ||
|
|
be5f660dfe | ||
|
|
49522e7a5e | ||
|
|
a47b32816b | ||
|
|
35f88608d2 | ||
|
|
752c0f68fd | ||
|
|
b9fa405fd4 | ||
|
|
75dcf8042e | ||
|
|
5a5b721576 | ||
|
|
df3a0b2f25 | ||
|
|
4bec0ad1c6 | ||
|
|
772b92edde | ||
|
|
2ff1fba6ed | ||
|
|
3438fb2850 | ||
|
|
d34b2cf8f0 | ||
|
|
bfd873a8b8 | ||
|
|
f379ea8327 | ||
|
|
04d55fa5c6 | ||
|
|
72e892a57d | ||
|
|
a477835970 | ||
|
|
5f496c955c | ||
|
|
00543942aa | ||
|
|
4828fb3ba6 | ||
|
|
20d9099bf3 | ||
|
|
3a7a5205fb | ||
|
|
81c9b05f97 | ||
|
|
2055145a7e | ||
|
|
1b17587585 | ||
|
|
98fff96f1c | ||
|
|
90717e5ef7 | ||
|
|
c71c78c1a0 | ||
|
|
a64cfac2ca | ||
|
|
30fb9d73cc | ||
|
|
14232ad0d2 | ||
|
|
f723974924 | ||
|
|
9f8090c830 | ||
|
|
42b969b088 | ||
|
|
4ebdadea45 | ||
|
|
ac48643447 | ||
|
|
26b3445d14 | ||
|
|
8bc46cf83d | ||
|
|
6003c87582 | ||
|
|
c578861598 | ||
|
|
79cd048e1f | ||
|
|
5365805312 | ||
|
|
db95946b8d | ||
|
|
a99164fd7b | ||
|
|
cc534d2954 | ||
|
|
904cc0a575 | ||
|
|
0760551cf7 | ||
|
|
7f15c8c1bd | ||
|
|
614cb84e7a | ||
|
|
df7694ca51 | ||
|
|
087e812bc0 | ||
|
|
808c8afbe1 | ||
|
|
d432cfd350 | ||
|
|
7a5d81a469 | ||
|
|
e4b9b26037 | ||
|
|
bb4e30a6df | ||
|
|
b200f0e8d2 | ||
|
|
7157697f96 | ||
|
|
bafaa1a826 | ||
|
|
abfd148d63 | ||
|
|
a484ba4790 | ||
|
|
abc5c223a6 | ||
|
|
42d0a583e8 | ||
|
|
649606fc58 | ||
|
|
4438dba49c | ||
|
|
638a408eff | ||
|
|
a3d638ab5f | ||
|
|
bf565e917a | ||
|
|
2a425351e2 | ||
|
|
4592787946 | ||
|
|
8ad7f912ea | ||
|
|
7e34ab743a | ||
|
|
4e6ef4150d | ||
|
|
7250b1f854 | ||
|
|
42d497a142 | ||
|
|
876368e476 | ||
|
|
cab41c807b | ||
|
|
f4b8b019ca | ||
|
|
a14dd5921d | ||
|
|
cb7450df31 | ||
|
|
590da47499 | ||
|
|
aea956d601 | ||
|
|
56a7fb129a | ||
|
|
b3141d1231 | ||
|
|
89bcae7bea | ||
|
|
c826f9d809 | ||
|
|
6f786b58ad | ||
|
|
d8dde123b1 | ||
|
|
517987c7b3 | ||
|
|
c2ca9a7d31 | ||
|
|
2b90e826ec | ||
|
|
f49c18578a | ||
|
|
59f422de66 | ||
|
|
305fb489e7 | ||
|
|
9f203ee7d2 | ||
|
|
26d5307520 | ||
|
|
d287d1e063 | ||
|
|
563dc0fc9e | ||
|
|
bf8152b4b8 | ||
|
|
9130dd8068 | ||
|
|
8e0a86d163 | ||
|
|
b84f7c19ad | ||
|
|
4051becc9e | ||
|
|
6782fa2219 | ||
|
|
e5aec9d7fd | ||
|
|
4fcce4d0b9 | ||
|
|
95b8c60756 | ||
|
|
7d8f0b989c | ||
|
|
a81912857d | ||
|
|
23ec5a55ad | ||
|
|
fef9de22c4 | ||
|
|
9528ee8246 | ||
|
|
8af105fc4f | ||
|
|
14cf4e32d0 | ||
|
|
f37ea52565 | ||
|
|
0e6e256f8e | ||
|
|
1b3261a090 | ||
|
|
896144edc3 | ||
|
|
081a8f0451 | ||
|
|
e733b7ecf9 | ||
|
|
1e7a49036a | ||
|
|
ea3eb2a9ef | ||
|
|
817f91b5bf | ||
|
|
f61b62e5a2 | ||
|
|
a9469a20d5 | ||
|
|
567fb4dec0 | ||
|
|
f7ddcd2795 | ||
|
|
bab03bd75b | ||
|
|
9134cca17b | ||
|
|
eac8968f84 | ||
|
|
5fb5db9617 | ||
|
|
916d3ba94b | ||
|
|
88eca9693a | ||
|
|
1a783fc9ec | ||
|
|
7c4c1eabf0 | ||
|
|
1568d2a7fb | ||
|
|
93e4e42b53 | ||
|
|
6991a80e13 | ||
|
|
4f8ef16937 | ||
|
|
8715ae76f1 | ||
|
|
8968aeafb9 | ||
|
|
041dd30602 | ||
|
|
9e60940f1b | ||
|
|
6479a2063c | ||
|
|
cc2e84b9fc | ||
|
|
7f45ba4c9c | ||
|
|
477c9b32f0 | ||
|
|
2071fa2e69 | ||
|
|
bb54c5b0e6 | ||
|
|
3728879baf | ||
|
|
4b7e49a17e | ||
|
|
5eac229eae | ||
|
|
609d99f1e3 | ||
|
|
db1adaa2c2 | ||
|
|
9ce85862ce | ||
|
|
4562930233 | ||
|
|
0f8c0bc8a8 | ||
|
|
2d4617236e | ||
|
|
c9ba9560b5 | ||
|
|
9792001703 | ||
|
|
d8a4a09ec0 | ||
|
|
ed9a61d956 | ||
|
|
5cad66cebf | ||
|
|
377151a57f | ||
|
|
a7e8ac684b | ||
|
|
e096ce7bc7 | ||
|
|
9e7d291b63 | ||
|
|
614028f9a8 | ||
|
|
8f048dd9fd | ||
|
|
fda9ceea54 | ||
|
|
2acc3be6cf | ||
|
|
b6e8a875ac | ||
|
|
6b25fe5c95 | ||
|
|
0e15e771c3 | ||
|
|
d63e44aa3a | ||
|
|
9a2792a44b | ||
|
|
517b55b8b5 | ||
|
|
37915336ea | ||
|
|
2e7a8b4735 | ||
|
|
c6c931aa0b | ||
|
|
1d23f6e8df | ||
|
|
9825d8a376 | ||
|
|
79dcba8fe7 | ||
|
|
3ec905e08a | ||
|
|
2463bdff0e | ||
|
|
153d0eef51 | ||
|
|
f22aed2614 | ||
|
|
0fb7d0fae2 | ||
|
|
efd3a4e44b | ||
|
|
5d9e62390c | ||
|
|
a50cc9b915 | ||
|
|
715529bef1 | ||
|
|
68e7310d22 | ||
|
|
604b708741 | ||
|
|
e069244f89 | ||
|
|
296da56190 | ||
|
|
624a561145 | ||
|
|
3f7deb49c8 | ||
|
|
9557acb1c3 | ||
|
|
aa56ebb057 | ||
|
|
84b254209f | ||
|
|
e18354b990 | ||
|
|
360f8caead | ||
|
|
9b400a9b6f | ||
|
|
19b6241ef9 | ||
|
|
b15e537692 | ||
|
|
2faa05dcfb | ||
|
|
25a464eeae | ||
|
|
13329f0a48 | ||
|
|
fcf196935e | ||
|
|
9a5d5d6194 | ||
|
|
3473d824a8 | ||
|
|
3b48dcc7c1 | ||
|
|
b56e469a5f | ||
|
|
7986fee56f | ||
|
|
c979059eeb | ||
|
|
3a6fda4daf | ||
|
|
1aea1467da | ||
|
|
3c01be29c4 | ||
|
|
24f4fbad82 | ||
|
|
30cbc41298 | ||
|
|
150e8ef43d | ||
|
|
4b91e985ac | ||
|
|
fdae128cec | ||
|
|
11e1330758 | ||
|
|
aa0eb4bedf | ||
|
|
141ed72693 | ||
|
|
62067aefd3 | ||
|
|
b7d9d7d9ae | ||
|
|
67958ec791 | ||
|
|
b666c52df9 | ||
|
|
6ead82154e | ||
|
|
ca95366219 | ||
|
|
43d643ad09 | ||
|
|
cc4ebe6256 | ||
|
|
f429ac4939 | ||
|
|
0c3d14affc | ||
|
|
63de4387e7 | ||
|
|
7044d43dc8 | ||
|
|
74c2d490ac | ||
|
|
59d71be85f | ||
|
|
9da2e04880 | ||
|
|
1d53e0c923 | ||
|
|
da71b92dd3 | ||
|
|
b640ef1241 | ||
|
|
c5c2bd050d | ||
|
|
85ca28094e | ||
|
|
48d8031f0c | ||
|
|
0c8ae3a384 | ||
|
|
056795eed4 | ||
|
|
cca4fe6d80 | ||
|
|
d17dcd817e | ||
|
|
1688e744ba | ||
|
|
8b6e1e398b | ||
|
|
fa91df6539 | ||
|
|
2b67f40c34 | ||
|
|
1d1aa663f0 | ||
|
|
1fe5fd55d3 | ||
|
|
711f95ec09 | ||
|
|
020692e56b | ||
|
|
de3cb8cdbb | ||
|
|
2e16f51c68 | ||
|
|
a2b194a6f8 | ||
|
|
373de5ee57 | ||
|
|
a1c11cdc40 | ||
|
|
41fb6c5a1a | ||
|
|
e00a95d15c | ||
|
|
d732c307dc | ||
|
|
6a3c348351 | ||
|
|
ec8f37dcd6 | ||
|
|
88506059f9 | ||
|
|
15b1b62adb | ||
|
|
6a4d4b727c | ||
|
|
d9ffc47c43 | ||
|
|
ed6caed3d9 | ||
|
|
37324b443b | ||
|
|
88b12bafc9 | ||
|
|
b56082a980 | ||
|
|
75093873b8 | ||
|
|
8437b06dad | ||
|
|
dc4be47751 | ||
|
|
51cbf57470 | ||
|
|
1c992f84e5 | ||
|
|
763d5d48b5 | ||
|
|
f4543f5f51 | ||
|
|
be54bc0dfd | ||
|
|
b807bc7fc4 | ||
|
|
21fb4aafcf | ||
|
|
4b638011bb | ||
|
|
a109a596c8 | ||
|
|
8ae2dc75f6 | ||
|
|
50c557419e | ||
|
|
c6d1a5784a | ||
|
|
776cabb883 | ||
|
|
16cdcfb96f | ||
|
|
711b1a62d5 | ||
|
|
dae95849ea | ||
|
|
5c6f92d497 | ||
|
|
e7c87969f0 | ||
|
|
6cb00e2ae9 | ||
|
|
d28164c150 | ||
|
|
61bc6e8d1c | ||
|
|
6a232a8830 | ||
|
|
d82554124c | ||
|
|
1fbd1cd28f | ||
|
|
7e03ec7812 | ||
|
|
fa3ecba7a5 | ||
|
|
2de20539a9 | ||
|
|
933db1075f | ||
|
|
c0b3cc9048 | ||
|
|
713cb7043e | ||
|
|
b4d086b540 | ||
|
|
e3e0e8a364 | ||
|
|
e909e32f31 | ||
|
|
9417e1023d | ||
|
|
109e64c2ba | ||
|
|
ceb7046bc4 | ||
|
|
bfc513e997 | ||
|
|
527ce3a872 | ||
|
|
44a8c9f0b3 | ||
|
|
e6001a48d7 | ||
|
|
242814fa72 | ||
|
|
ddc08498cc | ||
|
|
a9a9e3bf11 | ||
|
|
d56fcd0774 | ||
|
|
33ea50c2e9 | ||
|
|
e922722191 | ||
|
|
158c027c23 | ||
|
|
133aada0b7 | ||
|
|
4ae5a1f894 | ||
|
|
93eacdac20 | ||
|
|
cac4948afe | ||
|
|
b480d234dd | ||
|
|
91979a3d0e | ||
|
|
f5a77a1f68 | ||
|
|
a58d582001 | ||
|
|
c4a805107f | ||
|
|
72fc3c0ba4 | ||
|
|
566d9aabae | ||
|
|
18a9bf0caf | ||
|
|
4841776856 | ||
|
|
710941c27f | ||
|
|
2d65c7f859 | ||
|
|
92fdfc4c37 | ||
|
|
7f1fc3602f | ||
|
|
ec0a2325e4 | ||
|
|
c3754cdca2 | ||
|
|
b2d6594bd9 | ||
|
|
f8891ffe3a | ||
|
|
36cc6d1945 | ||
|
|
f32a5b105a | ||
|
|
1c55f9eee2 | ||
|
|
1e6da5f430 | ||
|
|
cee579e7ea | ||
|
|
4bf32a04f4 | ||
|
|
9583af057b | ||
|
|
d46c882347 | ||
|
|
053cfeecce | ||
|
|
f402deef6d | ||
|
|
59a8a569dd | ||
|
|
57029f6efa | ||
|
|
d8f1d188c3 | ||
|
|
89c58d678a | ||
|
|
38ca076cb5 | ||
|
|
69f6423424 | ||
|
|
d5793e04ec | ||
|
|
cbe975818e | ||
|
|
06196fa4f4 | ||
|
|
0d1a02583a | ||
|
|
4079776c36 | ||
|
|
b829333f1d | ||
|
|
0e3ff8ae5f | ||
|
|
73f5ceb79b | ||
|
|
a5b240aab8 | ||
|
|
0648e975d9 | ||
|
|
5d9f9acb1d | ||
|
|
26cdbfe048 | ||
|
|
17e60754f6 | ||
|
|
bb08398957 | ||
|
|
0d1a68dfab | ||
|
|
7f97034055 | ||
|
|
409f5dda9f | ||
|
|
68cd33f37e | ||
|
|
4fb5736694 | ||
|
|
b51f6de0c8 | ||
|
|
3058d99fd5 | ||
|
|
74201365c6 | ||
|
|
c232b7f1f8 | ||
|
|
ae03bacb39 | ||
|
|
fb9147736d | ||
|
|
9499d39f55 | ||
|
|
6a9579efc7 | ||
|
|
8621b3d7da | ||
|
|
24f2524e6e | ||
|
|
74bee7cbbe | ||
|
|
01edb93957 | ||
|
|
bcf56279ec | ||
|
|
6bce5e1616 | ||
|
|
b7336366cb | ||
|
|
96f47a415e | ||
|
|
582e25b11b | ||
|
|
d897f9e0e0 | ||
|
|
9995cc60b5 | ||
|
|
ba22ad2c0c | ||
|
|
57bdf35ee6 | ||
|
|
bbe98ddd86 | ||
|
|
52395497dd | ||
|
|
bd7947ec8f | ||
|
|
a1ee7f5461 | ||
|
|
cd58d154cf | ||
|
|
3bce41baab | ||
|
|
99c1afe0be | ||
|
|
c497061290 | ||
|
|
8177525d49 | ||
|
|
4e0c1aa83d | ||
|
|
ff35dcd95a | ||
|
|
5284b73320 | ||
|
|
7736df030a | ||
|
|
6b773f6e14 | ||
|
|
6f80ac0edd | ||
|
|
d7a9b69995 | ||
|
|
4f9dd41041 | ||
|
|
9739592798 | ||
|
|
cb6057a50c | ||
|
|
11b8e73566 | ||
|
|
da5a44ee01 | ||
|
|
95880cee72 | ||
|
|
6b1e4a7964 | ||
|
|
fa7849d43f | ||
|
|
bd8e557b70 | ||
|
|
f8d03fd680 | ||
|
|
799f38baea | ||
|
|
1fcb656363 | ||
|
|
80a9e0ed54 | ||
|
|
559a7a56e5 | ||
|
|
5f1290d86e | ||
|
|
63e6c01924 | ||
|
|
04407be6b2 | ||
|
|
a0a6f735a1 | ||
|
|
4515c614bf | ||
|
|
9e22580a95 | ||
|
|
058c76cee8 | ||
|
|
323b2da2dd | ||
|
|
55e2c97154 | ||
|
|
2c87988f8d | ||
|
|
f6d755b4ff | ||
|
|
0fbe6ce268 | ||
|
|
e064f8cef2 | ||
|
|
f53a8d919a | ||
|
|
966d6d2d26 | ||
|
|
6a3dff63bb | ||
|
|
177bfb7077 | ||
|
|
5309e8c7c4 | ||
|
|
5957f5d31a | ||
|
|
f465af3a7c | ||
|
|
6d2152cafe | ||
|
|
a54873d302 | ||
|
|
b965ce7392 | ||
|
|
2dd0ce54f9 | ||
|
|
1d3603419e | ||
|
|
d4f85cf073 | ||
|
|
ed433837b3 | ||
|
|
fd35c7a706 | ||
|
|
dd4f0c3a9f | ||
|
|
406be9cd15 | ||
|
|
eef8890f32 | ||
|
|
6342ad4fa7 | ||
|
|
14ce76e6c8 | ||
|
|
09489712e6 | ||
|
|
635b4afff1 | ||
|
|
36f4d30e01 | ||
|
|
4fe4ff4f99 | ||
|
|
5ab64b7002 | ||
|
|
97e85b220e | ||
|
|
d42e8f0042 | ||
|
|
ed8d7157d9 | ||
|
|
04d8d69a8c | ||
|
|
8ab7aa2c6b | ||
|
|
16aaad7aeb | ||
|
|
52d46326de | ||
|
|
e21ec550d4 | ||
|
|
ac20b00e26 | ||
|
|
e75aebb967 | ||
|
|
fef660e6b3 | ||
|
|
3ef0cfc50c | ||
|
|
1303ace453 | ||
|
|
3f9a5fddbb | ||
|
|
f19478edec | ||
|
|
c4234c1692 | ||
|
|
c3967bf849 | ||
|
|
59c7fcbb98 | ||
|
|
50c241fd71 | ||
|
|
392f9c26c5 | ||
|
|
0ae6b4575c | ||
|
|
8b6c78c884 | ||
|
|
9b133b8560 | ||
|
|
2f94a63958 | ||
|
|
2c30e01ae2 | ||
|
|
01f15065fa | ||
|
|
38837e587b | ||
|
|
089c39f741 | ||
|
|
4c7523080a | ||
|
|
ef385a9efa | ||
|
|
b2013cddc9 | ||
|
|
8c4c63673e | ||
|
|
18d7c1baf1 | ||
|
|
f608df4a23 | ||
|
|
54ff198409 | ||
|
|
cbd9b30bd1 | ||
|
|
5985ec8be0 | ||
|
|
3a6a766a03 | ||
|
|
e6fdef43dc | ||
|
|
f0cf9e6492 | ||
|
|
2f9459cf02 | ||
|
|
341341520e | ||
|
|
c195473a29 | ||
|
|
d58c500129 | ||
|
|
5c8b027af4 | ||
|
|
ec577e556d | ||
|
|
bd54e537fd | ||
|
|
481e03abe4 | ||
|
|
999976a76c | ||
|
|
7ebe0182e4 | ||
|
|
a576569a02 | ||
|
|
240f87a911 | ||
|
|
23a5c82a3a | ||
|
|
9336507be6 | ||
|
|
3c7ec04285 | ||
|
|
f57f6d7443 | ||
|
|
94a55eb479 | ||
|
|
0a59c1cad3 | ||
|
|
694fe50241 | ||
|
|
5b20bb851e | ||
|
|
4bd328906e | ||
|
|
69545c0798 | ||
|
|
05013e3d0b | ||
|
|
8a2a6af91b | ||
|
|
c529c247bb | ||
|
|
49422c5819 | ||
|
|
d40cbbb451 | ||
|
|
fffa735868 | ||
|
|
b6acfe0f9d | ||
|
|
43be4e48ad | ||
|
|
18fdf85510 | ||
|
|
fa7af0e5ea | ||
|
|
e25c2856ad | ||
|
|
3d951a883a | ||
|
|
4ebd485694 | ||
|
|
9f3c34a705 | ||
|
|
119e209ffd | ||
|
|
334a5a7c3c | ||
|
|
e7c18f19ab | ||
|
|
3f5566da53 | ||
|
|
c0b9694cda | ||
|
|
5a2007fd46 | ||
|
|
39d091f01c | ||
|
|
d5946d496d | ||
|
|
c7641260dd | ||
|
|
01117f4a30 | ||
|
|
d5d3507921 | ||
|
|
3bde2ddac3 | ||
|
|
5953a938bd | ||
|
|
b59a6666fe | ||
|
|
66a1138c66 | ||
|
|
b6ff541260 | ||
|
|
7c6d0632cc | ||
|
|
4cca8b0d32 | ||
|
|
159769876f | ||
|
|
47a7a00fbf | ||
|
|
0a1c8c3c7e | ||
|
|
7a6e5c465f | ||
|
|
28dff8af6c | ||
|
|
0ef22ab6f2 | ||
|
|
4ebecc0f5e | ||
|
|
07a04acfaa | ||
|
|
3a85499b11 | ||
|
|
8589b55203 | ||
|
|
8cac4335b4 | ||
|
|
e41d7f523a | ||
|
|
2bd52121bb | ||
|
|
910077d7ae | ||
|
|
ac26be7fd7 | ||
|
|
b9b1c8580f | ||
|
|
43302cec4d | ||
|
|
f5ccd4faed | ||
|
|
5504f6d9e8 | ||
|
|
f0c7cb26f7 | ||
|
|
0095941fb7 | ||
|
|
6b6534508f | ||
|
|
2ea77b4442 | ||
|
|
5a81533f61 | ||
|
|
a18029ee89 | ||
|
|
3673372d3d | ||
|
|
993ea8923c | ||
|
|
ef3cf70e30 | ||
|
|
4378a61c8b | ||
|
|
fe2c9bf361 | ||
|
|
0d492a6b02 | ||
|
|
3031d5ba45 | ||
|
|
6905d4375b | ||
|
|
1ae07813ee | ||
|
|
f9d9307503 | ||
|
|
ab09de8542 | ||
|
|
6a992d4fa2 | ||
|
|
3734f32e42 | ||
|
|
a8c9703cbc | ||
|
|
9c2a4695c1 | ||
|
|
638d2183dc | ||
|
|
86d8d23ad8 | ||
|
|
3566dcab28 | ||
|
|
0624ca622b | ||
|
|
70bcd78e85 | ||
|
|
52301ddbe5 | ||
|
|
9952c97edf | ||
|
|
33af632914 | ||
|
|
a288b77c4f | ||
|
|
0728da51fc | ||
|
|
b0b39194dd | ||
|
|
b1ecdf38b8 | ||
|
|
dfc8fe018f | ||
|
|
0a6e1e80d7 | ||
|
|
5d68313f91 | ||
|
|
e27263aebd | ||
|
|
4cdb8d18b7 | ||
|
|
c824016301 | ||
|
|
44d312419a | ||
|
|
3a54105e2c | ||
|
|
7473a43fab | ||
|
|
f31ebf83ac | ||
|
|
e6f5b6ad1a | ||
|
|
55740b2277 | ||
|
|
e03f762b20 | ||
|
|
efc6b5dbaf | ||
|
|
d973e6da10 | ||
|
|
3f1d39e5db | ||
|
|
9467632e1b | ||
|
|
829388b7b5 | ||
|
|
f0e742714a | ||
|
|
fc68da7f6c | ||
|
|
eea33df6b1 | ||
|
|
9574e14c0f | ||
|
|
f2a6c09007 | ||
|
|
e4c8f2bb43 | ||
|
|
d78ed67c26 | ||
|
|
17bc8455aa | ||
|
|
44d66dcdac | ||
|
|
a186a5a9f5 | ||
|
|
75531402e5 | ||
|
|
0dac02f443 | ||
|
|
c968fe0fd9 | ||
|
|
125f14190a | ||
|
|
a6dd2d805b | ||
|
|
6723bf30a7 | ||
|
|
2c61a12bed | ||
|
|
f560b4cbfb | ||
|
|
4e094eaa55 | ||
|
|
2e368baf2a | ||
|
|
323ea3b96b | ||
|
|
e27d03179f | ||
|
|
81a9002ef2 | ||
|
|
18521290bf | ||
|
|
5e682e3f17 | ||
|
|
163296d306 | ||
|
|
1ae98ee177 | ||
|
|
2c02e2776b | ||
|
|
72dc4954ad | ||
|
|
b696e56c5f | ||
|
|
d11d5c65e6 | ||
|
|
5c0f70c361 | ||
|
|
4e2e0b41c6 | ||
|
|
df380bca96 | ||
|
|
9bd7f1810b | ||
|
|
31e6cca916 | ||
|
|
b8095b84ff | ||
|
|
908386091b | ||
|
|
aee4c7ac69 | ||
|
|
dc75d203ff | ||
|
|
ce5524d72a | ||
|
|
06c4b151d6 | ||
|
|
79646fd222 | ||
|
|
d9c18c5593 | ||
|
|
acae154f1b | ||
|
|
a5016446f4 | ||
|
|
2042d877f9 | ||
|
|
2a1ea45659 | ||
|
|
48b08a2b7f | ||
|
|
9c88475b31 | ||
|
|
6aa80aa596 | ||
|
|
85f7ba51f4 | ||
|
|
94b38a51c4 | ||
|
|
253cc4e846 | ||
|
|
431b900084 | ||
|
|
301c32dba0 | ||
|
|
cd1c9be0e1 | ||
|
|
6461a91933 | ||
|
|
e49e743669 | ||
|
|
e8df06582e | ||
|
|
02f120aaf4 | ||
|
|
b84d6a36a6 | ||
|
|
d73f03b9ba | ||
|
|
1a4b035dac | ||
|
|
dd40bf7566 | ||
|
|
5e770e9c9e | ||
|
|
d1b7dc551c | ||
|
|
b0991e28a2 | ||
|
|
180746467e | ||
|
|
f55bf8f83b | ||
|
|
ff10a64727 | ||
|
|
5561b6ead4 | ||
|
|
392d1c04f6 | ||
|
|
d46bcd9291 | ||
|
|
ca496fb3b1 | ||
|
|
7aad6e03e3 | ||
|
|
c97f4d1daf | ||
|
|
7d2a7a0e35 | ||
|
|
92e72aabdc | ||
|
|
8603cd9387 | ||
|
|
1c64bde0ee | ||
|
|
70f3aec552 | ||
|
|
6567d1d6ec | ||
|
|
a5214a0de7 | ||
|
|
a85a84330f | ||
|
|
494918d9fe | ||
|
|
3bfafb0ecb | ||
|
|
ecbc6f7044 | ||
|
|
5ce860443c | ||
|
|
320d74527f | ||
|
|
82a62382d0 | ||
|
|
fbaeecc6a1 | ||
|
|
e434ff5f6e | ||
|
|
f0d46bfeaa | ||
|
|
e377e43f05 | ||
|
|
e640487572 | ||
|
|
397a1968c8 | ||
|
|
783e45f8ac | ||
|
|
fc331a154f | ||
|
|
12ceb9695c | ||
|
|
974c54ede2 | ||
|
|
a4d1bba74e | ||
|
|
960954eba5 | ||
|
|
dabd93c255 | ||
|
|
0d4faa00a7 | ||
|
|
fd665f6bb2 | ||
|
|
ecdb7d3229 | ||
|
|
10fd02e42e | ||
|
|
c53908fe9e | ||
|
|
b72f3a4928 | ||
|
|
55998c9e3b | ||
|
|
e73b4e9734 | ||
|
|
2e14b7b5e8 | ||
|
|
7eb361c92f | ||
|
|
2ca52bf3ba | ||
|
|
2e260761ae | ||
|
|
99a384a3c7 | ||
|
|
662d3a1b4a | ||
|
|
3a1a5d3cd0 | ||
|
|
a0226df166 | ||
|
|
4336b9e787 | ||
|
|
844090b0b8 | ||
|
|
213506d9ae | ||
|
|
4d53f5925c | ||
|
|
21272025c2 | ||
|
|
d44324d4d0 | ||
|
|
628287c14e | ||
|
|
e949c9aa3f | ||
|
|
09c199a1ba | ||
|
|
d47944b2fd | ||
|
|
456e555e8b | ||
|
|
c7042fd847 | ||
|
|
a917da3b1a | ||
|
|
fb0c4b6b3c | ||
|
|
49b348cc7e | ||
|
|
a7ad848270 | ||
|
|
c2c20758c9 | ||
|
|
c7df344805 | ||
|
|
4094c5bfc9 | ||
|
|
4ae2936a45 | ||
|
|
fd2764c7c7 | ||
|
|
b81ae9b954 | ||
|
|
d96374faba | ||
|
|
02533ace81 | ||
|
|
22333e755b | ||
|
|
8dc1718002 | ||
|
|
ad5ffb648f | ||
|
|
861055f558 | ||
|
|
3c6bc2cf9f | ||
|
|
be56911598 | ||
|
|
98137eb79c | ||
|
|
2230d6c751 | ||
|
|
d800a6bb98 | ||
|
|
e0a35e4322 | ||
|
|
c2665e38ba | ||
|
|
ab4dffb53c | ||
|
|
36cce6b174 | ||
|
|
5c854fc690 | ||
|
|
883e0cab29 | ||
|
|
d7113e11db | ||
|
|
66cf905e8b | ||
|
|
7ac61f3840 | ||
|
|
2c94b809ae | ||
|
|
d52081fe41 | ||
|
|
e7f04612f6 | ||
|
|
fd4ad3e4d1 | ||
|
|
f1d45ee5a7 | ||
|
|
6620aa07af | ||
|
|
8063148598 | ||
|
|
2bf2332806 | ||
|
|
64a0721616 | ||
|
|
c3ed002b12 | ||
|
|
f6a9754b4e | ||
|
|
264f3d792a | ||
|
|
cb967c697b | ||
|
|
e21a04cf4b | ||
|
|
f0bcad7116 | ||
|
|
57a83db69d | ||
|
|
3ad8fd711f | ||
|
|
160363fa46 | ||
|
|
0b35946972 | ||
|
|
24d995678f | ||
|
|
8e7039405e | ||
|
|
8f989e4a67 | ||
|
|
696dceacdc | ||
|
|
9e2f6bd187 | ||
|
|
b620f03728 | ||
|
|
ade45c2312 | ||
|
|
b067986576 | ||
|
|
763ba9fd6a | ||
|
|
fd270775a3 | ||
|
|
b99e7f62b2 | ||
|
|
bb8606dbed | ||
|
|
0abd8b1d87 | ||
|
|
58a17f337d | ||
|
|
d3d92bbb6f | ||
|
|
8081e0d281 | ||
|
|
f398321b1f | ||
|
|
7d05f881c4 | ||
|
|
030243a6f9 | ||
|
|
6b72d2ef5d | ||
|
|
b6aacbf401 | ||
|
|
dd467f6c73 | ||
|
|
cd89aa51f0 | ||
|
|
f27c1f7ea3 | ||
|
|
c7ca173c90 | ||
|
|
c9161c02b6 | ||
|
|
6e79a2aa8a | ||
|
|
bea25a0285 | ||
|
|
c96fd3d900 | ||
|
|
0a07cd931f | ||
|
|
c6cab4c43a | ||
|
|
2ab8716c95 | ||
|
|
e3d96aa3ca | ||
|
|
10d2837041 | ||
|
|
372e683063 | ||
|
|
5f6f6cce92 | ||
|
|
27bcba3027 | ||
|
|
053583f5a0 | ||
|
|
5e0eace8d9 | ||
|
|
e7fdfca5f5 | ||
|
|
2d4eec88d3 | ||
|
|
7f94c10be7 | ||
|
|
549cce24c8 | ||
|
|
97fa11d526 | ||
|
|
ad5788589b | ||
|
|
ec09599d84 | ||
|
|
f323d14ed3 | ||
|
|
bc38e9dedd | ||
|
|
7ac90f5cbc | ||
|
|
f3b8d5515a | ||
|
|
bd55baefa6 | ||
|
|
a86126419c | ||
|
|
5c22697344 | ||
|
|
cc6f3d3051 | ||
|
|
b36b32bfe8 | ||
|
|
3945d3b2fe | ||
|
|
ba1b5811ee | ||
|
|
7f4582bb23 | ||
|
|
cace112b1a | ||
|
|
e3a1031081 | ||
|
|
2a41072b44 | ||
|
|
01d470ff77 | ||
|
|
9779de98b8 | ||
|
|
c585678ec9 | ||
|
|
eaf4cdf5e1 | ||
|
|
948368fdb4 | ||
| 0717d5a331 | |||
| a9bfaa96c5 | |||
| 70a5774737 | |||
| 2d3b125d51 | |||
| 4081c08b5a | |||
| 70c36a48a8 | |||
|
|
37bd49cf38 | ||
|
|
081dc4e8ca | ||
|
|
a4466adf8b | ||
|
|
bfe72689fc | ||
|
|
950a8d5694 | ||
|
|
a6b4ef7f5d | ||
|
|
45c0fa0e77 | ||
|
|
a596568151 | ||
|
|
bd94d8d50c | ||
|
|
7fab64ed9c | ||
|
|
8e22c399df | ||
|
|
7546ea91a9 | ||
|
|
8da66978bf | ||
|
|
8484bfa2e0 | ||
|
|
ff970b0e1c | ||
|
|
8be7e4327d | ||
|
|
82eeb237dc | ||
|
|
cbbadf562f | ||
|
|
4308321f39 | ||
|
|
985eebf9b0 | ||
|
|
bf28152a32 | ||
|
|
bae0ad3aeb | ||
|
|
87fae150da | ||
|
|
97853bf0f1 | ||
|
|
cc0a1ce9e2 | ||
|
|
58ebf04bac | ||
|
|
7fea1e1b4a | ||
|
|
d2bf31724f | ||
|
|
b4929d258d | ||
|
|
ddf2e591c6 | ||
|
|
33940726a8 | ||
|
|
05cb8c0b73 | ||
|
|
8d2c6807d2 | ||
|
|
4d9804f188 | ||
|
|
3ae1160868 | ||
|
|
6804dd4363 | ||
|
|
defcadafbb | ||
|
|
40e3f49ab7 | ||
|
|
56196890f5 | ||
|
|
6daee1b00e | ||
|
|
8ff856d7ce | ||
|
|
6731c4b1ab | ||
|
|
544182ebfc | ||
|
|
12c3fa0b87 | ||
|
|
aa4774fe32 | ||
|
|
26bc4ba3dd | ||
|
|
b2abb1af84 | ||
|
|
cebbca9e63 | ||
|
|
1a2dda502a | ||
|
|
b870b0e1b5 | ||
|
|
df2354fdb7 | ||
|
|
c20c90fbc4 | ||
|
|
aba3d5c082 | ||
|
|
8c5602f2fb | ||
|
|
4468dcbe34 | ||
|
|
235adafa0b | ||
|
|
864e8598f8 | ||
|
|
f426348a94 | ||
|
|
085ac88438 | ||
|
|
4881f61be6 | ||
|
|
ff9947ff14 | ||
|
|
92e02a7f79 | ||
|
|
0a8b0406f5 | ||
|
|
1c3b198a17 | ||
|
|
52208b3c90 | ||
|
|
2721f2de3f | ||
|
|
b55a55afc7 | ||
|
|
d7a72b5755 | ||
|
|
1f3a9672c3 | ||
|
|
31c5c3eb9d | ||
|
|
7fada8b97e | ||
|
|
429754fd33 | ||
|
|
b4f0a589ed | ||
|
|
331a3c108f | ||
|
|
d698b28ce5 | ||
|
|
23236aa8c7 | ||
|
|
a9331bb34d | ||
|
|
65dcf1ce1c | ||
|
|
e2b0fe4266 | ||
|
|
fa2acd7cde | ||
|
|
a71c16f8cb | ||
|
|
f466971312 | ||
|
|
69b1a9910f | ||
|
|
4ed735b424 | ||
|
|
175afd97d8 | ||
|
|
72338d578b | ||
|
|
9856d94f2d | ||
|
|
517ffed401 | ||
|
|
38a6a8d984 | ||
|
|
630c1ff8d1 | ||
|
|
7e1568a1ff | ||
|
|
6788010f7d | ||
|
|
9e310934d3 | ||
|
|
e8a3406624 | ||
|
|
fde87a340c | ||
|
|
19050afc3f | ||
|
|
e96557b3e1 | ||
|
|
a5364973d9 | ||
|
|
a46ce8ec3a | ||
|
|
6e35e20e49 | ||
|
|
2d5e4d89b0 | ||
|
|
c9e62002ec | ||
|
|
465627f104 | ||
|
|
3de1c2ab56 | ||
|
|
8f5cc6174c | ||
|
|
29d014a0e1 | ||
|
|
396a0ca563 | ||
|
|
a500178b3c | ||
|
|
7d770f55fb | ||
|
|
db283a66e8 | ||
|
|
c642aef8ca | ||
|
|
396df1a506 | ||
|
|
9c9fa7e537 | ||
|
|
86e2eb0648 | ||
|
|
491db2f0c6 | ||
|
|
f0fb375028 | ||
|
|
16d8bab61a | ||
|
|
2d83a67bd6 | ||
|
|
5ad7e97e05 | ||
|
|
b7a7b6b923 | ||
|
|
0e00d2328d | ||
|
|
53db70e784 | ||
|
|
76c699b4ba | ||
|
|
c901bc07a4 | ||
|
|
b7db23bbae | ||
|
|
389b20d977 | ||
|
|
d06459fa49 | ||
|
|
e2a55cbf34 | ||
|
|
a5e6ade9cb | ||
|
|
a1e32566d3 | ||
|
|
8c7bfb3e1a | ||
|
|
bb0480e978 | ||
|
|
2ccc745513 | ||
|
|
bea83fe94d | ||
|
|
3feaf689d8 | ||
|
|
bd627b58af | ||
|
|
c561d33cb2 | ||
|
|
c8fd3bd683 | ||
|
|
fef1e31634 | ||
|
|
1abaf87abe | ||
|
|
38593fbd85 | ||
|
|
8d187fd275 | ||
|
|
646cc81656 | ||
|
|
01f7536b36 | ||
|
|
97e5ec02f8 | ||
|
|
3dced01af0 | ||
| 0cf4534c5c | |||
| 044f66fba3 | |||
| 40a9ddad4e | |||
|
|
848db340da | ||
|
|
c6da4ffcdd | ||
|
|
8ac7e34be2 | ||
|
|
e4be882994 | ||
|
|
adc05e663f | ||
|
|
0bc236ac98 | ||
| da3d3d9296 | |||
| 6a6af6a952 | |||
| 582a4ab267 | |||
| ebde8546c9 | |||
| fffd0f5465 | |||
|
|
3b6db8e21a | ||
|
|
19a0b7dd76 | ||
|
|
9647ebe206 | ||
|
|
0c726a1275 | ||
|
|
f71eb55f1f | ||
|
|
edcc0b7dbc | ||
|
|
4910b308ee | ||
|
|
0cc1c323ed | ||
|
|
24fd19b35d | ||
|
|
fbd0c7a99e | ||
| 4f5b26698c | |||
| d887e5c24c | |||
| 0c69391bbd | |||
| 91598f9121 | |||
|
|
98d09e7816 | ||
| 7ae9432524 | |||
|
|
c883f0ad8a | ||
|
|
eae60113af | ||
|
|
1aab5240cf | ||
|
|
edb7abde5a | ||
|
|
29d95340b0 | ||
|
|
6fb19ac74f | ||
|
|
37dc28a69d | ||
|
|
7623668256 | ||
|
|
bbf7aacd4d | ||
|
|
d6c1e08ef4 | ||
|
|
d1f95f04a7 | ||
|
|
0dacf8a326 | ||
|
|
e534b10722 | ||
|
|
0785cc5aa9 | ||
|
|
48025e0102 | ||
|
|
eeb719e8f7 | ||
|
|
7933edad43 | ||
|
|
5f75728ede | ||
|
|
8cff0ed6c2 | ||
|
|
a8964f4108 | ||
|
|
740aeedca3 | ||
|
|
8152e7e62c | ||
|
|
b36c95b91e | ||
|
|
b5c1fd95dc | ||
|
|
23f7cbf1c3 | ||
|
|
ae78ecc2c5 | ||
|
|
dd06516d1c | ||
|
|
8066f36a4e | ||
|
|
3bbf6b9d53 | ||
|
|
8bc5944178 | ||
|
|
a13b99642b | ||
|
|
060db5983c | ||
|
|
42bba6920e | ||
|
|
ea11d78995 | ||
|
|
fe63ca762f | ||
|
|
a82982725e | ||
|
|
7dfb760421 | ||
|
|
b6e9a3f37e | ||
|
|
3f28309b7b | ||
|
|
d2a4a330f9 | ||
|
|
d8c35770ab | ||
|
|
dbb6794a41 | ||
|
|
63c9909aa0 | ||
|
|
f1dac5087e | ||
|
|
4f80d7013e | ||
|
|
2919df1df5 | ||
|
|
a2df2f014b | ||
|
|
8673169ee7 | ||
|
|
2c2e0fb172 | ||
|
|
d45c5de906 | ||
|
|
eb985beed6 | ||
|
|
ff9aa0fc54 | ||
|
|
0bd1a3efb2 | ||
|
|
08e7ec948b | ||
|
|
6092c07eb5 | ||
|
|
09d74336fa | ||
|
|
694354e5e6 | ||
|
|
55f04db6b8 | ||
|
|
6f8360a878 | ||
|
|
6ca4685fee | ||
|
|
d86b592add | ||
|
|
9944b3dac1 | ||
|
|
8ed79bd1af | ||
|
|
0600ae6213 | ||
|
|
623415d843 | ||
|
|
87b84a83cc | ||
|
|
aa5eba9723 | ||
|
|
c2cd883f93 | ||
|
|
43e20a8cd9 | ||
|
|
48299cdd7f | ||
|
|
2335ca0f72 | ||
|
|
e42c1a33b5 | ||
|
|
b1a38c2d50 | ||
|
|
f1f05303db | ||
|
|
8424a733a2 | ||
|
|
2bf7a4a08c | ||
|
|
b4bae8173b | ||
|
|
3c10888e3a | ||
|
|
1bd143027a | ||
|
|
18d31f2d5b | ||
|
|
e142d655b9 | ||
|
|
e1ce1f66cd | ||
|
|
08c9a4d86b | ||
|
|
a620b38ba0 | ||
|
|
8e3c0cd658 | ||
|
|
f709a2805d | ||
|
|
adbadfc4f5 | ||
|
|
9c7a9cbace | ||
|
|
1333688a4e | ||
|
|
17852b78d5 | ||
|
|
dd15ae5560 | ||
|
|
9d33990199 | ||
|
|
866e7cf371 | ||
|
|
f5c1596ddf | ||
|
|
64cff172a1 | ||
|
|
c10b99b29a | ||
|
|
bca90f191c | ||
|
|
7996a10116 | ||
|
|
db4562f67d | ||
|
|
3fd0382a51 | ||
|
|
3e98e5e86c | ||
|
|
7b17965b26 | ||
|
|
5c050d5779 | ||
|
|
5d6d1a6da7 | ||
|
|
b59d876e76 | ||
|
|
9028aede6e | ||
|
|
aa0353a501 | ||
|
|
63c3f31c99 | ||
|
|
2fe44af8ba | ||
|
|
515f0bf9c2 | ||
|
|
27a52a7962 | ||
|
|
61feb43aba | ||
|
|
07fdcb6160 | ||
|
|
ac08f4447f | ||
|
|
d243c90d0a | ||
|
|
72033ab689 | ||
|
|
3f63501fa8 | ||
|
|
019496e3a8 | ||
|
|
52695a9ece | ||
|
|
a20ce951e7 | ||
|
|
85fa157d5a | ||
|
|
1068219c56 | ||
|
|
427ec50624 | ||
|
|
52ce1509a5 | ||
|
|
fe06583643 | ||
|
|
3e3c69b5bc | ||
|
|
3b0802535d | ||
|
|
75415090bd | ||
|
|
6f664cb05a | ||
|
|
a8ea67178d | ||
|
|
64a464aefb | ||
|
|
67885ad9fa | ||
|
|
e5bf72e897 | ||
|
|
1f62190eef | ||
|
|
f839e6cc79 | ||
|
|
52aad1ec08 | ||
|
|
5f0d1252b6 | ||
|
|
3370f0f23d | ||
|
|
e1574b1485 | ||
|
|
78fae736f9 | ||
|
|
ed3811a65c | ||
|
|
4e915de35d | ||
|
|
94e6648ee5 | ||
|
|
75a985e63e | ||
|
|
5714558785 | ||
|
|
c3ccb5b7bf | ||
|
|
87849cda4f | ||
|
|
f62114c7eb | ||
|
|
24f57dde2f | ||
|
|
cdfcd8a4c9 | ||
|
|
1bc1370701 | ||
|
|
6b7b080827 | ||
|
|
3556e1320d | ||
|
|
621640ca71 | ||
|
|
2c0e439d0d | ||
|
|
a392d8425e | ||
|
|
0273d78de9 | ||
|
|
ed51d8dcf6 | ||
|
|
3d78ca8b09 | ||
|
|
1cec856c63 | ||
|
|
6212c81ec6 | ||
|
|
408459b883 | ||
|
|
f6d88042b5 | ||
|
|
b58bdc9e32 | ||
|
|
f3501f44cb | ||
|
|
61f37b10e2 | ||
|
|
2856aff757 | ||
|
|
3949ecc054 | ||
|
|
acd9ea57d7 | ||
|
|
794a2a1892 | ||
|
|
af8bed6d3f | ||
|
|
462f54f906 | ||
|
|
1c4e99cf2c | ||
|
|
a349b9bdfe | ||
|
|
75e790836a | ||
|
|
5ba677cd36 | ||
|
|
a7b761a89c | ||
|
|
6e0b00d60c | ||
|
|
c107a938be | ||
|
|
c6fc9cee8e | ||
|
|
99a4bf5eb5 | ||
|
|
46410ff933 | ||
|
|
75e868e71e | ||
|
|
53b1b4d682 | ||
|
|
566d9bee5c | ||
|
|
b8bf65d68e | ||
|
|
075c69a3cd | ||
|
|
ada10067dd | ||
|
|
d0f680b379 | ||
|
|
ca2c1d0b48 | ||
|
|
5fd5939395 | ||
|
|
e5ca631b52 | ||
|
|
3313fdeb32 | ||
|
|
2bd8ff0b6c | ||
|
|
5c9d6d455d | ||
|
|
8f6cf6fef7 | ||
|
|
a5f8d42d34 | ||
|
|
153ec2823d | ||
|
|
9f65a30504 | ||
|
|
b018123f19 | ||
|
|
97e3e969e4 | ||
|
|
84099514e6 | ||
|
|
3286e683e6 | ||
|
|
be6ea2a7b8 | ||
|
|
4a939141f6 | ||
|
|
1034477f1e | ||
|
|
49cfcd817d | ||
|
|
39822d7e57 | ||
|
|
142d333c5c | ||
|
|
13c491712b | ||
|
|
5c527c2a28 | ||
|
|
41979f0823 | ||
|
|
192b0a8fb5 | ||
|
|
6a7be1e142 | ||
|
|
b3666060d4 | ||
|
|
c9c07a6bbc | ||
|
|
d57287b89b | ||
|
|
79e7015a32 | ||
|
|
6c4ee951e0 | ||
|
|
f0f4633346 | ||
|
|
6c37e8f4e7 | ||
|
|
4d05416ce3 | ||
|
|
60993b9d23 | ||
|
|
959e7c11ce | ||
|
|
21ae739eec | ||
|
|
b1c3c9a9d6 | ||
|
|
bf985c8ac6 | ||
|
|
21c5eae717 | ||
|
|
4d5cf249a0 | ||
|
|
8c7b811135 | ||
|
|
940b879950 | ||
|
|
ccf650d4ca | ||
|
|
6342f16eb5 |
@@ -3,3 +3,6 @@ rustflags = "-C link-arg=/STACK:8000000"
|
||||
|
||||
[target.'cfg(all(target_os = "windows", not(target_env = "msvc")))']
|
||||
rustflags = "-C link-args=-Wl,--stack,8000000"
|
||||
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ["--cfg=getrandom_backend=\"wasm_js\""]
|
||||
|
||||
46
.claude/commands/apple-container.md
Normal file
46
.claude/commands/apple-container.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
allowed-tools: Bash(container *), Bash(cargo *), Read, Grep, Glob
|
||||
---
|
||||
|
||||
# Run Tests in Linux Container (Apple `container` CLI)
|
||||
|
||||
Run RustPython tests inside a Linux container using Apple's `container` CLI.
|
||||
**NEVER use Docker, Podman, or any other container runtime.** Only use the `container` command.
|
||||
|
||||
## Arguments
|
||||
- `$ARGUMENTS`: Test command to run (e.g., `test_io`, `test_codecs -v`, `test_io -v -m "test_errors"`)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The `container` CLI is installed via `brew install container`.
|
||||
The dev image `rustpython-dev` is already built.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Check if the container is already running**
|
||||
```shell
|
||||
container list 2>/dev/null | grep rustpython-test
|
||||
```
|
||||
|
||||
2. **Start the container if not running**
|
||||
```shell
|
||||
container run -d --name rustpython-test -m 8G -c 4 \
|
||||
--mount type=bind,source=/Users/al03219714/Projects/RustPython3,target=/workspace \
|
||||
-w /workspace rustpython-dev sleep infinity
|
||||
```
|
||||
|
||||
3. **Run the test inside the container**
|
||||
```shell
|
||||
container exec rustpython-test sh -c "cargo run --release -- -m test $ARGUMENTS"
|
||||
```
|
||||
|
||||
4. **Report results**
|
||||
- Show test summary (pass/fail counts, expected failures, unexpected successes)
|
||||
- Highlight any new failures compared to macOS results if available
|
||||
- Do NOT stop or remove the container after testing (keep it for reuse)
|
||||
|
||||
## Notes
|
||||
- The workspace is bind-mounted, so local code changes are immediately available
|
||||
- Use `container exec rustpython-test sh -c "..."` for any command inside the container
|
||||
- To rebuild after code changes, run: `container exec rustpython-test sh -c "cargo build --release"`
|
||||
- To stop the container when done: `container rm -f rustpython-test`
|
||||
49
.claude/commands/investigate-test-failure.md
Normal file
49
.claude/commands/investigate-test-failure.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
allowed-tools: Bash(python3:*), Bash(cargo run:*), Read, Grep, Glob, Bash(git add:*), Bash(git commit:*), Bash(cargo fmt:*), Bash(git diff:*), Task
|
||||
---
|
||||
|
||||
# Investigate Test Failure
|
||||
|
||||
Investigate why a specific test is failing and determine if it can be fixed or needs an issue.
|
||||
|
||||
## Arguments
|
||||
- `$ARGUMENTS`: Failed test identifier (e.g., `test_inspect.TestGetSourceBase.test_getsource_reload`)
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Analyze failure cause**
|
||||
- Read the test code
|
||||
- Analyze failure message/traceback
|
||||
- Check related RustPython code
|
||||
|
||||
2. **Verify behavior in CPython**
|
||||
- Run the test with `python3 -m unittest` to confirm expected behavior
|
||||
- Document the expected output
|
||||
|
||||
3. **Determine fix feasibility**
|
||||
- **Simple fix** (import issues, small logic bugs): Fix code → Run `cargo fmt --all` → Pre-commit review → Commit
|
||||
- **Complex fix** (major unimplemented features): Collect issue info and report to user
|
||||
|
||||
**Pre-commit review process**:
|
||||
- Run `git diff` to see the changes
|
||||
- Use Task tool with `general-purpose` subagent to review:
|
||||
- Compare implementation against cpython/ source code
|
||||
- Verify the fix aligns with CPython behavior
|
||||
- Check for any missed edge cases
|
||||
- Proceed to commit only after review passes
|
||||
|
||||
4. **For complex issues - Collect issue information**
|
||||
Following `.github/ISSUE_TEMPLATE/report-incompatibility.md` format:
|
||||
|
||||
- **Feature**: Description of missing/broken Python feature
|
||||
- **Minimal reproduction code**: Smallest code that reproduces the issue
|
||||
- **CPython behavior**: Result when running with python3
|
||||
- **RustPython behavior**: Result when running with cargo run
|
||||
- **Python Documentation link**: Link to relevant CPython docs
|
||||
|
||||
Report collected information to the user. Issue creation is done only upon user request.
|
||||
|
||||
Example issue creation command:
|
||||
```
|
||||
gh issue create --template report-incompatibility.md --title "..." --body "..."
|
||||
```
|
||||
33
.claude/commands/upgrade-pylib-next.md
Normal file
33
.claude/commands/upgrade-pylib-next.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
allowed-tools: Skill(upgrade-pylib), Bash(gh pr list:*)
|
||||
---
|
||||
|
||||
# Upgrade Next Python Library
|
||||
|
||||
Find the next Python library module ready for upgrade and run `/upgrade-pylib` for it.
|
||||
|
||||
## Current TODO Status
|
||||
|
||||
!`cargo run --release -- scripts/update_lib todo 2>/dev/null`
|
||||
|
||||
## Open Upgrade PRs
|
||||
|
||||
!`gh pr list --search "Update in:title" --json number,title --template '{{range .}}#{{.number}} {{.title}}{{"\n"}}{{end}}'`
|
||||
|
||||
## Instructions
|
||||
|
||||
From the TODO list above, find modules matching these patterns (in priority order):
|
||||
|
||||
1. `[ ] [no deps]` - Modules with no dependencies (can be upgraded immediately)
|
||||
2. `[ ] [0/n]` - Modules where all dependencies are already upgraded (e.g., `[0/3]`, `[0/5]`)
|
||||
|
||||
These patterns indicate modules that are ready to upgrade without blocking dependencies.
|
||||
|
||||
**Important**: Skip any modules that already have an open PR in the "Open Upgrade PRs" list above.
|
||||
|
||||
**After identifying a suitable module**, run:
|
||||
```
|
||||
/upgrade-pylib <module_name>
|
||||
```
|
||||
|
||||
If no modules match these criteria, inform the user that all eligible modules have dependencies that need to be upgraded first.
|
||||
157
.claude/commands/upgrade-pylib.md
Normal file
157
.claude/commands/upgrade-pylib.md
Normal file
@@ -0,0 +1,157 @@
|
||||
---
|
||||
allowed-tools: Bash(git add:*), Bash(git commit:*), Bash(python3 scripts/update_lib quick:*), Bash(python3 scripts/update_lib auto-mark:*)
|
||||
---
|
||||
|
||||
# Upgrade Python Library from CPython
|
||||
|
||||
Upgrade a Python standard library module from CPython to RustPython.
|
||||
|
||||
## Arguments
|
||||
- `$ARGUMENTS`: Library name to upgrade (e.g., `inspect`, `asyncio`, `json`)
|
||||
|
||||
## Important: Report Tool Issues First
|
||||
|
||||
If during the upgrade process you encounter any of the following issues with `scripts/update_lib`:
|
||||
- A feature that should be automated but isn't supported
|
||||
- A bug or unexpected behavior in the tool
|
||||
- Missing functionality that would make the upgrade easier
|
||||
|
||||
**STOP the upgrade and report the issue first.** Describe:
|
||||
1. What you were trying to do
|
||||
- Library name
|
||||
- The full command executed (e.g. python scripts/update_lib quick cpython/Lib/$ARGUMENTS.py)
|
||||
2. What went wrong or what's missing
|
||||
3. Expected vs actual behavior
|
||||
|
||||
This helps improve the tooling for future upgrades.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Run quick upgrade with update_lib**
|
||||
- Run: `python3 scripts/update_lib quick $ARGUMENTS` (module name)
|
||||
- Or: `python3 scripts/update_lib quick cpython/Lib/$ARGUMENTS.py` (library file path)
|
||||
- Or: `python3 scripts/update_lib quick cpython/Lib/$ARGUMENTS/` (library directory path)
|
||||
- This will:
|
||||
- Copy library files (delete existing `Lib/$ARGUMENTS.py` or `Lib/$ARGUMENTS/`, then copy from `cpython/Lib/`)
|
||||
- Patch test files preserving existing RustPython markers
|
||||
- Run tests and auto-mark new test failures (not regressions)
|
||||
- Remove `@unittest.expectedFailure` from tests that now pass
|
||||
- Create a git commit with the changes
|
||||
- **Handle warnings**: If you see warnings like `WARNING: TestCFoo does not exist in remote file`, it means the class structure changed and markers couldn't be transferred automatically. These need to be manually restored in step 2 or added in step 3.
|
||||
|
||||
2. **Review git diff and restore RUSTPYTHON-specific changes**
|
||||
- Run `git diff Lib/test/test_$ARGUMENTS` to review all changes
|
||||
- **Only restore changes that have explicit `RUSTPYTHON` comments**. Look for:
|
||||
- `# XXX: RUSTPYTHON` or `# XXX RUSTPYTHON` - Comments marking RustPython-specific code modifications
|
||||
- `# TODO: RUSTPYTHON` - Comments marking tests that need work
|
||||
- Code changes with inline `# ... RUSTPYTHON` comments
|
||||
- **Do NOT restore other diff changes** - these are likely upstream CPython changes, not RustPython-specific modifications
|
||||
- When restoring, preserve the original context and formatting
|
||||
|
||||
3. **Investigate test failures with subagent**
|
||||
- First, get dependent tests using the deps command:
|
||||
```
|
||||
cargo run --release -- scripts/update_lib deps $ARGUMENTS
|
||||
```
|
||||
- Look for the line `- [ ] $ARGUMENTS: test_xxx test_yyy ...` to get the direct dependent tests
|
||||
- Run those tests to collect failures:
|
||||
```
|
||||
cargo run --release -- -m test test_xxx test_yyy ... 2>&1 | grep -E "^(FAIL|ERROR):"
|
||||
```
|
||||
- For example, if deps output shows `- [ ] linecache: test_bdb test_inspect test_linecache test_traceback test_zipimport`, run:
|
||||
```
|
||||
cargo run --release -- -m test test_bdb test_inspect test_linecache test_traceback test_zipimport 2>&1 | grep -E "^(FAIL|ERROR):"
|
||||
```
|
||||
- For each failure, use the Task tool with `general-purpose` subagent to investigate:
|
||||
- Subagent should follow the `/investigate-test-failure` skill workflow
|
||||
- Pass the failed test identifier as the argument (e.g., `test_inspect.TestGetSourceBase.test_getsource_reload`)
|
||||
- If subagent can fix the issue easily: fix and commit
|
||||
- If complex issue: subagent collects issue info and reports back (issue creation on user request only)
|
||||
- Using subagent prevents context pollution in the main conversation
|
||||
|
||||
4. **Mark remaining test failures with auto-mark**
|
||||
- Run: `python3 scripts/update_lib auto-mark Lib/test/test_$ARGUMENTS.py --mark-failure`
|
||||
- Or for directory: `python3 scripts/update_lib auto-mark Lib/test/test_$ARGUMENTS/ --mark-failure`
|
||||
- This will:
|
||||
- Run tests and mark ALL failing tests with `@unittest.expectedFailure`
|
||||
- Remove `@unittest.expectedFailure` from tests that now pass
|
||||
- **Note**: The `--mark-failure` flag marks all failures including regressions. Review the changes before committing.
|
||||
|
||||
5. **Handle panics manually**
|
||||
- If any tests cause panics/crashes (not just assertion failures), they need `@unittest.skip` instead:
|
||||
```python
|
||||
@unittest.skip("TODO: RUSTPYTHON; panics with 'index out of bounds'")
|
||||
def test_crashes(self):
|
||||
...
|
||||
```
|
||||
- auto-mark cannot detect panics automatically - check the test output for crash messages
|
||||
|
||||
6. **Handle class-specific failures**
|
||||
- If a test fails only in the C implementation (TestCFoo) but passes in the Python implementation (TestPyFoo), or vice versa, move the marker to the specific subclass:
|
||||
```python
|
||||
# Base class - no marker here
|
||||
class TestFoo:
|
||||
def test_something(self):
|
||||
...
|
||||
|
||||
class TestPyFoo(TestFoo, PyTest): pass
|
||||
|
||||
class TestCFoo(TestFoo, CTest):
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_something(self):
|
||||
return super().test_something()
|
||||
```
|
||||
|
||||
7. **Commit the test fixes**
|
||||
- Run: `git add -u && git commit -m "Mark failing tests"`
|
||||
- This creates a separate commit for the test markers added in steps 2-6
|
||||
|
||||
## Example Usage
|
||||
```
|
||||
# Using module names (recommended)
|
||||
/upgrade-pylib inspect
|
||||
/upgrade-pylib json
|
||||
/upgrade-pylib asyncio
|
||||
|
||||
# Using library paths (alternative)
|
||||
/upgrade-pylib cpython/Lib/inspect.py
|
||||
/upgrade-pylib cpython/Lib/json/
|
||||
```
|
||||
|
||||
## Example: Restoring RUSTPYTHON changes
|
||||
|
||||
When git diff shows removed RUSTPYTHON-specific code like:
|
||||
```diff
|
||||
-# XXX RUSTPYTHON: we don't import _json as fresh since...
|
||||
-cjson = import_helper.import_fresh_module('json') #, fresh=['_json'])
|
||||
+cjson = import_helper.import_fresh_module('json', fresh=['_json'])
|
||||
```
|
||||
|
||||
You should restore the RustPython version:
|
||||
```python
|
||||
# XXX RUSTPYTHON: we don't import _json as fresh since...
|
||||
cjson = import_helper.import_fresh_module('json') #, fresh=['_json'])
|
||||
```
|
||||
|
||||
## Notes
|
||||
- The cpython/ directory should contain the CPython source that we're syncing from
|
||||
- `scripts/update_lib` package handles patching and auto-marking:
|
||||
- `quick` - Combined patch + auto-mark (recommended)
|
||||
- `migrate` - Only migrate (patch), no test running
|
||||
- `auto-mark` - Only run tests and mark failures
|
||||
- `copy-lib` - Copy library files (not tests)
|
||||
- The patching:
|
||||
- Transfers `@unittest.expectedFailure` and `@unittest.skip` decorators with `TODO: RUSTPYTHON` markers
|
||||
- Adds `import unittest # XXX: RUSTPYTHON` if needed for the decorators
|
||||
- **Limitation**: If a class was restructured (e.g., method overrides removed), update_lib will warn and skip those markers
|
||||
- The smart auto-mark:
|
||||
- Marks NEW test failures automatically (tests that didn't exist before)
|
||||
- Does NOT mark regressions (existing tests that now fail) - these are warnings
|
||||
- Removes `@unittest.expectedFailure` from tests that now pass
|
||||
- The script does NOT preserve all RustPython-specific changes - you must review `git diff` and restore them
|
||||
- Common RustPython markers to look for:
|
||||
- `# XXX: RUSTPYTHON` or `# XXX RUSTPYTHON` - Inline comments for code modifications
|
||||
- `# TODO: RUSTPYTHON` - Test skip/failure markers
|
||||
- Any code with `RUSTPYTHON` in comments that was removed in the diff
|
||||
- **Important**: Not all changes in the git diff need to be restored. Only restore changes that have explicit `RUSTPYTHON` comments. Other changes are upstream CPython updates.
|
||||
50
.claude/scripts/setup-env.sh
Executable file
50
.claude/scripts/setup-env.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# Claude Code web session startup script
|
||||
# Sets up the development environment for RustPython
|
||||
|
||||
set -e
|
||||
|
||||
cd /home/user/RustPython
|
||||
|
||||
echo "=== RustPython dev environment setup ==="
|
||||
|
||||
# 1. Ensure python3 points to 3.13+ (needed for scripts/update_lib)
|
||||
# /usr/local/bin takes precedence over /usr/bin in PATH,
|
||||
# so we update the symlink there directly.
|
||||
CURRENT_PY=$(python3 --version 2>&1 | grep -oP '\d+\.\d+')
|
||||
if [ "$(printf '%s\n' "3.13" "$CURRENT_PY" | sort -V | head -1)" != "3.13" ]; then
|
||||
echo "Upgrading python3 default to 3.13..."
|
||||
# Find best available Python >= 3.13
|
||||
TARGET=""
|
||||
for ver in python3.14 python3.13; do
|
||||
if command -v "$ver" &>/dev/null; then
|
||||
TARGET=$(command -v "$ver")
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -n "$TARGET" ]; then
|
||||
# Override /usr/local/bin/python3 if it exists and is outdated
|
||||
if [ -e /usr/local/bin/python3 ]; then
|
||||
sudo ln -sf "$TARGET" /usr/local/bin/python3
|
||||
fi
|
||||
# Also set /usr/bin via update-alternatives
|
||||
sudo update-alternatives --install /usr/bin/python3 python3 "$TARGET" 3 2>/dev/null || true
|
||||
sudo update-alternatives --set python3 "$TARGET" 2>/dev/null || true
|
||||
echo "python3 now: $(python3 --version)"
|
||||
else
|
||||
echo "WARNING: No Python 3.13+ found. scripts/update_lib may not work."
|
||||
fi
|
||||
else
|
||||
echo "python3 already >= 3.13: $(python3 --version)"
|
||||
fi
|
||||
|
||||
# 2. Clone CPython source if not present (needed for scripts/update_lib)
|
||||
if [ ! -d "cpython" ]; then
|
||||
echo "Cloning CPython v3.14.3 (shallow)..."
|
||||
git clone --depth 1 --branch v3.14.3 https://github.com/python/cpython.git cpython
|
||||
echo "CPython source ready."
|
||||
else
|
||||
echo "CPython source already present."
|
||||
fi
|
||||
|
||||
echo "=== Setup complete ==="
|
||||
15
.claude/settings.json
Normal file
15
.claude/settings.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/scripts/setup-env.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
3
.coderabbit.yml
Normal file
3
.coderabbit.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
reviews:
|
||||
path_filters:
|
||||
- "!Lib/**"
|
||||
273
.cspell.dict/cpython.txt
Normal file
273
.cspell.dict/cpython.txt
Normal file
@@ -0,0 +1,273 @@
|
||||
ADDOP
|
||||
aftersign
|
||||
argdefs
|
||||
argtypes
|
||||
asdl
|
||||
asname
|
||||
atopen
|
||||
atext
|
||||
attro
|
||||
augassign
|
||||
badcert
|
||||
badsyntax
|
||||
baseinfo
|
||||
basetype
|
||||
binop
|
||||
bltin
|
||||
boolop
|
||||
BUFMAX
|
||||
BUILDSTDLIB
|
||||
bxor
|
||||
byteswap
|
||||
cached_tsver
|
||||
cadata
|
||||
cafile
|
||||
calldepth
|
||||
callinfo
|
||||
callproc
|
||||
capath
|
||||
carg
|
||||
cellarg
|
||||
cellvar
|
||||
cellvars
|
||||
ceval
|
||||
cfield
|
||||
cfws
|
||||
CFWS
|
||||
CLASSDEREF
|
||||
classdict
|
||||
cmpop
|
||||
CNOTAB
|
||||
codedepth
|
||||
CODEUNIT
|
||||
CONIN
|
||||
CONOUT
|
||||
constevaluator
|
||||
consti
|
||||
CONVFUNC
|
||||
convparam
|
||||
copyslot
|
||||
cpucount
|
||||
datastack
|
||||
defaultdict
|
||||
denom
|
||||
deopt
|
||||
deopts
|
||||
dictbytype
|
||||
DICTFLAG
|
||||
dictoffset
|
||||
distpoint
|
||||
dynload
|
||||
elts
|
||||
eooh
|
||||
eofs
|
||||
EOOH
|
||||
evalloop
|
||||
excepthandler
|
||||
exceptiontable
|
||||
fastlocal
|
||||
fastlocals
|
||||
fblock
|
||||
fblocks
|
||||
fdescr
|
||||
fdst
|
||||
ffi_argtypes
|
||||
fielddesc
|
||||
fieldlist
|
||||
fileutils
|
||||
finalbody
|
||||
finalizers
|
||||
firsttraceable
|
||||
flowgraph
|
||||
formatfloat
|
||||
freelist
|
||||
freevar
|
||||
freevars
|
||||
fromlist
|
||||
fsrc
|
||||
getdict
|
||||
getfunc
|
||||
getiter
|
||||
getsets
|
||||
getslice
|
||||
globalgetvar
|
||||
HASARRAY
|
||||
HASBITFIELD
|
||||
HASPOINTER
|
||||
HASSTRUCT
|
||||
HASUNION
|
||||
heaptype
|
||||
hexdigit
|
||||
HIGHRES
|
||||
ialloc
|
||||
IFUNC
|
||||
IMMUTABLETYPE
|
||||
INCREF
|
||||
inlinedepth
|
||||
inplace
|
||||
inpos
|
||||
ioffset
|
||||
isbytecode
|
||||
ishidden
|
||||
ismine
|
||||
ISPOINTER
|
||||
isoctal
|
||||
iteminfo
|
||||
Itertool
|
||||
iused
|
||||
keeped
|
||||
kwnames
|
||||
kwonlyarg
|
||||
kwonlyargs
|
||||
kwonlydefaults
|
||||
lasti
|
||||
libffi
|
||||
linearise
|
||||
lineful
|
||||
lineiterator
|
||||
linetable
|
||||
LNOTAB
|
||||
loadfast
|
||||
localsplus
|
||||
localspluskinds
|
||||
Lshift
|
||||
lslpp
|
||||
lsprof
|
||||
MAXBLOCKS
|
||||
maxdepth
|
||||
metavars
|
||||
miscompiles
|
||||
mult
|
||||
multibytecodec
|
||||
nameobj
|
||||
nameop
|
||||
nargsf
|
||||
nblocks
|
||||
ncells
|
||||
ncellsused
|
||||
ncellvars
|
||||
nconsts
|
||||
newargs
|
||||
newfree
|
||||
NEWLOCALS
|
||||
newsemlockobject
|
||||
nextop
|
||||
nfrees
|
||||
nkwargs
|
||||
nkwelts
|
||||
nlocalsplus
|
||||
nointerrupt
|
||||
noffsets
|
||||
Nondescriptor
|
||||
noninteger
|
||||
nops
|
||||
noraise
|
||||
nseen
|
||||
NSIGNALS
|
||||
numer
|
||||
nvars
|
||||
opname
|
||||
opnames
|
||||
orelse
|
||||
outparam
|
||||
outparm
|
||||
paramfunc
|
||||
parg
|
||||
pathconfig
|
||||
patma
|
||||
peepholer
|
||||
phcount
|
||||
platstdlib
|
||||
ploc
|
||||
posonlyarg
|
||||
posonlyargs
|
||||
prec
|
||||
preds
|
||||
preinitialized
|
||||
pybuilddir
|
||||
pycore
|
||||
pyinner
|
||||
pydecimal
|
||||
pyerrors
|
||||
Pyfunc
|
||||
pylifecycle
|
||||
pymain
|
||||
pyrepl
|
||||
pystate
|
||||
PYTHONTRACEMALLOC
|
||||
PYTHONUTF8
|
||||
pythonw
|
||||
PYTHREAD_NAME
|
||||
releasebuffer
|
||||
repr
|
||||
resinfo
|
||||
retarget
|
||||
Rshift
|
||||
SA_ONSTACK
|
||||
saveall
|
||||
scls
|
||||
setdict
|
||||
setfunc
|
||||
setprofileallthreads
|
||||
SETREF
|
||||
setresult
|
||||
setslice
|
||||
settraceallthreads
|
||||
sget
|
||||
SLOTDEFINED
|
||||
SMALLBUF
|
||||
SOABI
|
||||
SSLEOF
|
||||
stackdepth
|
||||
stackref
|
||||
staticbase
|
||||
stginfo
|
||||
storefast
|
||||
stringlib
|
||||
stringized
|
||||
structseq
|
||||
subkwargs
|
||||
subparams
|
||||
subscr
|
||||
sval
|
||||
swappedbytes
|
||||
swaptimize
|
||||
sysdict
|
||||
tbstderr
|
||||
templatelib
|
||||
testconsole
|
||||
threadstate
|
||||
ticketer
|
||||
tmptype
|
||||
tok_oldval
|
||||
tstate
|
||||
tvars
|
||||
typeobject
|
||||
typeparam
|
||||
Typeparam
|
||||
typeparams
|
||||
typeslots
|
||||
unaryop
|
||||
uncollectable
|
||||
Unhandle
|
||||
unparse
|
||||
unparser
|
||||
untargeted
|
||||
untracking
|
||||
VARKEYWORDS
|
||||
varkwarg
|
||||
venvlauncher
|
||||
venvlaunchert
|
||||
venvw
|
||||
venvwlauncher
|
||||
venvwlaunchert
|
||||
wbits
|
||||
weakreflist
|
||||
weakrefobject
|
||||
webpki
|
||||
winconsoleio
|
||||
withitem
|
||||
withs
|
||||
worklist
|
||||
xstat
|
||||
XXPRIME
|
||||
307
.cspell.dict/python-more.txt
Normal file
307
.cspell.dict/python-more.txt
Normal file
@@ -0,0 +1,307 @@
|
||||
abiflags
|
||||
abstractmethods
|
||||
addcompare
|
||||
aenter
|
||||
aexit
|
||||
aiter
|
||||
altzone
|
||||
anext
|
||||
anextawaitable
|
||||
annotationlib
|
||||
appendleft
|
||||
argcount
|
||||
arrayiterator
|
||||
arraytype
|
||||
asend
|
||||
asyncgen
|
||||
athrow
|
||||
backslashreplace
|
||||
baserepl
|
||||
basicsize
|
||||
bdfl
|
||||
bigcharset
|
||||
bignum
|
||||
bivariant
|
||||
breakpointhook
|
||||
cformat
|
||||
chunksize
|
||||
classcell
|
||||
classmethods
|
||||
closefd
|
||||
closesocket
|
||||
codepoint
|
||||
codepoints
|
||||
codesize
|
||||
contextvar
|
||||
cpython
|
||||
cratio
|
||||
ctype
|
||||
ctypes
|
||||
dealloc
|
||||
debugbuild
|
||||
decompressor
|
||||
defaultaction
|
||||
descr
|
||||
dictcomp
|
||||
dictitems
|
||||
dictkeys
|
||||
dictview
|
||||
digestmod
|
||||
dllhandle
|
||||
docstring
|
||||
docstrings
|
||||
dunder
|
||||
endianness
|
||||
endpos
|
||||
eventmask
|
||||
excepthook
|
||||
exceptiongroup
|
||||
exitfuncs
|
||||
extendleft
|
||||
fastlocals
|
||||
fdel
|
||||
fedcba
|
||||
fget
|
||||
fileencoding
|
||||
fillchar
|
||||
fillvalue
|
||||
finallyhandler
|
||||
firstiter
|
||||
fobj
|
||||
firstlineno
|
||||
fnctl
|
||||
frombytes
|
||||
fromhex
|
||||
fromunicode
|
||||
frozensets
|
||||
fset
|
||||
fspath
|
||||
fstring
|
||||
fstrings
|
||||
ftruncate
|
||||
genexpr
|
||||
genexpressions
|
||||
getargs
|
||||
getattro
|
||||
getcodesize
|
||||
getdefaultencoding
|
||||
getfilesystemencodeerrors
|
||||
getfilesystemencoding
|
||||
getformat
|
||||
getframe
|
||||
getframemodulename
|
||||
getnewargs
|
||||
getopt
|
||||
getpip
|
||||
getrandom
|
||||
getrecursionlimit
|
||||
getrefcount
|
||||
getsizeof
|
||||
getswitchinterval
|
||||
getweakref
|
||||
getweakrefcount
|
||||
getweakrefs
|
||||
getweakrefs
|
||||
getwindowsversion
|
||||
gmtoff
|
||||
groupdict
|
||||
groupindex
|
||||
hamt
|
||||
hostnames
|
||||
idfunc
|
||||
idiv
|
||||
idxs
|
||||
impls
|
||||
infd
|
||||
indexgroup
|
||||
infj
|
||||
inittab
|
||||
Inittab
|
||||
instancecheck
|
||||
instanceof
|
||||
instrs
|
||||
interpchannels
|
||||
interpqueues
|
||||
irepeat
|
||||
isabstractmethod
|
||||
isbytes
|
||||
iscased
|
||||
isfinal
|
||||
istext
|
||||
itemiterator
|
||||
itemsize
|
||||
iternext
|
||||
keepends
|
||||
keyfunc
|
||||
keyiterator
|
||||
kwarg
|
||||
kwargs
|
||||
kwdefaults
|
||||
kwonlyargcount
|
||||
lastgroup
|
||||
lastindex
|
||||
linearization
|
||||
linearize
|
||||
listcomp
|
||||
longrange
|
||||
lvalue
|
||||
mappingproxy
|
||||
markupbase
|
||||
maskpri
|
||||
maxdigits
|
||||
MAXGROUPS
|
||||
MAXREPEAT
|
||||
maxsplit
|
||||
maxunicode
|
||||
memoryview
|
||||
memoryviewiterator
|
||||
metaclass
|
||||
metaclasses
|
||||
metatype
|
||||
mformat
|
||||
mro
|
||||
mros
|
||||
multiarch
|
||||
mymodule
|
||||
namereplace
|
||||
nanj
|
||||
nbytes
|
||||
ncallbacks
|
||||
ndigits
|
||||
ndim
|
||||
needsfree
|
||||
nldecoder
|
||||
nlocals
|
||||
NOARGS
|
||||
nonbytes
|
||||
Nonprintable
|
||||
onceregistry
|
||||
origname
|
||||
ospath
|
||||
outfd
|
||||
pendingcr
|
||||
phello
|
||||
platlibdir
|
||||
popleft
|
||||
posixsubprocess
|
||||
posonly
|
||||
posonlyargcount
|
||||
prepending
|
||||
profilefunc
|
||||
pycache
|
||||
pycapsule
|
||||
pycodecs
|
||||
pycs
|
||||
pydatetime
|
||||
pyexpat
|
||||
PYGILSTATE
|
||||
pyio
|
||||
pymain
|
||||
PYTHONAPI
|
||||
PYTHONBREAKPOINT
|
||||
PYTHONDEBUG
|
||||
PYTHONDONTWRITEBYTECODE
|
||||
PYTHONFAULTHANDLER
|
||||
PYTHONHASHSEED
|
||||
PYTHONHOME
|
||||
PYTHONINSPECT
|
||||
PYTHONINTMAXSTRDIGITS
|
||||
PYTHONIOENCODING
|
||||
PYTHONNODEBUGRANGES
|
||||
PYTHONNOUSERSITE
|
||||
PYTHONOPTIMIZE
|
||||
PYTHONPATH
|
||||
PYTHONPATH
|
||||
PYTHONSAFEPATH
|
||||
PYTHONUNBUFFERED
|
||||
PYTHONVERBOSE
|
||||
PYTHONWARNDEFAULTENCODING
|
||||
PYTHONWARNINGS
|
||||
pytraverse
|
||||
PYVENV
|
||||
qualname
|
||||
quotetabs
|
||||
radd
|
||||
rdiv
|
||||
rdivmod
|
||||
readall
|
||||
readbuffer
|
||||
reconstructor
|
||||
refcnt
|
||||
releaselevel
|
||||
reraised
|
||||
reverseitemiterator
|
||||
reverseiterator
|
||||
reversekeyiterator
|
||||
reversevalueiterator
|
||||
rfloordiv
|
||||
rlshift
|
||||
rmod
|
||||
rpow
|
||||
rrshift
|
||||
rsub
|
||||
rtruediv
|
||||
rvalue
|
||||
scproxy
|
||||
seennl
|
||||
setattro
|
||||
setcomp
|
||||
setprofileallthreads
|
||||
setrecursionlimit
|
||||
setswitchinterval
|
||||
settraceallthreads
|
||||
showwarnmsg
|
||||
signum
|
||||
sitebuiltins
|
||||
slotnames
|
||||
STACKLESS
|
||||
stacklevel
|
||||
stacksize
|
||||
startpos
|
||||
subclassable
|
||||
subclasscheck
|
||||
subclasshook
|
||||
subclassing
|
||||
suboffset
|
||||
suboffsets
|
||||
SUBPATTERN
|
||||
subpatterns
|
||||
sumprod
|
||||
surrogateescape
|
||||
surrogatepass
|
||||
sysconf
|
||||
sysconfigdata
|
||||
sysdict
|
||||
sysvars
|
||||
teedata
|
||||
thisclass
|
||||
titlecased
|
||||
tkapp
|
||||
tobytes
|
||||
tolist
|
||||
toreadonly
|
||||
TPFLAGS
|
||||
tracefunc
|
||||
unimportable
|
||||
unionable
|
||||
unraisablehook
|
||||
unsliceable
|
||||
urandom
|
||||
valueiterator
|
||||
vararg
|
||||
varargs
|
||||
varnames
|
||||
warningregistry
|
||||
warnmsg
|
||||
warnoptions
|
||||
warnopts
|
||||
weaklist
|
||||
weakproxy
|
||||
weakrefs
|
||||
weakrefset
|
||||
winver
|
||||
withdata
|
||||
xmlcharrefreplace
|
||||
xoptions
|
||||
xopts
|
||||
yieldfrom
|
||||
94
.cspell.dict/rust-more.txt
Normal file
94
.cspell.dict/rust-more.txt
Normal file
@@ -0,0 +1,94 @@
|
||||
ahash
|
||||
arrayvec
|
||||
bidi
|
||||
biguint
|
||||
bindgen
|
||||
bitand
|
||||
bitflags
|
||||
bitflagset
|
||||
bitor
|
||||
bitvec
|
||||
bitxor
|
||||
bstr
|
||||
byteorder
|
||||
byteset
|
||||
caseless
|
||||
chrono
|
||||
consts
|
||||
cranelift
|
||||
cstring
|
||||
datelike
|
||||
deserializer
|
||||
deserializers
|
||||
fdiv
|
||||
flamescope
|
||||
flate2
|
||||
fract
|
||||
getres
|
||||
hasher
|
||||
hexf
|
||||
hexversion
|
||||
idents
|
||||
illumos
|
||||
ilog
|
||||
indexmap
|
||||
insta
|
||||
keccak
|
||||
lalrpop
|
||||
lexopt
|
||||
libc
|
||||
libcall
|
||||
libloading
|
||||
libz
|
||||
longlong
|
||||
Manually
|
||||
maplit
|
||||
memmap
|
||||
memmem
|
||||
metas
|
||||
modpow
|
||||
msvc
|
||||
muldiv
|
||||
nanos
|
||||
nonoverlapping
|
||||
objclass
|
||||
peekable
|
||||
pemfile
|
||||
powc
|
||||
powf
|
||||
powi
|
||||
prepended
|
||||
punct
|
||||
replacen
|
||||
retag
|
||||
rmatch
|
||||
rposition
|
||||
rsplitn
|
||||
rustc
|
||||
rustfmt
|
||||
rustls
|
||||
rustyline
|
||||
seedable
|
||||
seekfrom
|
||||
siphash
|
||||
siphasher
|
||||
splitn
|
||||
subsec
|
||||
thiserror
|
||||
timelike
|
||||
timsort
|
||||
trai
|
||||
ulonglong
|
||||
unic
|
||||
unistd
|
||||
unraw
|
||||
unsync
|
||||
wasip1
|
||||
wasip2
|
||||
wasmbind
|
||||
wasmer
|
||||
wasmtime
|
||||
widestring
|
||||
winapi
|
||||
winresource
|
||||
winsock
|
||||
32
.cspell.dict/rustpython.txt
Normal file
32
.cspell.dict/rustpython.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
cfgs
|
||||
miri
|
||||
py
|
||||
pyarg
|
||||
pyargs
|
||||
pyast
|
||||
pyattr
|
||||
pyclass
|
||||
pyclassmethod
|
||||
pyexception
|
||||
pyfunction
|
||||
pygetset
|
||||
pyimpl
|
||||
pylib
|
||||
pymath
|
||||
pymethod
|
||||
pymodule
|
||||
pyname
|
||||
pyobj
|
||||
pyobject
|
||||
pypayload
|
||||
pyref
|
||||
pyslot
|
||||
pystaticmethod
|
||||
pystone
|
||||
pystr
|
||||
pystruct
|
||||
pystructseq
|
||||
pytype
|
||||
rustix
|
||||
struc
|
||||
zelf
|
||||
332
.cspell.json
332
.cspell.json
@@ -1,287 +1,103 @@
|
||||
// See: https://github.com/streetsidesoftware/cspell/tree/master/packages/cspell
|
||||
{
|
||||
"version": "0.2",
|
||||
"import": [
|
||||
"@cspell/dict-en_us/cspell-ext.json",
|
||||
// "@cspell/dict-cpp/cspell-ext.json",
|
||||
"@cspell/dict-python/cspell-ext.json",
|
||||
"@cspell/dict-rust/cspell-ext.json",
|
||||
"@cspell/dict-win32/cspell-ext.json",
|
||||
"@cspell/dict-shell/cspell-ext.json",
|
||||
],
|
||||
"allowCompoundWords": true,
|
||||
// language - current active spelling language
|
||||
"language": "en",
|
||||
// dictionaries - list of the names of the dictionaries to use
|
||||
"dictionaries": [
|
||||
"cpython", // Sometimes keeping same terms with cpython is easy
|
||||
"python-more", // Python API terms not listed in python
|
||||
"rust-more", // Rust API terms not listed in rust
|
||||
"rustpython", // RustPython derive macros and internal terms
|
||||
"en_US",
|
||||
"softwareTerms",
|
||||
"c",
|
||||
"cpp",
|
||||
"python",
|
||||
"python-custom",
|
||||
"rust",
|
||||
"unix",
|
||||
"posix",
|
||||
"winapi"
|
||||
"shell",
|
||||
"win32"
|
||||
],
|
||||
// dictionaryDefinitions - this list defines any custom dictionaries to use
|
||||
"dictionaryDefinitions": [],
|
||||
"dictionaryDefinitions": [
|
||||
{
|
||||
"name": "cpython",
|
||||
"path": "./.cspell.dict/cpython.txt"
|
||||
},
|
||||
{
|
||||
"name": "python-more",
|
||||
"path": "./.cspell.dict/python-more.txt"
|
||||
},
|
||||
{
|
||||
"name": "rust-more",
|
||||
"path": "./.cspell.dict/rust-more.txt"
|
||||
},
|
||||
{
|
||||
"name": "rustpython",
|
||||
"path": "./.cspell.dict/rustpython.txt"
|
||||
}
|
||||
],
|
||||
"ignorePaths": [
|
||||
"**/__pycache__/**",
|
||||
"Lib/**"
|
||||
"target/**",
|
||||
"Lib/**",
|
||||
"crates/host_env/**"
|
||||
],
|
||||
// words - list of words to be always considered correct
|
||||
// (compound words like pyarg, baseclass, microbenchmark are handled by allowCompoundWords)
|
||||
"words": [
|
||||
// Rust
|
||||
"ahash",
|
||||
"bidi",
|
||||
"biguint",
|
||||
"bindgen",
|
||||
"bitflags",
|
||||
"bstr",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"consts",
|
||||
"cstring",
|
||||
"flate2",
|
||||
"fract",
|
||||
"hasher",
|
||||
"idents",
|
||||
"indexmap",
|
||||
"insta",
|
||||
"keccak",
|
||||
"lalrpop",
|
||||
"libc",
|
||||
"libz",
|
||||
"longlong",
|
||||
"Manually",
|
||||
"maplit",
|
||||
"memmap",
|
||||
"metas",
|
||||
"modpow",
|
||||
"nanos",
|
||||
"peekable",
|
||||
"powc",
|
||||
"powf",
|
||||
"prepended",
|
||||
"punct",
|
||||
"replacen",
|
||||
"rsplitn",
|
||||
"rustc",
|
||||
"rustfmt",
|
||||
"seekfrom",
|
||||
"splitn",
|
||||
"subsec",
|
||||
"timsort",
|
||||
"trai",
|
||||
"ulonglong",
|
||||
"unic",
|
||||
"unistd",
|
||||
"winapi",
|
||||
"winsock",
|
||||
// Python
|
||||
"abstractmethods",
|
||||
"aiter",
|
||||
"anext",
|
||||
"arrayiterator",
|
||||
"arraytype",
|
||||
"asend",
|
||||
"athrow",
|
||||
"basicsize",
|
||||
"cformat",
|
||||
"classcell",
|
||||
"closesocket",
|
||||
"codepoint",
|
||||
"codepoints",
|
||||
"cpython",
|
||||
"decompressor",
|
||||
"defaultaction",
|
||||
"descr",
|
||||
"dictcomp",
|
||||
"dictitems",
|
||||
"dictkeys",
|
||||
"dictview",
|
||||
"docstring",
|
||||
"docstrings",
|
||||
"dunder",
|
||||
"eventmask",
|
||||
"fdel",
|
||||
"fget",
|
||||
"fileencoding",
|
||||
"fillchar",
|
||||
"finallyhandler",
|
||||
"frombytes",
|
||||
"fromhex",
|
||||
"fromunicode",
|
||||
"fset",
|
||||
"fspath",
|
||||
"fstring",
|
||||
"fstrings",
|
||||
"genexpr",
|
||||
"getattro",
|
||||
"getformat",
|
||||
"getnewargs",
|
||||
"getweakrefcount",
|
||||
"getweakrefs",
|
||||
"hostnames",
|
||||
"idiv",
|
||||
"impls",
|
||||
"infj",
|
||||
"instancecheck",
|
||||
"instanceof",
|
||||
"isabstractmethod",
|
||||
"itemiterator",
|
||||
"itemsize",
|
||||
"iternext",
|
||||
"keyiterator",
|
||||
"kwarg",
|
||||
"kwargs",
|
||||
"linearization",
|
||||
"linearize",
|
||||
"listcomp",
|
||||
"mappingproxy",
|
||||
"maxsplit",
|
||||
"memoryview",
|
||||
"memoryviewiterator",
|
||||
"metaclass",
|
||||
"metaclasses",
|
||||
"metatype",
|
||||
"mro",
|
||||
"mros",
|
||||
"nanj",
|
||||
"ndigits",
|
||||
"ndim",
|
||||
"nonbytes",
|
||||
"origname",
|
||||
"posixsubprocess",
|
||||
"pyexpat",
|
||||
"PYTHONDEBUG",
|
||||
"PYTHONHOME",
|
||||
"PYTHONINSPECT",
|
||||
"PYTHONOPTIMIZE",
|
||||
"PYTHONPATH",
|
||||
"PYTHONPATH",
|
||||
"PYTHONVERBOSE",
|
||||
"PYTHONWARNINGS",
|
||||
"qualname",
|
||||
"radd",
|
||||
"rdiv",
|
||||
"rdivmod",
|
||||
"reconstructor",
|
||||
"reversevalueiterator",
|
||||
"rfloordiv",
|
||||
"rlshift",
|
||||
"rmod",
|
||||
"rpow",
|
||||
"rrshift",
|
||||
"rsub",
|
||||
"rtruediv",
|
||||
"scproxy",
|
||||
"setattro",
|
||||
"setcomp",
|
||||
"showwarnmsg",
|
||||
"warnmsg",
|
||||
"stacklevel",
|
||||
"subclasscheck",
|
||||
"subclasshook",
|
||||
"unionable",
|
||||
"unraisablehook",
|
||||
"valueiterator",
|
||||
"vararg",
|
||||
"varargs",
|
||||
"varnames",
|
||||
"warningregistry",
|
||||
"warnopts",
|
||||
"weakproxy",
|
||||
"xopts",
|
||||
// RustPython
|
||||
"baseclass",
|
||||
"Bytecode",
|
||||
"cfgs",
|
||||
"codegen",
|
||||
"aiterable",
|
||||
"alnum",
|
||||
"csock",
|
||||
"coro",
|
||||
"dedentations",
|
||||
"dedents",
|
||||
"deduped",
|
||||
"downcasted",
|
||||
"dumpable",
|
||||
"GetSet",
|
||||
"internable",
|
||||
"makeunicodedata",
|
||||
"miri",
|
||||
"notrace",
|
||||
"pyarg",
|
||||
"pyarg",
|
||||
"pyargs",
|
||||
"PyAttr",
|
||||
"deoptimized",
|
||||
"deoptimize",
|
||||
"emscripten",
|
||||
"excs",
|
||||
"fnfe",
|
||||
"ifexp",
|
||||
"interps",
|
||||
"jitted",
|
||||
"jitting",
|
||||
"kwonly",
|
||||
"lossily",
|
||||
"mcache",
|
||||
"oparg",
|
||||
"opargs",
|
||||
"pyc",
|
||||
"PyClass",
|
||||
"PyClassMethod",
|
||||
"PyException",
|
||||
"PyFunction",
|
||||
"pygetset",
|
||||
"pyimpl",
|
||||
"pymember",
|
||||
"PyMethod",
|
||||
"PyModule",
|
||||
"pyname",
|
||||
"pyobj",
|
||||
"PyObject",
|
||||
"pypayload",
|
||||
"PyProperty",
|
||||
"pyref",
|
||||
"PyResult",
|
||||
"pyslot",
|
||||
"PyStaticMethod",
|
||||
"pystr",
|
||||
"pystruct",
|
||||
"pystructseq",
|
||||
"pytrace",
|
||||
"reducelib",
|
||||
"richcompare",
|
||||
"RustPython",
|
||||
"struc",
|
||||
"tracebacks",
|
||||
"typealiases",
|
||||
"Unconstructible",
|
||||
"unhashable",
|
||||
"uninit",
|
||||
"reborrow",
|
||||
"reraises",
|
||||
"reraising",
|
||||
"significand",
|
||||
"summands",
|
||||
"TESTFN",
|
||||
"TZPATH",
|
||||
"unraisable",
|
||||
"wasi",
|
||||
"zelf",
|
||||
// cpython
|
||||
"argtypes",
|
||||
"asdl",
|
||||
"asname",
|
||||
"augassign",
|
||||
"badsyntax",
|
||||
"basetype",
|
||||
"boolop",
|
||||
"bxor",
|
||||
"cellarg",
|
||||
"cellvar",
|
||||
"cellvars",
|
||||
"cmpop",
|
||||
"dictoffset",
|
||||
"elts",
|
||||
"excepthandler",
|
||||
"finalbody",
|
||||
"freevar",
|
||||
"freevars",
|
||||
"fromlist",
|
||||
"heaptype",
|
||||
"IMMUTABLETYPE",
|
||||
"kwonlyarg",
|
||||
"kwonlyargs",
|
||||
"linearise",
|
||||
"maxdepth",
|
||||
"mult",
|
||||
"nkwargs",
|
||||
"orelse",
|
||||
"patma",
|
||||
"posonlyarg",
|
||||
"posonlyargs",
|
||||
"prec",
|
||||
"stackdepth",
|
||||
"unaryop",
|
||||
"unparse",
|
||||
"unparser",
|
||||
"VARKEYWORDS",
|
||||
"varkwarg",
|
||||
"wbits",
|
||||
"withitem",
|
||||
"withs"
|
||||
"weaked",
|
||||
// unix
|
||||
"posixshmem",
|
||||
"shm",
|
||||
"CLOEXEC",
|
||||
"endgrent",
|
||||
"gethrvtime",
|
||||
"getrusage",
|
||||
"sigaction",
|
||||
"WRLCK",
|
||||
// win32
|
||||
"IFEXEC"
|
||||
],
|
||||
// flagWords - list of words to be always considered incorrect
|
||||
"flagWords": [
|
||||
|
||||
6
.devcontainer/Dockerfile
Normal file
6
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM rust:bullseye
|
||||
|
||||
# Install clang
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y clang \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
@@ -1,6 +1,25 @@
|
||||
{
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/rust:1": {}
|
||||
}
|
||||
"name": "Rust",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"lldb.executable": "/usr/bin/lldb",
|
||||
// VS Code don't watch files under ./target
|
||||
"files.watcherExclude": {
|
||||
"**/target/**": true
|
||||
},
|
||||
"extensions": [
|
||||
"rust-lang.rust-analyzer",
|
||||
"tamasfe.even-better-toml",
|
||||
"vadimcn.vscode-lldb",
|
||||
"mutantdino.resourcemonitor"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
|
||||
2
.gemini/config.yaml
Normal file
2
.gemini/config.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
ignore_patterns:
|
||||
- "Lib/**"
|
||||
67
.gitattributes
vendored
67
.gitattributes
vendored
@@ -1,6 +1,71 @@
|
||||
Lib/** linguist-vendored
|
||||
Cargo.lock linguist-generated -merge
|
||||
Cargo.lock linguist-generated
|
||||
*.snap linguist-generated -merge
|
||||
vm/src/stdlib/ast/gen.rs linguist-generated -merge
|
||||
Lib/*.py text working-tree-encoding=UTF-8 eol=LF
|
||||
**/*.rs text working-tree-encoding=UTF-8 eol=LF
|
||||
crates/rustpython_doc_db/src/*.inc.rs linguist-generated=true
|
||||
|
||||
# Binary data types
|
||||
*.aif binary
|
||||
*.aifc binary
|
||||
*.aiff binary
|
||||
*.au binary
|
||||
*.bmp binary
|
||||
*.exe binary
|
||||
*.icns binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.jpg binary
|
||||
*.pck binary
|
||||
*.pdf binary
|
||||
*.png binary
|
||||
*.psd binary
|
||||
*.tar binary
|
||||
*.wav binary
|
||||
*.whl binary
|
||||
*.zip binary
|
||||
|
||||
# Text files that should not be subject to eol conversion
|
||||
[attr]noeol -text
|
||||
|
||||
Lib/test/cjkencodings/* noeol
|
||||
Lib/test/tokenizedata/coding20731.py noeol
|
||||
Lib/test/decimaltestdata/*.decTest noeol
|
||||
Lib/test/test_email/data/*.txt noeol
|
||||
Lib/test/xmltestdata/* noeol
|
||||
|
||||
# Shell scripts should have LF even on Windows because of Cygwin
|
||||
Lib/venv/scripts/common/activate text eol=lf
|
||||
Lib/venv/scripts/posix/* text eol=lf
|
||||
|
||||
# CRLF files
|
||||
[attr]dos text eol=crlf
|
||||
|
||||
# Language aware diff headers
|
||||
# https://tekin.co.uk/2020/10/better-git-diff-output-for-ruby-python-elixir-and-more
|
||||
# https://gist.github.com/tekin/12500956bd56784728e490d8cef9cb81
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.py diff=python
|
||||
*.md diff=markdown
|
||||
|
||||
# Generated files
|
||||
# https://github.com/github/linguist/blob/master/docs/overrides.md
|
||||
#
|
||||
# To always hide generated files in local diffs, mark them as binary:
|
||||
# $ git config diff.generated.binary true
|
||||
#
|
||||
[attr]generated linguist-generated=true diff=generated
|
||||
|
||||
Lib/_opcode_metadata.py generated
|
||||
Lib/keyword.py generated
|
||||
Lib/idlelib/help.html generated
|
||||
Lib/test/certdata/*.pem generated
|
||||
Lib/test/certdata/*.0 generated
|
||||
Lib/test/levenshtein_examples.json generated
|
||||
Lib/test/test_stable_abi_ctypes.py generated
|
||||
Lib/token.py generated
|
||||
crates/compiler-core/src/bytecode/opcode_metadata.rs generated
|
||||
|
||||
.github/workflows/*.lock.yml linguist-generated=true merge=ours
|
||||
|
||||
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<!--
|
||||
Thanks for your contribution!
|
||||
-->
|
||||
|
||||
- [ ] Closes #xxxx <!-- Replace xxxx with the GitHub issue number -->
|
||||
- [ ] This PR follows our [AI policy](https://github.com/RustPython/.github/blob/main/AI_POLICY.md)
|
||||
|
||||
## Summary
|
||||
<!-- What's the purpose of the change? What does it do, and why? -->
|
||||
|
||||
49
.github/actions/install-linux-deps/action.yml
vendored
Normal file
49
.github/actions/install-linux-deps/action.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
# This action installs a few dependencies necessary to build RustPython on Linux.
|
||||
# It can be configured depending on which libraries are needed:
|
||||
#
|
||||
# ```
|
||||
# - uses: ./.github/actions/install-linux-deps
|
||||
# with:
|
||||
# gcc-multilib: true
|
||||
# musl-tools: false
|
||||
# ```
|
||||
#
|
||||
# See the `inputs` section for all options and their defaults. Note that you must checkout the
|
||||
# repository before you can use this action.
|
||||
#
|
||||
# This action will only install dependencies when the current operating system is Linux. It will do
|
||||
# nothing on any other OS (macOS, Windows).
|
||||
|
||||
name: Install Linux dependencies
|
||||
description: Installs the dependencies necessary to build RustPython on Linux.
|
||||
inputs:
|
||||
gcc-multilib:
|
||||
description: Install gcc-multilib (gcc-multilib)
|
||||
required: false
|
||||
default: "false"
|
||||
musl-tools:
|
||||
description: Install musl-tools (musl-tools)
|
||||
required: false
|
||||
default: "false"
|
||||
gcc-aarch64-linux-gnu:
|
||||
description: Install gcc-aarch64-linux-gnu (gcc-aarch64-linux-gnu)
|
||||
required: false
|
||||
default: "false"
|
||||
clang:
|
||||
description: Install clang (clang)
|
||||
required: false
|
||||
default: "false"
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install Linux dependencies
|
||||
shell: bash
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: >
|
||||
sudo apt-get update
|
||||
|
||||
sudo apt-get install --no-install-recommends
|
||||
${{ fromJSON(inputs.gcc-multilib) && 'gcc-multilib' || '' }}
|
||||
${{ fromJSON(inputs.musl-tools) && 'musl-tools' || '' }}
|
||||
${{ fromJSON(inputs.clang) && 'clang' || '' }}
|
||||
${{ fromJSON(inputs.gcc-aarch64-linux-gnu) && 'gcc-aarch64-linux-gnu linux-libc-dev-arm64-cross libc6-dev-arm64-cross' || '' }}
|
||||
47
.github/actions/install-macos-deps/action.yml
vendored
Normal file
47
.github/actions/install-macos-deps/action.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# This action installs a few dependencies necessary to build RustPython on macOS. By default it installs
|
||||
# autoconf, automake and libtool, but can be configured depending on which libraries are needed:
|
||||
#
|
||||
# ```
|
||||
# - uses: ./.github/actions/install-macos-deps
|
||||
# with:
|
||||
# openssl: true
|
||||
# libtool: false
|
||||
# ```
|
||||
#
|
||||
# See the `inputs` section for all options and their defaults. Note that you must checkout the
|
||||
# repository before you can use this action.
|
||||
#
|
||||
# This action will only install dependencies when the current operating system is macOS. It will do
|
||||
# nothing on any other OS (Linux, Windows).
|
||||
|
||||
name: Install macOS dependencies
|
||||
description: Installs the dependencies necessary to build RustPython on macOS.
|
||||
inputs:
|
||||
autoconf:
|
||||
description: Install autoconf (autoconf)
|
||||
required: false
|
||||
default: "true"
|
||||
automake:
|
||||
description: Install automake (automake)
|
||||
required: false
|
||||
default: "true"
|
||||
libtool:
|
||||
description: Install libtool (libtool)
|
||||
required: false
|
||||
default: "true"
|
||||
openssl:
|
||||
description: Install openssl (openssl@3)
|
||||
required: false
|
||||
default: "false"
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install macOS dependencies
|
||||
shell: bash
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: >
|
||||
brew install
|
||||
${{ fromJSON(inputs.autoconf) && 'autoconf' || '' }}
|
||||
${{ fromJSON(inputs.automake) && 'automake' || '' }}
|
||||
${{ fromJSON(inputs.libtool) && 'libtool' || '' }}
|
||||
${{ fromJSON(inputs.openssl) && 'openssl@3' || '' }}
|
||||
14
.github/aw/actions-lock.json
vendored
Normal file
14
.github/aw/actions-lock.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"entries": {
|
||||
"actions/github-script@v8": {
|
||||
"repo": "actions/github-script",
|
||||
"version": "v8",
|
||||
"sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
|
||||
},
|
||||
"github/gh-aw/actions/setup@v0.43.22": {
|
||||
"repo": "github/gh-aw/actions/setup",
|
||||
"version": "v0.43.22",
|
||||
"sha": "fe858c3e14589bf396594a0b106e634d9065823e"
|
||||
}
|
||||
}
|
||||
}
|
||||
175
.github/dependabot.yml
vendored
Normal file
175
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
# cspell:ignore manyhow tinyvec zeroize
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directories:
|
||||
- "/"
|
||||
- "crates/*"
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
semver-major-days: 30
|
||||
semver-minor-days: 7
|
||||
semver-patch-days: 3
|
||||
groups:
|
||||
criterion:
|
||||
patterns:
|
||||
- "criterion*"
|
||||
crypto:
|
||||
patterns:
|
||||
- "digest"
|
||||
- "md-5"
|
||||
- "sha-1"
|
||||
- "sha1"
|
||||
- "sha2"
|
||||
- "sha3"
|
||||
- "blake2"
|
||||
- "hmac"
|
||||
- "pbkdf2"
|
||||
futures:
|
||||
patterns:
|
||||
- "futures*"
|
||||
get-size2:
|
||||
patterns:
|
||||
- "get-size*2"
|
||||
iana-time-zone:
|
||||
patterns:
|
||||
- "iana-time-zone*"
|
||||
jiff:
|
||||
patterns:
|
||||
- "jiff*"
|
||||
lexical:
|
||||
patterns:
|
||||
- "lexical*"
|
||||
libffi:
|
||||
patterns:
|
||||
- "libffi*"
|
||||
malachite:
|
||||
patterns:
|
||||
- "malachite*"
|
||||
manyhow:
|
||||
patterns:
|
||||
- "manyhow*"
|
||||
num:
|
||||
patterns:
|
||||
- "num-bigint"
|
||||
- "num-complex"
|
||||
- "num-integer"
|
||||
- "num-iter"
|
||||
- "num-rational"
|
||||
- "num-traits"
|
||||
num_enum:
|
||||
patterns:
|
||||
- "num_enum*"
|
||||
openssl:
|
||||
patterns:
|
||||
- "openssl*"
|
||||
parking_lot:
|
||||
patterns:
|
||||
- "parking_lot*"
|
||||
phf:
|
||||
patterns:
|
||||
- "phf*"
|
||||
plotters:
|
||||
patterns:
|
||||
- "plotters*"
|
||||
portable-atomic:
|
||||
patterns:
|
||||
- "portable-atomic*"
|
||||
pyo3:
|
||||
patterns:
|
||||
- "pyo3*"
|
||||
quote-use:
|
||||
patterns:
|
||||
- "quote-use*"
|
||||
random:
|
||||
patterns:
|
||||
- "getrandom"
|
||||
- "mt19937"
|
||||
- "rand*"
|
||||
rayon:
|
||||
patterns:
|
||||
- "rayon*"
|
||||
regex:
|
||||
patterns:
|
||||
- "regex*"
|
||||
result-like:
|
||||
patterns:
|
||||
- "result-like*"
|
||||
security-framework:
|
||||
patterns:
|
||||
- "security-framework*"
|
||||
serde:
|
||||
patterns:
|
||||
- "serde"
|
||||
- "serde_core"
|
||||
- "serde_derive"
|
||||
system-configuration:
|
||||
patterns:
|
||||
- "system-configuration*"
|
||||
thiserror:
|
||||
patterns:
|
||||
- "thiserror*"
|
||||
time:
|
||||
patterns:
|
||||
- "time*"
|
||||
tinyvec:
|
||||
patterns:
|
||||
- "tinyvec*"
|
||||
tls_codec:
|
||||
patterns:
|
||||
- "tls_codec*"
|
||||
toml:
|
||||
patterns:
|
||||
- "toml*"
|
||||
unix:
|
||||
patterns:
|
||||
- "mac_address"
|
||||
- "nix"
|
||||
- "rustyline"
|
||||
wasm-bindgen:
|
||||
patterns:
|
||||
- "wasm-bindgen*"
|
||||
wasmtime:
|
||||
patterns:
|
||||
- "cranelift*"
|
||||
- "wasmtime*"
|
||||
webpki-root:
|
||||
patterns:
|
||||
- "webpki-root*"
|
||||
windows:
|
||||
patterns:
|
||||
- "windows*"
|
||||
zerocopy:
|
||||
patterns:
|
||||
- "zerocopy*"
|
||||
zeroize:
|
||||
patterns:
|
||||
- "zeroize*"
|
||||
ignore:
|
||||
# TODO: Remove when we use ruff from crates.io
|
||||
# for some reason dependabot only updates the Cargo.lock file when dealing
|
||||
# with git dependencies. i.e. not updating the version in Cargo.toml
|
||||
- dependency-name: "ruff_*"
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
semver-major-days: 30
|
||||
semver-minor-days: 7
|
||||
semver-patch-days: 3
|
||||
- package-ecosystem: pre-commit
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
959
.github/workflows/ci.yaml
vendored
959
.github/workflows/ci.yaml
vendored
File diff suppressed because it is too large
Load Diff
23
.github/workflows/comment-commands.yml
vendored
Normal file
23
.github/workflows/comment-commands.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Comment Commands
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: created
|
||||
|
||||
jobs:
|
||||
issue_assign:
|
||||
if: (!github.event.issue.pull_request) && github.event.comment.body == 'take'
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.actor }}-issue-assign
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
# Using REST API and not `gh issue edit`. https://github.com/cli/cli/issues/6235#issuecomment-1243487651
|
||||
- run: |
|
||||
curl -H "Authorization: token ${{ github.token }}" -d '{"assignees": ["${{ env.USER }}"]}' https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees
|
||||
env:
|
||||
USER: ${{ github.event.comment.user.login }}
|
||||
142
.github/workflows/cron-ci.yaml
vendored
142
.github/workflows/cron-ci.yaml
vendored
@@ -1,13 +1,21 @@
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 6'
|
||||
workflow_dispatch:
|
||||
|
||||
name: Periodic checks/tasks
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * 6"
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/cron-ci.yaml
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/cron-ci.yaml
|
||||
|
||||
env:
|
||||
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit
|
||||
PYTHON_VERSION: "3.12.0"
|
||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls-aws-lc,jit,host_env
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' # TODO: Remove on 2026/06/02
|
||||
|
||||
jobs:
|
||||
# codecov collects code coverage data from the rust tests, python snippets and python test suite.
|
||||
@@ -15,42 +23,65 @@ jobs:
|
||||
codecov:
|
||||
name: Collect code coverage data
|
||||
runs-on: ubuntu-latest
|
||||
# Disable this scheduled job when running on a fork.
|
||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@cargo-llvm-cov
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: taiki-e/install-action@b550161ef8a7bc4f2a671c0b03a18ac9ccedea1e # v2.79.1
|
||||
with:
|
||||
tool: cargo-llvm-cov
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- run: sudo apt-get update && sudo apt-get -y install lcov
|
||||
|
||||
- name: Run cargo-llvm-cov with Rust tests.
|
||||
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --verbose --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit
|
||||
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher --verbose --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls-aws-lc,jit,host_env
|
||||
|
||||
- name: Run cargo-llvm-cov with Python snippets.
|
||||
run: python scripts/cargo-llvm-cov.py
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run cargo-llvm-cov with Python test suite.
|
||||
run: cargo llvm-cov --no-report run -- -m test -u all --slowest --fail-env-changed
|
||||
run: cargo llvm-cov --no-report run -- -m test -u all --slowest --fail-env-changed
|
||||
continue-on-error: true
|
||||
|
||||
- name: Prepare code coverage data
|
||||
run: cargo llvm-cov report --lcov --output-path='codecov.lcov'
|
||||
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
file: ./codecov.lcov
|
||||
files: ./codecov.lcov
|
||||
|
||||
testdata:
|
||||
name: Collect regression test data
|
||||
runs-on: ubuntu-latest
|
||||
# Disable this scheduled job when running on a fork.
|
||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: true
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: build rustpython
|
||||
run: cargo build --release --verbose
|
||||
|
||||
- name: collect tests data
|
||||
run: cargo run --release extra_tests/jsontests.py
|
||||
env:
|
||||
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
||||
|
||||
- name: upload tests data to the website
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
env:
|
||||
SSHKEY: ${{ secrets.ACTIONS_TESTS_DATA_DEPLOY_KEY }}
|
||||
GITHUB_ACTOR: ${{ github.actor }}
|
||||
@@ -70,21 +101,29 @@ jobs:
|
||||
whatsleft:
|
||||
name: Collect what is left data
|
||||
runs-on: ubuntu-latest
|
||||
# Disable this scheduled job when running on a fork.
|
||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
persist-credentials: true
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- name: build rustpython
|
||||
run: cargo build --release --verbose
|
||||
|
||||
- name: Collect what is left data
|
||||
run: |
|
||||
chmod +x ./whats_left.py
|
||||
./whats_left.py > whats_left.temp
|
||||
chmod +x ./scripts/whats_left.py
|
||||
./scripts/whats_left.py --features "ssl,sqlite" > whats_left.temp
|
||||
env:
|
||||
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
||||
|
||||
- name: Upload data to the website
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
env:
|
||||
SSHKEY: ${{ secrets.ACTIONS_TESTS_DATA_DEPLOY_KEY }}
|
||||
GITHUB_ACTOR: ${{ github.actor }}
|
||||
@@ -97,6 +136,26 @@ jobs:
|
||||
cd website
|
||||
[ -f ./_data/whats_left.temp ] && cp ./_data/whats_left.temp ./_data/whats_left_lastrun.temp
|
||||
cp ../whats_left.temp ./_data/whats_left.temp
|
||||
rm ./_data/whats_left/modules.csv
|
||||
echo -e "module" > ./_data/whats_left/modules.csv
|
||||
cat ./_data/whats_left.temp | grep "(entire module)" | cut -d ' ' -f 1 | sort >> ./_data/whats_left/modules.csv
|
||||
awk -f - ./_data/whats_left.temp > ./_data/whats_left/builtin_items.csv <<'EOF'
|
||||
BEGIN {
|
||||
OFS=","
|
||||
print "builtin,name,is_inherited"
|
||||
}
|
||||
/^# builtin items/ { in_section=1; next }
|
||||
/^$/ { if (in_section) exit }
|
||||
in_section {
|
||||
split($1, a, ".")
|
||||
rest = ""
|
||||
idx = index($0, " ")
|
||||
if (idx > 0) {
|
||||
rest = substr($0, idx+1)
|
||||
}
|
||||
print a[1], $1, rest
|
||||
}
|
||||
EOF
|
||||
git add -A
|
||||
if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update what is left results" --author="$GITHUB_ACTOR"; then
|
||||
git push
|
||||
@@ -105,32 +164,43 @@ jobs:
|
||||
benchmark:
|
||||
name: Collect benchmark data
|
||||
runs-on: ubuntu-latest
|
||||
# Disable this scheduled job when running on a fork.
|
||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
python-version: 3.9
|
||||
persist-credentials: true
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- run: cargo install cargo-criterion
|
||||
|
||||
- name: build benchmarks
|
||||
run: cargo build --release --benches
|
||||
|
||||
- name: collect execution benchmark data
|
||||
run: cargo criterion --bench execution
|
||||
|
||||
- name: collect microbenchmarks data
|
||||
run: cargo criterion --bench microbenchmarks
|
||||
|
||||
- name: restructure generated files
|
||||
run: |
|
||||
cd ./target/criterion/reports
|
||||
find -type d -name cpython | xargs rm -rf
|
||||
find -type d -name rustpython | xargs rm -rf
|
||||
find -mindepth 2 -maxdepth 2 -name violin.svg | xargs rm -rf
|
||||
find -type f -not -name violin.svg | xargs rm -rf
|
||||
for file in $(find -type f -name violin.svg); do mv $file $(echo $file | sed -E "s_\./([^/]+)/([^/]+)/violin\.svg_./\1/\2.svg_"); done
|
||||
find -mindepth 2 -maxdepth 2 -type d | xargs rm -rf
|
||||
find . -type d -name cpython -print0 | xargs -0 rm -rf
|
||||
find . -type d -name rustpython -print0 | xargs -0 rm -rf
|
||||
find . -mindepth 2 -maxdepth 2 -name violin.svg -print0 | xargs -0 rm -rf
|
||||
find . -type f -not -name violin.svg -print0 | xargs -0 rm -rf
|
||||
find . -type f -name violin.svg -exec sh -c 'for file; do mv "$file" "$(echo "$file" | sed -E "s_\./([^/]+)/([^/]+)/violin\.svg_./\1/\2.svg_")"; done' _ {} +
|
||||
find . -mindepth 2 -maxdepth 2 -type d -print0 | xargs -0 rm -rf
|
||||
cd ..
|
||||
mv reports/* .
|
||||
rmdir reports
|
||||
|
||||
- name: upload benchmark data to the website
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
env:
|
||||
SSHKEY: ${{ secrets.ACTIONS_TESTS_DATA_DEPLOY_KEY }}
|
||||
run: |
|
||||
@@ -142,7 +212,11 @@ jobs:
|
||||
cd website
|
||||
rm -rf ./assets/criterion
|
||||
cp -r ../target/criterion ./assets/criterion
|
||||
git add ./assets/criterion
|
||||
printf '{\n "generated_at": "%s",\n "rustpython_commit": "%s",\n "rustpython_ref": "%s"\n}\n' \
|
||||
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
"${{ github.sha }}" \
|
||||
"${{ github.ref_name }}" > ./_data/criterion-metadata.json
|
||||
git add ./assets/criterion ./_data/criterion-metadata.json
|
||||
if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update benchmark results"; then
|
||||
git push
|
||||
fi
|
||||
|
||||
138
.github/workflows/lib-deps-check.yaml
vendored
Normal file
138
.github/workflows/lib-deps-check.yaml
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
name: Lib Dependencies Check
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "Lib/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check_deps:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout base branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# Use base branch for scripts (security: don't run PR code with elevated permissions)
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Fetch PR head
|
||||
run: |
|
||||
git fetch origin ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Checkout PR Lib files
|
||||
run: |
|
||||
# Checkout only Lib/ directory from PR head for accurate comparison
|
||||
git checkout ${{ github.event.pull_request.head.sha }} -- Lib/
|
||||
|
||||
- name: Get target CPython version
|
||||
id: cpython-version
|
||||
run: |
|
||||
version=$(cat .python-version)
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout CPython
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: python/cpython
|
||||
path: cpython
|
||||
ref: "v${{ steps.cpython-version.outputs.version }}"
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Get changed Lib files
|
||||
id: all-changed-files
|
||||
run: |
|
||||
# Get the list of changed files under Lib/
|
||||
{
|
||||
echo 'changed<<EOF'
|
||||
|
||||
git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- 'Lib/*.py' 'Lib/**/*.py'
|
||||
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Parse changed files
|
||||
id: changed-files
|
||||
run: |
|
||||
from os import environ
|
||||
|
||||
files = environ["FILES"]
|
||||
modules = set()
|
||||
for file in files.splitlines():
|
||||
file = file.strip()
|
||||
|
||||
if file.startswith("Lib/test/"):
|
||||
# Test files:
|
||||
# Lib/test/test_pydoc.py -> test_pydoc
|
||||
# Lib/test/test_pydoc/foo.py -> test_pydoc
|
||||
module = file.removeprefix("Lib/test/").split("/")[0]
|
||||
if not module.startswith("test_"):
|
||||
continue
|
||||
else:
|
||||
# Lib files:
|
||||
# Lib/foo.py -> foo
|
||||
# Lib/foo/__init__.py -> foo
|
||||
module = file.removeprefix("Lib/").split("/")[0]
|
||||
|
||||
module = module.split(".")[0]
|
||||
modules.add(module)
|
||||
|
||||
print(f"{modules=}")
|
||||
output = " ".join(sorted(modules))
|
||||
output_file = environ["GITHUB_OUTPUT"]
|
||||
with open(output_file, mode="a", encoding="utf-8") as fd:
|
||||
fd.write(f"modules={output}\n")
|
||||
env:
|
||||
FILES: ${{ steps.all-changed-files.outputs.changed }}
|
||||
shell: python
|
||||
|
||||
- name: Setup Python
|
||||
if: steps.changed-files.outputs.modules != ''
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- name: Run deps check
|
||||
if: steps.changed-files.outputs.modules != ''
|
||||
id: deps-check
|
||||
run: |
|
||||
# Run deps for all modules at once
|
||||
echo "deps_output<<EOF" >> "$GITHUB_OUTPUT"
|
||||
output=$(python scripts/update_lib deps "${MODULES}" --depth 2 2>&1 || true)
|
||||
echo "$output" >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
MODULES: ${{ steps.changed-files.outputs.modules }}
|
||||
|
||||
- name: Post comment
|
||||
if: steps.deps-check.outputs.deps_output != ''
|
||||
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
|
||||
with:
|
||||
header: lib-deps-check
|
||||
number: ${{ github.event.pull_request.number }}
|
||||
message: |
|
||||
## 📦 Library Dependencies
|
||||
|
||||
The following Lib/ modules were modified. Here are their dependencies:
|
||||
|
||||
${{ steps.deps-check.outputs.deps_output }}
|
||||
|
||||
**Legend:**
|
||||
- `[+]` path exists in CPython
|
||||
- `[x]` up-to-date, `[ ]` outdated
|
||||
|
||||
- name: Remove comment if no Lib changes
|
||||
if: steps.changed-files.outputs.modules == ''
|
||||
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
|
||||
with:
|
||||
header: lib-deps-check
|
||||
number: ${{ github.event.pull_request.number }}
|
||||
delete: true
|
||||
201
.github/workflows/release.yml
vendored
Normal file
201
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 9 AM UTC on every Monday
|
||||
- cron: "0 9 * * Mon"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pre-release:
|
||||
type: boolean
|
||||
description: Mark "Pre-Release"
|
||||
required: false
|
||||
default: true
|
||||
|
||||
env:
|
||||
X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD
|
||||
X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
# Disable this scheduled job when running on a fork.
|
||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
- os: windows-2025
|
||||
target: x86_64-pc-windows-msvc
|
||||
# - os: ubuntu-latest
|
||||
# target: i686-unknown-linux-gnu
|
||||
# - os: ubuntu-latest
|
||||
# target: aarch64-unknown-linux-gnu
|
||||
# - os: ubuntu-latest
|
||||
# target: armv7-unknown-linux-gnueabi
|
||||
# - os: ubuntu-latest
|
||||
# target: s390x-unknown-linux-gnu
|
||||
# - os: ubuntu-latest
|
||||
# target: powerpc64le-unknown-linux-gnu
|
||||
# - os: macos-latest
|
||||
# target: x86_64-apple-darwin
|
||||
# - os: windows-2025
|
||||
# target: i686-pc-windows-msvc
|
||||
# - os: windows-2025
|
||||
# target: aarch64-pc-windows-msvc
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Install macOS dependencies
|
||||
uses: ./.github/actions/install-macos-deps
|
||||
with:
|
||||
autoconf: true
|
||||
automake: true
|
||||
libtool: true
|
||||
|
||||
- name: Build RustPython
|
||||
run: cargo build --release --target=${{ matrix.target }} --verbose --no-default-features --features stdlib,stdio,importlib,encodings,sqlite,host_env,ssl-rustls-aws-lc,threading,jit
|
||||
|
||||
- name: Rename Binary
|
||||
run: cp target/${{ matrix.target }}/release/rustpython target/rustpython-release-${{ runner.os }}-${{ matrix.target }}
|
||||
if: runner.os != 'Windows'
|
||||
|
||||
- name: Rename Binary
|
||||
run: cp target/${{ matrix.target }}/release/rustpython.exe target/rustpython-release-${{ runner.os }}-${{ matrix.target }}.exe
|
||||
if: runner.os == 'Windows'
|
||||
|
||||
- name: Upload Binary Artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: rustpython-release-${{ runner.os }}-${{ matrix.target }}
|
||||
path: target/rustpython-release-${{ runner.os }}-${{ matrix.target }}*
|
||||
|
||||
build-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
# Disable this scheduled job when running on a fork.
|
||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-wasip1
|
||||
|
||||
- name: Build RustPython
|
||||
run: cargo build --target wasm32-wasip1 --no-default-features --features freeze-stdlib,stdlib --release
|
||||
|
||||
- name: Rename Binary
|
||||
run: cp target/wasm32-wasip1/release/rustpython.wasm target/rustpython-release-wasm32-wasip1.wasm
|
||||
|
||||
- name: Upload Binary Artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: rustpython-release-wasm32-wasip1
|
||||
path: target/rustpython-release-wasm32-wasip1.wasm
|
||||
|
||||
- name: install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
package-manager-cache: false
|
||||
|
||||
- uses: mwilliamson/setup-wabt-action@427f2fdd70bc4dbc2e53c2eb4f19f66162d71bd2 # v4.0.0
|
||||
with:
|
||||
wabt-version: "1.0.30"
|
||||
|
||||
- name: build demo
|
||||
run: |
|
||||
npm install
|
||||
npm run dist
|
||||
env:
|
||||
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||
working-directory: ./wasm/demo
|
||||
|
||||
- name: build notebook demo
|
||||
run: |
|
||||
npm install
|
||||
npm run dist
|
||||
mv dist ../demo/dist/notebook
|
||||
env:
|
||||
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||
working-directory: ./wasm/notebook
|
||||
|
||||
- name: Deploy demo to Github Pages
|
||||
if: ${{ github.repository == 'RustPython/RustPython' }}
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
|
||||
with:
|
||||
deploy_key: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
||||
publish_dir: ./wasm/demo/dist
|
||||
external_repository: RustPython/demo
|
||||
publish_branch: master
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
# Disable this scheduled job when running on a fork.
|
||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||
needs: [build, build-wasm]
|
||||
permissions:
|
||||
contents: write # for creating a release
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download Binary Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: bin
|
||||
pattern: rustpython-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create Lib Archive
|
||||
run: zip -r bin/rustpython-lib.zip Lib/
|
||||
|
||||
- name: List Binaries
|
||||
run: |
|
||||
ls -lah bin/
|
||||
file bin/*
|
||||
|
||||
- name: Create Release
|
||||
run: |
|
||||
if [[ "${PRE_RELEASE_INPUT}" == "false" ]]; then
|
||||
RELEASE_TYPE_NAME=Release
|
||||
PRERELEASE_ARG=
|
||||
else
|
||||
RELEASE_TYPE_NAME=Pre-Release
|
||||
PRERELEASE_ARG=--prerelease
|
||||
fi
|
||||
|
||||
today=$(date '+%Y-%m-%d')
|
||||
gh release create "$today-$tag-$run" \
|
||||
--repo="$GITHUB_REPOSITORY" \
|
||||
--title="RustPython $RELEASE_TYPE_NAME $today-$tag #$run" \
|
||||
--target="$tag" \
|
||||
--notes "⚠️ **Important**: To run RustPython, you must download both the binary for your platform AND the \`rustpython-lib.zip\` archive. Extract the Lib directory from the archive to the same location as the binary, or set the \`RUSTPYTHONPATH\` environment variable to point to the Lib directory." \
|
||||
--generate-notes \
|
||||
$PRERELEASE_ARG \
|
||||
bin/rustpython-release-*
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
tag: ${{ github.ref_name }}
|
||||
run: ${{ github.run_number }}
|
||||
PRE_RELEASE_INPUT: ${{ github.event.inputs.pre-release }}
|
||||
77
.github/workflows/update-caches.yml
vendored
Normal file
77
.github/workflows/update-caches.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Update Actions Caches
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_PROFILE_TEST_DEBUG: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
CARGO_PROFILE_RELEASE_DEBUG: 0
|
||||
CARGO_ARGS: --workspace --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls-aws-lc,host_env,threading,jit --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher
|
||||
|
||||
jobs:
|
||||
build-caches:
|
||||
name: Build Caches
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
toolchain: stable
|
||||
target: ""
|
||||
- os: ubuntu-latest
|
||||
toolchain: stable
|
||||
target: ""
|
||||
- os: windows-latest
|
||||
toolchain: stable
|
||||
target: ""
|
||||
steps:
|
||||
- name: Checkout RustPython main branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: RustPython/RustPython
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Install macos dependencies
|
||||
uses: ./.github/actions/install-macos-deps
|
||||
with:
|
||||
openssl: true
|
||||
|
||||
- name: Build dev cache # dev profile used by check & doc
|
||||
run: cargo build --profile dev ${{ env.CARGO_ARGS }}
|
||||
|
||||
- name: Build test cache
|
||||
run: cargo build --profile test ${{ env.CARGO_ARGS }}
|
||||
|
||||
- name: Build release cache
|
||||
run: cargo build --profile release ${{ env.CARGO_ARGS }}
|
||||
|
||||
- name: Save cache
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-${{ matrix.toolchain }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('Cargo.lock') }}-${{ github.sha }}
|
||||
125
.github/workflows/update-doc-db.yml
vendored
Normal file
125
.github/workflows/update-doc-db.yml
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
name: Update doc DB
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
python-version:
|
||||
description: Target python version to generate doc db for
|
||||
type: string
|
||||
default: "3.14.3"
|
||||
base-ref:
|
||||
description: Base branch to create the update branch from
|
||||
type: string
|
||||
default: "main"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
crates/doc
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
- name: Generate docs
|
||||
run: python crates/doc/generate.py
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: doc-db-${{ inputs.python-version }}-${{ matrix.os }}
|
||||
path: "crates/doc/generated/*.json"
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
overwrite: true
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs: generate
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ inputs.base-ref }}
|
||||
|
||||
- name: Create update branch
|
||||
run: git switch -c "update-doc-${PYTHON_VERSION}"
|
||||
env:
|
||||
PYTHON_VERSION: ${{ inputs.python-version }}
|
||||
|
||||
- name: Download generated doc DBs
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: "doc-db-${{ inputs.python-version }}-**"
|
||||
path: crates/doc/generated/
|
||||
merge-multiple: true
|
||||
|
||||
- name: Transform JSON
|
||||
env:
|
||||
PYTHON_VERSION: ${{ inputs.python-version }}
|
||||
run: |
|
||||
# Merge all artifacts
|
||||
jq -s "add" --sort-keys crates/doc/generated/*.json > crates/doc/generated/merged.json
|
||||
|
||||
# Format merged json for the phf macro
|
||||
jq -r 'to_entries[] | " \(.key | @json) => \(.value | @json),"' crates/doc/generated/merged.json > crates/doc/generated/raw_entries.txt
|
||||
|
||||
OUTPUT_FILE='crates/doc/src/data.inc.rs'
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
{
|
||||
echo '// This file was auto-generated by `.github/workflows/update-doc-db.yml`.'
|
||||
echo "// CPython version: ${PYTHON_VERSION}"
|
||||
echo '// spell-checker: disable'
|
||||
echo ''
|
||||
echo "pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! {"
|
||||
cat crates/doc/generated/raw_entries.txt
|
||||
echo '};'
|
||||
} > "$OUTPUT_FILE"
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: doc-db-${{ inputs.python-version }}
|
||||
path: "crates/doc/src/data.inc.rs"
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
overwrite: true
|
||||
|
||||
- name: Commit, push and create PR
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PYTHON_VERSION: ${{ inputs.python-version }}
|
||||
BASE_REF: ${{ inputs.base-ref }}
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git add crates/doc/src/data.inc.rs
|
||||
git commit -m "Update doc DB for CPython ${PYTHON_VERSION}"
|
||||
git push -u origin HEAD
|
||||
gh pr create \
|
||||
--base "${BASE_REF}" \
|
||||
--title "Update doc DB for CPython ${PYTHON_VERSION}" \
|
||||
--body "Auto-generated by update-doc-db workflow."
|
||||
fi
|
||||
96
.github/workflows/update-libs-status.yaml
vendored
Normal file
96
.github/workflows/update-libs-status.yaml
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
name: Updated libs status
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "Lib/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
env:
|
||||
ISSUE_ID: "6839"
|
||||
|
||||
jobs:
|
||||
update-issue:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'RustPython/RustPython' }}
|
||||
steps:
|
||||
- name: Clone RustPython
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
path: rustpython
|
||||
persist-credentials: false
|
||||
sparse-checkout: |-
|
||||
Lib
|
||||
scripts/update_lib
|
||||
.python-version
|
||||
|
||||
- name: Get target CPython version
|
||||
id: cpython-version
|
||||
run: |
|
||||
version=$(cat rustpython/.python-version)
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clone CPython
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: python/cpython
|
||||
path: cpython
|
||||
ref: "v${{ steps.cpython-version.outputs.version }}"
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
Lib
|
||||
|
||||
- name: Get current date
|
||||
id: current_date
|
||||
run: |
|
||||
now=$(date -u +"%Y-%m-%d %H:%M:%S")
|
||||
echo "date=$now" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Write body prefix
|
||||
run: |
|
||||
cat > body.txt <<EOF
|
||||
|
||||
<!--
|
||||
THIS BODY IS AUTO-GENERATED. DO NOT EDIT MANUALLY!
|
||||
-->
|
||||
|
||||
## Summary
|
||||
|
||||
Check \`scripts/update_lib\` for tools. As a note, the current latest Python version is \`${{ steps.cpython-version.outputs.version }}\`.
|
||||
|
||||
Previous versions' issues as reference
|
||||
- 3.13: #5529
|
||||
|
||||
<!--
|
||||
Quick guideline for Copilot:
|
||||
# Clone \`github.com/python/cpython\` \`${{ steps.cpython-version.outputs.version }}\` tag under RustPython working dir with depth 1 option; never 3.14.0 or 3.14.1 or 3.14.2
|
||||
# Pick a library or test to update. Probably user give one.
|
||||
# Run \`python3 scripts/update_lib quick <name>\`
|
||||
# A commit is automatically created. push the commit.
|
||||
# Do not include keywords that automatically close an issue (e.g., Fix, Close, Resolve) next to an issue link or issue number.
|
||||
-->
|
||||
|
||||
## Details
|
||||
|
||||
${{ steps.current_date.outputs.date }} (UTC)
|
||||
\`\`\`shell
|
||||
$ python3 scripts/update_lib todo --done
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
- name: Run todo
|
||||
run: python3 rustpython/scripts/update_lib todo --cpython cpython --lib rustpython/Lib --done >> body.txt
|
||||
|
||||
- name: Update GH issue
|
||||
run: gh issue edit ${{ env.ISSUE_ID }} --body-file ../body.txt
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
working-directory: rustpython
|
||||
|
||||
|
||||
1095
.github/workflows/upgrade-pylib.lock.yml
generated
vendored
Normal file
1095
.github/workflows/upgrade-pylib.lock.yml
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
134
.github/workflows/upgrade-pylib.md
vendored
Normal file
134
.github/workflows/upgrade-pylib.md
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
description: |
|
||||
Pick an out-of-sync Python library from the todo list and upgrade it
|
||||
by running `scripts/update_lib quick`, then open a pull request.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
name:
|
||||
description: "Module name to upgrade (leave empty to auto-pick)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
timeout-minutes: 45
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
pull-requests: read
|
||||
|
||||
network:
|
||||
allowed:
|
||||
- defaults
|
||||
- rust
|
||||
- python
|
||||
|
||||
engine: copilot
|
||||
|
||||
runtimes:
|
||||
python:
|
||||
version: "3.14"
|
||||
|
||||
tools:
|
||||
bash:
|
||||
- ":*"
|
||||
edit:
|
||||
github:
|
||||
toolsets: [repos, issues, pull_requests]
|
||||
read-only: true
|
||||
|
||||
safe-outputs:
|
||||
create-pull-request:
|
||||
title-prefix: "Update "
|
||||
labels: [pylib-sync]
|
||||
draft: false
|
||||
expires: 30
|
||||
|
||||
cache:
|
||||
key: cpython-lib-${{ env.PYTHON_VERSION }}
|
||||
path: cpython
|
||||
restore-keys:
|
||||
- cpython-lib-
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "v3.14.4"
|
||||
ISSUE_ID: "6839"
|
||||
---
|
||||
|
||||
# Upgrade Python Library
|
||||
|
||||
You are an automated maintenance agent for RustPython, a Python 3 interpreter written in Rust. Your task is to upgrade one out-of-sync Python standard library module from CPython.
|
||||
|
||||
## Step 1: Set up the environment
|
||||
|
||||
The CPython source may already be cached. Check if the `cpython` directory exists and has the correct version:
|
||||
|
||||
```bash
|
||||
if [ -d "cpython/Lib" ]; then
|
||||
echo "CPython cache hit, skipping clone"
|
||||
else
|
||||
git clone --depth 1 --branch "$PYTHON_VERSION" https://github.com/python/cpython.git cpython
|
||||
fi
|
||||
```
|
||||
|
||||
## Step 2: Determine module name
|
||||
|
||||
Run this script to determine the module name:
|
||||
|
||||
```bash
|
||||
MODULE_NAME="${{ github.event.inputs.name }}"
|
||||
if [ -z "$MODULE_NAME" ]; then
|
||||
echo "No module specified, running todo to find one..."
|
||||
python3 scripts/update_lib todo
|
||||
echo "Pick one module from the list above that is marked [ ], has no unmet deps, and has a small Δ number."
|
||||
echo "Do NOT pick: opcode, datetime, random, hashlib, tokenize, pdb, _pyrepl, concurrent, asyncio, multiprocessing, ctypes, idlelib, tkinter, shutil, tarfile, email, unittest"
|
||||
else
|
||||
echo "Module specified by user: $MODULE_NAME"
|
||||
fi
|
||||
```
|
||||
|
||||
If the script printed "Module specified by user: ...", use that exact name. If it printed the todo list, pick one suitable module from it.
|
||||
|
||||
## Step 3: Run the upgrade
|
||||
|
||||
Run the quick upgrade command. This will copy the library from CPython, migrate test files preserving RustPython markers, auto-mark test failures, and create a git commit:
|
||||
|
||||
```bash
|
||||
python3 scripts/update_lib quick <module_name>
|
||||
```
|
||||
|
||||
This takes a while because it builds RustPython (`cargo build --release`) and runs tests to determine which ones pass or fail.
|
||||
|
||||
If the command fails, report the error and stop. Do not try to fix Rust code or modify test files manually.
|
||||
|
||||
## Step 4: Verify the result
|
||||
|
||||
After the script succeeds, check what changed:
|
||||
|
||||
```bash
|
||||
git log -1 --stat
|
||||
git diff HEAD~1 --stat
|
||||
```
|
||||
|
||||
Make sure the commit was created with the correct message format: `Update <name> from <version>`.
|
||||
|
||||
## Step 5: Create the pull request
|
||||
|
||||
Create a pull request. Reference issue #${{ env.ISSUE_ID }} in the body but do **NOT** use keywords that auto-close issues (Fix, Close, Resolve).
|
||||
|
||||
Use this format for the PR body:
|
||||
|
||||
```
|
||||
## Summary
|
||||
|
||||
Upgrade `<module_name>` from CPython $PYTHON_VERSION.
|
||||
|
||||
Part of #$ISSUE_ID
|
||||
|
||||
## Changes
|
||||
|
||||
- Updated `Lib/<module_name>` from CPython
|
||||
- Migrated test files preserving RustPython markers
|
||||
- Auto-marked test failures with `@expectedFailure`
|
||||
```
|
||||
14
.github/zizmor.yml
vendored
Normal file
14
.github/zizmor.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
rules:
|
||||
unpinned-uses:
|
||||
config:
|
||||
policies:
|
||||
# dtolnay/rust-toolchain is a trusted action that uses lightweight branch
|
||||
# refs (@stable, @nightly, etc.) by design. Pinning to a hash would break
|
||||
# the intended usage pattern.
|
||||
# We can remove this once https://github.com/dtolnay/rust-toolchain/issues/180 is resolved
|
||||
dtolnay/rust-toolchain: any
|
||||
# dtolnay/rust-toolchain handles component installation, target addition, and
|
||||
# override configuration beyond what a bare `rustup` invocation provides.
|
||||
# See: https://github.com/zizmorcore/zizmor/issues/1817
|
||||
superfluous-actions:
|
||||
disable: true
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -2,13 +2,14 @@
|
||||
/*/target
|
||||
**/*.rs.bk
|
||||
**/*.bytecode
|
||||
__pycache__
|
||||
__pycache__/
|
||||
**/*.pytest_cache
|
||||
.*sw*
|
||||
.repl_history.txt
|
||||
.vscode
|
||||
.vscode/
|
||||
wasm-pack.log
|
||||
.idea/
|
||||
.envrc
|
||||
|
||||
flame-graph.html
|
||||
flame.txt
|
||||
@@ -19,3 +20,11 @@ flamescope.json
|
||||
|
||||
extra_tests/snippets/resources
|
||||
extra_tests/not_impl.py
|
||||
|
||||
Lib/_sysconfig_vars*.json
|
||||
Lib/site-packages/*
|
||||
!Lib/site-packages/README.txt
|
||||
Lib/test/data/*
|
||||
!Lib/test/data/README
|
||||
cpython/
|
||||
.claude/scheduled_tasks.lock
|
||||
84
.pre-commit-config.yaml
Normal file
84
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,84 @@
|
||||
# NOTE: Reason for not using `prek.toml` is dependabot supports `pre-commit` as an ecosystem
|
||||
# See: https://github.blog/changelog/2026-03-10-dependabot-now-supports-pre-commit-hooks/
|
||||
|
||||
fail_fast: false
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.12
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
priority: 0
|
||||
|
||||
- id: ruff-check
|
||||
args: [--select, I, --fix, --exit-non-zero-on-fix]
|
||||
types_or: [python]
|
||||
require_serial: true
|
||||
priority: 1
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: redundant-test-patches
|
||||
name: check redundant test patches
|
||||
entry: scripts/check_redundant_patches.py
|
||||
files: '^Lib/test/.*\.py$'
|
||||
language: script
|
||||
types: [python]
|
||||
priority: 0
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: rustfmt
|
||||
name: rustfmt
|
||||
entry: rustfmt
|
||||
language: system
|
||||
types: [rust]
|
||||
priority: 0
|
||||
|
||||
- id: generate-rs-opcode-metadata
|
||||
name: generate rust opcode metadata
|
||||
entry: python tools/opcode_metadata/generate_rs_opcode_metadata.py
|
||||
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$'
|
||||
pass_filenames: false
|
||||
language: system
|
||||
require_serial: true
|
||||
priority: 1 # so rustfmt runs first
|
||||
stages:
|
||||
- manual
|
||||
|
||||
- id: generate-py-opcode-metadata
|
||||
name: generate python opcode metadata
|
||||
entry: python tools/opcode_metadata/generate_py_opcode_metadata.py
|
||||
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$'
|
||||
pass_filenames: false
|
||||
language: system
|
||||
require_serial: true
|
||||
priority: 1 # so rustfmt runs first
|
||||
stages:
|
||||
- manual
|
||||
|
||||
- repo: https://github.com/streetsidesoftware/cspell-cli
|
||||
rev: v10.0.0
|
||||
hooks:
|
||||
- id: cspell
|
||||
types: [rust]
|
||||
additional_dependencies:
|
||||
- '@cspell/dict-en_us'
|
||||
- '@cspell/dict-cpp'
|
||||
- '@cspell/dict-python'
|
||||
- '@cspell/dict-rust'
|
||||
- '@cspell/dict-win32'
|
||||
- '@cspell/dict-shell'
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.8.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
files: '^wasm/.*$'
|
||||
priority: 0
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.14.5
|
||||
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@@ -16,18 +16,6 @@
|
||||
},
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'rustpython' without SSL",
|
||||
"preLaunchTask": "Build RustPython Debug without SSL",
|
||||
"program": "target/debug/rustpython",
|
||||
"args": [],
|
||||
"env": {
|
||||
"RUST_BACKTRACE": "1"
|
||||
},
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
||||
16
.vscode/tasks.json
vendored
16
.vscode/tasks.json
vendored
@@ -1,28 +1,12 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build RustPython Debug without SSL",
|
||||
"type": "shell",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build",
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc",
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Build RustPython Debug",
|
||||
"type": "shell",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build",
|
||||
"--features=ssl"
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc",
|
||||
|
||||
307
AGENTS.md
Normal file
307
AGENTS.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# GitHub Copilot Instructions for RustPython
|
||||
|
||||
This document provides guidelines for working with GitHub Copilot when contributing to the RustPython project.
|
||||
|
||||
## Project Overview
|
||||
|
||||
RustPython is a Python 3 interpreter written in Rust, implementing Python 3.14.0+ compatibility. The project aims to provide:
|
||||
|
||||
- A complete Python-3 environment entirely in Rust (not CPython bindings)
|
||||
- A clean implementation without compatibility hacks
|
||||
- Cross-platform support, including WebAssembly compilation
|
||||
- The ability to embed Python scripting in Rust applications
|
||||
|
||||
## Repository Structure
|
||||
|
||||
- `src/` - Top-level code for the RustPython binary
|
||||
- `vm/` - The Python virtual machine implementation
|
||||
- `builtins/` - Python built-in types and functions
|
||||
- `stdlib/` - Essential standard library modules implemented in Rust, required to run the Python core
|
||||
- `compiler/` - Python compiler components
|
||||
- `parser/` - Parser for converting Python source to AST
|
||||
- `core/` - Bytecode representation in Rust structures
|
||||
- `codegen/` - AST to bytecode compiler
|
||||
- `Lib/` - CPython's standard library in Python (copied from CPython). **IMPORTANT**: Do not edit this directory directly; The only allowed operation is copying files from CPython.
|
||||
- `derive/` - Rust macros for RustPython
|
||||
- `common/` - Common utilities
|
||||
- `extra_tests/` - Integration tests and snippets
|
||||
- `stdlib/` - Non-essential Python standard library modules implemented in Rust (useful but not required for core functionality)
|
||||
- `wasm/` - WebAssembly support
|
||||
- `jit/` - Experimental JIT compiler implementation
|
||||
- `pylib/` - Python standard library packaging (do not modify this directory directly - its contents are generated automatically)
|
||||
|
||||
## AI Agent Rules
|
||||
|
||||
**CRITICAL: Git Operations**
|
||||
- NEVER create pull requests directly without explicit user permission
|
||||
- NEVER push commits to remote without explicit user permission
|
||||
- Always ask the user before performing any git operations that affect the remote repository
|
||||
- Commits can be created locally when requested, but pushing and PR creation require explicit approval
|
||||
|
||||
**CRITICAL: Pre-commit Checks**
|
||||
- Before creating ANY commit, you MUST run `prek run --all-files` (or `pre-commit run --all-files`) AND the full test suite. Both must pass — do not commit if either fails.
|
||||
- Test commands are documented in the [Testing](#testing) section below. At minimum run `cargo test --workspace --exclude rustpython_wasm --exclude rustpython-venvlauncher`; if the change touches `extra_tests/snippets/` run `pytest -v` there too, and if it touches `Lib/` or interpreter behavior, run the relevant `cargo run --release -- -m test <module>` modules.
|
||||
- If a hook auto-fixes files (e.g. `ruff-format`, `rustfmt`), re-stage the fixes, re-run `prek` until it reports a clean pass, then re-run the tests, then commit.
|
||||
- NEVER bypass these checks with `--no-verify`, `--no-gpg-sign`, or by skipping tests "because the change is small". If a hook or test fails, fix the underlying issue and create a new commit — do not amend or force the failing commit through.
|
||||
|
||||
## Important Development Notes
|
||||
|
||||
### Running Python Code
|
||||
|
||||
When testing Python code, always use RustPython instead of the standard `python` command:
|
||||
|
||||
```bash
|
||||
# Use this instead of python script.py
|
||||
cargo run -- script.py
|
||||
|
||||
# For interactive REPL
|
||||
cargo run
|
||||
|
||||
# With specific features
|
||||
cargo run --features jit
|
||||
|
||||
# Release mode (recommended for better performance)
|
||||
cargo run --release -- script.py
|
||||
```
|
||||
|
||||
### Comparing with CPython
|
||||
|
||||
When you need to compare behavior with CPython or run test suites:
|
||||
|
||||
```bash
|
||||
# Use python command to explicitly run CPython
|
||||
python my_test_script.py
|
||||
|
||||
# Run RustPython
|
||||
cargo run -- my_test_script.py
|
||||
```
|
||||
|
||||
### Working with the Lib Directory
|
||||
|
||||
The `Lib/` directory contains Python standard library files copied from the CPython repository. Important notes:
|
||||
|
||||
- These files should be edited very conservatively
|
||||
- Modifications should be minimal and only to work around RustPython limitations
|
||||
- Tests in `Lib/test` often use one of the following markers:
|
||||
- Add a `# TODO: RUSTPYTHON` comment when modifications are made
|
||||
- `unittest.skip("TODO: RustPython <reason>")`
|
||||
- `unittest.expectedFailure` with `# TODO: RUSTPYTHON <reason>` comment
|
||||
|
||||
#### Choosing the right marker
|
||||
|
||||
When marking a test that fails on RustPython, prefer one of the following forms:
|
||||
|
||||
```python
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; <reason>
|
||||
# or
|
||||
@unittest.expectedFailureIf(<condition>, "TODO: RUSTPYTHON; <reason>")
|
||||
```
|
||||
|
||||
If the test would crash the interpreter (segfault, Rust panic, abort, infinite loop), use `skip` instead so the rest of the suite can still run:
|
||||
|
||||
```python
|
||||
@unittest.skip("TODO: RUSTPYTHON; <reason>")
|
||||
# or
|
||||
@unittest.skipIf(<condition>, "TODO: RUSTPYTHON; <reason>")
|
||||
```
|
||||
|
||||
**When to use which:**
|
||||
|
||||
- **Prefer `expectedFailure` / `expectedFailureIf`** by default. The test body still runs, so if RustPython is later fixed, the unexpected pass surfaces immediately and the decorator can be removed. Use the conditional `*If` form when the failure is environment-specific (e.g., a platform or build flag).
|
||||
- **Use `skip` / `skipIf` only when running the test would take down the test process** — segfaults, Rust panics, aborts, or hangs that block subsequent tests. Skipping keeps the suite usable; `expectedFailure` cannot help here, because the test body still executes.
|
||||
|
||||
To find WIP entries that are partly modified and may need follow-up:
|
||||
|
||||
```bash
|
||||
grep -d recurse 'TODO: RUSTPYTHON' Lib/test/
|
||||
```
|
||||
|
||||
### Clean Build
|
||||
|
||||
When you modify bytecode instructions, a full clean is required:
|
||||
|
||||
```bash
|
||||
rm -r target/debug/build/rustpython-* && find . | grep -E "\.pyc$" | xargs rm -r
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run Rust unit tests
|
||||
cargo test --workspace --exclude rustpython_wasm --exclude rustpython-venvlauncher
|
||||
|
||||
# Run Python snippets tests (debug mode recommended for faster compilation)
|
||||
cargo run -- extra_tests/snippets/builtin_bytes.py
|
||||
|
||||
# Run all Python snippets tests with pytest
|
||||
cd extra_tests
|
||||
pytest -v
|
||||
|
||||
# Run the Python test module (release mode recommended for better performance)
|
||||
cargo run --release -- -m test ${TEST_MODULE}
|
||||
cargo run --release -- -m test test_unicode # to test test_unicode.py
|
||||
|
||||
# Run the Python test module with specific function
|
||||
cargo run --release -- -m test test_unicode -k test_unicode_escape
|
||||
```
|
||||
|
||||
**Note**: For `extra_tests/snippets` tests, use debug mode (`cargo run`) as compilation is faster. For `unittest` (`-m test`), use release mode (`cargo run --release`) for better runtime performance.
|
||||
|
||||
### Determining What to Implement
|
||||
|
||||
Run `./scripts/whats_left.py` to get a list of unimplemented methods, which is helpful when looking for contribution opportunities.
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
### Rust Code
|
||||
|
||||
- Follow the default rustfmt code style (`cargo fmt` to format)
|
||||
- **IMPORTANT**: Always run clippy to lint code (`cargo clippy`) before completing tasks. Fix any warnings or lints that are introduced by your changes
|
||||
- Follow Rust best practices for error handling and memory management
|
||||
- Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust
|
||||
|
||||
#### Comments
|
||||
|
||||
- Do not delete or rewrite existing comments unless they are factually wrong or directly contradict the new code.
|
||||
- Do not add decorative section separators (e.g. `// -----------`, `// ===`, `/* *** */`). Use `///` doc-comments or short `//` comments only when they add value.
|
||||
- Do not put `///` doc comments on items annotated with `#[pyattr]`, `#[pyclass]`, or `#[pyfunction]`. The derive macros pull authoritative docstrings from CPython via the `rustpython-doc` crate; a Rust doc comment overrides that source, and on `#[pyattr]` it is silently dropped.
|
||||
|
||||
#### Avoid Duplicate Code in Branches
|
||||
|
||||
When branches differ only in a value but share common logic, extract the differing value first, then call the common logic once.
|
||||
|
||||
**Bad:**
|
||||
```rust
|
||||
let result = if condition {
|
||||
let msg = format!("message A: {x}");
|
||||
some_function(msg, shared_arg)
|
||||
} else {
|
||||
let msg = format!("message B");
|
||||
some_function(msg, shared_arg)
|
||||
};
|
||||
```
|
||||
|
||||
**Good:**
|
||||
```rust
|
||||
let msg = if condition {
|
||||
format!("message A: {x}")
|
||||
} else {
|
||||
format!("message B")
|
||||
};
|
||||
let result = some_function(msg, shared_arg);
|
||||
```
|
||||
|
||||
### Python Code
|
||||
|
||||
- **IMPORTANT**: In most cases, Python code should not be edited. Bug fixes should be made through Rust code modifications only
|
||||
- Follow PEP 8 style for custom Python code
|
||||
- Use ruff for linting Python code
|
||||
- Minimize modifications to CPython standard library files
|
||||
|
||||
## Integration Between Rust and Python
|
||||
|
||||
The project provides several mechanisms for integration:
|
||||
|
||||
- `pymodule` macro for creating Python modules in Rust
|
||||
- `pyclass` macro for implementing Python classes in Rust
|
||||
- `pyfunction` macro for exposing Rust functions to Python
|
||||
- `PyObjectRef` and other types for working with Python objects in Rust
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Implementing a Python Module in Rust
|
||||
|
||||
```rust
|
||||
#[pymodule]
|
||||
mod mymodule {
|
||||
use rustpython_vm::prelude::*;
|
||||
|
||||
#[pyfunction]
|
||||
fn my_function(value: i32) -> i32 {
|
||||
value * 2
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "MyClass")]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct MyClass {
|
||||
value: usize,
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
impl MyClass {
|
||||
#[pymethod]
|
||||
fn get_value(&self) -> usize {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Adding a Python Module to the Interpreter
|
||||
|
||||
```rust
|
||||
vm.add_native_module(
|
||||
"my_module_name".to_owned(),
|
||||
Box::new(my_module::make_module),
|
||||
);
|
||||
```
|
||||
|
||||
## Building for Different Targets
|
||||
|
||||
### WebAssembly
|
||||
|
||||
```bash
|
||||
# Build for WASM
|
||||
cargo build --target wasm32-wasip1 --no-default-features --features freeze-stdlib,stdlib --release
|
||||
```
|
||||
|
||||
### JIT Support
|
||||
|
||||
```bash
|
||||
# Enable JIT support
|
||||
cargo run --features jit
|
||||
```
|
||||
|
||||
### Linux Build and Debug on macOS
|
||||
|
||||
See the "Testing on Linux from macOS" section in [DEVELOPMENT.md](DEVELOPMENT.md#testing-on-linux-from-macos).
|
||||
|
||||
### Building venvlauncher (Windows)
|
||||
|
||||
See DEVELOPMENT.md "CPython Version Upgrade Checklist" section.
|
||||
|
||||
**IMPORTANT**: All 4 venvlauncher binaries use the same source code. Do NOT add multiple `[[bin]]` entries to Cargo.toml. Build once and copy with different names.
|
||||
|
||||
## Test Code Modification Rules
|
||||
|
||||
**CRITICAL: Test code modification restrictions**
|
||||
- NEVER comment out or delete any test code lines except for removing `@unittest.expectedFailure` decorators and upper TODO comments
|
||||
- NEVER modify test assertions, test logic, or test data
|
||||
- When a test cannot pass due to missing language features, keep it as expectedFailure and document the reason
|
||||
- The only acceptable modifications to test files are:
|
||||
1. Removing `@unittest.expectedFailure` decorators and the upper TODO comments when tests actually pass
|
||||
2. Adding `@unittest.expectedFailure` decorators when tests cannot be fixed
|
||||
|
||||
**Examples of FORBIDDEN modifications:**
|
||||
- Commenting out test lines
|
||||
- Changing test assertions
|
||||
- Modifying test data or expected results
|
||||
- Removing test logic
|
||||
|
||||
**Correct approach when tests fail due to unsupported syntax:**
|
||||
- Keep the test as `@unittest.expectedFailure`
|
||||
- Document that it requires PEP 695 support
|
||||
- Focus on tests that can be fixed through Rust code changes only
|
||||
|
||||
## CI Workflows
|
||||
|
||||
If you modify any file under `.github/workflows/`, the change must pass a [zizmor](https://docs.zizmor.sh/) scan in CI.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Check the [architecture document](/architecture/architecture.md) for a high-level overview
|
||||
- Read the [development guide](/DEVELOPMENT.md) for detailed setup instructions
|
||||
- Generate documentation with `cargo doc --no-deps --all`
|
||||
- Online documentation is available at [docs.rs/rustpython](https://docs.rs/rustpython/)
|
||||
- [How to update test files](https://github.com/RustPython/RustPython/wiki/How-to-update-test-files#checkout-cpython-source-code-initial-setup) — guide for syncing test cases from upstream CPython into the `Lib/` directory
|
||||
@@ -1,4 +1,28 @@
|
||||
# RustPython Development Guide and Tips
|
||||
# Contributing to RustPython
|
||||
|
||||
Contributions are more than welcome, and in many cases we are happy to guide
|
||||
contributors through PRs or on [**Discord**](https://discord.gg/vru8NypEhv).
|
||||
|
||||
## Finding ways to help
|
||||
|
||||
We label issues that would be good for a first time contributor as [`good first issue`](https://github.com/RustPython/RustPython/issues?q=label%3A%22good+first+issue%22+is%3Aissue+is%3Aopen+).
|
||||
Also checkout the [issue tracker](https://github.com/RustPython/RustPython/issues) for all open issues.
|
||||
|
||||
You can enhance CPython compatibility by increasing our unittest coverage, you can see [This pinned issue](https://github.com/RustPython/RustPython/issues/6839) to see which libs and tests need be updated to our current supported python version.
|
||||
|
||||
Another approach is to checkout the source code: builtin functions and object
|
||||
methods are often the simplest and easiest way to contribute.
|
||||
|
||||
You can also simply run `python -I scripts/whats_left.py` to assist in finding any unimplemented method.
|
||||
|
||||
## Use of AI
|
||||
|
||||
We **require all use of AI in contributions to follow our
|
||||
[AI Policy](https://github.com/RustPython/.github/blob/main/AI_POLICY.md)**.
|
||||
|
||||
If your contribution does not follow the policy, it will be closed.
|
||||
|
||||
## RustPython Development Guide and Tips
|
||||
|
||||
RustPython attracts developers with interest and experience in Rust, Python,
|
||||
or WebAssembly. Whether you are familiar with Rust, Python, or
|
||||
@@ -19,13 +43,13 @@ The contents of the Development Guide include:
|
||||
|
||||
RustPython requires the following:
|
||||
|
||||
- Rust latest stable version (e.g 1.69.0 as of Apr 20 2023)
|
||||
- Rust latest stable version (e.g 1.92.0 as of Jan 7 2026)
|
||||
- To check Rust version: `rustc --version`
|
||||
- If you have `rustup` on your system, enter to update to the latest
|
||||
stable version: `rustup update stable`
|
||||
- If you do not have Rust installed, use [rustup](https://rustup.rs/) to
|
||||
do so.
|
||||
- CPython version 3.12 or higher
|
||||
- CPython version 3.14 or higher
|
||||
- CPython can be installed by your operating system's package manager,
|
||||
from the [Python website](https://www.python.org/downloads/), or
|
||||
using a third-party distribution, such as
|
||||
@@ -65,7 +89,7 @@ $ pytest -v
|
||||
Rust unit tests can be run with `cargo`:
|
||||
|
||||
```shell
|
||||
$ cargo test --workspace --exclude rustpython_wasm
|
||||
$ cargo test --workspace --exclude rustpython_wasm --exclude rustpython-venvlauncher
|
||||
```
|
||||
|
||||
Python unit tests can be run by compiling RustPython and running the test module:
|
||||
@@ -95,6 +119,41 @@ To run only `test_cmath` (located at `Lib/test/test_cmath`) verbosely:
|
||||
$ cargo run --release -- -m test test_cmath -v
|
||||
```
|
||||
|
||||
### Testing on Linux from macOS
|
||||
|
||||
You can test RustPython on Linux from macOS using Apple's `container` CLI.
|
||||
|
||||
**Setup (one-time):**
|
||||
|
||||
```shell
|
||||
# Install container CLI
|
||||
$ brew install container
|
||||
|
||||
# Disable Rosetta requirement for arm64-only builds
|
||||
$ defaults write com.apple.container.defaults build.rosetta -bool false
|
||||
|
||||
# Build the development image
|
||||
$ container build --arch arm64 -t rustpython-dev -f .devcontainer/Dockerfile .
|
||||
```
|
||||
|
||||
**Running tests:**
|
||||
|
||||
```shell
|
||||
# Start a persistent container in background (8GB memory, 4 CPUs for compilation)
|
||||
$ container run -d --name rustpython-test -m 8G -c 4 \
|
||||
--mount type=bind,source=$(pwd),target=/workspace \
|
||||
-w /workspace rustpython-dev sleep infinity
|
||||
|
||||
# Run tests inside the container
|
||||
$ container exec rustpython-test sh -c "cargo run --release -- -m test test_ensurepip"
|
||||
|
||||
# Run any command
|
||||
$ container exec rustpython-test sh -c "cargo test --workspace"
|
||||
|
||||
# Stop and remove the container when done
|
||||
$ container rm -f rustpython-test
|
||||
```
|
||||
|
||||
## Profiling
|
||||
|
||||
To profile RustPython, build it in `release` mode with the `flame-it` feature.
|
||||
@@ -118,19 +177,19 @@ exists a raw html viewer which is currently broken, and we welcome a PR to fix i
|
||||
Understanding a new codebase takes time. Here's a brief view of the
|
||||
repository's structure:
|
||||
|
||||
- `compiler/src`: python compilation to bytecode
|
||||
- `core/src`: python bytecode representation in rust structures
|
||||
- `parser/src`: python lexing, parsing and ast
|
||||
- `derive/src`: Rust language extensions and macros specific to rustpython
|
||||
- `crates/compiler/src`: python compilation to bytecode
|
||||
- `crates/compiler-core/src`: python bytecode representation in rust structures
|
||||
- `crates/derive/src` and `crates/derive-impl/src`: Rust language extensions and macros specific to rustpython
|
||||
- `Lib`: Carefully selected / copied files from CPython sourcecode. This is
|
||||
the python side of the standard library.
|
||||
- `test`: CPython test suite
|
||||
- `vm/src`: python virtual machine
|
||||
- `crates/vm/src`: python virtual machine
|
||||
- `builtins`: Builtin functions and types
|
||||
- `stdlib`: Standard library parts implemented in rust.
|
||||
- `src`: using the other subcrates to bring rustpython to life.
|
||||
- `wasm`: Binary crate and resources for WebAssembly build
|
||||
- `extra_tests`: extra integration test snippets as a supplement to `Lib/test`
|
||||
- `crates/wasm`: Binary crate and resources for WebAssembly build
|
||||
- `extra_tests`: extra integration test snippets as a supplement to `Lib/test`.
|
||||
Add new RustPython-only regression tests here; do not place new tests under `Lib/test`.
|
||||
|
||||
## Understanding Internals
|
||||
|
||||
@@ -140,9 +199,9 @@ implementation is found in the `src` directory (specifically, `src/lib.rs`).
|
||||
|
||||
The top-level `rustpython` binary depends on several lower-level crates including:
|
||||
|
||||
- `rustpython-parser` (implementation in `compiler/parser/src`)
|
||||
- `rustpython-compiler` (implementation in `compiler/src`)
|
||||
- `rustpython-vm` (implementation in `vm/src`)
|
||||
- `ruff_python_parser` and `ruff_python_ast` (external dependencies from the Ruff project)
|
||||
- `rustpython-compiler` (implementation in `crates/compiler/src`)
|
||||
- `rustpython-vm` (implementation in `crates/vm/src`)
|
||||
|
||||
Together, these crates provide the functions of a programming language and
|
||||
enable a line of code to go through a series of steps:
|
||||
@@ -153,31 +212,26 @@ enable a line of code to go through a series of steps:
|
||||
- compile the AST into bytecode
|
||||
- execute the bytecode in the virtual machine (VM).
|
||||
|
||||
### rustpython-parser
|
||||
### Parser and AST
|
||||
|
||||
This crate contains the lexer and parser to convert a line of code to
|
||||
an Abstract Syntax Tree (AST):
|
||||
RustPython uses the Ruff project's parser and AST implementation:
|
||||
|
||||
- Lexer: `compiler/parser/src/lexer.rs` converts Python source code into tokens
|
||||
- Parser: `compiler/parser/src/parser.rs` takes the tokens generated by the lexer and parses
|
||||
the tokens into an AST (Abstract Syntax Tree) where the nodes of the syntax
|
||||
tree are Rust structs and enums.
|
||||
- The Parser relies on `LALRPOP`, a Rust parser generator framework. The
|
||||
LALRPOP definition of Python's grammar is in `compiler/parser/src/python.lalrpop`.
|
||||
- More information on parsers and a tutorial can be found in the
|
||||
[LALRPOP book](https://lalrpop.github.io/lalrpop/).
|
||||
- AST: `compiler/ast/` implements in Rust the Python types and expressions
|
||||
represented by the AST nodes.
|
||||
- Parser: `ruff_python_parser` is used to convert Python source code into tokens
|
||||
and parse them into an Abstract Syntax Tree (AST)
|
||||
- AST: `ruff_python_ast` provides the Rust types and expressions represented by
|
||||
the AST nodes
|
||||
- These are external dependencies maintained by the Ruff project
|
||||
- For more information, visit the [Ruff GitHub repository](https://github.com/astral-sh/ruff)
|
||||
|
||||
### rustpython-compiler
|
||||
|
||||
The `rustpython-compiler` crate's purpose is to transform the AST (Abstract Syntax
|
||||
Tree) to bytecode. The implementation of the compiler is found in the
|
||||
`compiler/src` directory. The compiler implements Python's symbol table,
|
||||
`crates/compiler/src` directory. The compiler implements Python's symbol table,
|
||||
ast->bytecode compiler, and bytecode optimizer in Rust.
|
||||
|
||||
Implementation of bytecode structure in Rust is found in the `compiler/core/src`
|
||||
directory. `compiler/core/src/bytecode.rs` contains the representation of
|
||||
Implementation of bytecode structure in Rust is found in the `crates/compiler-core/src`
|
||||
directory. `crates/compiler-core/src/bytecode.rs` contains the representation of
|
||||
instructions and operations in Rust. Further information about Python's
|
||||
bytecode instructions can be found in the
|
||||
[Python documentation](https://docs.python.org/3/library/dis.html#bytecodes).
|
||||
@@ -185,14 +239,14 @@ bytecode instructions can be found in the
|
||||
### rustpython-vm
|
||||
|
||||
The `rustpython-vm` crate has the important job of running the virtual machine that
|
||||
executes Python's instructions. The `vm/src` directory contains code to
|
||||
executes Python's instructions. The `crates/vm/src` directory contains code to
|
||||
implement the read and evaluation loop that fetches and dispatches
|
||||
instructions. This directory also contains the implementation of the
|
||||
Python Standard Library modules in Rust (`vm/src/stdlib`). In Python
|
||||
everything can be represented as an object. The `vm/src/builtins` directory holds
|
||||
Python Standard Library modules in Rust (`crates/vm/src/stdlib`). In Python
|
||||
everything can be represented as an object. The `crates/vm/src/builtins` directory holds
|
||||
the Rust code used to represent different Python objects and their methods. The
|
||||
core implementation of what a Python object is can be found in
|
||||
`vm/src/object/core.rs`.
|
||||
`crates/vm/src/object/core.rs`.
|
||||
|
||||
### Code generation
|
||||
|
||||
4744
Cargo.lock
generated
4744
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
432
Cargo.toml
432
Cargo.toml
@@ -1,117 +1,56 @@
|
||||
[package]
|
||||
name = "rustpython"
|
||||
version = "0.3.1"
|
||||
authors = ["RustPython Team"]
|
||||
edition = "2021"
|
||||
rust-version = "1.67.1"
|
||||
description = "A python interpreter written in rust."
|
||||
repository = "https://github.com/RustPython/RustPython"
|
||||
license = "MIT"
|
||||
include = ["LICENSE", "Cargo.toml", "src/**/*.rs"]
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"compiler", "compiler/core", "compiler/codegen",
|
||||
".", "common", "derive", "jit", "vm", "vm/sre_engine", "pylib", "stdlib", "wasm/lib", "derive-impl",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
rustpython-compiler-core = { path = "compiler/core", version = "0.3.1" }
|
||||
rustpython-compiler = { path = "compiler", version = "0.3.1" }
|
||||
rustpython-codegen = { path = "compiler/codegen", version = "0.3.1" }
|
||||
rustpython-common = { path = "common", version = "0.3.1" }
|
||||
rustpython-derive = { path = "derive", version = "0.3.1" }
|
||||
rustpython-derive-impl = { path = "derive-impl", version = "0.3.1" }
|
||||
rustpython-jit = { path = "jit", version = "0.3.1" }
|
||||
rustpython-vm = { path = "vm", default-features = false, version = "0.3.1" }
|
||||
rustpython-pylib = { path = "pylib", version = "0.3.1" }
|
||||
rustpython-stdlib = { path = "stdlib", default-features = false, version = "0.3.1" }
|
||||
rustpython-sre_engine = { path = "vm/sre_engine", version = "0.3.1" }
|
||||
rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0", version = "0.3.0" }
|
||||
|
||||
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", version = "0.3.1", rev = "a95045bc627b2fbf84caf4f010e521846be7b37f" }
|
||||
rustpython-parser-core = { git = "https://github.com/RustPython/Parser.git", version = "0.3.1", rev = "a95045bc627b2fbf84caf4f010e521846be7b37f" }
|
||||
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", version = "0.3.1", rev = "a95045bc627b2fbf84caf4f010e521846be7b37f" }
|
||||
rustpython-ast = { git = "https://github.com/RustPython/Parser.git", version = "0.3.1", rev = "a95045bc627b2fbf84caf4f010e521846be7b37f" }
|
||||
rustpython-format = { git = "https://github.com/RustPython/Parser.git", version = "0.3.1", rev = "a95045bc627b2fbf84caf4f010e521846be7b37f" }
|
||||
# rustpython-literal = { path = "../RustPython-parser/literal" }
|
||||
# rustpython-parser-core = { path = "../RustPython-parser/core" }
|
||||
# rustpython-parser = { path = "../RustPython-parser/parser" }
|
||||
# rustpython-ast = { path = "../RustPython-parser/ast" }
|
||||
# rustpython-format = { path = "../RustPython-parser/format" }
|
||||
|
||||
ahash = "0.8.11"
|
||||
ascii = "1.0"
|
||||
atty = "0.2.14"
|
||||
bitflags = "2.4.1"
|
||||
bstr = "0.2.17"
|
||||
cfg-if = "1.0"
|
||||
chrono = "0.4.37"
|
||||
crossbeam-utils = "0.8.19"
|
||||
flame = "0.2.2"
|
||||
glob = "0.3"
|
||||
hex = "0.4.3"
|
||||
indexmap = { version = "2.2.6", features = ["std"] }
|
||||
insta = "1.38.0"
|
||||
itertools = "0.11.0"
|
||||
is-macro = "0.3.0"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.16"
|
||||
nix = { version = "0.27", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
|
||||
malachite-bigint = "0.2.0"
|
||||
malachite-q = "0.4.4"
|
||||
malachite-base = "0.4.4"
|
||||
memchr = "2.7.2"
|
||||
num-complex = "0.4.0"
|
||||
num-integer = "0.1.44"
|
||||
num-traits = "0.2"
|
||||
num_enum = "0.7"
|
||||
once_cell = "1.19.0"
|
||||
parking_lot = "0.12.1"
|
||||
paste = "1.0.7"
|
||||
rand = "0.8.5"
|
||||
rustyline = "14.0.0"
|
||||
serde = { version = "1.0.133", default-features = false }
|
||||
schannel = "0.1.22"
|
||||
static_assertions = "1.1"
|
||||
syn = "1.0.109"
|
||||
thiserror = "1.0"
|
||||
thread_local = "1.1.4"
|
||||
unicode_names2 = "1.1.0"
|
||||
widestring = "1.1.0"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["threading", "stdlib", "zlib", "importlib"]
|
||||
capi = ["dep:rustpython-capi", "threading"]
|
||||
default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls-aws-lc", "host_env"]
|
||||
host_env = ["rustpython-vm/host_env", "rustpython-stdlib?/host_env"]
|
||||
importlib = ["rustpython-vm/importlib"]
|
||||
encodings = ["rustpython-vm/encodings"]
|
||||
stdio = ["rustpython-vm/stdio"]
|
||||
stdlib = ["rustpython-stdlib", "rustpython-pylib", "encodings"]
|
||||
flame-it = ["rustpython-vm/flame-it", "flame", "flamescope"]
|
||||
freeze-stdlib = ["rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"]
|
||||
flame-it = ["rustpython-vm/flame-it", "rustpython-stdlib/flame-it", "flame", "flamescope"]
|
||||
freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"]
|
||||
jit = ["rustpython-vm/jit"]
|
||||
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
|
||||
zlib = ["stdlib", "rustpython-stdlib/zlib"]
|
||||
bz2 = ["stdlib", "rustpython-stdlib/bz2"]
|
||||
ssl = ["rustpython-stdlib/ssl"]
|
||||
ssl-vendor = ["rustpython-stdlib/ssl-vendor"]
|
||||
sqlite = ["rustpython-stdlib/sqlite"]
|
||||
ssl = ["host_env"]
|
||||
ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"]
|
||||
ssl-rustls-aws-lc = ["ssl-rustls", "dep:rustls", "rustls/aws_lc_rs"]
|
||||
ssl-rustls-aws-lc-fips = ["ssl-rustls-aws-lc", "rustls/fips"]
|
||||
ssl-openssl = ["ssl", "rustpython-stdlib/ssl-openssl"]
|
||||
ssl-openssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-openssl-vendor"]
|
||||
tkinter = ["rustpython-stdlib/tkinter"]
|
||||
|
||||
[build-dependencies]
|
||||
winresource = "0.1"
|
||||
|
||||
[dependencies]
|
||||
rustpython-capi = { workspace = true, optional = true }
|
||||
rustpython-compiler = { workspace = true }
|
||||
rustpython-pylib = { workspace = true, optional = true }
|
||||
rustpython-stdlib = { workspace = true, optional = true }
|
||||
rustpython-vm = { workspace = true, features = ["compiler"] }
|
||||
rustpython-parser = { workspace = true }
|
||||
rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] }
|
||||
rustpython-vm = { workspace = true, features = ["compiler", "gc"] }
|
||||
|
||||
atty = { workspace = true }
|
||||
cfg-if = { workspace = true }
|
||||
log = { workspace = true }
|
||||
flame = { workspace = true, optional = true }
|
||||
|
||||
clap = "2.34"
|
||||
dirs = { package = "dirs-next", version = "2.0.0" }
|
||||
env_logger = { version = "0.9.0", default-features = false, features = ["atty", "termcolor"] }
|
||||
lexopt = "0.3"
|
||||
dirs = "6"
|
||||
env_logger = "0.11"
|
||||
flamescope = { version = "0.1.2", optional = true }
|
||||
|
||||
rustls = { workspace = true, optional = true }
|
||||
rustls-graviola = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
@@ -119,8 +58,10 @@ libc = { workspace = true }
|
||||
rustyline = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3.5", features = ["html_reports"] }
|
||||
pyo3 = { version = "0.20.2", features = ["auto-initialize"] }
|
||||
criterion = { workspace = true }
|
||||
pyo3 = { workspace = true, features = ["auto-initialize"] }
|
||||
rustpython-stdlib = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "execution"
|
||||
@@ -134,6 +75,17 @@ harness = false
|
||||
name = "rustpython"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[example]]
|
||||
name = "custom_tls_providers"
|
||||
path = "examples/custom_tls_providers.rs"
|
||||
required-features = [
|
||||
"rustls-graviola",
|
||||
"rustls/ring",
|
||||
"rustpython-pylib/freeze-stdlib",
|
||||
"rustpython-stdlib/ssl-rustls",
|
||||
"rustpython-vm/freeze-stdlib",
|
||||
]
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
@@ -142,6 +94,21 @@ opt-level = 3
|
||||
# https://github.com/rust-lang/rust/issues/92869
|
||||
# lto = "thin"
|
||||
|
||||
# Some crates don't change as much but benefit more from
|
||||
# more expensive optimization passes, so we selectively
|
||||
# decrease codegen-units in some cases.
|
||||
[profile.release.package.rustpython-doc]
|
||||
codegen-units = 1
|
||||
|
||||
[profile.release.package.rustpython-literal]
|
||||
codegen-units = 1
|
||||
|
||||
[profile.release.package.rustpython-common]
|
||||
codegen-units = 1
|
||||
|
||||
[profile.release.package.rustpython-wtf8]
|
||||
codegen-units = 1
|
||||
|
||||
[profile.bench]
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
@@ -151,15 +118,274 @@ opt-level = 3
|
||||
lto = "thin"
|
||||
|
||||
[patch.crates-io]
|
||||
parking_lot_core = { git = "https://github.com/youknowone/parking_lot", branch = "rustpython" }
|
||||
# REDOX START, Uncomment when you want to compile/check with redoxer
|
||||
# REDOX END
|
||||
|
||||
# Used only on Windows to build the vcpkg dependencies
|
||||
[package.metadata.vcpkg]
|
||||
git = "https://github.com/microsoft/vcpkg"
|
||||
# The revision of the vcpkg repository to use
|
||||
# https://github.com/microsoft/vcpkg/tags
|
||||
rev = "2024.02.14"
|
||||
[package.metadata.packager]
|
||||
product-name = "RustPython"
|
||||
identifier = "com.rustpython.rustpython"
|
||||
description = "An open source Python 3 interpreter written in Rust"
|
||||
homepage = "https://rustpython.github.io/"
|
||||
license_file = "LICENSE"
|
||||
authors = ["RustPython Team"]
|
||||
publisher = "RustPython Team"
|
||||
resources = ["LICENSE", "README.md", "Lib"]
|
||||
icons = ["32x32.png"]
|
||||
|
||||
[package.metadata.vcpkg.target]
|
||||
x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md", dev-dependencies = ["openssl" ] }
|
||||
[package.metadata.packager.nsis]
|
||||
installer_mode = "both"
|
||||
template = "installer-config/installer.nsi"
|
||||
|
||||
[package.metadata.packager.wix]
|
||||
template = "installer-config/installer.wxs"
|
||||
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
".",
|
||||
"crates/*",
|
||||
]
|
||||
exclude = ["pymath"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.5.0"
|
||||
authors = ["RustPython Team"]
|
||||
edition = "2024"
|
||||
rust-version = "1.95.0"
|
||||
repository = "https://github.com/RustPython/RustPython"
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
rustpython-capi = { path = "crates/capi", version = "0.5.0" }
|
||||
rustpython-compiler-core = { path = "crates/compiler-core", version = "0.5.0" }
|
||||
rustpython-compiler = { path = "crates/compiler", version = "0.5.0" }
|
||||
rustpython-codegen = { path = "crates/codegen", version = "0.5.0" }
|
||||
rustpython-common = { path = "crates/common", version = "0.5.0" }
|
||||
rustpython-host_env = { path = "crates/host_env", version = "0.5.0" }
|
||||
rustpython-derive = { path = "crates/derive", version = "0.5.0" }
|
||||
rustpython-derive-impl = { path = "crates/derive-impl", version = "0.5.0" }
|
||||
rustpython-jit = { path = "crates/jit", version = "0.5.0" }
|
||||
rustpython-literal = { path = "crates/literal", version = "0.5.0" }
|
||||
rustpython-vm = { path = "crates/vm", default-features = false, version = "0.5.0" }
|
||||
rustpython-pylib = { path = "crates/pylib", version = "0.5.0" }
|
||||
rustpython-stdlib = { path = "crates/stdlib", default-features = false, version = "0.5.0" }
|
||||
rustpython-sre_engine = { path = "crates/sre_engine", version = "0.5.0" }
|
||||
rustpython-wtf8 = { path = "crates/wtf8", version = "0.5.0" }
|
||||
rustpython-doc = { path = "crates/doc", version = "0.5.0" }
|
||||
|
||||
# Use RustPython-packaged Ruff crates from the published fork while keeping
|
||||
# existing crate names in the codebase.
|
||||
ruff_python_parser = { package = "rustpython-ruff_python_parser", version = "0.15.8" }
|
||||
ruff_python_ast = { package = "rustpython-ruff_python_ast", version = "0.15.8" }
|
||||
ruff_text_size = { package = "rustpython-ruff_text_size", version = "0.15.8" }
|
||||
ruff_source_file = { package = "rustpython-ruff_source_file", version = "0.15.8" }
|
||||
# To update ruff crates, comment out the above lines and uncomment the following lines to pull directly from the Ruff repository at the specified commit hash.
|
||||
# Ruff tag 0.15.8 is based on commit c2a8815842f9dc5d24ec19385eae0f1a7188b0d9
|
||||
# at the time of this capture. We use the commit hash to ensure reproducible builds.
|
||||
# ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
# ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
# ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
# ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
|
||||
der = { version = "0.8", features = ["alloc", "oid", "pem", "zeroize"] }
|
||||
phf = { version = "0.13.1", default-features = false, features = ["macros"]}
|
||||
adler32 = "1.2.0"
|
||||
approx = "0.5.1"
|
||||
ascii = "1.1"
|
||||
base64 = "0.22"
|
||||
blake2 = "0.10.4"
|
||||
bitflags = "2.11.0"
|
||||
bitflagset = "0.0.3"
|
||||
bstr = "1"
|
||||
bzip2 = "0.6"
|
||||
chrono = { version = "0.4.44", default-features = false, features = ["clock", "std"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
constant_time_eq = "0.4"
|
||||
cranelift = "0.131.2"
|
||||
cranelift-jit = "0.131.2"
|
||||
cranelift-module = "0.131.0"
|
||||
crc32fast = "1.3.2"
|
||||
criterion = { version = "0.8", features = ["html_reports"] }
|
||||
crossbeam-utils = "0.8.21"
|
||||
csv-core = "0.1.11"
|
||||
digest = "0.10.7"
|
||||
dns-lookup = "3.0"
|
||||
dyn-clone = "1.0.10"
|
||||
exitcode = "1.1.2"
|
||||
flame = "0.2.2"
|
||||
flamer = "0.5"
|
||||
flate2 = { version = "1.1.9", default-features = false }
|
||||
# Bump only when the openssl crate bumps it
|
||||
foreign-types-shared = "0.1"
|
||||
gethostname = "1.0.2"
|
||||
getrandom = { version = "0.3", features = ["std"] }
|
||||
glob = "0.3"
|
||||
half = "2"
|
||||
hex = "0.4.3"
|
||||
hexf-parse = "0.2.1"
|
||||
hmac = "0.12"
|
||||
indexmap = { version = "2.14.0", features = ["std"] }
|
||||
insta = "1.47"
|
||||
itertools = "0.14.0"
|
||||
is-macro = "0.3.7"
|
||||
js-sys = "0.3"
|
||||
junction = "1.4.2"
|
||||
lexical-parse-float = "1.0.6"
|
||||
libc = "0.2.186"
|
||||
libffi = "5"
|
||||
libloading = "0.9"
|
||||
liblzma = "0.4"
|
||||
liblzma-sys = "0.4"
|
||||
libsqlite3-sys = "0.37"
|
||||
libz-rs-sys = "0.6"
|
||||
lock_api = "0.4"
|
||||
log = "0.4.29"
|
||||
lz4_flex = "0.13"
|
||||
nix = { version = "0.31", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
|
||||
mac_address = "1.1.3"
|
||||
malachite-bigint = "0.9.1"
|
||||
malachite-q = "0.9.1"
|
||||
malachite-base = "0.9.1"
|
||||
md-5 = "0.10.1"
|
||||
memchr = "2.8.0"
|
||||
memmap2 = "0.9.10"
|
||||
mt19937 = "<=3.2" # upgrade it once rand is upgraded
|
||||
num-complex = "0.4.6"
|
||||
num-integer = "0.1.46"
|
||||
num-traits = "0.2"
|
||||
num_cpus = "1.17.0"
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
oid-registry = "0.8"
|
||||
openssl = "0.10.80"
|
||||
openssl-sys = "0.9.110"
|
||||
openssl-probe = "0.2.1"
|
||||
optional = "0.5"
|
||||
parking_lot = "0.12.3"
|
||||
paste = "1.0.15"
|
||||
pbkdf2 = "0.12"
|
||||
pem-rfc7468 = "1.0"
|
||||
pkcs8 = "0.11"
|
||||
proc-macro2 = "1.0.105"
|
||||
psm = "0.1"
|
||||
pymath = { version = "0.2.0", features = ["mul_add", "malachite-bigint", "complex"] }
|
||||
pyo3 = "0.28"
|
||||
quote = "1.0.45"
|
||||
radium = "1.1.1"
|
||||
rand = "0.9"
|
||||
rand_core = { version = "0.9", features = ["os_rng"] }
|
||||
rapidhash = "4.4.1"
|
||||
result-like = "0.5.0"
|
||||
rustix = { version = "1.1", features = ["event", "param", "system"] }
|
||||
rustls = { version = "0.23.39", default-features = false }
|
||||
rustls-graviola = "0.3"
|
||||
rustls-native-certs = "0.8"
|
||||
rustls-pemfile = "2.2"
|
||||
rustls-platform-verifier = "0.7"
|
||||
rustyline = "18"
|
||||
serde = { package = "serde_core", version = "1.0.225", default-features = false, features = ["alloc"] }
|
||||
schannel = "0.1.29"
|
||||
scopeguard = "1"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
sha-1 = "0.10.0"
|
||||
sha2 = "0.10.2"
|
||||
sha3 = "0.10.1"
|
||||
siphasher = "1"
|
||||
socket2 = "0.6.3"
|
||||
static_assertions = "1.1"
|
||||
strum = "0.28"
|
||||
strum_macros = "0.28"
|
||||
syn = "2"
|
||||
syn-ext = "0.5.0"
|
||||
system-configuration = "0.7.0"
|
||||
tcl-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0" }
|
||||
textwrap = { version = "0.16.2", default-features = false }
|
||||
termios = "0.3.3"
|
||||
thiserror = "2.0"
|
||||
timsort = "0.1.2"
|
||||
tk-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0" }
|
||||
icu_casemap = "2"
|
||||
icu_locale = "2"
|
||||
icu_properties = "2"
|
||||
icu_normalizer = "2"
|
||||
uuid = "1.23.1"
|
||||
ucd = "0.1.1"
|
||||
unic-ucd-age = "0.9.0"
|
||||
unicode_names2 = "2.0.0"
|
||||
widestring = "1.2.0"
|
||||
windows-sys = "0.61.2"
|
||||
wasm-bindgen = "0.2.106"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = "0.3"
|
||||
webpki-roots = "1.0"
|
||||
which = "8"
|
||||
x509-cert = "0.2.5"
|
||||
x509-parser = "0.18"
|
||||
xml = "1.2"
|
||||
writeable = "0.6"
|
||||
|
||||
# Lints
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_code = "allow"
|
||||
unsafe_op_in_unsafe_fn = "deny"
|
||||
elided_lifetimes_in_paths = "warn"
|
||||
unreachable_pub = "warn"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
correctness = { level = "warn", priority = -2 }
|
||||
suspicious = { level = "warn", priority = -2 }
|
||||
perf = { level = "warn", priority = -2 }
|
||||
style = { level = "warn", priority = -2 }
|
||||
complexity = { level = "warn", priority = -2 }
|
||||
# pedantic = { level = "warn", priority = -2 } # TODO: Enable this
|
||||
|
||||
missing_errors_doc = "allow" # Too many errors. No auto-fix available
|
||||
missing_panics_doc = "allow" # Too many errors. No auto-fix available
|
||||
match_same_arms = "allow" # Not always more readable
|
||||
if_not_else = "allow" # Not always more readable
|
||||
single_match_else = "allow"
|
||||
similar_names = "allow"
|
||||
|
||||
# restriction lints
|
||||
alloc_instead_of_core = "warn"
|
||||
cfg_not_test = "warn"
|
||||
redundant_test_prefix = "warn"
|
||||
std_instead_of_alloc = "warn"
|
||||
std_instead_of_core = "warn"
|
||||
tests_outside_test_module = "warn"
|
||||
|
||||
# nursery lints to enforce gradually
|
||||
debug_assert_with_mut_call = "warn"
|
||||
derive_partial_eq_without_eq = "warn"
|
||||
imprecise_flops = "warn"
|
||||
or_fun_call = "warn"
|
||||
redundant_clone = "warn"
|
||||
search_is_some = "warn"
|
||||
single_option_map = "warn"
|
||||
trait_duplication_in_bounds = "warn"
|
||||
unused_peekable = "warn"
|
||||
unused_rounding = "warn"
|
||||
use_self = "warn"
|
||||
useless_let_if_seq = "warn"
|
||||
|
||||
# pedantic lints to enforce gradually
|
||||
cloned_instead_of_copied = "warn"
|
||||
collapsible_else_if = "warn"
|
||||
comparison_chain = "warn"
|
||||
explicit_into_iter_loop = "warn"
|
||||
explicit_iter_loop = "warn"
|
||||
filter_map_next = "warn"
|
||||
flat_map_option = "warn"
|
||||
format_collect = "warn"
|
||||
from_iter_instead_of_collect = "warn"
|
||||
inconsistent_struct_constructor = "warn"
|
||||
inefficient_to_string = "warn"
|
||||
manual_is_variant_and = "warn"
|
||||
map_unwrap_or = "warn"
|
||||
must_use_candidate = "warn"
|
||||
redundant_else = "warn"
|
||||
uninlined_format_args = "warn"
|
||||
unnecessary_wraps = "warn"
|
||||
unnested_or_patterns = "warn"
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 RustPython Team
|
||||
Copyright (c) 2026 RustPython Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
6
Lib/__future__.py
vendored
6
Lib/__future__.py
vendored
@@ -33,7 +33,7 @@ in releases at or after that, modules no longer need
|
||||
to use the feature in question, but may continue to use such imports.
|
||||
|
||||
MandatoryRelease may also be None, meaning that a planned feature got
|
||||
dropped.
|
||||
dropped or that the release version is undetermined.
|
||||
|
||||
Instances of class _Feature have two corresponding methods,
|
||||
.getOptionalRelease() and .getMandatoryRelease().
|
||||
@@ -96,7 +96,7 @@ class _Feature:
|
||||
"""Return release in which this feature will become mandatory.
|
||||
|
||||
This is a 5-tuple, of the same form as sys.version_info, or, if
|
||||
the feature was dropped, is None.
|
||||
the feature was dropped, or the release date is undetermined, is None.
|
||||
"""
|
||||
return self.mandatory
|
||||
|
||||
@@ -143,5 +143,5 @@ generator_stop = _Feature((3, 5, 0, "beta", 1),
|
||||
CO_FUTURE_GENERATOR_STOP)
|
||||
|
||||
annotations = _Feature((3, 7, 0, "beta", 1),
|
||||
(3, 11, 0, "alpha", 0),
|
||||
None,
|
||||
CO_FUTURE_ANNOTATIONS)
|
||||
|
||||
108
Lib/_aix_support.py
vendored
Normal file
108
Lib/_aix_support.py
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Shared AIX support functions."""
|
||||
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
|
||||
# Taken from _osx_support _read_output function
|
||||
def _read_cmd_output(commandstring, capture_stderr=False):
|
||||
"""Output from successful command execution or None"""
|
||||
# Similar to os.popen(commandstring, "r").read(),
|
||||
# but without actually using os.popen because that
|
||||
# function is not usable during python bootstrap.
|
||||
import os
|
||||
import contextlib
|
||||
fp = open("/tmp/_aix_support.%s"%(
|
||||
os.getpid(),), "w+b")
|
||||
|
||||
with contextlib.closing(fp) as fp:
|
||||
if capture_stderr:
|
||||
cmd = "%s >'%s' 2>&1" % (commandstring, fp.name)
|
||||
else:
|
||||
cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name)
|
||||
return fp.read() if not os.system(cmd) else None
|
||||
|
||||
|
||||
def _aix_tag(vrtl, bd):
|
||||
# type: (List[int], int) -> str
|
||||
# Infer the ABI bitwidth from maxsize (assuming 64 bit as the default)
|
||||
_sz = 32 if sys.maxsize == (2**31-1) else 64
|
||||
_bd = bd if bd != 0 else 9988
|
||||
# vrtl[version, release, technology_level]
|
||||
return "aix-{:1x}{:1d}{:02d}-{:04d}-{}".format(vrtl[0], vrtl[1], vrtl[2], _bd, _sz)
|
||||
|
||||
|
||||
# extract version, release and technology level from a VRMF string
|
||||
def _aix_vrtl(vrmf):
|
||||
# type: (str) -> List[int]
|
||||
v, r, tl = vrmf.split(".")[:3]
|
||||
return [int(v[-1]), int(r), int(tl)]
|
||||
|
||||
|
||||
def _aix_bos_rte():
|
||||
# type: () -> Tuple[str, int]
|
||||
"""
|
||||
Return a Tuple[str, int] e.g., ['7.1.4.34', 1806]
|
||||
The fileset bos.rte represents the current AIX run-time level. It's VRMF and
|
||||
builddate reflect the current ABI levels of the runtime environment.
|
||||
If no builddate is found give a value that will satisfy pep425 related queries
|
||||
"""
|
||||
# All AIX systems to have lslpp installed in this location
|
||||
# subprocess may not be available during python bootstrap
|
||||
try:
|
||||
import subprocess
|
||||
out = subprocess.check_output(["/usr/bin/lslpp", "-Lqc", "bos.rte"])
|
||||
except ImportError:
|
||||
out = _read_cmd_output("/usr/bin/lslpp -Lqc bos.rte")
|
||||
out = out.decode("utf-8")
|
||||
out = out.strip().split(":") # type: ignore
|
||||
_bd = int(out[-1]) if out[-1] != '' else 9988
|
||||
return (str(out[2]), _bd)
|
||||
|
||||
|
||||
def aix_platform():
|
||||
# type: () -> str
|
||||
"""
|
||||
AIX filesets are identified by four decimal values: V.R.M.F.
|
||||
V (version) and R (release) can be retrieved using ``uname``
|
||||
Since 2007, starting with AIX 5.3 TL7, the M value has been
|
||||
included with the fileset bos.rte and represents the Technology
|
||||
Level (TL) of AIX. The F (Fix) value also increases, but is not
|
||||
relevant for comparing releases and binary compatibility.
|
||||
For binary compatibility the so-called builddate is needed.
|
||||
Again, the builddate of an AIX release is associated with bos.rte.
|
||||
AIX ABI compatibility is described as guaranteed at: https://www.ibm.com/\
|
||||
support/knowledgecenter/en/ssw_aix_72/install/binary_compatability.html
|
||||
|
||||
For pep425 purposes the AIX platform tag becomes:
|
||||
"aix-{:1x}{:1d}{:02d}-{:04d}-{}".format(v, r, tl, builddate, bitsize)
|
||||
e.g., "aix-6107-1415-32" for AIX 6.1 TL7 bd 1415, 32-bit
|
||||
and, "aix-6107-1415-64" for AIX 6.1 TL7 bd 1415, 64-bit
|
||||
"""
|
||||
vrmf, bd = _aix_bos_rte()
|
||||
return _aix_tag(_aix_vrtl(vrmf), bd)
|
||||
|
||||
|
||||
# extract vrtl from the BUILD_GNU_TYPE as an int
|
||||
def _aix_bgt():
|
||||
# type: () -> List[int]
|
||||
gnu_type = sysconfig.get_config_var("BUILD_GNU_TYPE")
|
||||
if not gnu_type:
|
||||
raise ValueError("BUILD_GNU_TYPE is not defined")
|
||||
return _aix_vrtl(vrmf=gnu_type)
|
||||
|
||||
|
||||
def aix_buildtag():
|
||||
# type: () -> str
|
||||
"""
|
||||
Return the platform_tag of the system Python was built on.
|
||||
"""
|
||||
# AIX_BUILDDATE is defined by configure with:
|
||||
# lslpp -Lcq bos.rte | awk -F: '{ print $NF }'
|
||||
build_date = sysconfig.get_config_var("AIX_BUILDDATE")
|
||||
try:
|
||||
build_date = int(build_date)
|
||||
except (ValueError, TypeError):
|
||||
raise ValueError(f"AIX_BUILDDATE is not defined or invalid: "
|
||||
f"{build_date!r}")
|
||||
return _aix_tag(_aix_bgt(), build_date)
|
||||
192
Lib/_android_support.py
vendored
Normal file
192
Lib/_android_support.py
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
import io
|
||||
import sys
|
||||
from threading import RLock
|
||||
from time import sleep, time
|
||||
|
||||
# The maximum length of a log message in bytes, including the level marker and
|
||||
# tag, is defined as LOGGER_ENTRY_MAX_PAYLOAD at
|
||||
# https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:system/logging/liblog/include/log/log.h;l=71.
|
||||
# Messages longer than this will be truncated by logcat. This limit has already
|
||||
# been reduced at least once in the history of Android (from 4076 to 4068 between
|
||||
# API level 23 and 26), so leave some headroom.
|
||||
MAX_BYTES_PER_WRITE = 4000
|
||||
|
||||
# UTF-8 uses a maximum of 4 bytes per character, so limiting text writes to this
|
||||
# size ensures that we can always avoid exceeding MAX_BYTES_PER_WRITE.
|
||||
# However, if the actual number of bytes per character is smaller than that,
|
||||
# then we may still join multiple consecutive text writes into binary
|
||||
# writes containing a larger number of characters.
|
||||
MAX_CHARS_PER_WRITE = MAX_BYTES_PER_WRITE // 4
|
||||
|
||||
|
||||
# When embedded in an app on current versions of Android, there's no easy way to
|
||||
# monitor the C-level stdout and stderr. The testbed comes with a .c file to
|
||||
# redirect them to the system log using a pipe, but that wouldn't be convenient
|
||||
# or appropriate for all apps. So we redirect at the Python level instead.
|
||||
def init_streams(android_log_write, stdout_prio, stderr_prio):
|
||||
if sys.executable:
|
||||
return # Not embedded in an app.
|
||||
|
||||
global logcat
|
||||
logcat = Logcat(android_log_write)
|
||||
sys.stdout = TextLogStream(stdout_prio, "python.stdout", sys.stdout)
|
||||
sys.stderr = TextLogStream(stderr_prio, "python.stderr", sys.stderr)
|
||||
|
||||
|
||||
class TextLogStream(io.TextIOWrapper):
|
||||
def __init__(self, prio, tag, original=None, **kwargs):
|
||||
# Respect the -u option.
|
||||
if original:
|
||||
kwargs.setdefault("write_through", original.write_through)
|
||||
fileno = original.fileno()
|
||||
else:
|
||||
fileno = None
|
||||
|
||||
# The default is surrogateescape for stdout and backslashreplace for
|
||||
# stderr, but in the context of an Android log, readability is more
|
||||
# important than reversibility.
|
||||
kwargs.setdefault("encoding", "UTF-8")
|
||||
kwargs.setdefault("errors", "backslashreplace")
|
||||
|
||||
super().__init__(BinaryLogStream(prio, tag, fileno), **kwargs)
|
||||
self._lock = RLock()
|
||||
self._pending_bytes = []
|
||||
self._pending_bytes_count = 0
|
||||
|
||||
def __repr__(self):
|
||||
return f"<TextLogStream {self.buffer.tag!r}>"
|
||||
|
||||
def write(self, s):
|
||||
if not isinstance(s, str):
|
||||
raise TypeError(
|
||||
f"write() argument must be str, not {type(s).__name__}")
|
||||
|
||||
# In case `s` is a str subclass that writes itself to stdout or stderr
|
||||
# when we call its methods, convert it to an actual str.
|
||||
s = str.__str__(s)
|
||||
|
||||
# We want to emit one log message per line wherever possible, so split
|
||||
# the string into lines first. Note that "".splitlines() == [], so
|
||||
# nothing will be logged for an empty string.
|
||||
with self._lock:
|
||||
for line in s.splitlines(keepends=True):
|
||||
while line:
|
||||
chunk = line[:MAX_CHARS_PER_WRITE]
|
||||
line = line[MAX_CHARS_PER_WRITE:]
|
||||
self._write_chunk(chunk)
|
||||
|
||||
return len(s)
|
||||
|
||||
# The size and behavior of TextIOWrapper's buffer is not part of its public
|
||||
# API, so we handle buffering ourselves to avoid truncation.
|
||||
def _write_chunk(self, s):
|
||||
b = s.encode(self.encoding, self.errors)
|
||||
if self._pending_bytes_count + len(b) > MAX_BYTES_PER_WRITE:
|
||||
self.flush()
|
||||
|
||||
self._pending_bytes.append(b)
|
||||
self._pending_bytes_count += len(b)
|
||||
if (
|
||||
self.write_through
|
||||
or b.endswith(b"\n")
|
||||
or self._pending_bytes_count > MAX_BYTES_PER_WRITE
|
||||
):
|
||||
self.flush()
|
||||
|
||||
def flush(self):
|
||||
with self._lock:
|
||||
self.buffer.write(b"".join(self._pending_bytes))
|
||||
self._pending_bytes.clear()
|
||||
self._pending_bytes_count = 0
|
||||
|
||||
# Since this is a line-based logging system, line buffering cannot be turned
|
||||
# off, i.e. a newline always causes a flush.
|
||||
@property
|
||||
def line_buffering(self):
|
||||
return True
|
||||
|
||||
|
||||
class BinaryLogStream(io.RawIOBase):
|
||||
def __init__(self, prio, tag, fileno=None):
|
||||
self.prio = prio
|
||||
self.tag = tag
|
||||
self._fileno = fileno
|
||||
|
||||
def __repr__(self):
|
||||
return f"<BinaryLogStream {self.tag!r}>"
|
||||
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
def write(self, b):
|
||||
if type(b) is not bytes:
|
||||
try:
|
||||
b = bytes(memoryview(b))
|
||||
except TypeError:
|
||||
raise TypeError(
|
||||
f"write() argument must be bytes-like, not {type(b).__name__}"
|
||||
) from None
|
||||
|
||||
# Writing an empty string to the stream should have no effect.
|
||||
if b:
|
||||
logcat.write(self.prio, self.tag, b)
|
||||
return len(b)
|
||||
|
||||
# This is needed by the test suite --timeout option, which uses faulthandler.
|
||||
def fileno(self):
|
||||
if self._fileno is None:
|
||||
raise io.UnsupportedOperation("fileno")
|
||||
return self._fileno
|
||||
|
||||
|
||||
# When a large volume of data is written to logcat at once, e.g. when a test
|
||||
# module fails in --verbose3 mode, there's a risk of overflowing logcat's own
|
||||
# buffer and losing messages. We avoid this by imposing a rate limit using the
|
||||
# token bucket algorithm, based on a conservative estimate of how fast `adb
|
||||
# logcat` can consume data.
|
||||
MAX_BYTES_PER_SECOND = 1024 * 1024
|
||||
|
||||
# The logcat buffer size of a device can be determined by running `logcat -g`.
|
||||
# We set the token bucket size to half of the buffer size of our current minimum
|
||||
# API level, because other things on the system will be producing messages as
|
||||
# well.
|
||||
BUCKET_SIZE = 128 * 1024
|
||||
|
||||
# https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:system/logging/liblog/include/log/log_read.h;l=39
|
||||
PER_MESSAGE_OVERHEAD = 28
|
||||
|
||||
|
||||
class Logcat:
|
||||
def __init__(self, android_log_write):
|
||||
self.android_log_write = android_log_write
|
||||
self._lock = RLock()
|
||||
self._bucket_level = 0
|
||||
self._prev_write_time = time()
|
||||
|
||||
def write(self, prio, tag, message):
|
||||
# Encode null bytes using "modified UTF-8" to avoid them truncating the
|
||||
# message.
|
||||
message = message.replace(b"\x00", b"\xc0\x80")
|
||||
|
||||
# On API level 30 and higher, Logcat will strip any number of leading
|
||||
# newlines. This is visible in all `logcat` modes, even --binary. Work
|
||||
# around this by adding a leading space, which shouldn't make any
|
||||
# difference to the log's usability.
|
||||
if message.startswith(b"\n"):
|
||||
message = b" " + message
|
||||
|
||||
with self._lock:
|
||||
now = time()
|
||||
self._bucket_level += (
|
||||
(now - self._prev_write_time) * MAX_BYTES_PER_SECOND)
|
||||
|
||||
# If the bucket level is still below zero, the clock must have gone
|
||||
# backwards, so reset it to zero and continue.
|
||||
self._bucket_level = max(0, min(self._bucket_level, BUCKET_SIZE))
|
||||
self._prev_write_time = now
|
||||
|
||||
self._bucket_level -= PER_MESSAGE_OVERHEAD + len(tag) + len(message)
|
||||
if self._bucket_level < 0:
|
||||
sleep(-self._bucket_level / MAX_BYTES_PER_SECOND)
|
||||
|
||||
self.android_log_write(prio, tag, message)
|
||||
66
Lib/_apple_support.py
vendored
Normal file
66
Lib/_apple_support.py
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
import io
|
||||
import sys
|
||||
|
||||
|
||||
def init_streams(log_write, stdout_level, stderr_level):
|
||||
# Redirect stdout and stderr to the Apple system log. This method is
|
||||
# invoked by init_apple_streams() (initconfig.c) if config->use_system_logger
|
||||
# is enabled.
|
||||
sys.stdout = SystemLog(log_write, stdout_level, errors=sys.stderr.errors)
|
||||
sys.stderr = SystemLog(log_write, stderr_level, errors=sys.stderr.errors)
|
||||
|
||||
|
||||
class SystemLog(io.TextIOWrapper):
|
||||
def __init__(self, log_write, level, **kwargs):
|
||||
kwargs.setdefault("encoding", "UTF-8")
|
||||
kwargs.setdefault("line_buffering", True)
|
||||
super().__init__(LogStream(log_write, level), **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SystemLog (level {self.buffer.level})>"
|
||||
|
||||
def write(self, s):
|
||||
if not isinstance(s, str):
|
||||
raise TypeError(
|
||||
f"write() argument must be str, not {type(s).__name__}")
|
||||
|
||||
# In case `s` is a str subclass that writes itself to stdout or stderr
|
||||
# when we call its methods, convert it to an actual str.
|
||||
s = str.__str__(s)
|
||||
|
||||
# We want to emit one log message per line, so split
|
||||
# the string before sending it to the superclass.
|
||||
for line in s.splitlines(keepends=True):
|
||||
super().write(line)
|
||||
|
||||
return len(s)
|
||||
|
||||
|
||||
class LogStream(io.RawIOBase):
|
||||
def __init__(self, log_write, level):
|
||||
self.log_write = log_write
|
||||
self.level = level
|
||||
|
||||
def __repr__(self):
|
||||
return f"<LogStream (level {self.level!r})>"
|
||||
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
def write(self, b):
|
||||
if type(b) is not bytes:
|
||||
try:
|
||||
b = bytes(memoryview(b))
|
||||
except TypeError:
|
||||
raise TypeError(
|
||||
f"write() argument must be bytes-like, not {type(b).__name__}"
|
||||
) from None
|
||||
|
||||
# Writing an empty string to the stream should have no effect.
|
||||
if b:
|
||||
# Encode null bytes using "modified UTF-8" to avoid truncating the
|
||||
# message. This should not affect the return value, as the caller
|
||||
# may be expecting it to match the length of the input.
|
||||
self.log_write(self.level, b.replace(b"\x00", b"\xc0\x80"))
|
||||
|
||||
return len(b)
|
||||
1161
Lib/_ast_unparse.py
vendored
Normal file
1161
Lib/_ast_unparse.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
44
Lib/_collections_abc.py
vendored
44
Lib/_collections_abc.py
vendored
@@ -85,6 +85,10 @@ dict_values = type({}.values())
|
||||
dict_items = type({}.items())
|
||||
## misc ##
|
||||
mappingproxy = type(type.__dict__)
|
||||
def _get_framelocalsproxy():
|
||||
return type(sys._getframe().f_locals)
|
||||
framelocalsproxy = _get_framelocalsproxy()
|
||||
del _get_framelocalsproxy
|
||||
generator = type((lambda: (yield))())
|
||||
## coroutine ##
|
||||
async def _coro(): pass
|
||||
@@ -481,9 +485,10 @@ class _CallableGenericAlias(GenericAlias):
|
||||
def __repr__(self):
|
||||
if len(self.__args__) == 2 and _is_param_expr(self.__args__[0]):
|
||||
return super().__repr__()
|
||||
from annotationlib import type_repr
|
||||
return (f'collections.abc.Callable'
|
||||
f'[[{", ".join([_type_repr(a) for a in self.__args__[:-1]])}], '
|
||||
f'{_type_repr(self.__args__[-1])}]')
|
||||
f'[[{", ".join([type_repr(a) for a in self.__args__[:-1]])}], '
|
||||
f'{type_repr(self.__args__[-1])}]')
|
||||
|
||||
def __reduce__(self):
|
||||
args = self.__args__
|
||||
@@ -520,23 +525,6 @@ def _is_param_expr(obj):
|
||||
names = ('ParamSpec', '_ConcatenateGenericAlias')
|
||||
return obj.__module__ == 'typing' and any(obj.__name__ == name for name in names)
|
||||
|
||||
def _type_repr(obj):
|
||||
"""Return the repr() of an object, special-casing types (internal helper).
|
||||
|
||||
Copied from :mod:`typing` since collections.abc
|
||||
shouldn't depend on that module.
|
||||
(Keep this roughly in sync with the typing version.)
|
||||
"""
|
||||
if isinstance(obj, type):
|
||||
if obj.__module__ == 'builtins':
|
||||
return obj.__qualname__
|
||||
return f'{obj.__module__}.{obj.__qualname__}'
|
||||
if obj is Ellipsis:
|
||||
return '...'
|
||||
if isinstance(obj, FunctionType):
|
||||
return obj.__name__
|
||||
return repr(obj)
|
||||
|
||||
|
||||
class Callable(metaclass=ABCMeta):
|
||||
|
||||
@@ -836,6 +824,7 @@ class Mapping(Collection):
|
||||
__reversed__ = None
|
||||
|
||||
Mapping.register(mappingproxy)
|
||||
Mapping.register(framelocalsproxy)
|
||||
|
||||
|
||||
class MappingView(Sized):
|
||||
@@ -973,7 +962,7 @@ class MutableMapping(Mapping):
|
||||
|
||||
def update(self, other=(), /, **kwds):
|
||||
''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F.
|
||||
If E present and has a .keys() method, does: for k in E: D[k] = E[k]
|
||||
If E present and has a .keys() method, does: for k in E.keys(): D[k] = E[k]
|
||||
If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
|
||||
In either case, this is followed by: for k, v in F.items(): D[k] = v
|
||||
'''
|
||||
@@ -1068,6 +1057,7 @@ class Sequence(Reversible, Collection):
|
||||
|
||||
Sequence.register(tuple)
|
||||
Sequence.register(str)
|
||||
Sequence.register(bytes)
|
||||
Sequence.register(range)
|
||||
Sequence.register(memoryview)
|
||||
|
||||
@@ -1078,7 +1068,7 @@ class _DeprecateByteStringMeta(ABCMeta):
|
||||
|
||||
warnings._deprecated(
|
||||
"collections.abc.ByteString",
|
||||
remove=(3, 14),
|
||||
remove=(3, 17),
|
||||
)
|
||||
return super().__new__(cls, name, bases, namespace, **kwargs)
|
||||
|
||||
@@ -1087,14 +1077,18 @@ class _DeprecateByteStringMeta(ABCMeta):
|
||||
|
||||
warnings._deprecated(
|
||||
"collections.abc.ByteString",
|
||||
remove=(3, 14),
|
||||
remove=(3, 17),
|
||||
)
|
||||
return super().__instancecheck__(instance)
|
||||
|
||||
class ByteString(Sequence, metaclass=_DeprecateByteStringMeta):
|
||||
"""This unifies bytes and bytearray.
|
||||
"""Deprecated ABC serving as a common supertype of ``bytes`` and ``bytearray``.
|
||||
|
||||
XXX Should add all their methods.
|
||||
This ABC is scheduled for removal in Python 3.17.
|
||||
Use ``isinstance(obj, collections.abc.Buffer)`` to test if ``obj``
|
||||
implements the buffer protocol at runtime. For use in type annotations,
|
||||
either use ``Buffer`` or a union that explicitly specifies the types your
|
||||
code supports (e.g., ``bytes | bytearray | memoryview``).
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
@@ -1170,4 +1164,4 @@ class MutableSequence(Sequence):
|
||||
|
||||
|
||||
MutableSequence.register(list)
|
||||
MutableSequence.register(bytearray) # Multiply inheriting, see ByteString
|
||||
MutableSequence.register(bytearray)
|
||||
|
||||
355
Lib/_colorize.py
vendored
Normal file
355
Lib/_colorize.py
vendored
Normal file
@@ -0,0 +1,355 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from collections.abc import Callable, Iterator, Mapping
|
||||
from dataclasses import dataclass, field, Field
|
||||
|
||||
COLORIZE = True
|
||||
|
||||
|
||||
# types
|
||||
if False:
|
||||
from typing import IO, Self, ClassVar
|
||||
_theme: Theme
|
||||
|
||||
|
||||
class ANSIColors:
|
||||
RESET = "\x1b[0m"
|
||||
|
||||
BLACK = "\x1b[30m"
|
||||
BLUE = "\x1b[34m"
|
||||
CYAN = "\x1b[36m"
|
||||
GREEN = "\x1b[32m"
|
||||
GREY = "\x1b[90m"
|
||||
MAGENTA = "\x1b[35m"
|
||||
RED = "\x1b[31m"
|
||||
WHITE = "\x1b[37m" # more like LIGHT GRAY
|
||||
YELLOW = "\x1b[33m"
|
||||
|
||||
BOLD = "\x1b[1m"
|
||||
BOLD_BLACK = "\x1b[1;30m" # DARK GRAY
|
||||
BOLD_BLUE = "\x1b[1;34m"
|
||||
BOLD_CYAN = "\x1b[1;36m"
|
||||
BOLD_GREEN = "\x1b[1;32m"
|
||||
BOLD_MAGENTA = "\x1b[1;35m"
|
||||
BOLD_RED = "\x1b[1;31m"
|
||||
BOLD_WHITE = "\x1b[1;37m" # actual WHITE
|
||||
BOLD_YELLOW = "\x1b[1;33m"
|
||||
|
||||
# intense = like bold but without being bold
|
||||
INTENSE_BLACK = "\x1b[90m"
|
||||
INTENSE_BLUE = "\x1b[94m"
|
||||
INTENSE_CYAN = "\x1b[96m"
|
||||
INTENSE_GREEN = "\x1b[92m"
|
||||
INTENSE_MAGENTA = "\x1b[95m"
|
||||
INTENSE_RED = "\x1b[91m"
|
||||
INTENSE_WHITE = "\x1b[97m"
|
||||
INTENSE_YELLOW = "\x1b[93m"
|
||||
|
||||
BACKGROUND_BLACK = "\x1b[40m"
|
||||
BACKGROUND_BLUE = "\x1b[44m"
|
||||
BACKGROUND_CYAN = "\x1b[46m"
|
||||
BACKGROUND_GREEN = "\x1b[42m"
|
||||
BACKGROUND_MAGENTA = "\x1b[45m"
|
||||
BACKGROUND_RED = "\x1b[41m"
|
||||
BACKGROUND_WHITE = "\x1b[47m"
|
||||
BACKGROUND_YELLOW = "\x1b[43m"
|
||||
|
||||
INTENSE_BACKGROUND_BLACK = "\x1b[100m"
|
||||
INTENSE_BACKGROUND_BLUE = "\x1b[104m"
|
||||
INTENSE_BACKGROUND_CYAN = "\x1b[106m"
|
||||
INTENSE_BACKGROUND_GREEN = "\x1b[102m"
|
||||
INTENSE_BACKGROUND_MAGENTA = "\x1b[105m"
|
||||
INTENSE_BACKGROUND_RED = "\x1b[101m"
|
||||
INTENSE_BACKGROUND_WHITE = "\x1b[107m"
|
||||
INTENSE_BACKGROUND_YELLOW = "\x1b[103m"
|
||||
|
||||
|
||||
ColorCodes = set()
|
||||
NoColors = ANSIColors()
|
||||
|
||||
for attr, code in ANSIColors.__dict__.items():
|
||||
if not attr.startswith("__"):
|
||||
ColorCodes.add(code)
|
||||
setattr(NoColors, attr, "")
|
||||
|
||||
|
||||
#
|
||||
# Experimental theming support (see gh-133346)
|
||||
#
|
||||
|
||||
# - Create a theme by copying an existing `Theme` with one or more sections
|
||||
# replaced, using `default_theme.copy_with()`;
|
||||
# - create a theme section by copying an existing `ThemeSection` with one or
|
||||
# more colors replaced, using for example `default_theme.syntax.copy_with()`;
|
||||
# - create a theme from scratch by instantiating a `Theme` data class with
|
||||
# the required sections (which are also dataclass instances).
|
||||
#
|
||||
# Then call `_colorize.set_theme(your_theme)` to set it.
|
||||
#
|
||||
# Put your theme configuration in $PYTHONSTARTUP for the interactive shell,
|
||||
# or sitecustomize.py in your virtual environment or Python installation for
|
||||
# other uses. Your applications can call `_colorize.set_theme()` too.
|
||||
#
|
||||
# Note that thanks to the dataclasses providing default values for all fields,
|
||||
# creating a new theme or theme section from scratch is possible without
|
||||
# specifying all keys.
|
||||
#
|
||||
# For example, here's a theme that makes punctuation and operators less prominent:
|
||||
#
|
||||
# try:
|
||||
# from _colorize import set_theme, default_theme, Syntax, ANSIColors
|
||||
# except ImportError:
|
||||
# pass
|
||||
# else:
|
||||
# theme_with_dim_operators = default_theme.copy_with(
|
||||
# syntax=Syntax(op=ANSIColors.INTENSE_BLACK),
|
||||
# )
|
||||
# set_theme(theme_with_dim_operators)
|
||||
# del set_theme, default_theme, Syntax, ANSIColors, theme_with_dim_operators
|
||||
#
|
||||
# Guarding the import ensures that your .pythonstartup file will still work in
|
||||
# Python 3.13 and older. Deleting the variables ensures they don't remain in your
|
||||
# interactive shell's global scope.
|
||||
|
||||
class ThemeSection(Mapping[str, str]):
|
||||
"""A mixin/base class for theme sections.
|
||||
|
||||
It enables dictionary access to a section, as well as implements convenience
|
||||
methods.
|
||||
"""
|
||||
|
||||
# The two types below are just that: types to inform the type checker that the
|
||||
# mixin will work in context of those fields existing
|
||||
__dataclass_fields__: ClassVar[dict[str, Field[str]]]
|
||||
_name_to_value: Callable[[str], str]
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
name_to_value = {}
|
||||
for color_name in self.__dataclass_fields__:
|
||||
name_to_value[color_name] = getattr(self, color_name)
|
||||
super().__setattr__('_name_to_value', name_to_value.__getitem__)
|
||||
|
||||
def copy_with(self, **kwargs: str) -> Self:
|
||||
color_state: dict[str, str] = {}
|
||||
for color_name in self.__dataclass_fields__:
|
||||
color_state[color_name] = getattr(self, color_name)
|
||||
color_state.update(kwargs)
|
||||
return type(self)(**color_state)
|
||||
|
||||
@classmethod
|
||||
def no_colors(cls) -> Self:
|
||||
color_state: dict[str, str] = {}
|
||||
for color_name in cls.__dataclass_fields__:
|
||||
color_state[color_name] = ""
|
||||
return cls(**color_state)
|
||||
|
||||
def __getitem__(self, key: str) -> str:
|
||||
return self._name_to_value(key)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__dataclass_fields__)
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
return iter(self.__dataclass_fields__)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class Argparse(ThemeSection):
|
||||
usage: str = ANSIColors.BOLD_BLUE
|
||||
prog: str = ANSIColors.BOLD_MAGENTA
|
||||
prog_extra: str = ANSIColors.MAGENTA
|
||||
heading: str = ANSIColors.BOLD_BLUE
|
||||
summary_long_option: str = ANSIColors.CYAN
|
||||
summary_short_option: str = ANSIColors.GREEN
|
||||
summary_label: str = ANSIColors.YELLOW
|
||||
summary_action: str = ANSIColors.GREEN
|
||||
long_option: str = ANSIColors.BOLD_CYAN
|
||||
short_option: str = ANSIColors.BOLD_GREEN
|
||||
label: str = ANSIColors.BOLD_YELLOW
|
||||
action: str = ANSIColors.BOLD_GREEN
|
||||
reset: str = ANSIColors.RESET
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Syntax(ThemeSection):
|
||||
prompt: str = ANSIColors.BOLD_MAGENTA
|
||||
keyword: str = ANSIColors.BOLD_BLUE
|
||||
keyword_constant: str = ANSIColors.BOLD_BLUE
|
||||
builtin: str = ANSIColors.CYAN
|
||||
comment: str = ANSIColors.RED
|
||||
string: str = ANSIColors.GREEN
|
||||
number: str = ANSIColors.YELLOW
|
||||
op: str = ANSIColors.RESET
|
||||
definition: str = ANSIColors.BOLD
|
||||
soft_keyword: str = ANSIColors.BOLD_BLUE
|
||||
reset: str = ANSIColors.RESET
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Traceback(ThemeSection):
|
||||
type: str = ANSIColors.BOLD_MAGENTA
|
||||
message: str = ANSIColors.MAGENTA
|
||||
filename: str = ANSIColors.MAGENTA
|
||||
line_no: str = ANSIColors.MAGENTA
|
||||
frame: str = ANSIColors.MAGENTA
|
||||
error_highlight: str = ANSIColors.BOLD_RED
|
||||
error_range: str = ANSIColors.RED
|
||||
reset: str = ANSIColors.RESET
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Unittest(ThemeSection):
|
||||
passed: str = ANSIColors.GREEN
|
||||
warn: str = ANSIColors.YELLOW
|
||||
fail: str = ANSIColors.RED
|
||||
fail_info: str = ANSIColors.BOLD_RED
|
||||
reset: str = ANSIColors.RESET
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Theme:
|
||||
"""A suite of themes for all sections of Python.
|
||||
|
||||
When adding a new one, remember to also modify `copy_with` and `no_colors`
|
||||
below.
|
||||
"""
|
||||
argparse: Argparse = field(default_factory=Argparse)
|
||||
syntax: Syntax = field(default_factory=Syntax)
|
||||
traceback: Traceback = field(default_factory=Traceback)
|
||||
unittest: Unittest = field(default_factory=Unittest)
|
||||
|
||||
def copy_with(
|
||||
self,
|
||||
*,
|
||||
argparse: Argparse | None = None,
|
||||
syntax: Syntax | None = None,
|
||||
traceback: Traceback | None = None,
|
||||
unittest: Unittest | None = None,
|
||||
) -> Self:
|
||||
"""Return a new Theme based on this instance with some sections replaced.
|
||||
|
||||
Themes are immutable to protect against accidental modifications that
|
||||
could lead to invalid terminal states.
|
||||
"""
|
||||
return type(self)(
|
||||
argparse=argparse or self.argparse,
|
||||
syntax=syntax or self.syntax,
|
||||
traceback=traceback or self.traceback,
|
||||
unittest=unittest or self.unittest,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def no_colors(cls) -> Self:
|
||||
"""Return a new Theme where colors in all sections are empty strings.
|
||||
|
||||
This allows writing user code as if colors are always used. The color
|
||||
fields will be ANSI color code strings when colorization is desired
|
||||
and possible, and empty strings otherwise.
|
||||
"""
|
||||
return cls(
|
||||
argparse=Argparse.no_colors(),
|
||||
syntax=Syntax.no_colors(),
|
||||
traceback=Traceback.no_colors(),
|
||||
unittest=Unittest.no_colors(),
|
||||
)
|
||||
|
||||
|
||||
def get_colors(
|
||||
colorize: bool = False, *, file: IO[str] | IO[bytes] | None = None
|
||||
) -> ANSIColors:
|
||||
if colorize or can_colorize(file=file):
|
||||
return ANSIColors()
|
||||
else:
|
||||
return NoColors
|
||||
|
||||
|
||||
def decolor(text: str) -> str:
|
||||
"""Remove ANSI color codes from a string."""
|
||||
for code in ColorCodes:
|
||||
text = text.replace(code, "")
|
||||
return text
|
||||
|
||||
|
||||
def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool:
|
||||
|
||||
def _safe_getenv(k: str, fallback: str | None = None) -> str | None:
|
||||
"""Exception-safe environment retrieval. See gh-128636."""
|
||||
try:
|
||||
return os.environ.get(k, fallback)
|
||||
except Exception:
|
||||
return fallback
|
||||
|
||||
if file is None:
|
||||
file = sys.stdout
|
||||
|
||||
if not sys.flags.ignore_environment:
|
||||
if _safe_getenv("PYTHON_COLORS") == "0":
|
||||
return False
|
||||
if _safe_getenv("PYTHON_COLORS") == "1":
|
||||
return True
|
||||
if _safe_getenv("NO_COLOR"):
|
||||
return False
|
||||
if not COLORIZE:
|
||||
return False
|
||||
if _safe_getenv("FORCE_COLOR"):
|
||||
return True
|
||||
if _safe_getenv("TERM") == "dumb":
|
||||
return False
|
||||
|
||||
if not hasattr(file, "fileno"):
|
||||
return False
|
||||
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import nt
|
||||
|
||||
if not nt._supports_virtual_terminal():
|
||||
return False
|
||||
except (ImportError, AttributeError):
|
||||
return False
|
||||
|
||||
try:
|
||||
return os.isatty(file.fileno())
|
||||
except OSError:
|
||||
return hasattr(file, "isatty") and file.isatty()
|
||||
|
||||
|
||||
default_theme = Theme()
|
||||
theme_no_color = default_theme.no_colors()
|
||||
|
||||
|
||||
def get_theme(
|
||||
*,
|
||||
tty_file: IO[str] | IO[bytes] | None = None,
|
||||
force_color: bool = False,
|
||||
force_no_color: bool = False,
|
||||
) -> Theme:
|
||||
"""Returns the currently set theme, potentially in a zero-color variant.
|
||||
|
||||
In cases where colorizing is not possible (see `can_colorize`), the returned
|
||||
theme contains all empty strings in all color definitions.
|
||||
See `Theme.no_colors()` for more information.
|
||||
|
||||
It is recommended not to cache the result of this function for extended
|
||||
periods of time because the user might influence theme selection by
|
||||
the interactive shell, a debugger, or application-specific code. The
|
||||
environment (including environment variable state and console configuration
|
||||
on Windows) can also change in the course of the application life cycle.
|
||||
"""
|
||||
if force_color or (not force_no_color and
|
||||
can_colorize(file=tty_file)):
|
||||
return _theme
|
||||
return theme_no_color
|
||||
|
||||
|
||||
def set_theme(t: Theme) -> None:
|
||||
global _theme
|
||||
|
||||
if not isinstance(t, Theme):
|
||||
raise ValueError(f"Expected Theme object, found {t}")
|
||||
|
||||
_theme = t
|
||||
|
||||
|
||||
set_theme(default_theme)
|
||||
2
Lib/_compat_pickle.py
vendored
2
Lib/_compat_pickle.py
vendored
@@ -22,7 +22,6 @@ IMPORT_MAPPING = {
|
||||
'tkMessageBox': 'tkinter.messagebox',
|
||||
'ScrolledText': 'tkinter.scrolledtext',
|
||||
'Tkconstants': 'tkinter.constants',
|
||||
'Tix': 'tkinter.tix',
|
||||
'ttk': 'tkinter.ttk',
|
||||
'Tkinter': 'tkinter',
|
||||
'markupbase': '_markupbase',
|
||||
@@ -257,3 +256,4 @@ PYTHON3_IMPORTERROR_EXCEPTIONS = (
|
||||
|
||||
for excname in PYTHON3_IMPORTERROR_EXCEPTIONS:
|
||||
REVERSE_NAME_MAPPING[('builtins', excname)] = ('exceptions', 'ImportError')
|
||||
del excname
|
||||
|
||||
22
Lib/_dummy_os.py
vendored
22
Lib/_dummy_os.py
vendored
@@ -5,22 +5,30 @@ A shim of the os module containing only simple path-related utilities
|
||||
try:
|
||||
from os import *
|
||||
except ImportError:
|
||||
import abc
|
||||
import abc, sys
|
||||
|
||||
def __getattr__(name):
|
||||
raise OSError("no os specific module found")
|
||||
if name in {"_path_normpath", "__path__"}:
|
||||
raise AttributeError(name)
|
||||
if name.isupper():
|
||||
return 0
|
||||
def dummy(*args, **kwargs):
|
||||
import io
|
||||
return io.UnsupportedOperation(f"{name}: no os specific module found")
|
||||
dummy.__name__ = f"dummy_{name}"
|
||||
return dummy
|
||||
|
||||
def _shim():
|
||||
import _dummy_os, sys
|
||||
sys.modules['os'] = _dummy_os
|
||||
sys.modules['os.path'] = _dummy_os.path
|
||||
sys.modules['os'] = sys.modules['posix'] = sys.modules[__name__]
|
||||
|
||||
import posixpath as path
|
||||
import sys
|
||||
sys.modules['os.path'] = path
|
||||
del sys
|
||||
|
||||
sep = path.sep
|
||||
supports_dir_fd = set()
|
||||
supports_effective_ids = set()
|
||||
supports_fd = set()
|
||||
supports_follow_symlinks = set()
|
||||
|
||||
|
||||
def fspath(path):
|
||||
|
||||
149
Lib/_dummy_thread.py
vendored
149
Lib/_dummy_thread.py
vendored
@@ -11,15 +11,35 @@ Suggested usage is::
|
||||
import _dummy_thread as _thread
|
||||
|
||||
"""
|
||||
|
||||
# Exports only things specified by thread documentation;
|
||||
# skipping obsolete synonyms allocate(), start_new(), exit_thread().
|
||||
__all__ = ['error', 'start_new_thread', 'exit', 'get_ident', 'allocate_lock',
|
||||
'interrupt_main', 'LockType', 'RLock',
|
||||
'_count']
|
||||
__all__ = [
|
||||
"error",
|
||||
"start_new_thread",
|
||||
"exit",
|
||||
"get_ident",
|
||||
"allocate_lock",
|
||||
"interrupt_main",
|
||||
"LockType",
|
||||
"RLock",
|
||||
"_count",
|
||||
"start_joinable_thread",
|
||||
"daemon_threads_allowed",
|
||||
"_shutdown",
|
||||
"_make_thread_handle",
|
||||
"_ThreadHandle",
|
||||
"_get_main_thread_ident",
|
||||
"_is_main_interpreter",
|
||||
"_local",
|
||||
]
|
||||
|
||||
# A dummy value
|
||||
TIMEOUT_MAX = 2**31
|
||||
|
||||
# Main thread ident for dummy implementation
|
||||
_MAIN_THREAD_IDENT = -1
|
||||
|
||||
# NOTE: this module can be imported early in the extension building process,
|
||||
# and so top level imports of other modules should be avoided. Instead, all
|
||||
# imports are done when needed on a function-by-function basis. Since threads
|
||||
@@ -27,6 +47,7 @@ TIMEOUT_MAX = 2**31
|
||||
|
||||
error = RuntimeError
|
||||
|
||||
|
||||
def start_new_thread(function, args, kwargs={}):
|
||||
"""Dummy implementation of _thread.start_new_thread().
|
||||
|
||||
@@ -52,6 +73,7 @@ def start_new_thread(function, args, kwargs={}):
|
||||
pass
|
||||
except:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
_main = True
|
||||
global _interrupt
|
||||
@@ -59,10 +81,58 @@ def start_new_thread(function, args, kwargs={}):
|
||||
_interrupt = False
|
||||
raise KeyboardInterrupt
|
||||
|
||||
|
||||
def start_joinable_thread(function, handle=None, daemon=True):
|
||||
"""Dummy implementation of _thread.start_joinable_thread().
|
||||
|
||||
In dummy thread, we just run the function synchronously.
|
||||
"""
|
||||
if handle is None:
|
||||
handle = _ThreadHandle()
|
||||
try:
|
||||
function()
|
||||
except SystemExit:
|
||||
pass
|
||||
except:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
handle._set_done()
|
||||
return handle
|
||||
|
||||
|
||||
def daemon_threads_allowed():
|
||||
"""Dummy implementation of _thread.daemon_threads_allowed()."""
|
||||
return True
|
||||
|
||||
|
||||
def _shutdown():
|
||||
"""Dummy implementation of _thread._shutdown()."""
|
||||
pass
|
||||
|
||||
|
||||
def _make_thread_handle(ident):
|
||||
"""Dummy implementation of _thread._make_thread_handle()."""
|
||||
handle = _ThreadHandle()
|
||||
handle._ident = ident
|
||||
return handle
|
||||
|
||||
|
||||
def _get_main_thread_ident():
|
||||
"""Dummy implementation of _thread._get_main_thread_ident()."""
|
||||
return _MAIN_THREAD_IDENT
|
||||
|
||||
|
||||
def _is_main_interpreter():
|
||||
"""Dummy implementation of _thread._is_main_interpreter()."""
|
||||
return True
|
||||
|
||||
|
||||
def exit():
|
||||
"""Dummy implementation of _thread.exit()."""
|
||||
raise SystemExit
|
||||
|
||||
|
||||
def get_ident():
|
||||
"""Dummy implementation of _thread.get_ident().
|
||||
|
||||
@@ -70,26 +140,31 @@ def get_ident():
|
||||
available, it is safe to assume that the current process is the
|
||||
only thread. Thus a constant can be safely returned.
|
||||
"""
|
||||
return -1
|
||||
return _MAIN_THREAD_IDENT
|
||||
|
||||
|
||||
def allocate_lock():
|
||||
"""Dummy implementation of _thread.allocate_lock()."""
|
||||
return LockType()
|
||||
|
||||
|
||||
def stack_size(size=None):
|
||||
"""Dummy implementation of _thread.stack_size()."""
|
||||
if size is not None:
|
||||
raise error("setting thread stack size not supported")
|
||||
return 0
|
||||
|
||||
|
||||
def _set_sentinel():
|
||||
"""Dummy implementation of _thread._set_sentinel()."""
|
||||
return LockType()
|
||||
|
||||
|
||||
def _count():
|
||||
"""Dummy implementation of _thread._count()."""
|
||||
return 0
|
||||
|
||||
|
||||
class LockType(object):
|
||||
"""Class implementing dummy implementation of _thread.LockType.
|
||||
|
||||
@@ -125,6 +200,7 @@ class LockType(object):
|
||||
else:
|
||||
if timeout > 0:
|
||||
import time
|
||||
|
||||
time.sleep(timeout)
|
||||
return False
|
||||
|
||||
@@ -153,14 +229,41 @@ class LockType(object):
|
||||
"locked" if self.locked_status else "unlocked",
|
||||
self.__class__.__module__,
|
||||
self.__class__.__qualname__,
|
||||
hex(id(self))
|
||||
hex(id(self)),
|
||||
)
|
||||
|
||||
|
||||
class _ThreadHandle:
|
||||
"""Dummy implementation of _thread._ThreadHandle."""
|
||||
|
||||
def __init__(self):
|
||||
self._ident = _MAIN_THREAD_IDENT
|
||||
self._done = False
|
||||
|
||||
@property
|
||||
def ident(self):
|
||||
return self._ident
|
||||
|
||||
def _set_done(self):
|
||||
self._done = True
|
||||
|
||||
def is_done(self):
|
||||
return self._done
|
||||
|
||||
def join(self, timeout=None):
|
||||
# In dummy thread, thread is always done
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return f"<_ThreadHandle ident={self._ident}>"
|
||||
|
||||
|
||||
# Used to signal that interrupt_main was called in a "thread"
|
||||
_interrupt = False
|
||||
# True when not executing in a "thread"
|
||||
_main = True
|
||||
|
||||
|
||||
def interrupt_main():
|
||||
"""Set _interrupt flag to True to have start_new_thread raise
|
||||
KeyboardInterrupt upon exiting."""
|
||||
@@ -170,6 +273,7 @@ def interrupt_main():
|
||||
global _interrupt
|
||||
_interrupt = True
|
||||
|
||||
|
||||
class RLock:
|
||||
def __init__(self):
|
||||
self.locked_count = 0
|
||||
@@ -190,7 +294,7 @@ class RLock:
|
||||
return True
|
||||
|
||||
def locked(self):
|
||||
return self.locked_status != 0
|
||||
return self.locked_count != 0
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s.%s object owner=%s count=%s at %s>" % (
|
||||
@@ -199,5 +303,36 @@ class RLock:
|
||||
self.__class__.__qualname__,
|
||||
get_ident() if self.locked_count else 0,
|
||||
self.locked_count,
|
||||
hex(id(self))
|
||||
hex(id(self)),
|
||||
)
|
||||
|
||||
|
||||
class _local:
|
||||
"""Dummy implementation of _thread._local (thread-local storage)."""
|
||||
|
||||
def __init__(self):
|
||||
object.__setattr__(self, "_local__impl", {})
|
||||
|
||||
def __getattribute__(self, name):
|
||||
if name.startswith("_local__"):
|
||||
return object.__getattribute__(self, name)
|
||||
impl = object.__getattribute__(self, "_local__impl")
|
||||
try:
|
||||
return impl[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name.startswith("_local__"):
|
||||
return object.__setattr__(self, name, value)
|
||||
impl = object.__getattribute__(self, "_local__impl")
|
||||
impl[name] = value
|
||||
|
||||
def __delattr__(self, name):
|
||||
if name.startswith("_local__"):
|
||||
return object.__delattr__(self, name)
|
||||
impl = object.__getattribute__(self, "_local__impl")
|
||||
try:
|
||||
del impl[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
71
Lib/_ios_support.py
vendored
Normal file
71
Lib/_ios_support.py
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
import sys
|
||||
try:
|
||||
from ctypes import cdll, c_void_p, c_char_p, util
|
||||
except ImportError:
|
||||
# ctypes is an optional module. If it's not present, we're limited in what
|
||||
# we can tell about the system, but we don't want to prevent the module
|
||||
# from working.
|
||||
print("ctypes isn't available; iOS system calls will not be available", file=sys.stderr)
|
||||
objc = None
|
||||
else:
|
||||
# ctypes is available. Load the ObjC library, and wrap the objc_getClass,
|
||||
# sel_registerName methods
|
||||
lib = util.find_library("objc")
|
||||
if lib is None:
|
||||
# Failed to load the objc library
|
||||
raise ImportError("ObjC runtime library couldn't be loaded")
|
||||
|
||||
objc = cdll.LoadLibrary(lib)
|
||||
objc.objc_getClass.restype = c_void_p
|
||||
objc.objc_getClass.argtypes = [c_char_p]
|
||||
objc.sel_registerName.restype = c_void_p
|
||||
objc.sel_registerName.argtypes = [c_char_p]
|
||||
|
||||
|
||||
def get_platform_ios():
|
||||
# Determine if this is a simulator using the multiarch value
|
||||
is_simulator = sys.implementation._multiarch.endswith("simulator")
|
||||
|
||||
# We can't use ctypes; abort
|
||||
if not objc:
|
||||
return None
|
||||
|
||||
# Most of the methods return ObjC objects
|
||||
objc.objc_msgSend.restype = c_void_p
|
||||
# All the methods used have no arguments.
|
||||
objc.objc_msgSend.argtypes = [c_void_p, c_void_p]
|
||||
|
||||
# Equivalent of:
|
||||
# device = [UIDevice currentDevice]
|
||||
UIDevice = objc.objc_getClass(b"UIDevice")
|
||||
SEL_currentDevice = objc.sel_registerName(b"currentDevice")
|
||||
device = objc.objc_msgSend(UIDevice, SEL_currentDevice)
|
||||
|
||||
# Equivalent of:
|
||||
# device_systemVersion = [device systemVersion]
|
||||
SEL_systemVersion = objc.sel_registerName(b"systemVersion")
|
||||
device_systemVersion = objc.objc_msgSend(device, SEL_systemVersion)
|
||||
|
||||
# Equivalent of:
|
||||
# device_systemName = [device systemName]
|
||||
SEL_systemName = objc.sel_registerName(b"systemName")
|
||||
device_systemName = objc.objc_msgSend(device, SEL_systemName)
|
||||
|
||||
# Equivalent of:
|
||||
# device_model = [device model]
|
||||
SEL_model = objc.sel_registerName(b"model")
|
||||
device_model = objc.objc_msgSend(device, SEL_model)
|
||||
|
||||
# UTF8String returns a const char*;
|
||||
SEL_UTF8String = objc.sel_registerName(b"UTF8String")
|
||||
objc.objc_msgSend.restype = c_char_p
|
||||
|
||||
# Equivalent of:
|
||||
# system = [device_systemName UTF8String]
|
||||
# release = [device_systemVersion UTF8String]
|
||||
# model = [device_model UTF8String]
|
||||
system = objc.objc_msgSend(device_systemName, SEL_UTF8String).decode()
|
||||
release = objc.objc_msgSend(device_systemVersion, SEL_UTF8String).decode()
|
||||
model = objc.objc_msgSend(device_model, SEL_UTF8String).decode()
|
||||
|
||||
return system, release, model, is_simulator
|
||||
2
Lib/_markupbase.py
vendored
2
Lib/_markupbase.py
vendored
@@ -13,7 +13,7 @@ _commentclose = re.compile(r'--\s*>')
|
||||
_markedsectionclose = re.compile(r']\s*]\s*>')
|
||||
|
||||
# An analysis of the MS-Word extensions is available at
|
||||
# http://www.planetpublish.com/xmlarena/xap/Thursday/WordtoXML.pdf
|
||||
# http://web.archive.org/web/20060321153828/http://www.planetpublish.com/xmlarena/xap/Thursday/WordtoXML.pdf
|
||||
|
||||
_msmarkedsectionclose = re.compile(r']\s*>')
|
||||
|
||||
|
||||
371
Lib/_opcode_metadata.py
generated
vendored
Normal file
371
Lib/_opcode_metadata.py
generated
vendored
Normal file
@@ -0,0 +1,371 @@
|
||||
# This file is generated by tools/opcode_metadata/generate_py_opcode_metadata.py
|
||||
# for RustPython bytecode format (CPython 3.14 compatible opcode numbers).
|
||||
# Do not edit!
|
||||
|
||||
_specializations = {
|
||||
"RESUME": [
|
||||
"RESUME_CHECK",
|
||||
],
|
||||
"LOAD_CONST": [
|
||||
"LOAD_CONST_MORTAL",
|
||||
"LOAD_CONST_IMMORTAL",
|
||||
],
|
||||
"TO_BOOL": [
|
||||
"TO_BOOL_ALWAYS_TRUE",
|
||||
"TO_BOOL_BOOL",
|
||||
"TO_BOOL_INT",
|
||||
"TO_BOOL_LIST",
|
||||
"TO_BOOL_NONE",
|
||||
"TO_BOOL_STR",
|
||||
],
|
||||
"BINARY_OP": [
|
||||
"BINARY_OP_MULTIPLY_INT",
|
||||
"BINARY_OP_ADD_INT",
|
||||
"BINARY_OP_SUBTRACT_INT",
|
||||
"BINARY_OP_MULTIPLY_FLOAT",
|
||||
"BINARY_OP_ADD_FLOAT",
|
||||
"BINARY_OP_SUBTRACT_FLOAT",
|
||||
"BINARY_OP_ADD_UNICODE",
|
||||
"BINARY_OP_SUBSCR_LIST_INT",
|
||||
"BINARY_OP_SUBSCR_LIST_SLICE",
|
||||
"BINARY_OP_SUBSCR_TUPLE_INT",
|
||||
"BINARY_OP_SUBSCR_STR_INT",
|
||||
"BINARY_OP_SUBSCR_DICT",
|
||||
"BINARY_OP_SUBSCR_GETITEM",
|
||||
"BINARY_OP_EXTEND",
|
||||
"BINARY_OP_INPLACE_ADD_UNICODE",
|
||||
],
|
||||
"STORE_SUBSCR": [
|
||||
"STORE_SUBSCR_DICT",
|
||||
"STORE_SUBSCR_LIST_INT",
|
||||
],
|
||||
"SEND": [
|
||||
"SEND_GEN",
|
||||
],
|
||||
"UNPACK_SEQUENCE": [
|
||||
"UNPACK_SEQUENCE_TWO_TUPLE",
|
||||
"UNPACK_SEQUENCE_TUPLE",
|
||||
"UNPACK_SEQUENCE_LIST",
|
||||
],
|
||||
"STORE_ATTR": [
|
||||
"STORE_ATTR_INSTANCE_VALUE",
|
||||
"STORE_ATTR_SLOT",
|
||||
"STORE_ATTR_WITH_HINT",
|
||||
],
|
||||
"LOAD_GLOBAL": [
|
||||
"LOAD_GLOBAL_MODULE",
|
||||
"LOAD_GLOBAL_BUILTIN",
|
||||
],
|
||||
"LOAD_SUPER_ATTR": [
|
||||
"LOAD_SUPER_ATTR_ATTR",
|
||||
"LOAD_SUPER_ATTR_METHOD",
|
||||
],
|
||||
"LOAD_ATTR": [
|
||||
"LOAD_ATTR_INSTANCE_VALUE",
|
||||
"LOAD_ATTR_MODULE",
|
||||
"LOAD_ATTR_WITH_HINT",
|
||||
"LOAD_ATTR_SLOT",
|
||||
"LOAD_ATTR_CLASS",
|
||||
"LOAD_ATTR_CLASS_WITH_METACLASS_CHECK",
|
||||
"LOAD_ATTR_PROPERTY",
|
||||
"LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN",
|
||||
"LOAD_ATTR_METHOD_WITH_VALUES",
|
||||
"LOAD_ATTR_METHOD_NO_DICT",
|
||||
"LOAD_ATTR_METHOD_LAZY_DICT",
|
||||
"LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES",
|
||||
"LOAD_ATTR_NONDESCRIPTOR_NO_DICT",
|
||||
],
|
||||
"COMPARE_OP": [
|
||||
"COMPARE_OP_FLOAT",
|
||||
"COMPARE_OP_INT",
|
||||
"COMPARE_OP_STR",
|
||||
],
|
||||
"CONTAINS_OP": [
|
||||
"CONTAINS_OP_SET",
|
||||
"CONTAINS_OP_DICT",
|
||||
],
|
||||
"JUMP_BACKWARD": [
|
||||
"JUMP_BACKWARD_NO_JIT",
|
||||
"JUMP_BACKWARD_JIT",
|
||||
],
|
||||
"FOR_ITER": [
|
||||
"FOR_ITER_LIST",
|
||||
"FOR_ITER_TUPLE",
|
||||
"FOR_ITER_RANGE",
|
||||
"FOR_ITER_GEN",
|
||||
],
|
||||
"CALL": [
|
||||
"CALL_BOUND_METHOD_EXACT_ARGS",
|
||||
"CALL_PY_EXACT_ARGS",
|
||||
"CALL_TYPE_1",
|
||||
"CALL_STR_1",
|
||||
"CALL_TUPLE_1",
|
||||
"CALL_BUILTIN_CLASS",
|
||||
"CALL_BUILTIN_O",
|
||||
"CALL_BUILTIN_FAST",
|
||||
"CALL_BUILTIN_FAST_WITH_KEYWORDS",
|
||||
"CALL_LEN",
|
||||
"CALL_ISINSTANCE",
|
||||
"CALL_LIST_APPEND",
|
||||
"CALL_METHOD_DESCRIPTOR_O",
|
||||
"CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS",
|
||||
"CALL_METHOD_DESCRIPTOR_NOARGS",
|
||||
"CALL_METHOD_DESCRIPTOR_FAST",
|
||||
"CALL_ALLOC_AND_ENTER_INIT",
|
||||
"CALL_PY_GENERAL",
|
||||
"CALL_BOUND_METHOD_GENERAL",
|
||||
"CALL_NON_PY_GENERAL",
|
||||
],
|
||||
"CALL_KW": [
|
||||
"CALL_KW_BOUND_METHOD",
|
||||
"CALL_KW_PY",
|
||||
"CALL_KW_NON_PY",
|
||||
],
|
||||
}
|
||||
|
||||
_specialized_opmap = {
|
||||
'BINARY_OP_ADD_FLOAT': 129,
|
||||
'BINARY_OP_ADD_INT': 130,
|
||||
'BINARY_OP_ADD_UNICODE': 131,
|
||||
'BINARY_OP_EXTEND': 132,
|
||||
'BINARY_OP_INPLACE_ADD_UNICODE': 3,
|
||||
'BINARY_OP_MULTIPLY_FLOAT': 133,
|
||||
'BINARY_OP_MULTIPLY_INT': 134,
|
||||
'BINARY_OP_SUBSCR_DICT': 135,
|
||||
'BINARY_OP_SUBSCR_GETITEM': 136,
|
||||
'BINARY_OP_SUBSCR_LIST_INT': 137,
|
||||
'BINARY_OP_SUBSCR_LIST_SLICE': 138,
|
||||
'BINARY_OP_SUBSCR_STR_INT': 139,
|
||||
'BINARY_OP_SUBSCR_TUPLE_INT': 140,
|
||||
'BINARY_OP_SUBTRACT_FLOAT': 141,
|
||||
'BINARY_OP_SUBTRACT_INT': 142,
|
||||
'CALL_ALLOC_AND_ENTER_INIT': 143,
|
||||
'CALL_BOUND_METHOD_EXACT_ARGS': 144,
|
||||
'CALL_BOUND_METHOD_GENERAL': 145,
|
||||
'CALL_BUILTIN_CLASS': 146,
|
||||
'CALL_BUILTIN_FAST': 147,
|
||||
'CALL_BUILTIN_FAST_WITH_KEYWORDS': 148,
|
||||
'CALL_BUILTIN_O': 149,
|
||||
'CALL_ISINSTANCE': 150,
|
||||
'CALL_KW_BOUND_METHOD': 151,
|
||||
'CALL_KW_NON_PY': 152,
|
||||
'CALL_KW_PY': 153,
|
||||
'CALL_LEN': 154,
|
||||
'CALL_LIST_APPEND': 155,
|
||||
'CALL_METHOD_DESCRIPTOR_FAST': 156,
|
||||
'CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS': 157,
|
||||
'CALL_METHOD_DESCRIPTOR_NOARGS': 158,
|
||||
'CALL_METHOD_DESCRIPTOR_O': 159,
|
||||
'CALL_NON_PY_GENERAL': 160,
|
||||
'CALL_PY_EXACT_ARGS': 161,
|
||||
'CALL_PY_GENERAL': 162,
|
||||
'CALL_STR_1': 163,
|
||||
'CALL_TUPLE_1': 164,
|
||||
'CALL_TYPE_1': 165,
|
||||
'COMPARE_OP_FLOAT': 166,
|
||||
'COMPARE_OP_INT': 167,
|
||||
'COMPARE_OP_STR': 168,
|
||||
'CONTAINS_OP_DICT': 169,
|
||||
'CONTAINS_OP_SET': 170,
|
||||
'FOR_ITER_GEN': 171,
|
||||
'FOR_ITER_LIST': 172,
|
||||
'FOR_ITER_RANGE': 173,
|
||||
'FOR_ITER_TUPLE': 174,
|
||||
'JUMP_BACKWARD_JIT': 175,
|
||||
'JUMP_BACKWARD_NO_JIT': 176,
|
||||
'LOAD_ATTR_CLASS': 177,
|
||||
'LOAD_ATTR_CLASS_WITH_METACLASS_CHECK': 178,
|
||||
'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 179,
|
||||
'LOAD_ATTR_INSTANCE_VALUE': 180,
|
||||
'LOAD_ATTR_METHOD_LAZY_DICT': 181,
|
||||
'LOAD_ATTR_METHOD_NO_DICT': 182,
|
||||
'LOAD_ATTR_METHOD_WITH_VALUES': 183,
|
||||
'LOAD_ATTR_MODULE': 184,
|
||||
'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 185,
|
||||
'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 186,
|
||||
'LOAD_ATTR_PROPERTY': 187,
|
||||
'LOAD_ATTR_SLOT': 188,
|
||||
'LOAD_ATTR_WITH_HINT': 189,
|
||||
'LOAD_CONST_IMMORTAL': 190,
|
||||
'LOAD_CONST_MORTAL': 191,
|
||||
'LOAD_GLOBAL_BUILTIN': 192,
|
||||
'LOAD_GLOBAL_MODULE': 193,
|
||||
'LOAD_SUPER_ATTR_ATTR': 194,
|
||||
'LOAD_SUPER_ATTR_METHOD': 195,
|
||||
'RESUME_CHECK': 196,
|
||||
'SEND_GEN': 197,
|
||||
'STORE_ATTR_INSTANCE_VALUE': 198,
|
||||
'STORE_ATTR_SLOT': 199,
|
||||
'STORE_ATTR_WITH_HINT': 200,
|
||||
'STORE_SUBSCR_DICT': 201,
|
||||
'STORE_SUBSCR_LIST_INT': 202,
|
||||
'TO_BOOL_ALWAYS_TRUE': 203,
|
||||
'TO_BOOL_BOOL': 204,
|
||||
'TO_BOOL_INT': 205,
|
||||
'TO_BOOL_LIST': 206,
|
||||
'TO_BOOL_NONE': 207,
|
||||
'TO_BOOL_STR': 208,
|
||||
'UNPACK_SEQUENCE_LIST': 209,
|
||||
'UNPACK_SEQUENCE_TUPLE': 210,
|
||||
'UNPACK_SEQUENCE_TWO_TUPLE': 211,
|
||||
}
|
||||
|
||||
opmap = {
|
||||
'CACHE': 0,
|
||||
'RESERVED': 17,
|
||||
'RESUME': 128,
|
||||
'INSTRUMENTED_LINE': 254,
|
||||
'ENTER_EXECUTOR': 255,
|
||||
'BINARY_SLICE': 1,
|
||||
'BUILD_TEMPLATE': 2,
|
||||
'CALL_FUNCTION_EX': 4,
|
||||
'CHECK_EG_MATCH': 5,
|
||||
'CHECK_EXC_MATCH': 6,
|
||||
'CLEANUP_THROW': 7,
|
||||
'DELETE_SUBSCR': 8,
|
||||
'END_FOR': 9,
|
||||
'END_SEND': 10,
|
||||
'EXIT_INIT_CHECK': 11,
|
||||
'FORMAT_SIMPLE': 12,
|
||||
'FORMAT_WITH_SPEC': 13,
|
||||
'GET_AITER': 14,
|
||||
'GET_ANEXT': 15,
|
||||
'GET_ITER': 16,
|
||||
'GET_LEN': 18,
|
||||
'GET_YIELD_FROM_ITER': 19,
|
||||
'INTERPRETER_EXIT': 20,
|
||||
'LOAD_BUILD_CLASS': 21,
|
||||
'LOAD_LOCALS': 22,
|
||||
'MAKE_FUNCTION': 23,
|
||||
'MATCH_KEYS': 24,
|
||||
'MATCH_MAPPING': 25,
|
||||
'MATCH_SEQUENCE': 26,
|
||||
'NOP': 27,
|
||||
'NOT_TAKEN': 28,
|
||||
'POP_EXCEPT': 29,
|
||||
'POP_ITER': 30,
|
||||
'POP_TOP': 31,
|
||||
'PUSH_EXC_INFO': 32,
|
||||
'PUSH_NULL': 33,
|
||||
'RETURN_GENERATOR': 34,
|
||||
'RETURN_VALUE': 35,
|
||||
'SETUP_ANNOTATIONS': 36,
|
||||
'STORE_SLICE': 37,
|
||||
'STORE_SUBSCR': 38,
|
||||
'TO_BOOL': 39,
|
||||
'UNARY_INVERT': 40,
|
||||
'UNARY_NEGATIVE': 41,
|
||||
'UNARY_NOT': 42,
|
||||
'WITH_EXCEPT_START': 43,
|
||||
'BINARY_OP': 44,
|
||||
'BUILD_INTERPOLATION': 45,
|
||||
'BUILD_LIST': 46,
|
||||
'BUILD_MAP': 47,
|
||||
'BUILD_SET': 48,
|
||||
'BUILD_SLICE': 49,
|
||||
'BUILD_STRING': 50,
|
||||
'BUILD_TUPLE': 51,
|
||||
'CALL': 52,
|
||||
'CALL_INTRINSIC_1': 53,
|
||||
'CALL_INTRINSIC_2': 54,
|
||||
'CALL_KW': 55,
|
||||
'COMPARE_OP': 56,
|
||||
'CONTAINS_OP': 57,
|
||||
'CONVERT_VALUE': 58,
|
||||
'COPY': 59,
|
||||
'COPY_FREE_VARS': 60,
|
||||
'DELETE_ATTR': 61,
|
||||
'DELETE_DEREF': 62,
|
||||
'DELETE_FAST': 63,
|
||||
'DELETE_GLOBAL': 64,
|
||||
'DELETE_NAME': 65,
|
||||
'DICT_MERGE': 66,
|
||||
'DICT_UPDATE': 67,
|
||||
'END_ASYNC_FOR': 68,
|
||||
'EXTENDED_ARG': 69,
|
||||
'FOR_ITER': 70,
|
||||
'GET_AWAITABLE': 71,
|
||||
'IMPORT_FROM': 72,
|
||||
'IMPORT_NAME': 73,
|
||||
'IS_OP': 74,
|
||||
'JUMP_BACKWARD': 75,
|
||||
'JUMP_BACKWARD_NO_INTERRUPT': 76,
|
||||
'JUMP_FORWARD': 77,
|
||||
'LIST_APPEND': 78,
|
||||
'LIST_EXTEND': 79,
|
||||
'LOAD_ATTR': 80,
|
||||
'LOAD_COMMON_CONSTANT': 81,
|
||||
'LOAD_CONST': 82,
|
||||
'LOAD_DEREF': 83,
|
||||
'LOAD_FAST': 84,
|
||||
'LOAD_FAST_AND_CLEAR': 85,
|
||||
'LOAD_FAST_BORROW': 86,
|
||||
'LOAD_FAST_BORROW_LOAD_FAST_BORROW': 87,
|
||||
'LOAD_FAST_CHECK': 88,
|
||||
'LOAD_FAST_LOAD_FAST': 89,
|
||||
'LOAD_FROM_DICT_OR_DEREF': 90,
|
||||
'LOAD_FROM_DICT_OR_GLOBALS': 91,
|
||||
'LOAD_GLOBAL': 92,
|
||||
'LOAD_NAME': 93,
|
||||
'LOAD_SMALL_INT': 94,
|
||||
'LOAD_SPECIAL': 95,
|
||||
'LOAD_SUPER_ATTR': 96,
|
||||
'MAKE_CELL': 97,
|
||||
'MAP_ADD': 98,
|
||||
'MATCH_CLASS': 99,
|
||||
'POP_JUMP_IF_FALSE': 100,
|
||||
'POP_JUMP_IF_NONE': 101,
|
||||
'POP_JUMP_IF_NOT_NONE': 102,
|
||||
'POP_JUMP_IF_TRUE': 103,
|
||||
'RAISE_VARARGS': 104,
|
||||
'RERAISE': 105,
|
||||
'SEND': 106,
|
||||
'SET_ADD': 107,
|
||||
'SET_FUNCTION_ATTRIBUTE': 108,
|
||||
'SET_UPDATE': 109,
|
||||
'STORE_ATTR': 110,
|
||||
'STORE_DEREF': 111,
|
||||
'STORE_FAST': 112,
|
||||
'STORE_FAST_LOAD_FAST': 113,
|
||||
'STORE_FAST_STORE_FAST': 114,
|
||||
'STORE_GLOBAL': 115,
|
||||
'STORE_NAME': 116,
|
||||
'SWAP': 117,
|
||||
'UNPACK_EX': 118,
|
||||
'UNPACK_SEQUENCE': 119,
|
||||
'YIELD_VALUE': 120,
|
||||
'INSTRUMENTED_END_FOR': 234,
|
||||
'INSTRUMENTED_POP_ITER': 235,
|
||||
'INSTRUMENTED_END_SEND': 236,
|
||||
'INSTRUMENTED_FOR_ITER': 237,
|
||||
'INSTRUMENTED_INSTRUCTION': 238,
|
||||
'INSTRUMENTED_JUMP_FORWARD': 239,
|
||||
'INSTRUMENTED_NOT_TAKEN': 240,
|
||||
'INSTRUMENTED_POP_JUMP_IF_TRUE': 241,
|
||||
'INSTRUMENTED_POP_JUMP_IF_FALSE': 242,
|
||||
'INSTRUMENTED_POP_JUMP_IF_NONE': 243,
|
||||
'INSTRUMENTED_POP_JUMP_IF_NOT_NONE': 244,
|
||||
'INSTRUMENTED_RESUME': 245,
|
||||
'INSTRUMENTED_RETURN_VALUE': 246,
|
||||
'INSTRUMENTED_YIELD_VALUE': 247,
|
||||
'INSTRUMENTED_END_ASYNC_FOR': 248,
|
||||
'INSTRUMENTED_LOAD_SUPER_ATTR': 249,
|
||||
'INSTRUMENTED_CALL': 250,
|
||||
'INSTRUMENTED_CALL_KW': 251,
|
||||
'INSTRUMENTED_CALL_FUNCTION_EX': 252,
|
||||
'INSTRUMENTED_JUMP_BACKWARD': 253,
|
||||
'ANNOTATIONS_PLACEHOLDER': 256,
|
||||
'JUMP': 257,
|
||||
'JUMP_IF_FALSE': 258,
|
||||
'JUMP_IF_TRUE': 259,
|
||||
'JUMP_NO_INTERRUPT': 260,
|
||||
'LOAD_CLOSURE': 261,
|
||||
'POP_BLOCK': 262,
|
||||
'SETUP_CLEANUP': 263,
|
||||
'SETUP_FINALLY': 264,
|
||||
'SETUP_WITH': 265,
|
||||
'STORE_FAST_MAYBE_NULL': 266,
|
||||
}
|
||||
|
||||
HAVE_ARGUMENT = 43
|
||||
MIN_INSTRUMENTED_OPCODE = 234
|
||||
5
Lib/_osx_support.py
vendored
5
Lib/_osx_support.py
vendored
@@ -507,6 +507,11 @@ def get_platform_osx(_config_vars, osname, release, machine):
|
||||
# MACOSX_DEPLOYMENT_TARGET.
|
||||
|
||||
macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '')
|
||||
if macver and '.' not in macver:
|
||||
# Ensure that the version includes at least a major
|
||||
# and minor version, even if MACOSX_DEPLOYMENT_TARGET
|
||||
# is set to a single-label version like "14".
|
||||
macver += '.0'
|
||||
macrelease = _get_system_version() or macver
|
||||
macver = macver or macrelease
|
||||
|
||||
|
||||
869
Lib/_py_warnings.py
vendored
Normal file
869
Lib/_py_warnings.py
vendored
Normal file
@@ -0,0 +1,869 @@
|
||||
"""Python part of the warnings subsystem."""
|
||||
|
||||
import sys
|
||||
import _contextvars
|
||||
import _thread
|
||||
|
||||
|
||||
__all__ = ["warn", "warn_explicit", "showwarning",
|
||||
"formatwarning", "filterwarnings", "simplefilter",
|
||||
"resetwarnings", "catch_warnings", "deprecated"]
|
||||
|
||||
|
||||
# Normally '_wm' is sys.modules['warnings'] but for unit tests it can be
|
||||
# a different module. User code is allowed to reassign global attributes
|
||||
# of the 'warnings' module, commonly 'filters' or 'showwarning'. So we
|
||||
# need to lookup these global attributes dynamically on the '_wm' object,
|
||||
# rather than binding them earlier. The code in this module consistently uses
|
||||
# '_wm.<something>' rather than using the globals of this module. If the
|
||||
# '_warnings' C extension is in use, some globals are replaced by functions
|
||||
# and variables defined in that extension.
|
||||
_wm = None
|
||||
|
||||
|
||||
def _set_module(module):
|
||||
global _wm
|
||||
_wm = module
|
||||
|
||||
|
||||
# filters contains a sequence of filter 5-tuples
|
||||
# The components of the 5-tuple are:
|
||||
# - an action: error, ignore, always, all, default, module, or once
|
||||
# - a compiled regex that must match the warning message
|
||||
# - a class representing the warning category
|
||||
# - a compiled regex that must match the module that is being warned
|
||||
# - a line number for the line being warning, or 0 to mean any line
|
||||
# If either if the compiled regexs are None, match anything.
|
||||
filters = []
|
||||
|
||||
|
||||
defaultaction = "default"
|
||||
onceregistry = {}
|
||||
_lock = _thread.RLock()
|
||||
_filters_version = 1
|
||||
|
||||
|
||||
# If true, catch_warnings() will use a context var to hold the modified
|
||||
# filters list. Otherwise, catch_warnings() will operate on the 'filters'
|
||||
# global of the warnings module.
|
||||
_use_context = sys.flags.context_aware_warnings
|
||||
|
||||
|
||||
class _Context:
|
||||
def __init__(self, filters):
|
||||
self._filters = filters
|
||||
self.log = None # if set to a list, logging is enabled
|
||||
|
||||
def copy(self):
|
||||
context = _Context(self._filters[:])
|
||||
if self.log is not None:
|
||||
context.log = self.log
|
||||
return context
|
||||
|
||||
def _record_warning(self, msg):
|
||||
self.log.append(msg)
|
||||
|
||||
|
||||
class _GlobalContext(_Context):
|
||||
def __init__(self):
|
||||
self.log = None
|
||||
|
||||
@property
|
||||
def _filters(self):
|
||||
# Since there is quite a lot of code that assigns to
|
||||
# warnings.filters, this needs to return the current value of
|
||||
# the module global.
|
||||
try:
|
||||
return _wm.filters
|
||||
except AttributeError:
|
||||
# 'filters' global was deleted. Do we need to actually handle this case?
|
||||
return []
|
||||
|
||||
|
||||
_global_context = _GlobalContext()
|
||||
|
||||
|
||||
_warnings_context = _contextvars.ContextVar('warnings_context')
|
||||
|
||||
|
||||
def _get_context():
|
||||
if not _use_context:
|
||||
return _global_context
|
||||
try:
|
||||
return _wm._warnings_context.get()
|
||||
except LookupError:
|
||||
return _global_context
|
||||
|
||||
|
||||
def _set_context(context):
|
||||
assert _use_context
|
||||
_wm._warnings_context.set(context)
|
||||
|
||||
|
||||
def _new_context():
|
||||
assert _use_context
|
||||
old_context = _wm._get_context()
|
||||
new_context = old_context.copy()
|
||||
_wm._set_context(new_context)
|
||||
return old_context, new_context
|
||||
|
||||
|
||||
def _get_filters():
|
||||
"""Return the current list of filters. This is a non-public API used by
|
||||
module functions and by the unit tests."""
|
||||
return _wm._get_context()._filters
|
||||
|
||||
|
||||
def _filters_mutated_lock_held():
|
||||
_wm._filters_version += 1
|
||||
|
||||
|
||||
def showwarning(message, category, filename, lineno, file=None, line=None):
|
||||
"""Hook to write a warning to a file; replace if you like."""
|
||||
msg = _wm.WarningMessage(message, category, filename, lineno, file, line)
|
||||
_wm._showwarnmsg_impl(msg)
|
||||
|
||||
|
||||
def formatwarning(message, category, filename, lineno, line=None):
|
||||
"""Function to format a warning the standard way."""
|
||||
msg = _wm.WarningMessage(message, category, filename, lineno, None, line)
|
||||
return _wm._formatwarnmsg_impl(msg)
|
||||
|
||||
|
||||
def _showwarnmsg_impl(msg):
|
||||
context = _wm._get_context()
|
||||
if context.log is not None:
|
||||
context._record_warning(msg)
|
||||
return
|
||||
file = msg.file
|
||||
if file is None:
|
||||
file = sys.stderr
|
||||
if file is None:
|
||||
# sys.stderr is None when run with pythonw.exe:
|
||||
# warnings get lost
|
||||
return
|
||||
text = _wm._formatwarnmsg(msg)
|
||||
try:
|
||||
file.write(text)
|
||||
except OSError:
|
||||
# the file (probably stderr) is invalid - this warning gets lost.
|
||||
pass
|
||||
|
||||
|
||||
def _formatwarnmsg_impl(msg):
|
||||
category = msg.category.__name__
|
||||
s = f"{msg.filename}:{msg.lineno}: {category}: {msg.message}\n"
|
||||
|
||||
if msg.line is None:
|
||||
try:
|
||||
import linecache
|
||||
line = linecache.getline(msg.filename, msg.lineno)
|
||||
except Exception:
|
||||
# When a warning is logged during Python shutdown, linecache
|
||||
# and the import machinery don't work anymore
|
||||
line = None
|
||||
linecache = None
|
||||
else:
|
||||
line = msg.line
|
||||
if line:
|
||||
line = line.strip()
|
||||
s += " %s\n" % line
|
||||
|
||||
if msg.source is not None:
|
||||
try:
|
||||
import tracemalloc
|
||||
# Logging a warning should not raise a new exception:
|
||||
# catch Exception, not only ImportError and RecursionError.
|
||||
except Exception:
|
||||
# don't suggest to enable tracemalloc if it's not available
|
||||
suggest_tracemalloc = False
|
||||
tb = None
|
||||
else:
|
||||
try:
|
||||
suggest_tracemalloc = not tracemalloc.is_tracing()
|
||||
tb = tracemalloc.get_object_traceback(msg.source)
|
||||
except Exception:
|
||||
# When a warning is logged during Python shutdown, tracemalloc
|
||||
# and the import machinery don't work anymore
|
||||
suggest_tracemalloc = False
|
||||
tb = None
|
||||
|
||||
if tb is not None:
|
||||
s += 'Object allocated at (most recent call last):\n'
|
||||
for frame in tb:
|
||||
s += (' File "%s", lineno %s\n'
|
||||
% (frame.filename, frame.lineno))
|
||||
|
||||
try:
|
||||
if linecache is not None:
|
||||
line = linecache.getline(frame.filename, frame.lineno)
|
||||
else:
|
||||
line = None
|
||||
except Exception:
|
||||
line = None
|
||||
if line:
|
||||
line = line.strip()
|
||||
s += ' %s\n' % line
|
||||
elif suggest_tracemalloc:
|
||||
s += (f'{category}: Enable tracemalloc to get the object '
|
||||
f'allocation traceback\n')
|
||||
return s
|
||||
|
||||
|
||||
# Keep a reference to check if the function was replaced
|
||||
_showwarning_orig = showwarning
|
||||
|
||||
|
||||
def _showwarnmsg(msg):
|
||||
"""Hook to write a warning to a file; replace if you like."""
|
||||
try:
|
||||
sw = _wm.showwarning
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if sw is not _showwarning_orig:
|
||||
# warnings.showwarning() was replaced
|
||||
if not callable(sw):
|
||||
raise TypeError("warnings.showwarning() must be set to a "
|
||||
"function or method")
|
||||
|
||||
sw(msg.message, msg.category, msg.filename, msg.lineno,
|
||||
msg.file, msg.line)
|
||||
return
|
||||
_wm._showwarnmsg_impl(msg)
|
||||
|
||||
|
||||
# Keep a reference to check if the function was replaced
|
||||
_formatwarning_orig = formatwarning
|
||||
|
||||
|
||||
def _formatwarnmsg(msg):
|
||||
"""Function to format a warning the standard way."""
|
||||
try:
|
||||
fw = _wm.formatwarning
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if fw is not _formatwarning_orig:
|
||||
# warnings.formatwarning() was replaced
|
||||
return fw(msg.message, msg.category,
|
||||
msg.filename, msg.lineno, msg.line)
|
||||
return _wm._formatwarnmsg_impl(msg)
|
||||
|
||||
|
||||
def filterwarnings(action, message="", category=Warning, module="", lineno=0,
|
||||
append=False):
|
||||
"""Insert an entry into the list of warnings filters (at the front).
|
||||
|
||||
'action' -- one of "error", "ignore", "always", "all", "default", "module",
|
||||
or "once"
|
||||
'message' -- a regex that the warning message must match
|
||||
'category' -- a class that the warning must be a subclass of
|
||||
'module' -- a regex that the module name must match
|
||||
'lineno' -- an integer line number, 0 matches all warnings
|
||||
'append' -- if true, append to the list of filters
|
||||
"""
|
||||
if action not in {"error", "ignore", "always", "all", "default", "module", "once"}:
|
||||
raise ValueError(f"invalid action: {action!r}")
|
||||
if not isinstance(message, str):
|
||||
raise TypeError("message must be a string")
|
||||
if not isinstance(category, type) or not issubclass(category, Warning):
|
||||
raise TypeError("category must be a Warning subclass")
|
||||
if not isinstance(module, str):
|
||||
raise TypeError("module must be a string")
|
||||
if not isinstance(lineno, int):
|
||||
raise TypeError("lineno must be an int")
|
||||
if lineno < 0:
|
||||
raise ValueError("lineno must be an int >= 0")
|
||||
|
||||
if message or module:
|
||||
import re
|
||||
|
||||
if message:
|
||||
message = re.compile(message, re.I)
|
||||
else:
|
||||
message = None
|
||||
if module:
|
||||
module = re.compile(module)
|
||||
else:
|
||||
module = None
|
||||
|
||||
_wm._add_filter(action, message, category, module, lineno, append=append)
|
||||
|
||||
|
||||
def simplefilter(action, category=Warning, lineno=0, append=False):
|
||||
"""Insert a simple entry into the list of warnings filters (at the front).
|
||||
|
||||
A simple filter matches all modules and messages.
|
||||
'action' -- one of "error", "ignore", "always", "all", "default", "module",
|
||||
or "once"
|
||||
'category' -- a class that the warning must be a subclass of
|
||||
'lineno' -- an integer line number, 0 matches all warnings
|
||||
'append' -- if true, append to the list of filters
|
||||
"""
|
||||
if action not in {"error", "ignore", "always", "all", "default", "module", "once"}:
|
||||
raise ValueError(f"invalid action: {action!r}")
|
||||
if not isinstance(lineno, int):
|
||||
raise TypeError("lineno must be an int")
|
||||
if lineno < 0:
|
||||
raise ValueError("lineno must be an int >= 0")
|
||||
_wm._add_filter(action, None, category, None, lineno, append=append)
|
||||
|
||||
|
||||
def _filters_mutated():
|
||||
# Even though this function is not part of the public API, it's used by
|
||||
# a fair amount of user code.
|
||||
with _wm._lock:
|
||||
_wm._filters_mutated_lock_held()
|
||||
|
||||
|
||||
def _add_filter(*item, append):
|
||||
with _wm._lock:
|
||||
filters = _wm._get_filters()
|
||||
if not append:
|
||||
# Remove possible duplicate filters, so new one will be placed
|
||||
# in correct place. If append=True and duplicate exists, do nothing.
|
||||
try:
|
||||
filters.remove(item)
|
||||
except ValueError:
|
||||
pass
|
||||
filters.insert(0, item)
|
||||
else:
|
||||
if item not in filters:
|
||||
filters.append(item)
|
||||
_wm._filters_mutated_lock_held()
|
||||
|
||||
|
||||
def resetwarnings():
|
||||
"""Clear the list of warning filters, so that no filters are active."""
|
||||
with _wm._lock:
|
||||
del _wm._get_filters()[:]
|
||||
_wm._filters_mutated_lock_held()
|
||||
|
||||
|
||||
class _OptionError(Exception):
|
||||
"""Exception used by option processing helpers."""
|
||||
pass
|
||||
|
||||
|
||||
# Helper to process -W options passed via sys.warnoptions
|
||||
def _processoptions(args):
|
||||
for arg in args:
|
||||
try:
|
||||
_wm._setoption(arg)
|
||||
except _wm._OptionError as msg:
|
||||
print("Invalid -W option ignored:", msg, file=sys.stderr)
|
||||
|
||||
|
||||
# Helper for _processoptions()
|
||||
def _setoption(arg):
|
||||
parts = arg.split(':')
|
||||
if len(parts) > 5:
|
||||
raise _wm._OptionError("too many fields (max 5): %r" % (arg,))
|
||||
while len(parts) < 5:
|
||||
parts.append('')
|
||||
action, message, category, module, lineno = [s.strip()
|
||||
for s in parts]
|
||||
action = _wm._getaction(action)
|
||||
category = _wm._getcategory(category)
|
||||
if message or module:
|
||||
import re
|
||||
if message:
|
||||
message = re.escape(message)
|
||||
if module:
|
||||
module = re.escape(module) + r'\z'
|
||||
if lineno:
|
||||
try:
|
||||
lineno = int(lineno)
|
||||
if lineno < 0:
|
||||
raise ValueError
|
||||
except (ValueError, OverflowError):
|
||||
raise _wm._OptionError("invalid lineno %r" % (lineno,)) from None
|
||||
else:
|
||||
lineno = 0
|
||||
_wm.filterwarnings(action, message, category, module, lineno)
|
||||
|
||||
|
||||
# Helper for _setoption()
|
||||
def _getaction(action):
|
||||
if not action:
|
||||
return "default"
|
||||
for a in ('default', 'always', 'all', 'ignore', 'module', 'once', 'error'):
|
||||
if a.startswith(action):
|
||||
return a
|
||||
raise _wm._OptionError("invalid action: %r" % (action,))
|
||||
|
||||
|
||||
# Helper for _setoption()
|
||||
def _getcategory(category):
|
||||
if not category:
|
||||
return Warning
|
||||
if '.' not in category:
|
||||
import builtins as m
|
||||
klass = category
|
||||
else:
|
||||
module, _, klass = category.rpartition('.')
|
||||
try:
|
||||
m = __import__(module, None, None, [klass])
|
||||
except ImportError:
|
||||
raise _wm._OptionError("invalid module name: %r" % (module,)) from None
|
||||
try:
|
||||
cat = getattr(m, klass)
|
||||
except AttributeError:
|
||||
raise _wm._OptionError("unknown warning category: %r" % (category,)) from None
|
||||
if not issubclass(cat, Warning):
|
||||
raise _wm._OptionError("invalid warning category: %r" % (category,))
|
||||
return cat
|
||||
|
||||
|
||||
def _is_internal_filename(filename):
|
||||
return 'importlib' in filename and '_bootstrap' in filename
|
||||
|
||||
|
||||
def _is_filename_to_skip(filename, skip_file_prefixes):
|
||||
return any(filename.startswith(prefix) for prefix in skip_file_prefixes)
|
||||
|
||||
|
||||
def _is_internal_frame(frame):
|
||||
"""Signal whether the frame is an internal CPython implementation detail."""
|
||||
return _is_internal_filename(frame.f_code.co_filename)
|
||||
|
||||
|
||||
def _next_external_frame(frame, skip_file_prefixes):
|
||||
"""Find the next frame that doesn't involve Python or user internals."""
|
||||
frame = frame.f_back
|
||||
while frame is not None and (
|
||||
_is_internal_filename(filename := frame.f_code.co_filename) or
|
||||
_is_filename_to_skip(filename, skip_file_prefixes)):
|
||||
frame = frame.f_back
|
||||
return frame
|
||||
|
||||
|
||||
# Code typically replaced by _warnings
|
||||
def warn(message, category=None, stacklevel=1, source=None,
|
||||
*, skip_file_prefixes=()):
|
||||
"""Issue a warning, or maybe ignore it or raise an exception."""
|
||||
# Check if message is already a Warning object
|
||||
if isinstance(message, Warning):
|
||||
category = message.__class__
|
||||
# Check category argument
|
||||
if category is None:
|
||||
category = UserWarning
|
||||
if not (isinstance(category, type) and issubclass(category, Warning)):
|
||||
raise TypeError("category must be a Warning subclass, "
|
||||
"not '{:s}'".format(type(category).__name__))
|
||||
if not isinstance(skip_file_prefixes, tuple):
|
||||
# The C version demands a tuple for implementation performance.
|
||||
raise TypeError('skip_file_prefixes must be a tuple of strs.')
|
||||
if skip_file_prefixes:
|
||||
stacklevel = max(2, stacklevel)
|
||||
# Get context information
|
||||
try:
|
||||
if stacklevel <= 1 or _is_internal_frame(sys._getframe(1)):
|
||||
# If frame is too small to care or if the warning originated in
|
||||
# internal code, then do not try to hide any frames.
|
||||
frame = sys._getframe(stacklevel)
|
||||
else:
|
||||
frame = sys._getframe(1)
|
||||
# Look for one frame less since the above line starts us off.
|
||||
for x in range(stacklevel-1):
|
||||
frame = _next_external_frame(frame, skip_file_prefixes)
|
||||
if frame is None:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
globals = sys.__dict__
|
||||
filename = "<sys>"
|
||||
lineno = 0
|
||||
else:
|
||||
globals = frame.f_globals
|
||||
filename = frame.f_code.co_filename
|
||||
lineno = frame.f_lineno
|
||||
if '__name__' in globals:
|
||||
module = globals['__name__']
|
||||
else:
|
||||
module = "<string>"
|
||||
registry = globals.setdefault("__warningregistry__", {})
|
||||
_wm.warn_explicit(
|
||||
message,
|
||||
category,
|
||||
filename,
|
||||
lineno,
|
||||
module,
|
||||
registry,
|
||||
globals,
|
||||
source=source,
|
||||
)
|
||||
|
||||
|
||||
def warn_explicit(message, category, filename, lineno,
|
||||
module=None, registry=None, module_globals=None,
|
||||
source=None):
|
||||
lineno = int(lineno)
|
||||
if module is None:
|
||||
module = filename or "<unknown>"
|
||||
if module[-3:].lower() == ".py":
|
||||
module = module[:-3] # XXX What about leading pathname?
|
||||
if isinstance(message, Warning):
|
||||
text = str(message)
|
||||
category = message.__class__
|
||||
else:
|
||||
text = message
|
||||
message = category(message)
|
||||
key = (text, category, lineno)
|
||||
with _wm._lock:
|
||||
if registry is None:
|
||||
registry = {}
|
||||
if registry.get('version', 0) != _wm._filters_version:
|
||||
registry.clear()
|
||||
registry['version'] = _wm._filters_version
|
||||
# Quick test for common case
|
||||
if registry.get(key):
|
||||
return
|
||||
# Search the filters
|
||||
for item in _wm._get_filters():
|
||||
action, msg, cat, mod, ln = item
|
||||
if ((msg is None or msg.match(text)) and
|
||||
issubclass(category, cat) and
|
||||
(mod is None or mod.match(module)) and
|
||||
(ln == 0 or lineno == ln)):
|
||||
break
|
||||
else:
|
||||
action = _wm.defaultaction
|
||||
# Early exit actions
|
||||
if action == "ignore":
|
||||
return
|
||||
|
||||
if action == "error":
|
||||
raise message
|
||||
# Other actions
|
||||
if action == "once":
|
||||
registry[key] = 1
|
||||
oncekey = (text, category)
|
||||
if _wm.onceregistry.get(oncekey):
|
||||
return
|
||||
_wm.onceregistry[oncekey] = 1
|
||||
elif action in {"always", "all"}:
|
||||
pass
|
||||
elif action == "module":
|
||||
registry[key] = 1
|
||||
altkey = (text, category, 0)
|
||||
if registry.get(altkey):
|
||||
return
|
||||
registry[altkey] = 1
|
||||
elif action == "default":
|
||||
registry[key] = 1
|
||||
else:
|
||||
# Unrecognized actions are errors
|
||||
raise RuntimeError(
|
||||
"Unrecognized action (%r) in warnings.filters:\n %s" %
|
||||
(action, item))
|
||||
|
||||
# Prime the linecache for formatting, in case the
|
||||
# "file" is actually in a zipfile or something.
|
||||
import linecache
|
||||
linecache.getlines(filename, module_globals)
|
||||
|
||||
# Print message and context
|
||||
msg = _wm.WarningMessage(message, category, filename, lineno, source=source)
|
||||
_wm._showwarnmsg(msg)
|
||||
|
||||
|
||||
class WarningMessage(object):
|
||||
|
||||
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
|
||||
"line", "source")
|
||||
|
||||
def __init__(self, message, category, filename, lineno, file=None,
|
||||
line=None, source=None):
|
||||
self.message = message
|
||||
self.category = category
|
||||
self.filename = filename
|
||||
self.lineno = lineno
|
||||
self.file = file
|
||||
self.line = line
|
||||
self.source = source
|
||||
self._category_name = category.__name__ if category else None
|
||||
|
||||
def __str__(self):
|
||||
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
|
||||
"line : %r}" % (self.message, self._category_name,
|
||||
self.filename, self.lineno, self.line))
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{type(self).__qualname__} {self}>'
|
||||
|
||||
|
||||
class catch_warnings(object):
|
||||
|
||||
"""A context manager that copies and restores the warnings filter upon
|
||||
exiting the context.
|
||||
|
||||
The 'record' argument specifies whether warnings should be captured by a
|
||||
custom implementation of warnings.showwarning() and be appended to a list
|
||||
returned by the context manager. Otherwise None is returned by the context
|
||||
manager. The objects appended to the list are arguments whose attributes
|
||||
mirror the arguments to showwarning().
|
||||
|
||||
The 'module' argument is to specify an alternative module to the module
|
||||
named 'warnings' and imported under that name. This argument is only useful
|
||||
when testing the warnings module itself.
|
||||
|
||||
If the 'action' argument is not None, the remaining arguments are passed
|
||||
to warnings.simplefilter() as if it were called immediately on entering the
|
||||
context.
|
||||
"""
|
||||
|
||||
def __init__(self, *, record=False, module=None,
|
||||
action=None, category=Warning, lineno=0, append=False):
|
||||
"""Specify whether to record warnings and if an alternative module
|
||||
should be used other than sys.modules['warnings'].
|
||||
|
||||
"""
|
||||
self._record = record
|
||||
self._module = sys.modules['warnings'] if module is None else module
|
||||
self._entered = False
|
||||
if action is None:
|
||||
self._filter = None
|
||||
else:
|
||||
self._filter = (action, category, lineno, append)
|
||||
|
||||
def __repr__(self):
|
||||
args = []
|
||||
if self._record:
|
||||
args.append("record=True")
|
||||
if self._module is not sys.modules['warnings']:
|
||||
args.append("module=%r" % self._module)
|
||||
name = type(self).__name__
|
||||
return "%s(%s)" % (name, ", ".join(args))
|
||||
|
||||
def __enter__(self):
|
||||
if self._entered:
|
||||
raise RuntimeError("Cannot enter %r twice" % self)
|
||||
self._entered = True
|
||||
with _wm._lock:
|
||||
if _use_context:
|
||||
self._saved_context, context = self._module._new_context()
|
||||
else:
|
||||
context = None
|
||||
self._filters = self._module.filters
|
||||
self._module.filters = self._filters[:]
|
||||
self._showwarning = self._module.showwarning
|
||||
self._showwarnmsg_impl = self._module._showwarnmsg_impl
|
||||
self._module._filters_mutated_lock_held()
|
||||
if self._record:
|
||||
if _use_context:
|
||||
context.log = log = []
|
||||
else:
|
||||
log = []
|
||||
self._module._showwarnmsg_impl = log.append
|
||||
# Reset showwarning() to the default implementation to make sure
|
||||
# that _showwarnmsg() calls _showwarnmsg_impl()
|
||||
self._module.showwarning = self._module._showwarning_orig
|
||||
else:
|
||||
log = None
|
||||
if self._filter is not None:
|
||||
self._module.simplefilter(*self._filter)
|
||||
return log
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
if not self._entered:
|
||||
raise RuntimeError("Cannot exit %r without entering first" % self)
|
||||
with _wm._lock:
|
||||
if _use_context:
|
||||
self._module._warnings_context.set(self._saved_context)
|
||||
else:
|
||||
self._module.filters = self._filters
|
||||
self._module.showwarning = self._showwarning
|
||||
self._module._showwarnmsg_impl = self._showwarnmsg_impl
|
||||
self._module._filters_mutated_lock_held()
|
||||
|
||||
|
||||
class deprecated:
|
||||
"""Indicate that a class, function or overload is deprecated.
|
||||
|
||||
When this decorator is applied to an object, the type checker
|
||||
will generate a diagnostic on usage of the deprecated object.
|
||||
|
||||
Usage:
|
||||
|
||||
@deprecated("Use B instead")
|
||||
class A:
|
||||
pass
|
||||
|
||||
@deprecated("Use g instead")
|
||||
def f():
|
||||
pass
|
||||
|
||||
@overload
|
||||
@deprecated("int support is deprecated")
|
||||
def g(x: int) -> int: ...
|
||||
@overload
|
||||
def g(x: str) -> int: ...
|
||||
|
||||
The warning specified by *category* will be emitted at runtime
|
||||
on use of deprecated objects. For functions, that happens on calls;
|
||||
for classes, on instantiation and on creation of subclasses.
|
||||
If the *category* is ``None``, no warning is emitted at runtime.
|
||||
The *stacklevel* determines where the
|
||||
warning is emitted. If it is ``1`` (the default), the warning
|
||||
is emitted at the direct caller of the deprecated object; if it
|
||||
is higher, it is emitted further up the stack.
|
||||
Static type checker behavior is not affected by the *category*
|
||||
and *stacklevel* arguments.
|
||||
|
||||
The deprecation message passed to the decorator is saved in the
|
||||
``__deprecated__`` attribute on the decorated object.
|
||||
If applied to an overload, the decorator
|
||||
must be after the ``@overload`` decorator for the attribute to
|
||||
exist on the overload as returned by ``get_overloads()``.
|
||||
|
||||
See PEP 702 for details.
|
||||
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
/,
|
||||
*,
|
||||
category: type[Warning] | None = DeprecationWarning,
|
||||
stacklevel: int = 1,
|
||||
) -> None:
|
||||
if not isinstance(message, str):
|
||||
raise TypeError(
|
||||
f"Expected an object of type str for 'message', not {type(message).__name__!r}"
|
||||
)
|
||||
self.message = message
|
||||
self.category = category
|
||||
self.stacklevel = stacklevel
|
||||
|
||||
def __call__(self, arg, /):
|
||||
# Make sure the inner functions created below don't
|
||||
# retain a reference to self.
|
||||
msg = self.message
|
||||
category = self.category
|
||||
stacklevel = self.stacklevel
|
||||
if category is None:
|
||||
arg.__deprecated__ = msg
|
||||
return arg
|
||||
elif isinstance(arg, type):
|
||||
import functools
|
||||
from types import MethodType
|
||||
|
||||
original_new = arg.__new__
|
||||
|
||||
@functools.wraps(original_new)
|
||||
def __new__(cls, /, *args, **kwargs):
|
||||
if cls is arg:
|
||||
_wm.warn(msg, category=category, stacklevel=stacklevel + 1)
|
||||
if original_new is not object.__new__:
|
||||
return original_new(cls, *args, **kwargs)
|
||||
# Mirrors a similar check in object.__new__.
|
||||
elif cls.__init__ is object.__init__ and (args or kwargs):
|
||||
raise TypeError(f"{cls.__name__}() takes no arguments")
|
||||
else:
|
||||
return original_new(cls)
|
||||
|
||||
arg.__new__ = staticmethod(__new__)
|
||||
|
||||
if "__init_subclass__" in arg.__dict__:
|
||||
# __init_subclass__ is directly present on the decorated class.
|
||||
# Synthesize a wrapper that calls this method directly.
|
||||
original_init_subclass = arg.__init_subclass__
|
||||
# We need slightly different behavior if __init_subclass__
|
||||
# is a bound method (likely if it was implemented in Python).
|
||||
# Otherwise, it likely means it's a builtin such as
|
||||
# object's implementation of __init_subclass__.
|
||||
if isinstance(original_init_subclass, MethodType):
|
||||
original_init_subclass = original_init_subclass.__func__
|
||||
|
||||
@functools.wraps(original_init_subclass)
|
||||
def __init_subclass__(*args, **kwargs):
|
||||
_wm.warn(msg, category=category, stacklevel=stacklevel + 1)
|
||||
return original_init_subclass(*args, **kwargs)
|
||||
else:
|
||||
def __init_subclass__(cls, *args, **kwargs):
|
||||
_wm.warn(msg, category=category, stacklevel=stacklevel + 1)
|
||||
return super(arg, cls).__init_subclass__(*args, **kwargs)
|
||||
|
||||
arg.__init_subclass__ = classmethod(__init_subclass__)
|
||||
|
||||
arg.__deprecated__ = __new__.__deprecated__ = msg
|
||||
__init_subclass__.__deprecated__ = msg
|
||||
return arg
|
||||
elif callable(arg):
|
||||
import functools
|
||||
import inspect
|
||||
|
||||
@functools.wraps(arg)
|
||||
def wrapper(*args, **kwargs):
|
||||
_wm.warn(msg, category=category, stacklevel=stacklevel + 1)
|
||||
return arg(*args, **kwargs)
|
||||
|
||||
if inspect.iscoroutinefunction(arg):
|
||||
wrapper = inspect.markcoroutinefunction(wrapper)
|
||||
|
||||
arg.__deprecated__ = wrapper.__deprecated__ = msg
|
||||
return wrapper
|
||||
else:
|
||||
raise TypeError(
|
||||
"@deprecated decorator with non-None category must be applied to "
|
||||
f"a class or callable, not {arg!r}"
|
||||
)
|
||||
|
||||
|
||||
_DEPRECATED_MSG = "{name!r} is deprecated and slated for removal in Python {remove}"
|
||||
|
||||
|
||||
def _deprecated(name, message=_DEPRECATED_MSG, *, remove, _version=sys.version_info):
|
||||
"""Warn that *name* is deprecated or should be removed.
|
||||
|
||||
RuntimeError is raised if *remove* specifies a major/minor tuple older than
|
||||
the current Python version or the same version but past the alpha.
|
||||
|
||||
The *message* argument is formatted with *name* and *remove* as a Python
|
||||
version tuple (e.g. (3, 11)).
|
||||
|
||||
"""
|
||||
remove_formatted = f"{remove[0]}.{remove[1]}"
|
||||
if (_version[:2] > remove) or (_version[:2] == remove and _version[3] != "alpha"):
|
||||
msg = f"{name!r} was slated for removal after Python {remove_formatted} alpha"
|
||||
raise RuntimeError(msg)
|
||||
else:
|
||||
msg = message.format(name=name, remove=remove_formatted)
|
||||
_wm.warn(msg, DeprecationWarning, stacklevel=3)
|
||||
|
||||
|
||||
# Private utility function called by _PyErr_WarnUnawaitedCoroutine
|
||||
def _warn_unawaited_coroutine(coro):
|
||||
msg_lines = [
|
||||
f"coroutine '{coro.__qualname__}' was never awaited\n"
|
||||
]
|
||||
if coro.cr_origin is not None:
|
||||
import linecache, traceback
|
||||
def extract():
|
||||
for filename, lineno, funcname in reversed(coro.cr_origin):
|
||||
line = linecache.getline(filename, lineno)
|
||||
yield (filename, lineno, funcname, line)
|
||||
msg_lines.append("Coroutine created at (most recent call last)\n")
|
||||
msg_lines += traceback.format_list(list(extract()))
|
||||
msg = "".join(msg_lines).rstrip("\n")
|
||||
# Passing source= here means that if the user happens to have tracemalloc
|
||||
# enabled and tracking where the coroutine was created, the warning will
|
||||
# contain that traceback. This does mean that if they have *both*
|
||||
# coroutine origin tracking *and* tracemalloc enabled, they'll get two
|
||||
# partially-redundant tracebacks. If we wanted to be clever we could
|
||||
# probably detect this case and avoid it, but for now we don't bother.
|
||||
_wm.warn(
|
||||
msg, category=RuntimeWarning, stacklevel=2, source=coro
|
||||
)
|
||||
|
||||
|
||||
def _setup_defaults():
|
||||
# Several warning categories are ignored by default in regular builds
|
||||
if hasattr(sys, 'gettotalrefcount'):
|
||||
return
|
||||
_wm.filterwarnings("default", category=DeprecationWarning, module="__main__", append=1)
|
||||
_wm.simplefilter("ignore", category=DeprecationWarning, append=1)
|
||||
_wm.simplefilter("ignore", category=PendingDeprecationWarning, append=1)
|
||||
_wm.simplefilter("ignore", category=ImportWarning, append=1)
|
||||
_wm.simplefilter("ignore", category=ResourceWarning, append=1)
|
||||
2013
Lib/_pycodecs.py
vendored
2013
Lib/_pycodecs.py
vendored
File diff suppressed because it is too large
Load Diff
2723
Lib/_pydatetime.py
vendored
Normal file
2723
Lib/_pydatetime.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
374
Lib/_pydecimal.py
vendored
374
Lib/_pydecimal.py
vendored
@@ -13,104 +13,7 @@
|
||||
# bug) and will be backported. At this point the spec is stabilizing
|
||||
# and the updates are becoming fewer, smaller, and less significant.
|
||||
|
||||
"""
|
||||
This is an implementation of decimal floating point arithmetic based on
|
||||
the General Decimal Arithmetic Specification:
|
||||
|
||||
http://speleotrove.com/decimal/decarith.html
|
||||
|
||||
and IEEE standard 854-1987:
|
||||
|
||||
http://en.wikipedia.org/wiki/IEEE_854-1987
|
||||
|
||||
Decimal floating point has finite precision with arbitrarily large bounds.
|
||||
|
||||
The purpose of this module is to support arithmetic using familiar
|
||||
"schoolhouse" rules and to avoid some of the tricky representation
|
||||
issues associated with binary floating point. The package is especially
|
||||
useful for financial applications or for contexts where users have
|
||||
expectations that are at odds with binary floating point (for instance,
|
||||
in binary floating point, 1.00 % 0.1 gives 0.09999999999999995 instead
|
||||
of 0.0; Decimal('1.00') % Decimal('0.1') returns the expected
|
||||
Decimal('0.00')).
|
||||
|
||||
Here are some examples of using the decimal module:
|
||||
|
||||
>>> from decimal import *
|
||||
>>> setcontext(ExtendedContext)
|
||||
>>> Decimal(0)
|
||||
Decimal('0')
|
||||
>>> Decimal('1')
|
||||
Decimal('1')
|
||||
>>> Decimal('-.0123')
|
||||
Decimal('-0.0123')
|
||||
>>> Decimal(123456)
|
||||
Decimal('123456')
|
||||
>>> Decimal('123.45e12345678')
|
||||
Decimal('1.2345E+12345680')
|
||||
>>> Decimal('1.33') + Decimal('1.27')
|
||||
Decimal('2.60')
|
||||
>>> Decimal('12.34') + Decimal('3.87') - Decimal('18.41')
|
||||
Decimal('-2.20')
|
||||
>>> dig = Decimal(1)
|
||||
>>> print(dig / Decimal(3))
|
||||
0.333333333
|
||||
>>> getcontext().prec = 18
|
||||
>>> print(dig / Decimal(3))
|
||||
0.333333333333333333
|
||||
>>> print(dig.sqrt())
|
||||
1
|
||||
>>> print(Decimal(3).sqrt())
|
||||
1.73205080756887729
|
||||
>>> print(Decimal(3) ** 123)
|
||||
4.85192780976896427E+58
|
||||
>>> inf = Decimal(1) / Decimal(0)
|
||||
>>> print(inf)
|
||||
Infinity
|
||||
>>> neginf = Decimal(-1) / Decimal(0)
|
||||
>>> print(neginf)
|
||||
-Infinity
|
||||
>>> print(neginf + inf)
|
||||
NaN
|
||||
>>> print(neginf * inf)
|
||||
-Infinity
|
||||
>>> print(dig / 0)
|
||||
Infinity
|
||||
>>> getcontext().traps[DivisionByZero] = 1
|
||||
>>> print(dig / 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
...
|
||||
...
|
||||
decimal.DivisionByZero: x / 0
|
||||
>>> c = Context()
|
||||
>>> c.traps[InvalidOperation] = 0
|
||||
>>> print(c.flags[InvalidOperation])
|
||||
0
|
||||
>>> c.divide(Decimal(0), Decimal(0))
|
||||
Decimal('NaN')
|
||||
>>> c.traps[InvalidOperation] = 1
|
||||
>>> print(c.flags[InvalidOperation])
|
||||
1
|
||||
>>> c.flags[InvalidOperation] = 0
|
||||
>>> print(c.flags[InvalidOperation])
|
||||
0
|
||||
>>> print(c.divide(Decimal(0), Decimal(0)))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
...
|
||||
...
|
||||
decimal.InvalidOperation: 0 / 0
|
||||
>>> print(c.flags[InvalidOperation])
|
||||
1
|
||||
>>> c.flags[InvalidOperation] = 0
|
||||
>>> c.traps[InvalidOperation] = 0
|
||||
>>> print(c.divide(Decimal(0), Decimal(0)))
|
||||
NaN
|
||||
>>> print(c.flags[InvalidOperation])
|
||||
1
|
||||
>>>
|
||||
"""
|
||||
"""Python decimal arithmetic module"""
|
||||
|
||||
__all__ = [
|
||||
# Two major classes
|
||||
@@ -135,13 +38,16 @@ __all__ = [
|
||||
'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN', 'ROUND_05UP',
|
||||
|
||||
# Functions for manipulating contexts
|
||||
'setcontext', 'getcontext', 'localcontext',
|
||||
'setcontext', 'getcontext', 'localcontext', 'IEEEContext',
|
||||
|
||||
# Limits for the C version for compatibility
|
||||
'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY',
|
||||
'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY', 'IEEE_CONTEXT_MAX_BITS',
|
||||
|
||||
# C version: compile time choice that enables the thread local context
|
||||
'HAVE_THREADS'
|
||||
# C version: compile time choice that enables the thread local context (deprecated, now always true)
|
||||
'HAVE_THREADS',
|
||||
|
||||
# C version: compile time choice that enables the coroutine local context
|
||||
'HAVE_CONTEXTVAR'
|
||||
]
|
||||
|
||||
__xname__ = __name__ # sys.modules lookup (--without-threads)
|
||||
@@ -156,7 +62,7 @@ import sys
|
||||
|
||||
try:
|
||||
from collections import namedtuple as _namedtuple
|
||||
DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent')
|
||||
DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent', module='decimal')
|
||||
except ImportError:
|
||||
DecimalTuple = lambda *args: args
|
||||
|
||||
@@ -172,14 +78,17 @@ ROUND_05UP = 'ROUND_05UP'
|
||||
|
||||
# Compatibility with the C version
|
||||
HAVE_THREADS = True
|
||||
HAVE_CONTEXTVAR = True
|
||||
if sys.maxsize == 2**63-1:
|
||||
MAX_PREC = 999999999999999999
|
||||
MAX_EMAX = 999999999999999999
|
||||
MIN_EMIN = -999999999999999999
|
||||
IEEE_CONTEXT_MAX_BITS = 512
|
||||
else:
|
||||
MAX_PREC = 425000000
|
||||
MAX_EMAX = 425000000
|
||||
MIN_EMIN = -425000000
|
||||
IEEE_CONTEXT_MAX_BITS = 256
|
||||
|
||||
MIN_ETINY = MIN_EMIN - (MAX_PREC-1)
|
||||
|
||||
@@ -190,7 +99,7 @@ class DecimalException(ArithmeticError):
|
||||
|
||||
Used exceptions derive from this.
|
||||
If an exception derives from another exception besides this (such as
|
||||
Underflow (Inexact, Rounded, Subnormal) that indicates that it is only
|
||||
Underflow (Inexact, Rounded, Subnormal)) that indicates that it is only
|
||||
called if the others are present. This isn't actually used for
|
||||
anything, though.
|
||||
|
||||
@@ -238,7 +147,7 @@ class InvalidOperation(DecimalException):
|
||||
x ** (+-)INF
|
||||
An operand is invalid
|
||||
|
||||
The result of the operation after these is a quiet positive NaN,
|
||||
The result of the operation after this is a quiet positive NaN,
|
||||
except when the cause is a signaling NaN, in which case the result is
|
||||
also a quiet NaN, but with the original sign, and an optional
|
||||
diagnostic information.
|
||||
@@ -431,82 +340,40 @@ _rounding_modes = (ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_CEILING,
|
||||
##### Context Functions ##################################################
|
||||
|
||||
# The getcontext() and setcontext() function manage access to a thread-local
|
||||
# current context. Py2.4 offers direct support for thread locals. If that
|
||||
# is not available, use threading.current_thread() which is slower but will
|
||||
# work for older Pythons. If threads are not part of the build, create a
|
||||
# mock threading object with threading.local() returning the module namespace.
|
||||
# current context.
|
||||
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
# Python was compiled without threads; create a mock object instead
|
||||
class MockThreading(object):
|
||||
def local(self, sys=sys):
|
||||
return sys.modules[__xname__]
|
||||
threading = MockThreading()
|
||||
del MockThreading
|
||||
import contextvars
|
||||
|
||||
try:
|
||||
threading.local
|
||||
_current_context_var = contextvars.ContextVar('decimal_context')
|
||||
|
||||
except AttributeError:
|
||||
_context_attributes = frozenset(
|
||||
['prec', 'Emin', 'Emax', 'capitals', 'clamp', 'rounding', 'flags', 'traps']
|
||||
)
|
||||
|
||||
# To fix reloading, force it to create a new context
|
||||
# Old contexts have different exceptions in their dicts, making problems.
|
||||
if hasattr(threading.current_thread(), '__decimal_context__'):
|
||||
del threading.current_thread().__decimal_context__
|
||||
def getcontext():
|
||||
"""Returns this thread's context.
|
||||
|
||||
def setcontext(context):
|
||||
"""Set this thread's context to context."""
|
||||
if context in (DefaultContext, BasicContext, ExtendedContext):
|
||||
context = context.copy()
|
||||
context.clear_flags()
|
||||
threading.current_thread().__decimal_context__ = context
|
||||
If this thread does not yet have a context, returns
|
||||
a new context and sets this thread's context.
|
||||
New contexts are copies of DefaultContext.
|
||||
"""
|
||||
try:
|
||||
return _current_context_var.get()
|
||||
except LookupError:
|
||||
context = Context()
|
||||
_current_context_var.set(context)
|
||||
return context
|
||||
|
||||
def getcontext():
|
||||
"""Returns this thread's context.
|
||||
def setcontext(context):
|
||||
"""Set this thread's context to context."""
|
||||
if context in (DefaultContext, BasicContext, ExtendedContext):
|
||||
context = context.copy()
|
||||
context.clear_flags()
|
||||
_current_context_var.set(context)
|
||||
|
||||
If this thread does not yet have a context, returns
|
||||
a new context and sets this thread's context.
|
||||
New contexts are copies of DefaultContext.
|
||||
"""
|
||||
try:
|
||||
return threading.current_thread().__decimal_context__
|
||||
except AttributeError:
|
||||
context = Context()
|
||||
threading.current_thread().__decimal_context__ = context
|
||||
return context
|
||||
del contextvars # Don't contaminate the namespace
|
||||
|
||||
else:
|
||||
|
||||
local = threading.local()
|
||||
if hasattr(local, '__decimal_context__'):
|
||||
del local.__decimal_context__
|
||||
|
||||
def getcontext(_local=local):
|
||||
"""Returns this thread's context.
|
||||
|
||||
If this thread does not yet have a context, returns
|
||||
a new context and sets this thread's context.
|
||||
New contexts are copies of DefaultContext.
|
||||
"""
|
||||
try:
|
||||
return _local.__decimal_context__
|
||||
except AttributeError:
|
||||
context = Context()
|
||||
_local.__decimal_context__ = context
|
||||
return context
|
||||
|
||||
def setcontext(context, _local=local):
|
||||
"""Set this thread's context to context."""
|
||||
if context in (DefaultContext, BasicContext, ExtendedContext):
|
||||
context = context.copy()
|
||||
context.clear_flags()
|
||||
_local.__decimal_context__ = context
|
||||
|
||||
del threading, local # Don't contaminate the namespace
|
||||
|
||||
def localcontext(ctx=None):
|
||||
def localcontext(ctx=None, **kwargs):
|
||||
"""Return a context manager for a copy of the supplied context
|
||||
|
||||
Uses a copy of the current context if no context is specified
|
||||
@@ -542,8 +409,35 @@ def localcontext(ctx=None):
|
||||
>>> print(getcontext().prec)
|
||||
28
|
||||
"""
|
||||
if ctx is None: ctx = getcontext()
|
||||
return _ContextManager(ctx)
|
||||
if ctx is None:
|
||||
ctx = getcontext()
|
||||
ctx_manager = _ContextManager(ctx)
|
||||
for key, value in kwargs.items():
|
||||
if key not in _context_attributes:
|
||||
raise TypeError(f"'{key}' is an invalid keyword argument for this function")
|
||||
setattr(ctx_manager.new_context, key, value)
|
||||
return ctx_manager
|
||||
|
||||
|
||||
def IEEEContext(bits, /):
|
||||
"""
|
||||
Return a context object initialized to the proper values for one of the
|
||||
IEEE interchange formats. The argument must be a multiple of 32 and less
|
||||
than IEEE_CONTEXT_MAX_BITS.
|
||||
"""
|
||||
if bits <= 0 or bits > IEEE_CONTEXT_MAX_BITS or bits % 32:
|
||||
raise ValueError("argument must be a multiple of 32, "
|
||||
f"with a maximum of {IEEE_CONTEXT_MAX_BITS}")
|
||||
|
||||
ctx = Context()
|
||||
ctx.prec = 9 * (bits//32) - 2
|
||||
ctx.Emax = 3 * (1 << (bits//16 + 3))
|
||||
ctx.Emin = 1 - ctx.Emax
|
||||
ctx.rounding = ROUND_HALF_EVEN
|
||||
ctx.clamp = 1
|
||||
ctx.traps = dict.fromkeys(_signals, False)
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
##### Decimal class #######################################################
|
||||
@@ -553,7 +447,7 @@ def localcontext(ctx=None):
|
||||
# numbers.py for more detail.
|
||||
|
||||
class Decimal(object):
|
||||
"""Floating point class for decimal arithmetic."""
|
||||
"""Floating-point class for decimal arithmetic."""
|
||||
|
||||
__slots__ = ('_exp','_int','_sign', '_is_special')
|
||||
# Generally, the value of the Decimal instance is given by
|
||||
@@ -711,6 +605,21 @@ class Decimal(object):
|
||||
|
||||
raise TypeError("Cannot convert %r to Decimal" % value)
|
||||
|
||||
@classmethod
|
||||
def from_number(cls, number):
|
||||
"""Converts a real number to a decimal number, exactly.
|
||||
|
||||
>>> Decimal.from_number(314) # int
|
||||
Decimal('314')
|
||||
>>> Decimal.from_number(0.1) # float
|
||||
Decimal('0.1000000000000000055511151231257827021181583404541015625')
|
||||
>>> Decimal.from_number(Decimal('3.14')) # another decimal instance
|
||||
Decimal('3.14')
|
||||
"""
|
||||
if isinstance(number, (int, Decimal, float)):
|
||||
return cls(number)
|
||||
raise TypeError("Cannot convert %r to Decimal" % number)
|
||||
|
||||
@classmethod
|
||||
def from_float(cls, f):
|
||||
"""Converts a float to a decimal number, exactly.
|
||||
@@ -734,18 +643,23 @@ class Decimal(object):
|
||||
|
||||
"""
|
||||
if isinstance(f, int): # handle integer inputs
|
||||
return cls(f)
|
||||
if not isinstance(f, float):
|
||||
raise TypeError("argument must be int or float.")
|
||||
if _math.isinf(f) or _math.isnan(f):
|
||||
return cls(repr(f))
|
||||
if _math.copysign(1.0, f) == 1.0:
|
||||
sign = 0
|
||||
sign = 0 if f >= 0 else 1
|
||||
k = 0
|
||||
coeff = str(abs(f))
|
||||
elif isinstance(f, float):
|
||||
if _math.isinf(f) or _math.isnan(f):
|
||||
return cls(repr(f))
|
||||
if _math.copysign(1.0, f) == 1.0:
|
||||
sign = 0
|
||||
else:
|
||||
sign = 1
|
||||
n, d = abs(f).as_integer_ratio()
|
||||
k = d.bit_length() - 1
|
||||
coeff = str(n*5**k)
|
||||
else:
|
||||
sign = 1
|
||||
n, d = abs(f).as_integer_ratio()
|
||||
k = d.bit_length() - 1
|
||||
result = _dec_from_triple(sign, str(n*5**k), -k)
|
||||
raise TypeError("argument must be int or float.")
|
||||
|
||||
result = _dec_from_triple(sign, coeff, -k)
|
||||
if cls is Decimal:
|
||||
return result
|
||||
else:
|
||||
@@ -988,7 +902,7 @@ class Decimal(object):
|
||||
if self.is_snan():
|
||||
raise TypeError('Cannot hash a signaling NaN value.')
|
||||
elif self.is_nan():
|
||||
return _PyHASH_NAN
|
||||
return object.__hash__(self)
|
||||
else:
|
||||
if self._sign:
|
||||
return -_PyHASH_INF
|
||||
@@ -1669,13 +1583,13 @@ class Decimal(object):
|
||||
|
||||
__trunc__ = __int__
|
||||
|
||||
@property
|
||||
def real(self):
|
||||
return self
|
||||
real = property(real)
|
||||
|
||||
@property
|
||||
def imag(self):
|
||||
return Decimal(0)
|
||||
imag = property(imag)
|
||||
|
||||
def conjugate(self):
|
||||
return self
|
||||
@@ -2255,10 +2169,16 @@ class Decimal(object):
|
||||
else:
|
||||
return None
|
||||
|
||||
if xc >= 10**p:
|
||||
# An exact power of 10 is representable, but can convert to a
|
||||
# string of any length. But an exact power of 10 shouldn't be
|
||||
# possible at this point.
|
||||
assert xc > 1, self
|
||||
assert xc % 10 != 0, self
|
||||
strxc = str(xc)
|
||||
if len(strxc) > p:
|
||||
return None
|
||||
xe = -e-xe
|
||||
return _dec_from_triple(0, str(xc), xe)
|
||||
return _dec_from_triple(0, strxc, xe)
|
||||
|
||||
# now y is positive; find m and n such that y = m/n
|
||||
if ye >= 0:
|
||||
@@ -2267,7 +2187,7 @@ class Decimal(object):
|
||||
if xe != 0 and len(str(abs(yc*xe))) <= -ye:
|
||||
return None
|
||||
xc_bits = _nbits(xc)
|
||||
if xc != 1 and len(str(abs(yc)*xc_bits)) <= -ye:
|
||||
if len(str(abs(yc)*xc_bits)) <= -ye:
|
||||
return None
|
||||
m, n = yc, 10**(-ye)
|
||||
while m % 2 == n % 2 == 0:
|
||||
@@ -2280,7 +2200,7 @@ class Decimal(object):
|
||||
# compute nth root of xc*10**xe
|
||||
if n > 1:
|
||||
# if 1 < xc < 2**n then xc isn't an nth power
|
||||
if xc != 1 and xc_bits <= n:
|
||||
if xc_bits <= n:
|
||||
return None
|
||||
|
||||
xe, rem = divmod(xe, n)
|
||||
@@ -2308,13 +2228,18 @@ class Decimal(object):
|
||||
return None
|
||||
xc = xc**m
|
||||
xe *= m
|
||||
if xc > 10**p:
|
||||
# An exact power of 10 is representable, but can convert to a string
|
||||
# of any length. But an exact power of 10 shouldn't be possible at
|
||||
# this point.
|
||||
assert xc > 1, self
|
||||
assert xc % 10 != 0, self
|
||||
str_xc = str(xc)
|
||||
if len(str_xc) > p:
|
||||
return None
|
||||
|
||||
# by this point the result *is* exactly representable
|
||||
# adjust the exponent to get as close as possible to the ideal
|
||||
# exponent, if necessary
|
||||
str_xc = str(xc)
|
||||
if other._isinteger() and other._sign == 0:
|
||||
ideal_exponent = self._exp*int(other)
|
||||
zeros = min(xe-ideal_exponent, p-len(str_xc))
|
||||
@@ -2538,12 +2463,12 @@ class Decimal(object):
|
||||
|
||||
return ans
|
||||
|
||||
def __rpow__(self, other, context=None):
|
||||
def __rpow__(self, other, modulo=None, context=None):
|
||||
"""Swaps self/other and returns __pow__."""
|
||||
other = _convert_other(other)
|
||||
if other is NotImplemented:
|
||||
return other
|
||||
return other.__pow__(self, context=context)
|
||||
return other.__pow__(self, modulo, context=context)
|
||||
|
||||
def normalize(self, context=None):
|
||||
"""Normalize- strip trailing 0s, change anything equal to 0 to 0e0"""
|
||||
@@ -3415,7 +3340,10 @@ class Decimal(object):
|
||||
return opa, opb
|
||||
|
||||
def logical_and(self, other, context=None):
|
||||
"""Applies an 'and' operation between self and other's digits."""
|
||||
"""Applies an 'and' operation between self and other's digits.
|
||||
|
||||
Both self and other must be logical numbers.
|
||||
"""
|
||||
if context is None:
|
||||
context = getcontext()
|
||||
|
||||
@@ -3432,14 +3360,20 @@ class Decimal(object):
|
||||
return _dec_from_triple(0, result.lstrip('0') or '0', 0)
|
||||
|
||||
def logical_invert(self, context=None):
|
||||
"""Invert all its digits."""
|
||||
"""Invert all its digits.
|
||||
|
||||
The self must be logical number.
|
||||
"""
|
||||
if context is None:
|
||||
context = getcontext()
|
||||
return self.logical_xor(_dec_from_triple(0,'1'*context.prec,0),
|
||||
context)
|
||||
|
||||
def logical_or(self, other, context=None):
|
||||
"""Applies an 'or' operation between self and other's digits."""
|
||||
"""Applies an 'or' operation between self and other's digits.
|
||||
|
||||
Both self and other must be logical numbers.
|
||||
"""
|
||||
if context is None:
|
||||
context = getcontext()
|
||||
|
||||
@@ -3456,7 +3390,10 @@ class Decimal(object):
|
||||
return _dec_from_triple(0, result.lstrip('0') or '0', 0)
|
||||
|
||||
def logical_xor(self, other, context=None):
|
||||
"""Applies an 'xor' operation between self and other's digits."""
|
||||
"""Applies an 'xor' operation between self and other's digits.
|
||||
|
||||
Both self and other must be logical numbers.
|
||||
"""
|
||||
if context is None:
|
||||
context = getcontext()
|
||||
|
||||
@@ -3832,6 +3769,10 @@ class Decimal(object):
|
||||
# represented in fixed point; rescale them to 0e0.
|
||||
if not self and self._exp > 0 and spec['type'] in 'fF%':
|
||||
self = self._rescale(0, rounding)
|
||||
if not self and spec['no_neg_0'] and self._sign:
|
||||
adjusted_sign = 0
|
||||
else:
|
||||
adjusted_sign = self._sign
|
||||
|
||||
# figure out placement of the decimal point
|
||||
leftdigits = self._exp + len(self._int)
|
||||
@@ -3862,7 +3803,7 @@ class Decimal(object):
|
||||
|
||||
# done with the decimal-specific stuff; hand over the rest
|
||||
# of the formatting to the _format_number function
|
||||
return _format_number(self._sign, intpart, fracpart, exp, spec)
|
||||
return _format_number(adjusted_sign, intpart, fracpart, exp, spec)
|
||||
|
||||
def _dec_from_triple(sign, coefficient, exponent, special=False):
|
||||
"""Create a decimal instance directly, without any validation,
|
||||
@@ -5672,8 +5613,6 @@ class _WorkRep(object):
|
||||
def __repr__(self):
|
||||
return "(%r, %r, %r)" % (self.sign, self.int, self.exp)
|
||||
|
||||
__str__ = __repr__
|
||||
|
||||
|
||||
|
||||
def _normalize(op1, op2, prec = 0):
|
||||
@@ -6169,7 +6108,7 @@ _parser = re.compile(r""" # A numeric string consists of:
|
||||
(?P<diag>\d*) # with (possibly empty) diagnostic info.
|
||||
)
|
||||
# \s*
|
||||
\Z
|
||||
\z
|
||||
""", re.VERBOSE | re.IGNORECASE).match
|
||||
|
||||
_all_zeros = re.compile('0*$').match
|
||||
@@ -6182,7 +6121,7 @@ _exact_half = re.compile('50*$').match
|
||||
#
|
||||
# A format specifier for Decimal looks like:
|
||||
#
|
||||
# [[fill]align][sign][#][0][minimumwidth][,][.precision][type]
|
||||
# [[fill]align][sign][z][#][0][minimumwidth][,][.precision][type]
|
||||
|
||||
_parse_format_specifier_regex = re.compile(r"""\A
|
||||
(?:
|
||||
@@ -6190,13 +6129,18 @@ _parse_format_specifier_regex = re.compile(r"""\A
|
||||
(?P<align>[<>=^])
|
||||
)?
|
||||
(?P<sign>[-+ ])?
|
||||
(?P<no_neg_0>z)?
|
||||
(?P<alt>\#)?
|
||||
(?P<zeropad>0)?
|
||||
(?P<minimumwidth>(?!0)\d+)?
|
||||
(?P<thousands_sep>,)?
|
||||
(?:\.(?P<precision>0|(?!0)\d+))?
|
||||
(?P<minimumwidth>\d+)?
|
||||
(?P<thousands_sep>[,_])?
|
||||
(?:\.
|
||||
(?=[\d,_]) # lookahead for digit or separator
|
||||
(?P<precision>\d+)?
|
||||
(?P<frac_separators>[,_])?
|
||||
)?
|
||||
(?P<type>[eEfFgGn%])?
|
||||
\Z
|
||||
\z
|
||||
""", re.VERBOSE|re.DOTALL)
|
||||
|
||||
del re
|
||||
@@ -6287,6 +6231,9 @@ def _parse_format_specifier(format_spec, _localeconv=None):
|
||||
format_dict['grouping'] = [3, 0]
|
||||
format_dict['decimal_point'] = '.'
|
||||
|
||||
if format_dict['frac_separators'] is None:
|
||||
format_dict['frac_separators'] = ''
|
||||
|
||||
return format_dict
|
||||
|
||||
def _format_align(sign, body, spec):
|
||||
@@ -6406,6 +6353,11 @@ def _format_number(is_negative, intpart, fracpart, exp, spec):
|
||||
|
||||
sign = _format_sign(is_negative, spec)
|
||||
|
||||
frac_sep = spec['frac_separators']
|
||||
if fracpart and frac_sep:
|
||||
fracpart = frac_sep.join(fracpart[pos:pos + 3]
|
||||
for pos in range(0, len(fracpart), 3))
|
||||
|
||||
if fracpart or spec['alt']:
|
||||
fracpart = spec['decimal_point'] + fracpart
|
||||
|
||||
|
||||
331
Lib/_pyio.py
vendored
331
Lib/_pyio.py
vendored
@@ -16,15 +16,16 @@ else:
|
||||
_setmode = None
|
||||
|
||||
import io
|
||||
from io import (__all__, SEEK_SET, SEEK_CUR, SEEK_END)
|
||||
from io import (__all__, SEEK_SET, SEEK_CUR, SEEK_END, Reader, Writer) # noqa: F401
|
||||
|
||||
valid_seek_flags = {0, 1, 2} # Hardwired values
|
||||
if hasattr(os, 'SEEK_HOLE') :
|
||||
valid_seek_flags.add(os.SEEK_HOLE)
|
||||
valid_seek_flags.add(os.SEEK_DATA)
|
||||
|
||||
# open() uses st_blksize whenever we can
|
||||
DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes
|
||||
# open() uses max(min(blocksize, 8 MiB), DEFAULT_BUFFER_SIZE)
|
||||
# when the device block size is available.
|
||||
DEFAULT_BUFFER_SIZE = 128 * 1024 # bytes
|
||||
|
||||
# NOTE: Base classes defined here are registered with the "official" ABCs
|
||||
# defined in io.py. We don't use real inheritance though, because we don't want
|
||||
@@ -33,19 +34,17 @@ DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes
|
||||
# Rebind for compatibility
|
||||
BlockingIOError = BlockingIOError
|
||||
|
||||
# Does io.IOBase finalizer log the exception if the close() method fails?
|
||||
# The exception is ignored silently by default in release build.
|
||||
_IOBASE_EMITS_UNRAISABLE = (hasattr(sys, "gettotalrefcount") or sys.flags.dev_mode)
|
||||
# Does open() check its 'errors' argument?
|
||||
_CHECK_ERRORS = _IOBASE_EMITS_UNRAISABLE
|
||||
_CHECK_ERRORS = (hasattr(sys, "gettotalrefcount") or sys.flags.dev_mode)
|
||||
|
||||
|
||||
def text_encoding(encoding, stacklevel=2):
|
||||
"""
|
||||
A helper function to choose the text encoding.
|
||||
|
||||
When encoding is not None, just return it.
|
||||
Otherwise, return the default text encoding (i.e. "locale").
|
||||
When encoding is not None, this function returns it.
|
||||
Otherwise, this function returns the default text encoding
|
||||
(i.e. "locale" or "utf-8" depends on UTF-8 mode).
|
||||
|
||||
This function emits an EncodingWarning if *encoding* is None and
|
||||
sys.flags.warn_default_encoding is true.
|
||||
@@ -55,7 +54,10 @@ def text_encoding(encoding, stacklevel=2):
|
||||
However, please consider using encoding="utf-8" for new APIs.
|
||||
"""
|
||||
if encoding is None:
|
||||
encoding = "locale"
|
||||
if sys.flags.utf8_mode:
|
||||
encoding = "utf-8"
|
||||
else:
|
||||
encoding = "locale"
|
||||
if sys.flags.warn_default_encoding:
|
||||
import warnings
|
||||
warnings.warn("'encoding' argument not specified.",
|
||||
@@ -101,7 +103,6 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
||||
'b' binary mode
|
||||
't' text mode (default)
|
||||
'+' open a disk file for updating (reading and writing)
|
||||
'U' universal newline mode (deprecated)
|
||||
========= ===============================================================
|
||||
|
||||
The default mode is 'rt' (open for reading text). For binary random
|
||||
@@ -117,20 +118,16 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
||||
returned as strings, the bytes having been first decoded using a
|
||||
platform-dependent encoding or using the specified encoding if given.
|
||||
|
||||
'U' mode is deprecated and will raise an exception in future versions
|
||||
of Python. It has no effect in Python 3. Use newline to control
|
||||
universal newlines mode.
|
||||
|
||||
buffering is an optional integer used to set the buffering policy.
|
||||
Pass 0 to switch buffering off (only allowed in binary mode), 1 to select
|
||||
line buffering (only usable in text mode), and an integer > 1 to indicate
|
||||
the size of a fixed-size chunk buffer. When no buffering argument is
|
||||
given, the default buffering policy works as follows:
|
||||
|
||||
* Binary files are buffered in fixed-size chunks; the size of the buffer
|
||||
is chosen using a heuristic trying to determine the underlying device's
|
||||
"block size" and falling back on `io.DEFAULT_BUFFER_SIZE`.
|
||||
On many systems, the buffer will typically be 4096 or 8192 bytes long.
|
||||
* Binary files are buffered in fixed-size chunks; the size of the buffer
|
||||
is max(min(blocksize, 8 MiB), DEFAULT_BUFFER_SIZE)
|
||||
when the device block size is available.
|
||||
On most systems, the buffer will typically be 128 kilobytes long.
|
||||
|
||||
* "Interactive" text files (files for which isatty() returns True)
|
||||
use line buffering. Other text files use the policy described above
|
||||
@@ -206,7 +203,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
||||
if errors is not None and not isinstance(errors, str):
|
||||
raise TypeError("invalid errors: %r" % errors)
|
||||
modes = set(mode)
|
||||
if modes - set("axrwb+tU") or len(mode) > len(modes):
|
||||
if modes - set("axrwb+t") or len(mode) > len(modes):
|
||||
raise ValueError("invalid mode: %r" % mode)
|
||||
creating = "x" in modes
|
||||
reading = "r" in modes
|
||||
@@ -215,13 +212,6 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
||||
updating = "+" in modes
|
||||
text = "t" in modes
|
||||
binary = "b" in modes
|
||||
if "U" in modes:
|
||||
if creating or writing or appending or updating:
|
||||
raise ValueError("mode U cannot be combined with 'x', 'w', 'a', or '+'")
|
||||
import warnings
|
||||
warnings.warn("'U' mode is deprecated",
|
||||
DeprecationWarning, 2)
|
||||
reading = True
|
||||
if text and binary:
|
||||
raise ValueError("can't have text and binary mode at once")
|
||||
if creating + reading + writing + appending > 1:
|
||||
@@ -249,18 +239,11 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
||||
result = raw
|
||||
try:
|
||||
line_buffering = False
|
||||
if buffering == 1 or buffering < 0 and raw.isatty():
|
||||
if buffering == 1 or buffering < 0 and raw._isatty_open_only():
|
||||
buffering = -1
|
||||
line_buffering = True
|
||||
if buffering < 0:
|
||||
buffering = DEFAULT_BUFFER_SIZE
|
||||
try:
|
||||
bs = os.fstat(raw.fileno()).st_blksize
|
||||
except (OSError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
if bs > 1:
|
||||
buffering = bs
|
||||
buffering = max(min(raw._blksize, 8192 * 1024), DEFAULT_BUFFER_SIZE)
|
||||
if buffering < 0:
|
||||
raise ValueError("invalid buffering size")
|
||||
if buffering == 0:
|
||||
@@ -311,22 +294,6 @@ except AttributeError:
|
||||
open_code = _open_code_with_warning
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "OpenWrapper":
|
||||
# bpo-43680: Until Python 3.9, _pyio.open was not a static method and
|
||||
# builtins.open was set to OpenWrapper to not become a bound method
|
||||
# when set to a class variable. _io.open is a built-in function whereas
|
||||
# _pyio.open is a Python function. In Python 3.10, _pyio.open() is now
|
||||
# a static method, and builtins.open() is now io.open().
|
||||
import warnings
|
||||
warnings.warn('OpenWrapper is deprecated, use open instead',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
global OpenWrapper
|
||||
OpenWrapper = open
|
||||
return OpenWrapper
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
# In normal operation, both `UnsupportedOperation`s should be bound to the
|
||||
# same object.
|
||||
try:
|
||||
@@ -338,8 +305,7 @@ except AttributeError:
|
||||
|
||||
class IOBase(metaclass=abc.ABCMeta):
|
||||
|
||||
"""The abstract base class for all I/O classes, acting on streams of
|
||||
bytes. There is no public constructor.
|
||||
"""The abstract base class for all I/O classes.
|
||||
|
||||
This class provides dummy implementations for many methods that
|
||||
derived classes can override selectively; the default implementations
|
||||
@@ -441,18 +407,12 @@ class IOBase(metaclass=abc.ABCMeta):
|
||||
if closed:
|
||||
return
|
||||
|
||||
if _IOBASE_EMITS_UNRAISABLE:
|
||||
self.close()
|
||||
else:
|
||||
# The try/except block is in case this is called at program
|
||||
# exit time, when it's possible that globals have already been
|
||||
# deleted, and then the close() call might fail. Since
|
||||
# there's nothing we can do about such failures and they annoy
|
||||
# the end users, we suppress the traceback.
|
||||
try:
|
||||
self.close()
|
||||
except:
|
||||
pass
|
||||
if dealloc_warn := getattr(self, "_dealloc_warn", None):
|
||||
dealloc_warn(self)
|
||||
|
||||
# If close() fails, the caller logs the exception with
|
||||
# sys.unraisablehook. close() must be called at the end at __del__().
|
||||
self.close()
|
||||
|
||||
### Inquiries ###
|
||||
|
||||
@@ -657,16 +617,15 @@ class RawIOBase(IOBase):
|
||||
n = self.readinto(b)
|
||||
if n is None:
|
||||
return None
|
||||
if n < 0 or n > len(b):
|
||||
raise ValueError(f"readinto returned {n} outside buffer size {len(b)}")
|
||||
del b[n:]
|
||||
return bytes(b)
|
||||
|
||||
def readall(self):
|
||||
"""Read until EOF, using multiple read() call."""
|
||||
res = bytearray()
|
||||
while True:
|
||||
data = self.read(DEFAULT_BUFFER_SIZE)
|
||||
if not data:
|
||||
break
|
||||
while data := self.read(DEFAULT_BUFFER_SIZE):
|
||||
res += data
|
||||
if res:
|
||||
return bytes(res)
|
||||
@@ -691,8 +650,6 @@ class RawIOBase(IOBase):
|
||||
self._unsupported("write")
|
||||
|
||||
io.RawIOBase.register(RawIOBase)
|
||||
from _io import FileIO
|
||||
RawIOBase.register(FileIO)
|
||||
|
||||
|
||||
class BufferedIOBase(IOBase):
|
||||
@@ -899,6 +856,10 @@ class _BufferedIOMixin(BufferedIOBase):
|
||||
else:
|
||||
return "<{}.{} name={!r}>".format(modname, clsname, name)
|
||||
|
||||
def _dealloc_warn(self, source):
|
||||
if dealloc_warn := getattr(self.raw, "_dealloc_warn", None):
|
||||
dealloc_warn(source)
|
||||
|
||||
### Lower-level APIs ###
|
||||
|
||||
def fileno(self):
|
||||
@@ -974,22 +935,22 @@ class BytesIO(BufferedIOBase):
|
||||
return self.read(size)
|
||||
|
||||
def write(self, b):
|
||||
if self.closed:
|
||||
raise ValueError("write to closed file")
|
||||
if isinstance(b, str):
|
||||
raise TypeError("can't write str to binary stream")
|
||||
with memoryview(b) as view:
|
||||
if self.closed:
|
||||
raise ValueError("write to closed file")
|
||||
|
||||
n = view.nbytes # Size of any bytes-like object
|
||||
if n == 0:
|
||||
return 0
|
||||
pos = self._pos
|
||||
if pos > len(self._buffer):
|
||||
# Inserts null bytes between the current end of the file
|
||||
# and the new write position.
|
||||
padding = b'\x00' * (pos - len(self._buffer))
|
||||
self._buffer += padding
|
||||
self._buffer[pos:pos + n] = b
|
||||
self._pos += n
|
||||
if n == 0:
|
||||
return 0
|
||||
|
||||
pos = self._pos
|
||||
if pos > len(self._buffer):
|
||||
# Pad buffer to pos with null bytes.
|
||||
self._buffer.resize(pos)
|
||||
self._buffer[pos:pos + n] = view
|
||||
self._pos += n
|
||||
return n
|
||||
|
||||
def seek(self, pos, whence=0):
|
||||
@@ -1154,6 +1115,7 @@ class BufferedReader(_BufferedIOMixin):
|
||||
do at most one raw read to satisfy it. We never return more
|
||||
than self.buffer_size.
|
||||
"""
|
||||
self._checkClosed("peek of closed file")
|
||||
with self._read_lock:
|
||||
return self._peek_unlocked(size)
|
||||
|
||||
@@ -1172,6 +1134,7 @@ class BufferedReader(_BufferedIOMixin):
|
||||
"""Reads up to size bytes, with at most one read() system call."""
|
||||
# Returns up to size bytes. If at least one byte is buffered, we
|
||||
# only return buffered bytes. Otherwise, we do one raw read.
|
||||
self._checkClosed("read of closed file")
|
||||
if size < 0:
|
||||
size = self.buffer_size
|
||||
if size == 0:
|
||||
@@ -1189,6 +1152,8 @@ class BufferedReader(_BufferedIOMixin):
|
||||
def _readinto(self, buf, read1):
|
||||
"""Read data into *buf* with at most one system call."""
|
||||
|
||||
self._checkClosed("readinto of closed file")
|
||||
|
||||
# Need to create a memoryview object of type 'b', otherwise
|
||||
# we may not be able to assign bytes to it, and slicing it
|
||||
# would create a new object.
|
||||
@@ -1233,11 +1198,13 @@ class BufferedReader(_BufferedIOMixin):
|
||||
return written
|
||||
|
||||
def tell(self):
|
||||
return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos
|
||||
# GH-95782: Keep return value non-negative
|
||||
return max(_BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos, 0)
|
||||
|
||||
def seek(self, pos, whence=0):
|
||||
if whence not in valid_seek_flags:
|
||||
raise ValueError("invalid whence value")
|
||||
self._checkClosed("seek of closed file")
|
||||
with self._read_lock:
|
||||
if whence == 1:
|
||||
pos -= len(self._read_buf) - self._read_pos
|
||||
@@ -1497,6 +1464,17 @@ class BufferedRandom(BufferedWriter, BufferedReader):
|
||||
return BufferedWriter.write(self, b)
|
||||
|
||||
|
||||
def _new_buffersize(bytes_read):
|
||||
# Parallels _io/fileio.c new_buffersize
|
||||
if bytes_read > 65536:
|
||||
addend = bytes_read >> 3
|
||||
else:
|
||||
addend = 256 + bytes_read
|
||||
if addend < DEFAULT_BUFFER_SIZE:
|
||||
addend = DEFAULT_BUFFER_SIZE
|
||||
return bytes_read + addend
|
||||
|
||||
|
||||
class FileIO(RawIOBase):
|
||||
_fd = -1
|
||||
_created = False
|
||||
@@ -1521,6 +1499,7 @@ class FileIO(RawIOBase):
|
||||
"""
|
||||
if self._fd >= 0:
|
||||
# Have to close the existing file first.
|
||||
self._stat_atopen = None
|
||||
try:
|
||||
if self._closefd:
|
||||
os.close(self._fd)
|
||||
@@ -1530,6 +1509,11 @@ class FileIO(RawIOBase):
|
||||
if isinstance(file, float):
|
||||
raise TypeError('integer argument expected, got float')
|
||||
if isinstance(file, int):
|
||||
if isinstance(file, bool):
|
||||
import warnings
|
||||
warnings.warn("bool is used as a file descriptor",
|
||||
RuntimeWarning, stacklevel=2)
|
||||
file = int(file)
|
||||
fd = file
|
||||
if fd < 0:
|
||||
raise ValueError('negative file descriptor')
|
||||
@@ -1588,24 +1572,22 @@ class FileIO(RawIOBase):
|
||||
if not isinstance(fd, int):
|
||||
raise TypeError('expected integer from opener')
|
||||
if fd < 0:
|
||||
raise OSError('Negative file descriptor')
|
||||
# bpo-27066: Raise a ValueError for bad value.
|
||||
raise ValueError(f'opener returned {fd}')
|
||||
owned_fd = fd
|
||||
if not noinherit_flag:
|
||||
os.set_inheritable(fd, False)
|
||||
|
||||
self._closefd = closefd
|
||||
fdfstat = os.fstat(fd)
|
||||
self._stat_atopen = os.fstat(fd)
|
||||
try:
|
||||
if stat.S_ISDIR(fdfstat.st_mode):
|
||||
if stat.S_ISDIR(self._stat_atopen.st_mode):
|
||||
raise IsADirectoryError(errno.EISDIR,
|
||||
os.strerror(errno.EISDIR), file)
|
||||
except AttributeError:
|
||||
# Ignore the AttributeError if stat.S_ISDIR or errno.EISDIR
|
||||
# don't exist.
|
||||
pass
|
||||
self._blksize = getattr(fdfstat, 'st_blksize', 0)
|
||||
if self._blksize <= 1:
|
||||
self._blksize = DEFAULT_BUFFER_SIZE
|
||||
|
||||
if _setmode:
|
||||
# don't translate newlines (\r\n <=> \n)
|
||||
@@ -1622,17 +1604,17 @@ class FileIO(RawIOBase):
|
||||
if e.errno != errno.ESPIPE:
|
||||
raise
|
||||
except:
|
||||
self._stat_atopen = None
|
||||
if owned_fd is not None:
|
||||
os.close(owned_fd)
|
||||
raise
|
||||
self._fd = fd
|
||||
|
||||
def __del__(self):
|
||||
def _dealloc_warn(self, source):
|
||||
if self._fd >= 0 and self._closefd and not self.closed:
|
||||
import warnings
|
||||
warnings.warn('unclosed file %r' % (self,), ResourceWarning,
|
||||
warnings.warn(f'unclosed file {source!r}', ResourceWarning,
|
||||
stacklevel=2, source=self)
|
||||
self.close()
|
||||
|
||||
def __getstate__(self):
|
||||
raise TypeError(f"cannot pickle {self.__class__.__name__!r} object")
|
||||
@@ -1651,6 +1633,17 @@ class FileIO(RawIOBase):
|
||||
return ('<%s name=%r mode=%r closefd=%r>' %
|
||||
(class_name, name, self.mode, self._closefd))
|
||||
|
||||
@property
|
||||
def _blksize(self):
|
||||
if self._stat_atopen is None:
|
||||
return DEFAULT_BUFFER_SIZE
|
||||
|
||||
blksize = getattr(self._stat_atopen, "st_blksize", 0)
|
||||
# WASI sets blsize to 0
|
||||
if not blksize:
|
||||
return DEFAULT_BUFFER_SIZE
|
||||
return blksize
|
||||
|
||||
def _checkReadable(self):
|
||||
if not self._readable:
|
||||
raise UnsupportedOperation('File not open for reading')
|
||||
@@ -1662,7 +1655,13 @@ class FileIO(RawIOBase):
|
||||
def read(self, size=None):
|
||||
"""Read at most size bytes, returned as bytes.
|
||||
|
||||
Only makes one system call, so less data may be returned than requested
|
||||
If size is less than 0, read all bytes in the file making
|
||||
multiple read calls. See ``FileIO.readall``.
|
||||
|
||||
Attempts to make only one system call, retrying only per
|
||||
PEP 475 (EINTR). This means less data may be returned than
|
||||
requested.
|
||||
|
||||
In non-blocking mode, returns None if no data is available.
|
||||
Return an empty bytes object at EOF.
|
||||
"""
|
||||
@@ -1678,45 +1677,57 @@ class FileIO(RawIOBase):
|
||||
def readall(self):
|
||||
"""Read all data from the file, returned as bytes.
|
||||
|
||||
In non-blocking mode, returns as much as is immediately available,
|
||||
or None if no data is available. Return an empty bytes object at EOF.
|
||||
Reads until either there is an error or read() returns size 0
|
||||
(indicates EOF). If the file is already at EOF, returns an
|
||||
empty bytes object.
|
||||
|
||||
In non-blocking mode, returns as much data as could be read
|
||||
before EAGAIN. If no data is available (EAGAIN is returned
|
||||
before bytes are read) returns None.
|
||||
"""
|
||||
self._checkClosed()
|
||||
self._checkReadable()
|
||||
bufsize = DEFAULT_BUFFER_SIZE
|
||||
if self._stat_atopen is None or self._stat_atopen.st_size <= 0:
|
||||
bufsize = DEFAULT_BUFFER_SIZE
|
||||
else:
|
||||
# In order to detect end of file, need a read() of at least 1
|
||||
# byte which returns size 0. Oversize the buffer by 1 byte so the
|
||||
# I/O can be completed with two read() calls (one for all data, one
|
||||
# for EOF) without needing to resize the buffer.
|
||||
bufsize = self._stat_atopen.st_size + 1
|
||||
|
||||
if self._stat_atopen.st_size > 65536:
|
||||
try:
|
||||
pos = os.lseek(self._fd, 0, SEEK_CUR)
|
||||
if self._stat_atopen.st_size >= pos:
|
||||
bufsize = self._stat_atopen.st_size - pos + 1
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
result = bytearray(bufsize)
|
||||
bytes_read = 0
|
||||
try:
|
||||
pos = os.lseek(self._fd, 0, SEEK_CUR)
|
||||
end = os.fstat(self._fd).st_size
|
||||
if end >= pos:
|
||||
bufsize = end - pos + 1
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
result = bytearray()
|
||||
while True:
|
||||
if len(result) >= bufsize:
|
||||
bufsize = len(result)
|
||||
bufsize += max(bufsize, DEFAULT_BUFFER_SIZE)
|
||||
n = bufsize - len(result)
|
||||
try:
|
||||
chunk = os.read(self._fd, n)
|
||||
except BlockingIOError:
|
||||
if result:
|
||||
break
|
||||
while n := os.readinto(self._fd, memoryview(result)[bytes_read:]):
|
||||
bytes_read += n
|
||||
if bytes_read >= len(result):
|
||||
result.resize(_new_buffersize(bytes_read))
|
||||
except BlockingIOError:
|
||||
if not bytes_read:
|
||||
return None
|
||||
if not chunk: # reached the end of the file
|
||||
break
|
||||
result += chunk
|
||||
|
||||
assert len(result) - bytes_read >= 1, \
|
||||
"os.readinto buffer size 0 will result in erroneous EOF / returns 0"
|
||||
result.resize(bytes_read)
|
||||
return bytes(result)
|
||||
|
||||
def readinto(self, b):
|
||||
def readinto(self, buffer):
|
||||
"""Same as RawIOBase.readinto()."""
|
||||
m = memoryview(b).cast('B')
|
||||
data = self.read(len(m))
|
||||
n = len(data)
|
||||
m[:n] = data
|
||||
return n
|
||||
self._checkClosed()
|
||||
self._checkReadable()
|
||||
try:
|
||||
return os.readinto(self._fd, buffer)
|
||||
except BlockingIOError:
|
||||
return None
|
||||
|
||||
def write(self, b):
|
||||
"""Write bytes b to file, return number written.
|
||||
@@ -1766,6 +1777,7 @@ class FileIO(RawIOBase):
|
||||
if size is None:
|
||||
size = self.tell()
|
||||
os.ftruncate(self._fd, size)
|
||||
self._stat_atopen = None
|
||||
return size
|
||||
|
||||
def close(self):
|
||||
@@ -1775,8 +1787,9 @@ class FileIO(RawIOBase):
|
||||
called more than once without error.
|
||||
"""
|
||||
if not self.closed:
|
||||
self._stat_atopen = None
|
||||
try:
|
||||
if self._closefd:
|
||||
if self._closefd and self._fd >= 0:
|
||||
os.close(self._fd)
|
||||
finally:
|
||||
super().close()
|
||||
@@ -1813,6 +1826,21 @@ class FileIO(RawIOBase):
|
||||
self._checkClosed()
|
||||
return os.isatty(self._fd)
|
||||
|
||||
def _isatty_open_only(self):
|
||||
"""Checks whether the file is a TTY using an open-only optimization.
|
||||
|
||||
TTYs are always character devices. If the interpreter knows a file is
|
||||
not a character device when it would call ``isatty``, can skip that
|
||||
call. Inside ``open()`` there is a fresh stat result that contains that
|
||||
information. Use the stat result to skip a system call. Outside of that
|
||||
context TOCTOU issues (the fd could be arbitrarily modified by
|
||||
surrounding code).
|
||||
"""
|
||||
if (self._stat_atopen is not None
|
||||
and not stat.S_ISCHR(self._stat_atopen.st_mode)):
|
||||
return False
|
||||
return os.isatty(self._fd)
|
||||
|
||||
@property
|
||||
def closefd(self):
|
||||
"""True if the file descriptor will be closed by close()."""
|
||||
@@ -1845,7 +1873,7 @@ class TextIOBase(IOBase):
|
||||
"""Base class for text I/O.
|
||||
|
||||
This class provides a character and line based interface to stream
|
||||
I/O. There is no public constructor.
|
||||
I/O.
|
||||
"""
|
||||
|
||||
def read(self, size=-1):
|
||||
@@ -1997,7 +2025,7 @@ class TextIOWrapper(TextIOBase):
|
||||
r"""Character and line based layer over a BufferedIOBase object, buffer.
|
||||
|
||||
encoding gives the name of the encoding that the stream will be
|
||||
decoded or encoded with. It defaults to locale.getpreferredencoding(False).
|
||||
decoded or encoded with. It defaults to locale.getencoding().
|
||||
|
||||
errors determines the strictness of encoding and decoding (see the
|
||||
codecs.register) and defaults to "strict".
|
||||
@@ -2031,26 +2059,13 @@ class TextIOWrapper(TextIOBase):
|
||||
encoding = text_encoding(encoding)
|
||||
|
||||
if encoding == "locale":
|
||||
try:
|
||||
encoding = os.device_encoding(buffer.fileno()) or "locale"
|
||||
except (AttributeError, UnsupportedOperation):
|
||||
pass
|
||||
|
||||
if encoding == "locale":
|
||||
try:
|
||||
import locale
|
||||
except ImportError:
|
||||
# Importing locale may fail if Python is being built
|
||||
encoding = "utf-8"
|
||||
else:
|
||||
encoding = locale.getpreferredencoding(False)
|
||||
encoding = self._get_locale_encoding()
|
||||
|
||||
if not isinstance(encoding, str):
|
||||
raise ValueError("invalid encoding: %r" % encoding)
|
||||
|
||||
if not codecs.lookup(encoding)._is_text_encoding:
|
||||
msg = ("%r is not a text encoding; "
|
||||
"use codecs.open() to handle arbitrary codecs")
|
||||
msg = "%r is not a text encoding"
|
||||
raise LookupError(msg % encoding)
|
||||
|
||||
if errors is None:
|
||||
@@ -2176,6 +2191,8 @@ class TextIOWrapper(TextIOBase):
|
||||
else:
|
||||
if not isinstance(encoding, str):
|
||||
raise TypeError("invalid encoding: %r" % encoding)
|
||||
if encoding == "locale":
|
||||
encoding = self._get_locale_encoding()
|
||||
|
||||
if newline is Ellipsis:
|
||||
newline = self._readnl
|
||||
@@ -2243,8 +2260,9 @@ class TextIOWrapper(TextIOBase):
|
||||
self.buffer.write(b)
|
||||
if self._line_buffering and (haslf or "\r" in s):
|
||||
self.flush()
|
||||
self._set_decoded_chars('')
|
||||
self._snapshot = None
|
||||
if self._snapshot is not None:
|
||||
self._set_decoded_chars('')
|
||||
self._snapshot = None
|
||||
if self._decoder:
|
||||
self._decoder.reset()
|
||||
return length
|
||||
@@ -2280,6 +2298,15 @@ class TextIOWrapper(TextIOBase):
|
||||
self._decoded_chars_used += len(chars)
|
||||
return chars
|
||||
|
||||
def _get_locale_encoding(self):
|
||||
try:
|
||||
import locale
|
||||
except ImportError:
|
||||
# Importing locale may fail if Python is being built
|
||||
return "utf-8"
|
||||
else:
|
||||
return locale.getencoding()
|
||||
|
||||
def _rewind_decoded_chars(self, n):
|
||||
"""Rewind the _decoded_chars buffer."""
|
||||
if self._decoded_chars_used < n:
|
||||
@@ -2546,11 +2573,15 @@ class TextIOWrapper(TextIOBase):
|
||||
size = size_index()
|
||||
decoder = self._decoder or self._get_decoder()
|
||||
if size < 0:
|
||||
chunk = self.buffer.read()
|
||||
if chunk is None:
|
||||
raise BlockingIOError("Read returned None.")
|
||||
# Read everything.
|
||||
result = (self._get_decoded_chars() +
|
||||
decoder.decode(self.buffer.read(), final=True))
|
||||
self._set_decoded_chars('')
|
||||
self._snapshot = None
|
||||
decoder.decode(chunk, final=True))
|
||||
if self._snapshot is not None:
|
||||
self._set_decoded_chars('')
|
||||
self._snapshot = None
|
||||
return result
|
||||
else:
|
||||
# Keep reading chunks until we have size characters to return.
|
||||
@@ -2667,6 +2698,10 @@ class TextIOWrapper(TextIOBase):
|
||||
def newlines(self):
|
||||
return self._decoder.newlines if self._decoder else None
|
||||
|
||||
def _dealloc_warn(self, source):
|
||||
if dealloc_warn := getattr(self.buffer, "_dealloc_warn", None):
|
||||
dealloc_warn(source)
|
||||
|
||||
|
||||
class StringIO(TextIOWrapper):
|
||||
"""Text I/O implementation using an in-memory buffer.
|
||||
|
||||
729
Lib/_pylong.py
vendored
Normal file
729
Lib/_pylong.py
vendored
Normal file
@@ -0,0 +1,729 @@
|
||||
"""Python implementations of some algorithms for use by longobject.c.
|
||||
The goal is to provide asymptotically faster algorithms that can be
|
||||
used for operations on integers with many digits. In those cases, the
|
||||
performance overhead of the Python implementation is not significant
|
||||
since the asymptotic behavior is what dominates runtime. Functions
|
||||
provided by this module should be considered private and not part of any
|
||||
public API.
|
||||
|
||||
Note: for ease of maintainability, please prefer clear code and avoid
|
||||
"micro-optimizations". This module will only be imported and used for
|
||||
integers with a huge number of digits. Saving a few microseconds with
|
||||
tricky or non-obvious code is not worth it. For people looking for
|
||||
maximum performance, they should use something like gmpy2."""
|
||||
|
||||
import re
|
||||
import decimal
|
||||
try:
|
||||
import _decimal
|
||||
except ImportError:
|
||||
_decimal = None
|
||||
|
||||
# A number of functions have this form, where `w` is a desired number of
|
||||
# digits in base `base`:
|
||||
#
|
||||
# def inner(...w...):
|
||||
# if w <= LIMIT:
|
||||
# return something
|
||||
# lo = w >> 1
|
||||
# hi = w - lo
|
||||
# something involving base**lo, inner(...lo...), j, and inner(...hi...)
|
||||
# figure out largest w needed
|
||||
# result = inner(w)
|
||||
#
|
||||
# They all had some on-the-fly scheme to cache `base**lo` results for reuse.
|
||||
# Power is costly.
|
||||
#
|
||||
# This routine aims to compute all amd only the needed powers in advance, as
|
||||
# efficiently as reasonably possible. This isn't trivial, and all the
|
||||
# on-the-fly methods did needless work in many cases. The driving code above
|
||||
# changes to:
|
||||
#
|
||||
# figure out largest w needed
|
||||
# mycache = compute_powers(w, base, LIMIT)
|
||||
# result = inner(w)
|
||||
#
|
||||
# and `mycache[lo]` replaces `base**lo` in the inner function.
|
||||
#
|
||||
# If an algorithm wants the powers of ceiling(w/2) instead of the floor,
|
||||
# pass keyword argument `need_hi=True`.
|
||||
#
|
||||
# While this does give minor speedups (a few percent at best), the
|
||||
# primary intent is to simplify the functions using this, by eliminating
|
||||
# the need for them to craft their own ad-hoc caching schemes.
|
||||
#
|
||||
# See code near end of file for a block of code that can be enabled to
|
||||
# run millions of tests.
|
||||
def compute_powers(w, base, more_than, *, need_hi=False, show=False):
|
||||
seen = set()
|
||||
need = set()
|
||||
ws = {w}
|
||||
while ws:
|
||||
w = ws.pop() # any element is fine to use next
|
||||
if w in seen or w <= more_than:
|
||||
continue
|
||||
seen.add(w)
|
||||
lo = w >> 1
|
||||
hi = w - lo
|
||||
# only _need_ one here; the other may, or may not, be needed
|
||||
which = hi if need_hi else lo
|
||||
need.add(which)
|
||||
ws.add(which)
|
||||
if lo != hi:
|
||||
ws.add(w - which)
|
||||
|
||||
# `need` is the set of exponents needed. To compute them all
|
||||
# efficiently, possibly add other exponents to `extra`. The goal is
|
||||
# to ensure that each exponent can be gotten from a smaller one via
|
||||
# multiplying by the base, squaring it, or squaring and then
|
||||
# multiplying by the base.
|
||||
#
|
||||
# If need_hi is False, this is already the case (w can always be
|
||||
# gotten from w >> 1 via one of the squaring strategies). But we do
|
||||
# the work anyway, just in case ;-)
|
||||
#
|
||||
# Note that speed is irrelevant. These loops are working on little
|
||||
# ints (exponents) and go around O(log w) times. The total cost is
|
||||
# insignificant compared to just one of the bigint multiplies.
|
||||
cands = need.copy()
|
||||
extra = set()
|
||||
while cands:
|
||||
w = max(cands)
|
||||
cands.remove(w)
|
||||
lo = w >> 1
|
||||
if lo > more_than and w-1 not in cands and lo not in cands:
|
||||
extra.add(lo)
|
||||
cands.add(lo)
|
||||
assert need_hi or not extra
|
||||
|
||||
d = {}
|
||||
for n in sorted(need | extra):
|
||||
lo = n >> 1
|
||||
hi = n - lo
|
||||
if n-1 in d:
|
||||
if show:
|
||||
print("* base", end="")
|
||||
result = d[n-1] * base # cheap!
|
||||
elif lo in d:
|
||||
# Multiplying a bigint by itself is about twice as fast
|
||||
# in CPython provided it's the same object.
|
||||
if show:
|
||||
print("square", end="")
|
||||
result = d[lo] * d[lo] # same object
|
||||
if hi != lo:
|
||||
if show:
|
||||
print(" * base", end="")
|
||||
assert 2 * lo + 1 == n
|
||||
result *= base
|
||||
else: # rare
|
||||
if show:
|
||||
print("pow", end='')
|
||||
result = base ** n
|
||||
if show:
|
||||
print(" at", n, "needed" if n in need else "extra")
|
||||
d[n] = result
|
||||
|
||||
assert need <= d.keys()
|
||||
if excess := d.keys() - need:
|
||||
assert need_hi
|
||||
for n in excess:
|
||||
del d[n]
|
||||
return d
|
||||
|
||||
_unbounded_dec_context = decimal.getcontext().copy()
|
||||
_unbounded_dec_context.prec = decimal.MAX_PREC
|
||||
_unbounded_dec_context.Emax = decimal.MAX_EMAX
|
||||
_unbounded_dec_context.Emin = decimal.MIN_EMIN
|
||||
_unbounded_dec_context.traps[decimal.Inexact] = 1 # sanity check
|
||||
|
||||
def int_to_decimal(n):
|
||||
"""Asymptotically fast conversion of an 'int' to Decimal."""
|
||||
|
||||
# Function due to Tim Peters. See GH issue #90716 for details.
|
||||
# https://github.com/python/cpython/issues/90716
|
||||
#
|
||||
# The implementation in longobject.c of base conversion algorithms
|
||||
# between power-of-2 and non-power-of-2 bases are quadratic time.
|
||||
# This function implements a divide-and-conquer algorithm that is
|
||||
# faster for large numbers. Builds an equal decimal.Decimal in a
|
||||
# "clever" recursive way. If we want a string representation, we
|
||||
# apply str to _that_.
|
||||
|
||||
from decimal import Decimal as D
|
||||
BITLIM = 200
|
||||
|
||||
# Don't bother caching the "lo" mask in this; the time to compute it is
|
||||
# tiny compared to the multiply.
|
||||
def inner(n, w):
|
||||
if w <= BITLIM:
|
||||
return D(n)
|
||||
w2 = w >> 1
|
||||
hi = n >> w2
|
||||
lo = n & ((1 << w2) - 1)
|
||||
return inner(lo, w2) + inner(hi, w - w2) * w2pow[w2]
|
||||
|
||||
with decimal.localcontext(_unbounded_dec_context):
|
||||
nbits = n.bit_length()
|
||||
w2pow = compute_powers(nbits, D(2), BITLIM)
|
||||
if n < 0:
|
||||
negate = True
|
||||
n = -n
|
||||
else:
|
||||
negate = False
|
||||
result = inner(n, nbits)
|
||||
if negate:
|
||||
result = -result
|
||||
return result
|
||||
|
||||
def int_to_decimal_string(n):
|
||||
"""Asymptotically fast conversion of an 'int' to a decimal string."""
|
||||
w = n.bit_length()
|
||||
if w > 450_000 and _decimal is not None:
|
||||
# It is only usable with the C decimal implementation.
|
||||
# _pydecimal.py calls str() on very large integers, which in its
|
||||
# turn calls int_to_decimal_string(), causing very deep recursion.
|
||||
return str(int_to_decimal(n))
|
||||
|
||||
# Fallback algorithm for the case when the C decimal module isn't
|
||||
# available. This algorithm is asymptotically worse than the algorithm
|
||||
# using the decimal module, but better than the quadratic time
|
||||
# implementation in longobject.c.
|
||||
|
||||
DIGLIM = 1000
|
||||
def inner(n, w):
|
||||
if w <= DIGLIM:
|
||||
return str(n)
|
||||
w2 = w >> 1
|
||||
hi, lo = divmod(n, pow10[w2])
|
||||
return inner(hi, w - w2) + inner(lo, w2).zfill(w2)
|
||||
|
||||
# The estimation of the number of decimal digits.
|
||||
# There is no harm in small error. If we guess too large, there may
|
||||
# be leading 0's that need to be stripped. If we guess too small, we
|
||||
# may need to call str() recursively for the remaining highest digits,
|
||||
# which can still potentially be a large integer. This is manifested
|
||||
# only if the number has way more than 10**15 digits, that exceeds
|
||||
# the 52-bit physical address limit in both Intel64 and AMD64.
|
||||
w = int(w * 0.3010299956639812 + 1) # log10(2)
|
||||
pow10 = compute_powers(w, 5, DIGLIM)
|
||||
for k, v in pow10.items():
|
||||
pow10[k] = v << k # 5**k << k == 5**k * 2**k == 10**k
|
||||
if n < 0:
|
||||
n = -n
|
||||
sign = '-'
|
||||
else:
|
||||
sign = ''
|
||||
s = inner(n, w)
|
||||
if s[0] == '0' and n:
|
||||
# If our guess of w is too large, there may be leading 0's that
|
||||
# need to be stripped.
|
||||
s = s.lstrip('0')
|
||||
return sign + s
|
||||
|
||||
def _str_to_int_inner(s):
|
||||
"""Asymptotically fast conversion of a 'str' to an 'int'."""
|
||||
|
||||
# Function due to Bjorn Martinsson. See GH issue #90716 for details.
|
||||
# https://github.com/python/cpython/issues/90716
|
||||
#
|
||||
# The implementation in longobject.c of base conversion algorithms
|
||||
# between power-of-2 and non-power-of-2 bases are quadratic time.
|
||||
# This function implements a divide-and-conquer algorithm making use
|
||||
# of Python's built in big int multiplication. Since Python uses the
|
||||
# Karatsuba algorithm for multiplication, the time complexity
|
||||
# of this function is O(len(s)**1.58).
|
||||
|
||||
DIGLIM = 2048
|
||||
|
||||
def inner(a, b):
|
||||
if b - a <= DIGLIM:
|
||||
return int(s[a:b])
|
||||
mid = (a + b + 1) >> 1
|
||||
return (inner(mid, b)
|
||||
+ ((inner(a, mid) * w5pow[b - mid])
|
||||
<< (b - mid)))
|
||||
|
||||
w5pow = compute_powers(len(s), 5, DIGLIM)
|
||||
return inner(0, len(s))
|
||||
|
||||
|
||||
# Asymptotically faster version, using the C decimal module. See
|
||||
# comments at the end of the file. This uses decimal arithmetic to
|
||||
# convert from base 10 to base 256. The latter is just a string of
|
||||
# bytes, which CPython can convert very efficiently to a Python int.
|
||||
|
||||
# log of 10 to base 256 with best-possible 53-bit precision. Obtained
|
||||
# via:
|
||||
# from mpmath import mp
|
||||
# mp.prec = 1000
|
||||
# print(float(mp.log(10, 256)).hex())
|
||||
_LOG_10_BASE_256 = float.fromhex('0x1.a934f0979a371p-2') # about 0.415
|
||||
|
||||
# _spread is for internal testing. It maps a key to the number of times
|
||||
# that condition obtained in _dec_str_to_int_inner:
|
||||
# key 0 - quotient guess was right
|
||||
# key 1 - quotient had to be boosted by 1, one time
|
||||
# key 999 - one adjustment wasn't enough, so fell back to divmod
|
||||
from collections import defaultdict
|
||||
_spread = defaultdict(int)
|
||||
del defaultdict
|
||||
|
||||
def _dec_str_to_int_inner(s, *, GUARD=8):
|
||||
# Yes, BYTELIM is "large". Large enough that CPython will usually
|
||||
# use the Karatsuba _str_to_int_inner to convert the string. This
|
||||
# allowed reducing the cutoff for calling _this_ function from 3.5M
|
||||
# to 2M digits. We could almost certainly do even better by
|
||||
# fine-tuning this and/or using a larger output base than 256.
|
||||
BYTELIM = 100_000
|
||||
D = decimal.Decimal
|
||||
result = bytearray()
|
||||
# See notes at end of file for discussion of GUARD.
|
||||
assert GUARD > 0 # if 0, `decimal` can blow up - .prec 0 not allowed
|
||||
|
||||
def inner(n, w):
|
||||
#assert n < D256 ** w # required, but too expensive to check
|
||||
if w <= BYTELIM:
|
||||
# XXX Stefan Pochmann discovered that, for 1024-bit ints,
|
||||
# `int(Decimal)` took 2.5x longer than `int(str(Decimal))`.
|
||||
# Worse, `int(Decimal) is still quadratic-time for much
|
||||
# larger ints. So unless/until all that is repaired, the
|
||||
# seemingly redundant `str(Decimal)` is crucial to speed.
|
||||
result.extend(int(str(n)).to_bytes(w)) # big-endian default
|
||||
return
|
||||
w1 = w >> 1
|
||||
w2 = w - w1
|
||||
if 0:
|
||||
# This is maximally clear, but "too slow". `decimal`
|
||||
# division is asymptotically fast, but we have no way to
|
||||
# tell it to reuse the high-precision reciprocal it computes
|
||||
# for pow256[w2], so it has to recompute it over & over &
|
||||
# over again :-(
|
||||
hi, lo = divmod(n, pow256[w2][0])
|
||||
else:
|
||||
p256, recip = pow256[w2]
|
||||
# The integer part will have a number of digits about equal
|
||||
# to the difference between the log10s of `n` and `pow256`
|
||||
# (which, since these are integers, is roughly approximated
|
||||
# by `.adjusted()`). That's the working precision we need,
|
||||
ctx.prec = max(n.adjusted() - p256.adjusted(), 0) + GUARD
|
||||
hi = +n * +recip # unary `+` chops back to ctx.prec digits
|
||||
ctx.prec = decimal.MAX_PREC
|
||||
hi = hi.to_integral_value() # lose the fractional digits
|
||||
lo = n - hi * p256
|
||||
# Because we've been uniformly rounding down, `hi` is a
|
||||
# lower bound on the correct quotient.
|
||||
assert lo >= 0
|
||||
# Adjust quotient up if needed. It usually isn't. In random
|
||||
# testing on inputs through 5 billion digit strings, the
|
||||
# test triggered once in about 200 thousand tries.
|
||||
count = 0
|
||||
if lo >= p256:
|
||||
count = 1
|
||||
lo -= p256
|
||||
hi += 1
|
||||
if lo >= p256:
|
||||
# Complete correction via an exact computation. I
|
||||
# believe it's not possible to get here provided
|
||||
# GUARD >= 3. It's tested by reducing GUARD below
|
||||
# that.
|
||||
count = 999
|
||||
hi2, lo = divmod(lo, p256)
|
||||
hi += hi2
|
||||
_spread[count] += 1
|
||||
# The assert should always succeed, but way too slow to keep
|
||||
# enabled.
|
||||
#assert hi, lo == divmod(n, pow256[w2][0])
|
||||
inner(hi, w1)
|
||||
del hi # at top levels, can free a lot of RAM "early"
|
||||
inner(lo, w2)
|
||||
|
||||
# How many base 256 digits are needed?. Mathematically, exactly
|
||||
# floor(log256(int(s))) + 1. There is no cheap way to compute this.
|
||||
# But we can get an upper bound, and that's necessary for our error
|
||||
# analysis to make sense. int(s) < 10**len(s), so the log needed is
|
||||
# < log256(10**len(s)) = len(s) * log256(10). However, using
|
||||
# finite-precision floating point for this, it's possible that the
|
||||
# computed value is a little less than the true value. If the true
|
||||
# value is at - or a little higher than - an integer, we can get an
|
||||
# off-by-1 error too low. So we add 2 instead of 1 if chopping lost
|
||||
# a fraction > 0.9.
|
||||
|
||||
# The "WASI" test platform can complain about `len(s)` if it's too
|
||||
# large to fit in its idea of "an index-sized integer".
|
||||
lenS = s.__len__()
|
||||
log_ub = lenS * _LOG_10_BASE_256
|
||||
log_ub_as_int = int(log_ub)
|
||||
w = log_ub_as_int + 1 + (log_ub - log_ub_as_int > 0.9)
|
||||
# And what if we've plain exhausted the limits of HW floats? We
|
||||
# could compute the log to any desired precision using `decimal`,
|
||||
# but it's not plausible that anyone will pass a string requiring
|
||||
# trillions of bytes (unless they're just trying to "break things").
|
||||
if w.bit_length() >= 46:
|
||||
# "Only" had < 53 - 46 = 7 bits to spare in IEEE-754 double.
|
||||
raise ValueError(f"cannot convert string of len {lenS} to int")
|
||||
with decimal.localcontext(_unbounded_dec_context) as ctx:
|
||||
D256 = D(256)
|
||||
pow256 = compute_powers(w, D256, BYTELIM, need_hi=True)
|
||||
rpow256 = compute_powers(w, 1 / D256, BYTELIM, need_hi=True)
|
||||
# We're going to do inexact, chopped arithmetic, multiplying by
|
||||
# an approximation to the reciprocal of 256**i. We chop to get a
|
||||
# lower bound on the true integer quotient. Our approximation is
|
||||
# a lower bound, the multiplication is chopped too, and
|
||||
# to_integral_value() is also chopped.
|
||||
ctx.traps[decimal.Inexact] = 0
|
||||
ctx.rounding = decimal.ROUND_DOWN
|
||||
for k, v in pow256.items():
|
||||
# No need to save much more precision in the reciprocal than
|
||||
# the power of 256 has, plus some guard digits to absorb
|
||||
# most relevant rounding errors. This is highly significant:
|
||||
# 1/2**i has the same number of significant decimal digits
|
||||
# as 5**i, generally over twice the number in 2**i,
|
||||
ctx.prec = v.adjusted() + GUARD + 1
|
||||
# The unary "+" chops the reciprocal back to that precision.
|
||||
pow256[k] = v, +rpow256[k]
|
||||
del rpow256 # exact reciprocals no longer needed
|
||||
ctx.prec = decimal.MAX_PREC
|
||||
inner(D(s), w)
|
||||
return int.from_bytes(result)
|
||||
|
||||
def int_from_string(s):
|
||||
"""Asymptotically fast version of PyLong_FromString(), conversion
|
||||
of a string of decimal digits into an 'int'."""
|
||||
# PyLong_FromString() has already removed leading +/-, checked for invalid
|
||||
# use of underscore characters, checked that string consists of only digits
|
||||
# and underscores, and stripped leading whitespace. The input can still
|
||||
# contain underscores and have trailing whitespace.
|
||||
s = s.rstrip().replace('_', '')
|
||||
func = _str_to_int_inner
|
||||
if len(s) >= 2_000_000 and _decimal is not None:
|
||||
func = _dec_str_to_int_inner
|
||||
return func(s)
|
||||
|
||||
def str_to_int(s):
|
||||
"""Asymptotically fast version of decimal string to 'int' conversion."""
|
||||
# FIXME: this doesn't support the full syntax that int() supports.
|
||||
m = re.match(r'\s*([+-]?)([0-9_]+)\s*', s)
|
||||
if not m:
|
||||
raise ValueError('invalid literal for int() with base 10')
|
||||
v = int_from_string(m.group(2))
|
||||
if m.group(1) == '-':
|
||||
v = -v
|
||||
return v
|
||||
|
||||
|
||||
# Fast integer division, based on code from Mark Dickinson, fast_div.py
|
||||
# GH-47701. Additional refinements and optimizations by Bjorn Martinsson. The
|
||||
# algorithm is due to Burnikel and Ziegler, in their paper "Fast Recursive
|
||||
# Division".
|
||||
|
||||
_DIV_LIMIT = 4000
|
||||
|
||||
|
||||
def _div2n1n(a, b, n):
|
||||
"""Divide a 2n-bit nonnegative integer a by an n-bit positive integer
|
||||
b, using a recursive divide-and-conquer algorithm.
|
||||
|
||||
Inputs:
|
||||
n is a positive integer
|
||||
b is a positive integer with exactly n bits
|
||||
a is a nonnegative integer such that a < 2**n * b
|
||||
|
||||
Output:
|
||||
(q, r) such that a = b*q+r and 0 <= r < b.
|
||||
|
||||
"""
|
||||
if a.bit_length() - n <= _DIV_LIMIT:
|
||||
return divmod(a, b)
|
||||
pad = n & 1
|
||||
if pad:
|
||||
a <<= 1
|
||||
b <<= 1
|
||||
n += 1
|
||||
half_n = n >> 1
|
||||
mask = (1 << half_n) - 1
|
||||
b1, b2 = b >> half_n, b & mask
|
||||
q1, r = _div3n2n(a >> n, (a >> half_n) & mask, b, b1, b2, half_n)
|
||||
q2, r = _div3n2n(r, a & mask, b, b1, b2, half_n)
|
||||
if pad:
|
||||
r >>= 1
|
||||
return q1 << half_n | q2, r
|
||||
|
||||
|
||||
def _div3n2n(a12, a3, b, b1, b2, n):
|
||||
"""Helper function for _div2n1n; not intended to be called directly."""
|
||||
if a12 >> n == b1:
|
||||
q, r = (1 << n) - 1, a12 - (b1 << n) + b1
|
||||
else:
|
||||
q, r = _div2n1n(a12, b1, n)
|
||||
r = (r << n | a3) - q * b2
|
||||
while r < 0:
|
||||
q -= 1
|
||||
r += b
|
||||
return q, r
|
||||
|
||||
|
||||
def _int2digits(a, n):
|
||||
"""Decompose non-negative int a into base 2**n
|
||||
|
||||
Input:
|
||||
a is a non-negative integer
|
||||
|
||||
Output:
|
||||
List of the digits of a in base 2**n in little-endian order,
|
||||
meaning the most significant digit is last. The most
|
||||
significant digit is guaranteed to be non-zero.
|
||||
If a is 0 then the output is an empty list.
|
||||
|
||||
"""
|
||||
a_digits = [0] * ((a.bit_length() + n - 1) // n)
|
||||
|
||||
def inner(x, L, R):
|
||||
if L + 1 == R:
|
||||
a_digits[L] = x
|
||||
return
|
||||
mid = (L + R) >> 1
|
||||
shift = (mid - L) * n
|
||||
upper = x >> shift
|
||||
lower = x ^ (upper << shift)
|
||||
inner(lower, L, mid)
|
||||
inner(upper, mid, R)
|
||||
|
||||
if a:
|
||||
inner(a, 0, len(a_digits))
|
||||
return a_digits
|
||||
|
||||
|
||||
def _digits2int(digits, n):
|
||||
"""Combine base-2**n digits into an int. This function is the
|
||||
inverse of `_int2digits`. For more details, see _int2digits.
|
||||
"""
|
||||
|
||||
def inner(L, R):
|
||||
if L + 1 == R:
|
||||
return digits[L]
|
||||
mid = (L + R) >> 1
|
||||
shift = (mid - L) * n
|
||||
return (inner(mid, R) << shift) + inner(L, mid)
|
||||
|
||||
return inner(0, len(digits)) if digits else 0
|
||||
|
||||
|
||||
def _divmod_pos(a, b):
|
||||
"""Divide a non-negative integer a by a positive integer b, giving
|
||||
quotient and remainder."""
|
||||
# Use grade-school algorithm in base 2**n, n = nbits(b)
|
||||
n = b.bit_length()
|
||||
a_digits = _int2digits(a, n)
|
||||
|
||||
r = 0
|
||||
q_digits = []
|
||||
for a_digit in reversed(a_digits):
|
||||
q_digit, r = _div2n1n((r << n) + a_digit, b, n)
|
||||
q_digits.append(q_digit)
|
||||
q_digits.reverse()
|
||||
q = _digits2int(q_digits, n)
|
||||
return q, r
|
||||
|
||||
|
||||
def int_divmod(a, b):
|
||||
"""Asymptotically fast replacement for divmod, for 'int'.
|
||||
Its time complexity is O(n**1.58), where n = #bits(a) + #bits(b).
|
||||
"""
|
||||
if b == 0:
|
||||
raise ZeroDivisionError('division by zero')
|
||||
elif b < 0:
|
||||
q, r = int_divmod(-a, -b)
|
||||
return q, -r
|
||||
elif a < 0:
|
||||
q, r = int_divmod(~a, b)
|
||||
return ~q, b + ~r
|
||||
else:
|
||||
return _divmod_pos(a, b)
|
||||
|
||||
|
||||
# Notes on _dec_str_to_int_inner:
|
||||
#
|
||||
# Stefan Pochmann worked up a str->int function that used the decimal
|
||||
# module to, in effect, convert from base 10 to base 256. This is
|
||||
# "unnatural", in that it requires multiplying and dividing by large
|
||||
# powers of 2, which `decimal` isn't naturally suited to. But
|
||||
# `decimal`'s `*` and `/` are asymptotically superior to CPython's, so
|
||||
# at _some_ point it could be expected to win.
|
||||
#
|
||||
# Alas, the crossover point was too high to be of much real interest. I
|
||||
# (Tim) then worked on ways to replace its division with multiplication
|
||||
# by a cached reciprocal approximation instead, fixing up errors
|
||||
# afterwards. This reduced the crossover point significantly,
|
||||
#
|
||||
# I revisited the code, and found ways to improve and simplify it. The
|
||||
# crossover point is at about 3.4 million digits now.
|
||||
#
|
||||
# About .adjusted()
|
||||
# -----------------
|
||||
# Restrict to Decimal values x > 0. We don't use negative numbers in the
|
||||
# code, and I don't want to have to keep typing, e.g., "absolute value".
|
||||
#
|
||||
# For convenience, I'll use `x.a` to mean `x.adjusted()`. x.a doesn't
|
||||
# look at the digits of x, but instead returns an integer giving x's
|
||||
# order of magnitude. These are equivalent:
|
||||
#
|
||||
# - x.a is the power-of-10 exponent of x's most significant digit.
|
||||
# - x.a = the infinitely precise floor(log10(x))
|
||||
# - x can be written in this form, where f is a real with 1 <= f < 10:
|
||||
# x = f * 10**x.a
|
||||
#
|
||||
# Observation; if x is an integer, len(str(x)) = x.a + 1.
|
||||
#
|
||||
# Lemma 1: (x * y).a = x.a + y.a, or one larger
|
||||
#
|
||||
# Proof: Write x = f * 10**x.a and y = g * 10**y.a, where f and g are in
|
||||
# [1, 10). Then x*y = f*g * 10**(x.a + y.a), where 1 <= f*g < 100. If
|
||||
# f*g < 10, (x*y).a is x.a+y.a. Else divide f*g by 10 to bring it back
|
||||
# into [1, 10], and add 1 to the exponent to compensate. Then (x*y).a is
|
||||
# x.a+y.a+1.
|
||||
#
|
||||
# Lemma 2: ceiling(log10(x/y)) <= x.a - y.a + 1
|
||||
#
|
||||
# Proof: Express x and y as in Lemma 1. Then x/y = f/g * 10**(x.a -
|
||||
# y.a), where 1/10 < f/g < 10. If 1 <= f/g, (x/y).a is x.a-y.a. Else
|
||||
# multiply f/g by 10 to bring it back into [1, 10], and subtract 1 from
|
||||
# the exponent to compensate. Then (x/y).a is x.a-y.a-1. So the largest
|
||||
# (x/y).a can be is x.a-y.a. Since that's the floor of log10(x/y). the
|
||||
# ceiling is at most 1 larger (with equality iff f/g = 1 exactly).
|
||||
#
|
||||
# GUARD digits
|
||||
# ------------
|
||||
# We only want the integer part of divisions, so don't need to build
|
||||
# the full multiplication tree. But using _just_ the number of
|
||||
# digits expected in the integer part ignores too much. What's left
|
||||
# out can have a very significant effect on the quotient. So we use
|
||||
# GUARD additional digits.
|
||||
#
|
||||
# The default 8 is more than enough so no more than 1 correction step
|
||||
# was ever needed for all inputs tried through 2.5 billion digits. In
|
||||
# fact, I believe 3 guard digits are always enough - but the proof is
|
||||
# very involved, so better safe than sorry.
|
||||
#
|
||||
# Short course:
|
||||
#
|
||||
# If prec is the decimal precision in effect, and we're rounding down,
|
||||
# the result of an operation is exactly equal to the infinitely precise
|
||||
# result times 1-e for some real e with 0 <= e < 10**(1-prec). In
|
||||
#
|
||||
# ctx.prec = max(n.adjusted() - p256.adjusted(), 0) + GUARD
|
||||
# hi = +n * +recip # unary `+` chops to ctx.prec digits
|
||||
#
|
||||
# we have 3 visible chopped operations, but there's also a 4th:
|
||||
# precomputing a truncated `recip` as part of setup.
|
||||
#
|
||||
# So the computed product is exactly equal to the true product times
|
||||
# (1-e1)*(1-e2)*(1-e3)*(1-e4); since the e's are all very small, an
|
||||
# excellent approximation to the second factor is 1-(e1+e2+e3+e4) (the
|
||||
# 2nd and higher order terms in the expanded product are too tiny to
|
||||
# matter). If they're all as large as possible, that's
|
||||
#
|
||||
# 1 - 4*10**(1-prec). This, BTW, is all bog-standard FP error analysis.
|
||||
#
|
||||
# That implies the computed product is within 1 of the true product
|
||||
# provided prec >= log10(true_product) + 1.602.
|
||||
#
|
||||
# Here are telegraphic details, rephrasing the initial condition in
|
||||
# equivalent ways, step by step:
|
||||
#
|
||||
# prod - prod * (1 - 4*10**(1-prec)) <= 1
|
||||
# prod - prod + prod * 4*10**(1-prec)) <= 1
|
||||
# prod * 4*10**(1-prec)) <= 1
|
||||
# 10**(log10(prod)) * 4*10**(1-prec)) <= 1
|
||||
# 4*10**(1-prec+log10(prod))) <= 1
|
||||
# 10**(1-prec+log10(prod))) <= 1/4
|
||||
# 1-prec+log10(prod) <= log10(1/4) = -0.602
|
||||
# -prec <= -1.602 - log10(prod)
|
||||
# prec >= log10(prod) + 1.602
|
||||
#
|
||||
# The true product is the same as the true ratio n/p256. By Lemma 2
|
||||
# above, n.a - p256.a + 1 is an upper bound on the ceiling of
|
||||
# log10(prod). Then 2 is the ceiling of 1.602. so n.a - p256.a + 3 is an
|
||||
# upper bound on the right hand side of the inequality. Any prec >= that
|
||||
# will work.
|
||||
#
|
||||
# But since this is just a sketch of a proof ;-), the code uses the
|
||||
# empirically tested 8 instead of 3. 5 digits more or less makes no
|
||||
# practical difference to speed - these ints are huge. And while
|
||||
# increasing GUARD above 3 may not be necessary, every increase cuts the
|
||||
# percentage of cases that need a correction at all.
|
||||
#
|
||||
# On Computing Reciprocals
|
||||
# ------------------------
|
||||
# In general, the exact reciprocals we compute have over twice as many
|
||||
# significant digits as needed. 1/256**i has the same number of
|
||||
# significant decimal digits as 5**i. It's a significant waste of RAM
|
||||
# to store all those unneeded digits.
|
||||
#
|
||||
# So we cut exact reciprocals back to the least precision that can
|
||||
# be needed so that the error analysis above is valid,
|
||||
#
|
||||
# [Note: turns out it's very significantly faster to do it this way than
|
||||
# to compute 1 / 256**i directly to the desired precision, because the
|
||||
# power method doesn't require division. It's also faster than computing
|
||||
# (1/256)**i directly to the desired precision - no material division
|
||||
# there, but `compute_powers()` is much smarter about _how_ to compute
|
||||
# all the powers needed than repeated applications of `**` - that
|
||||
# function invokes `**` for at most the few smallest powers needed.]
|
||||
#
|
||||
# The hard part is that chopping back to a shorter width occurs
|
||||
# _outside_ of `inner`. We can't know then what `prec` `inner()` will
|
||||
# need. We have to pick, for each value of `w2`, the largest possible
|
||||
# value `prec` can become when `inner()` is working on `w2`.
|
||||
#
|
||||
# This is the `prec` inner() uses:
|
||||
# max(n.a - p256.a, 0) + GUARD
|
||||
# and what setup uses (renaming its `v` to `p256` - same thing):
|
||||
# p256.a + GUARD + 1
|
||||
#
|
||||
# We need that the second is always at least as large as the first,
|
||||
# which is the same as requiring
|
||||
#
|
||||
# n.a - 2 * p256.a <= 1
|
||||
#
|
||||
# What's the largest n can be? n < 255**w = 256**(w2 + (w - w2)). The
|
||||
# worst case in this context is when w ix even. and then w = 2*w2, so
|
||||
# n < 256**(2*w2) = (256**w2)**2 = p256**2. By Lemma 1, then, n.a
|
||||
# is at most p256.a + p256.a + 1.
|
||||
#
|
||||
# So the most n.a - 2 * p256.a can be is
|
||||
# p256.a + p256.a + 1 - 2 * p256.a = 1. QED
|
||||
#
|
||||
# Note: an earlier version of the code split on floor(e/2) instead of on
|
||||
# the ceiling. The worst case then is odd `w`, and a more involved proof
|
||||
# was needed to show that adding 4 (instead of 1) may be necessary.
|
||||
# Basically because, in that case, n may be up to 256 times larger than
|
||||
# p256**2. Curiously enough, by splitting on the ceiling instead,
|
||||
# nothing in any proof here actually depends on the output base (256).
|
||||
|
||||
# Enable for brute-force testing of compute_powers(). This takes about a
|
||||
# minute, because it tries millions of cases.
|
||||
if 0:
|
||||
def consumer(w, limit, need_hi):
|
||||
seen = set()
|
||||
need = set()
|
||||
def inner(w):
|
||||
if w <= limit:
|
||||
return
|
||||
if w in seen:
|
||||
return
|
||||
seen.add(w)
|
||||
lo = w >> 1
|
||||
hi = w - lo
|
||||
need.add(hi if need_hi else lo)
|
||||
inner(lo)
|
||||
inner(hi)
|
||||
inner(w)
|
||||
exp = compute_powers(w, 1, limit, need_hi=need_hi)
|
||||
assert exp.keys() == need
|
||||
|
||||
from itertools import chain
|
||||
for need_hi in (False, True):
|
||||
for limit in (0, 1, 10, 100, 1_000, 10_000, 100_000):
|
||||
for w in chain(range(1, 100_000),
|
||||
(10**i for i in range(5, 30))):
|
||||
consumer(w, limit, need_hi)
|
||||
19
Lib/_pyrepl/__init__.py
vendored
Normal file
19
Lib/_pyrepl/__init__.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
# Armin Rigo
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
6
Lib/_pyrepl/__main__.py
vendored
Normal file
6
Lib/_pyrepl/__main__.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Important: don't add things to this module, as they will end up in the REPL's
|
||||
# default globals. Use _pyrepl.main instead.
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .main import interactive_console as __pyrepl_interactive_console
|
||||
__pyrepl_interactive_console()
|
||||
68
Lib/_pyrepl/_minimal_curses.py
vendored
Normal file
68
Lib/_pyrepl/_minimal_curses.py
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
"""Minimal '_curses' module, the low-level interface for curses module
|
||||
which is not meant to be used directly.
|
||||
|
||||
Based on ctypes. It's too incomplete to be really called '_curses', so
|
||||
to use it, you have to import it and stick it in sys.modules['_curses']
|
||||
manually.
|
||||
|
||||
Note that there is also a built-in module _minimal_curses which will
|
||||
hide this one if compiled in.
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _find_clib() -> str:
|
||||
trylibs = ["ncursesw", "ncurses", "curses"]
|
||||
|
||||
for lib in trylibs:
|
||||
path = ctypes.util.find_library(lib)
|
||||
if path:
|
||||
return path
|
||||
raise ModuleNotFoundError("curses library not found", name="_pyrepl._minimal_curses")
|
||||
|
||||
|
||||
_clibpath = _find_clib()
|
||||
clib = ctypes.cdll.LoadLibrary(_clibpath)
|
||||
|
||||
clib.setupterm.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
|
||||
clib.setupterm.restype = ctypes.c_int
|
||||
|
||||
clib.tigetstr.argtypes = [ctypes.c_char_p]
|
||||
clib.tigetstr.restype = ctypes.c_ssize_t
|
||||
|
||||
clib.tparm.argtypes = [ctypes.c_char_p] + 9 * [ctypes.c_int] # type: ignore[operator]
|
||||
clib.tparm.restype = ctypes.c_char_p
|
||||
|
||||
OK = 0
|
||||
ERR = -1
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
|
||||
def setupterm(termstr, fd):
|
||||
err = ctypes.c_int(0)
|
||||
result = clib.setupterm(termstr, fd, ctypes.byref(err))
|
||||
if result == ERR:
|
||||
raise error("setupterm() failed (err=%d)" % err.value)
|
||||
|
||||
|
||||
def tigetstr(cap):
|
||||
if not isinstance(cap, bytes):
|
||||
cap = cap.encode("ascii")
|
||||
result = clib.tigetstr(cap)
|
||||
if result == ERR:
|
||||
return None
|
||||
return ctypes.cast(result, ctypes.c_char_p).value
|
||||
|
||||
|
||||
def tparm(str, i1=0, i2=0, i3=0, i4=0, i5=0, i6=0, i7=0, i8=0, i9=0):
|
||||
result = clib.tparm(str, i1, i2, i3, i4, i5, i6, i7, i8, i9)
|
||||
if result is None:
|
||||
raise error("tparm() returned NULL")
|
||||
return result
|
||||
74
Lib/_pyrepl/_threading_handler.py
vendored
Normal file
74
Lib/_pyrepl/_threading_handler.py
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
import traceback
|
||||
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from threading import Thread
|
||||
from types import TracebackType
|
||||
from typing import Protocol
|
||||
|
||||
class ExceptHookArgs(Protocol):
|
||||
@property
|
||||
def exc_type(self) -> type[BaseException]: ...
|
||||
@property
|
||||
def exc_value(self) -> BaseException | None: ...
|
||||
@property
|
||||
def exc_traceback(self) -> TracebackType | None: ...
|
||||
@property
|
||||
def thread(self) -> Thread | None: ...
|
||||
|
||||
class ShowExceptions(Protocol):
|
||||
def __call__(self) -> int: ...
|
||||
def add(self, s: str) -> None: ...
|
||||
|
||||
from .reader import Reader
|
||||
|
||||
|
||||
def install_threading_hook(reader: Reader) -> None:
|
||||
import threading
|
||||
|
||||
@dataclass
|
||||
class ExceptHookHandler:
|
||||
lock: threading.Lock = field(default_factory=threading.Lock)
|
||||
messages: list[str] = field(default_factory=list)
|
||||
|
||||
def show(self) -> int:
|
||||
count = 0
|
||||
with self.lock:
|
||||
if not self.messages:
|
||||
return 0
|
||||
reader.restore()
|
||||
for tb in self.messages:
|
||||
count += 1
|
||||
if tb:
|
||||
print(tb)
|
||||
self.messages.clear()
|
||||
reader.scheduled_commands.append("ctrl-c")
|
||||
reader.prepare()
|
||||
return count
|
||||
|
||||
def add(self, s: str) -> None:
|
||||
with self.lock:
|
||||
self.messages.append(s)
|
||||
|
||||
def exception(self, args: ExceptHookArgs) -> None:
|
||||
lines = traceback.format_exception(
|
||||
args.exc_type,
|
||||
args.exc_value,
|
||||
args.exc_traceback,
|
||||
colorize=reader.can_colorize,
|
||||
) # type: ignore[call-overload]
|
||||
pre = f"\nException in {args.thread.name}:\n" if args.thread else "\n"
|
||||
tb = pre + "".join(lines)
|
||||
self.add(tb)
|
||||
|
||||
def __call__(self) -> int:
|
||||
return self.show()
|
||||
|
||||
|
||||
handler = ExceptHookHandler()
|
||||
reader.threading_hook = handler
|
||||
threading.excepthook = handler.exception
|
||||
489
Lib/_pyrepl/commands.py
vendored
Normal file
489
Lib/_pyrepl/commands.py
vendored
Normal file
@@ -0,0 +1,489 @@
|
||||
# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
# Antonio Cuni
|
||||
# Armin Rigo
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
|
||||
# Categories of actions:
|
||||
# killing
|
||||
# yanking
|
||||
# motion
|
||||
# editing
|
||||
# history
|
||||
# finishing
|
||||
# [completion]
|
||||
|
||||
|
||||
# types
|
||||
if False:
|
||||
from .historical_reader import HistoricalReader
|
||||
|
||||
|
||||
class Command:
|
||||
finish: bool = False
|
||||
kills_digit_arg: bool = True
|
||||
|
||||
def __init__(
|
||||
self, reader: HistoricalReader, event_name: str, event: list[str]
|
||||
) -> None:
|
||||
# Reader should really be "any reader" but there's too much usage of
|
||||
# HistoricalReader methods and fields in the code below for us to
|
||||
# refactor at the moment.
|
||||
|
||||
self.reader = reader
|
||||
self.event = event
|
||||
self.event_name = event_name
|
||||
|
||||
def do(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class KillCommand(Command):
|
||||
def kill_range(self, start: int, end: int) -> None:
|
||||
if start == end:
|
||||
return
|
||||
r = self.reader
|
||||
b = r.buffer
|
||||
text = b[start:end]
|
||||
del b[start:end]
|
||||
if is_kill(r.last_command):
|
||||
if start < r.pos:
|
||||
r.kill_ring[-1] = text + r.kill_ring[-1]
|
||||
else:
|
||||
r.kill_ring[-1] = r.kill_ring[-1] + text
|
||||
else:
|
||||
r.kill_ring.append(text)
|
||||
r.pos = start
|
||||
r.dirty = True
|
||||
|
||||
|
||||
class YankCommand(Command):
|
||||
pass
|
||||
|
||||
|
||||
class MotionCommand(Command):
|
||||
pass
|
||||
|
||||
|
||||
class EditCommand(Command):
|
||||
pass
|
||||
|
||||
|
||||
class FinishCommand(Command):
|
||||
finish = True
|
||||
pass
|
||||
|
||||
|
||||
def is_kill(command: type[Command] | None) -> bool:
|
||||
return command is not None and issubclass(command, KillCommand)
|
||||
|
||||
|
||||
def is_yank(command: type[Command] | None) -> bool:
|
||||
return command is not None and issubclass(command, YankCommand)
|
||||
|
||||
|
||||
# etc
|
||||
|
||||
|
||||
class digit_arg(Command):
|
||||
kills_digit_arg = False
|
||||
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
c = self.event[-1]
|
||||
if c == "-":
|
||||
if r.arg is not None:
|
||||
r.arg = -r.arg
|
||||
else:
|
||||
r.arg = -1
|
||||
else:
|
||||
d = int(c)
|
||||
if r.arg is None:
|
||||
r.arg = d
|
||||
else:
|
||||
if r.arg < 0:
|
||||
r.arg = 10 * r.arg - d
|
||||
else:
|
||||
r.arg = 10 * r.arg + d
|
||||
r.dirty = True
|
||||
|
||||
|
||||
class clear_screen(Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
r.console.clear()
|
||||
r.dirty = True
|
||||
|
||||
|
||||
class refresh(Command):
|
||||
def do(self) -> None:
|
||||
self.reader.dirty = True
|
||||
|
||||
|
||||
class repaint(Command):
|
||||
def do(self) -> None:
|
||||
self.reader.dirty = True
|
||||
self.reader.console.repaint()
|
||||
|
||||
|
||||
class kill_line(KillCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
b = r.buffer
|
||||
eol = r.eol()
|
||||
for c in b[r.pos : eol]:
|
||||
if not c.isspace():
|
||||
self.kill_range(r.pos, eol)
|
||||
return
|
||||
else:
|
||||
self.kill_range(r.pos, eol + 1)
|
||||
|
||||
|
||||
class unix_line_discard(KillCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
self.kill_range(r.bol(), r.pos)
|
||||
|
||||
|
||||
class unix_word_rubout(KillCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
for i in range(r.get_arg()):
|
||||
self.kill_range(r.bow(), r.pos)
|
||||
|
||||
|
||||
class kill_word(KillCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
for i in range(r.get_arg()):
|
||||
self.kill_range(r.pos, r.eow())
|
||||
|
||||
|
||||
class backward_kill_word(KillCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
for i in range(r.get_arg()):
|
||||
self.kill_range(r.bow(), r.pos)
|
||||
|
||||
|
||||
class yank(YankCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
if not r.kill_ring:
|
||||
r.error("nothing to yank")
|
||||
return
|
||||
r.insert(r.kill_ring[-1])
|
||||
|
||||
|
||||
class yank_pop(YankCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
b = r.buffer
|
||||
if not r.kill_ring:
|
||||
r.error("nothing to yank")
|
||||
return
|
||||
if not is_yank(r.last_command):
|
||||
r.error("previous command was not a yank")
|
||||
return
|
||||
repl = len(r.kill_ring[-1])
|
||||
r.kill_ring.insert(0, r.kill_ring.pop())
|
||||
t = r.kill_ring[-1]
|
||||
b[r.pos - repl : r.pos] = t
|
||||
r.pos = r.pos - repl + len(t)
|
||||
r.dirty = True
|
||||
|
||||
|
||||
class interrupt(FinishCommand):
|
||||
def do(self) -> None:
|
||||
import signal
|
||||
|
||||
self.reader.console.finish()
|
||||
self.reader.finish()
|
||||
os.kill(os.getpid(), signal.SIGINT)
|
||||
|
||||
|
||||
class ctrl_c(Command):
|
||||
def do(self) -> None:
|
||||
self.reader.console.finish()
|
||||
self.reader.finish()
|
||||
raise KeyboardInterrupt
|
||||
|
||||
|
||||
class suspend(Command):
|
||||
def do(self) -> None:
|
||||
import signal
|
||||
|
||||
r = self.reader
|
||||
p = r.pos
|
||||
r.console.finish()
|
||||
os.kill(os.getpid(), signal.SIGSTOP)
|
||||
## this should probably be done
|
||||
## in a handler for SIGCONT?
|
||||
r.console.prepare()
|
||||
r.pos = p
|
||||
# r.posxy = 0, 0 # XXX this is invalid
|
||||
r.dirty = True
|
||||
r.console.screen = []
|
||||
|
||||
|
||||
class up(MotionCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
for _ in range(r.get_arg()):
|
||||
x, y = r.pos2xy()
|
||||
new_y = y - 1
|
||||
|
||||
if r.bol() == 0:
|
||||
if r.historyi > 0:
|
||||
r.select_item(r.historyi - 1)
|
||||
return
|
||||
r.pos = 0
|
||||
r.error("start of buffer")
|
||||
return
|
||||
|
||||
if (
|
||||
x
|
||||
> (
|
||||
new_x := r.max_column(new_y)
|
||||
) # we're past the end of the previous line
|
||||
or x == r.max_column(y)
|
||||
and any(
|
||||
not i.isspace() for i in r.buffer[r.bol() :]
|
||||
) # move between eols
|
||||
):
|
||||
x = new_x
|
||||
|
||||
r.setpos_from_xy(x, new_y)
|
||||
|
||||
|
||||
class down(MotionCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
b = r.buffer
|
||||
for _ in range(r.get_arg()):
|
||||
x, y = r.pos2xy()
|
||||
new_y = y + 1
|
||||
|
||||
if r.eol() == len(b):
|
||||
if r.historyi < len(r.history):
|
||||
r.select_item(r.historyi + 1)
|
||||
r.pos = r.eol(0)
|
||||
return
|
||||
r.pos = len(b)
|
||||
r.error("end of buffer")
|
||||
return
|
||||
|
||||
if (
|
||||
x
|
||||
> (
|
||||
new_x := r.max_column(new_y)
|
||||
) # we're past the end of the previous line
|
||||
or x == r.max_column(y)
|
||||
and any(
|
||||
not i.isspace() for i in r.buffer[r.bol() :]
|
||||
) # move between eols
|
||||
):
|
||||
x = new_x
|
||||
|
||||
r.setpos_from_xy(x, new_y)
|
||||
|
||||
|
||||
class left(MotionCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
for _ in range(r.get_arg()):
|
||||
p = r.pos - 1
|
||||
if p >= 0:
|
||||
r.pos = p
|
||||
else:
|
||||
self.reader.error("start of buffer")
|
||||
|
||||
|
||||
class right(MotionCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
b = r.buffer
|
||||
for _ in range(r.get_arg()):
|
||||
p = r.pos + 1
|
||||
if p <= len(b):
|
||||
r.pos = p
|
||||
else:
|
||||
self.reader.error("end of buffer")
|
||||
|
||||
|
||||
class beginning_of_line(MotionCommand):
|
||||
def do(self) -> None:
|
||||
self.reader.pos = self.reader.bol()
|
||||
|
||||
|
||||
class end_of_line(MotionCommand):
|
||||
def do(self) -> None:
|
||||
self.reader.pos = self.reader.eol()
|
||||
|
||||
|
||||
class home(MotionCommand):
|
||||
def do(self) -> None:
|
||||
self.reader.pos = 0
|
||||
|
||||
|
||||
class end(MotionCommand):
|
||||
def do(self) -> None:
|
||||
self.reader.pos = len(self.reader.buffer)
|
||||
|
||||
|
||||
class forward_word(MotionCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
for i in range(r.get_arg()):
|
||||
r.pos = r.eow()
|
||||
|
||||
|
||||
class backward_word(MotionCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
for i in range(r.get_arg()):
|
||||
r.pos = r.bow()
|
||||
|
||||
|
||||
class self_insert(EditCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
text = self.event * r.get_arg()
|
||||
r.insert(text)
|
||||
|
||||
|
||||
class insert_nl(EditCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
r.insert("\n" * r.get_arg())
|
||||
|
||||
|
||||
class transpose_characters(EditCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
b = r.buffer
|
||||
s = r.pos - 1
|
||||
if s < 0:
|
||||
r.error("cannot transpose at start of buffer")
|
||||
else:
|
||||
if s == len(b):
|
||||
s -= 1
|
||||
t = min(s + r.get_arg(), len(b) - 1)
|
||||
c = b[s]
|
||||
del b[s]
|
||||
b.insert(t, c)
|
||||
r.pos = t
|
||||
r.dirty = True
|
||||
|
||||
|
||||
class backspace(EditCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
b = r.buffer
|
||||
for i in range(r.get_arg()):
|
||||
if r.pos > 0:
|
||||
r.pos -= 1
|
||||
del b[r.pos]
|
||||
r.dirty = True
|
||||
else:
|
||||
self.reader.error("can't backspace at start")
|
||||
|
||||
|
||||
class delete(EditCommand):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
b = r.buffer
|
||||
if (
|
||||
r.pos == 0
|
||||
and len(b) == 0 # this is something of a hack
|
||||
and self.event[-1] == "\004"
|
||||
):
|
||||
r.update_screen()
|
||||
r.console.finish()
|
||||
raise EOFError
|
||||
for i in range(r.get_arg()):
|
||||
if r.pos != len(b):
|
||||
del b[r.pos]
|
||||
r.dirty = True
|
||||
else:
|
||||
self.reader.error("end of buffer")
|
||||
|
||||
|
||||
class accept(FinishCommand):
|
||||
def do(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class help(Command):
|
||||
def do(self) -> None:
|
||||
import _sitebuiltins
|
||||
|
||||
with self.reader.suspend():
|
||||
self.reader.msg = _sitebuiltins._Helper()() # type: ignore[assignment, call-arg]
|
||||
|
||||
|
||||
class invalid_key(Command):
|
||||
def do(self) -> None:
|
||||
pending = self.reader.console.getpending()
|
||||
s = "".join(self.event) + pending.data
|
||||
self.reader.error("`%r' not bound" % s)
|
||||
|
||||
|
||||
class invalid_command(Command):
|
||||
def do(self) -> None:
|
||||
s = self.event_name
|
||||
self.reader.error("command `%s' not known" % s)
|
||||
|
||||
|
||||
class show_history(Command):
|
||||
def do(self) -> None:
|
||||
from .pager import get_pager
|
||||
from site import gethistoryfile # type: ignore[attr-defined]
|
||||
|
||||
history = os.linesep.join(self.reader.history[:])
|
||||
self.reader.console.restore()
|
||||
pager = get_pager()
|
||||
pager(history, gethistoryfile())
|
||||
self.reader.console.prepare()
|
||||
|
||||
# We need to copy over the state so that it's consistent between
|
||||
# console and reader, and console does not overwrite/append stuff
|
||||
self.reader.console.screen = self.reader.screen.copy()
|
||||
self.reader.console.posxy = self.reader.cxy
|
||||
|
||||
|
||||
class paste_mode(Command):
|
||||
|
||||
def do(self) -> None:
|
||||
self.reader.paste_mode = not self.reader.paste_mode
|
||||
self.reader.dirty = True
|
||||
|
||||
|
||||
class enable_bracketed_paste(Command):
|
||||
def do(self) -> None:
|
||||
self.reader.paste_mode = True
|
||||
self.reader.in_bracketed_paste = True
|
||||
|
||||
class disable_bracketed_paste(Command):
|
||||
def do(self) -> None:
|
||||
self.reader.paste_mode = False
|
||||
self.reader.in_bracketed_paste = False
|
||||
self.reader.dirty = True
|
||||
295
Lib/_pyrepl/completing_reader.py
vendored
Normal file
295
Lib/_pyrepl/completing_reader.py
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
# Antonio Cuni
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import re
|
||||
from . import commands, console, reader
|
||||
from .reader import Reader
|
||||
|
||||
|
||||
# types
|
||||
Command = commands.Command
|
||||
if False:
|
||||
from .types import KeySpec, CommandName
|
||||
|
||||
|
||||
def prefix(wordlist: list[str], j: int = 0) -> str:
|
||||
d = {}
|
||||
i = j
|
||||
try:
|
||||
while 1:
|
||||
for word in wordlist:
|
||||
d[word[i]] = 1
|
||||
if len(d) > 1:
|
||||
return wordlist[0][j:i]
|
||||
i += 1
|
||||
d = {}
|
||||
except IndexError:
|
||||
return wordlist[0][j:i]
|
||||
return ""
|
||||
|
||||
|
||||
STRIPCOLOR_REGEX = re.compile(r"\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[m|K]")
|
||||
|
||||
def stripcolor(s: str) -> str:
|
||||
return STRIPCOLOR_REGEX.sub('', s)
|
||||
|
||||
|
||||
def real_len(s: str) -> int:
|
||||
return len(stripcolor(s))
|
||||
|
||||
|
||||
def left_align(s: str, maxlen: int) -> str:
|
||||
stripped = stripcolor(s)
|
||||
if len(stripped) > maxlen:
|
||||
# too bad, we remove the color
|
||||
return stripped[:maxlen]
|
||||
padding = maxlen - len(stripped)
|
||||
return s + ' '*padding
|
||||
|
||||
|
||||
def build_menu(
|
||||
cons: console.Console,
|
||||
wordlist: list[str],
|
||||
start: int,
|
||||
use_brackets: bool,
|
||||
sort_in_column: bool,
|
||||
) -> tuple[list[str], int]:
|
||||
if use_brackets:
|
||||
item = "[ %s ]"
|
||||
padding = 4
|
||||
else:
|
||||
item = "%s "
|
||||
padding = 2
|
||||
maxlen = min(max(map(real_len, wordlist)), cons.width - padding)
|
||||
cols = int(cons.width / (maxlen + padding))
|
||||
rows = int((len(wordlist) - 1)/cols + 1)
|
||||
|
||||
if sort_in_column:
|
||||
# sort_in_column=False (default) sort_in_column=True
|
||||
# A B C A D G
|
||||
# D E F B E
|
||||
# G C F
|
||||
#
|
||||
# "fill" the table with empty words, so we always have the same amout
|
||||
# of rows for each column
|
||||
missing = cols*rows - len(wordlist)
|
||||
wordlist = wordlist + ['']*missing
|
||||
indexes = [(i % cols) * rows + i // cols for i in range(len(wordlist))]
|
||||
wordlist = [wordlist[i] for i in indexes]
|
||||
menu = []
|
||||
i = start
|
||||
for r in range(rows):
|
||||
row = []
|
||||
for col in range(cols):
|
||||
row.append(item % left_align(wordlist[i], maxlen))
|
||||
i += 1
|
||||
if i >= len(wordlist):
|
||||
break
|
||||
menu.append(''.join(row))
|
||||
if i >= len(wordlist):
|
||||
i = 0
|
||||
break
|
||||
if r + 5 > cons.height:
|
||||
menu.append(" %d more... " % (len(wordlist) - i))
|
||||
break
|
||||
return menu, i
|
||||
|
||||
# this gets somewhat user interface-y, and as a result the logic gets
|
||||
# very convoluted.
|
||||
#
|
||||
# To summarise the summary of the summary:- people are a problem.
|
||||
# -- The Hitch-Hikers Guide to the Galaxy, Episode 12
|
||||
|
||||
#### Desired behaviour of the completions commands.
|
||||
# the considerations are:
|
||||
# (1) how many completions are possible
|
||||
# (2) whether the last command was a completion
|
||||
# (3) if we can assume that the completer is going to return the same set of
|
||||
# completions: this is controlled by the ``assume_immutable_completions``
|
||||
# variable on the reader, which is True by default to match the historical
|
||||
# behaviour of pyrepl, but e.g. False in the ReadlineAlikeReader to match
|
||||
# more closely readline's semantics (this is needed e.g. by
|
||||
# fancycompleter)
|
||||
#
|
||||
# if there's no possible completion, beep at the user and point this out.
|
||||
# this is easy.
|
||||
#
|
||||
# if there's only one possible completion, stick it in. if the last thing
|
||||
# user did was a completion, point out that he isn't getting anywhere, but
|
||||
# only if the ``assume_immutable_completions`` is True.
|
||||
#
|
||||
# now it gets complicated.
|
||||
#
|
||||
# for the first press of a completion key:
|
||||
# if there's a common prefix, stick it in.
|
||||
|
||||
# irrespective of whether anything got stuck in, if the word is now
|
||||
# complete, show the "complete but not unique" message
|
||||
|
||||
# if there's no common prefix and if the word is not now complete,
|
||||
# beep.
|
||||
|
||||
# common prefix -> yes no
|
||||
# word complete \/
|
||||
# yes "cbnu" "cbnu"
|
||||
# no - beep
|
||||
|
||||
# for the second bang on the completion key
|
||||
# there will necessarily be no common prefix
|
||||
# show a menu of the choices.
|
||||
|
||||
# for subsequent bangs, rotate the menu around (if there are sufficient
|
||||
# choices).
|
||||
|
||||
|
||||
class complete(commands.Command):
|
||||
def do(self) -> None:
|
||||
r: CompletingReader
|
||||
r = self.reader # type: ignore[assignment]
|
||||
last_is_completer = r.last_command_is(self.__class__)
|
||||
immutable_completions = r.assume_immutable_completions
|
||||
completions_unchangable = last_is_completer and immutable_completions
|
||||
stem = r.get_stem()
|
||||
if not completions_unchangable:
|
||||
r.cmpltn_menu_choices = r.get_completions(stem)
|
||||
|
||||
completions = r.cmpltn_menu_choices
|
||||
if not completions:
|
||||
r.error("no matches")
|
||||
elif len(completions) == 1:
|
||||
if completions_unchangable and len(completions[0]) == len(stem):
|
||||
r.msg = "[ sole completion ]"
|
||||
r.dirty = True
|
||||
r.insert(completions[0][len(stem):])
|
||||
else:
|
||||
p = prefix(completions, len(stem))
|
||||
if p:
|
||||
r.insert(p)
|
||||
if last_is_completer:
|
||||
r.cmpltn_menu_visible = True
|
||||
r.cmpltn_message_visible = False
|
||||
r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
|
||||
r.console, completions, r.cmpltn_menu_end,
|
||||
r.use_brackets, r.sort_in_column)
|
||||
r.dirty = True
|
||||
elif not r.cmpltn_menu_visible:
|
||||
r.cmpltn_message_visible = True
|
||||
if stem + p in completions:
|
||||
r.msg = "[ complete but not unique ]"
|
||||
r.dirty = True
|
||||
else:
|
||||
r.msg = "[ not unique ]"
|
||||
r.dirty = True
|
||||
|
||||
|
||||
class self_insert(commands.self_insert):
|
||||
def do(self) -> None:
|
||||
r: CompletingReader
|
||||
r = self.reader # type: ignore[assignment]
|
||||
|
||||
commands.self_insert.do(self)
|
||||
if r.cmpltn_menu_visible:
|
||||
stem = r.get_stem()
|
||||
if len(stem) < 1:
|
||||
r.cmpltn_reset()
|
||||
else:
|
||||
completions = [w for w in r.cmpltn_menu_choices
|
||||
if w.startswith(stem)]
|
||||
if completions:
|
||||
r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
|
||||
r.console, completions, 0,
|
||||
r.use_brackets, r.sort_in_column)
|
||||
else:
|
||||
r.cmpltn_reset()
|
||||
|
||||
|
||||
@dataclass
|
||||
class CompletingReader(Reader):
|
||||
"""Adds completion support"""
|
||||
|
||||
### Class variables
|
||||
# see the comment for the complete command
|
||||
assume_immutable_completions = True
|
||||
use_brackets = True # display completions inside []
|
||||
sort_in_column = False
|
||||
|
||||
### Instance variables
|
||||
cmpltn_menu: list[str] = field(init=False)
|
||||
cmpltn_menu_visible: bool = field(init=False)
|
||||
cmpltn_message_visible: bool = field(init=False)
|
||||
cmpltn_menu_end: int = field(init=False)
|
||||
cmpltn_menu_choices: list[str] = field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
super().__post_init__()
|
||||
self.cmpltn_reset()
|
||||
for c in (complete, self_insert):
|
||||
self.commands[c.__name__] = c
|
||||
self.commands[c.__name__.replace('_', '-')] = c
|
||||
|
||||
def collect_keymap(self) -> tuple[tuple[KeySpec, CommandName], ...]:
|
||||
return super().collect_keymap() + (
|
||||
(r'\t', 'complete'),)
|
||||
|
||||
def after_command(self, cmd: Command) -> None:
|
||||
super().after_command(cmd)
|
||||
if not isinstance(cmd, (complete, self_insert)):
|
||||
self.cmpltn_reset()
|
||||
|
||||
def calc_screen(self) -> list[str]:
|
||||
screen = super().calc_screen()
|
||||
if self.cmpltn_menu_visible:
|
||||
# We display the completions menu below the current prompt
|
||||
ly = self.lxy[1] + 1
|
||||
screen[ly:ly] = self.cmpltn_menu
|
||||
# If we're not in the middle of multiline edit, don't append to screeninfo
|
||||
# since that screws up the position calculation in pos2xy function.
|
||||
# This is a hack to prevent the cursor jumping
|
||||
# into the completions menu when pressing left or down arrow.
|
||||
if self.pos != len(self.buffer):
|
||||
self.screeninfo[ly:ly] = [(0, [])]*len(self.cmpltn_menu)
|
||||
return screen
|
||||
|
||||
def finish(self) -> None:
|
||||
super().finish()
|
||||
self.cmpltn_reset()
|
||||
|
||||
def cmpltn_reset(self) -> None:
|
||||
self.cmpltn_menu = []
|
||||
self.cmpltn_menu_visible = False
|
||||
self.cmpltn_message_visible = False
|
||||
self.cmpltn_menu_end = 0
|
||||
self.cmpltn_menu_choices = []
|
||||
|
||||
def get_stem(self) -> str:
|
||||
st = self.syntax_table
|
||||
SW = reader.SYNTAX_WORD
|
||||
b = self.buffer
|
||||
p = self.pos - 1
|
||||
while p >= 0 and st.get(b[p], SW) == SW:
|
||||
p -= 1
|
||||
return ''.join(b[p+1:self.pos])
|
||||
|
||||
def get_completions(self, stem: str) -> list[str]:
|
||||
return []
|
||||
213
Lib/_pyrepl/console.py
vendored
Normal file
213
Lib/_pyrepl/console.py
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import _colorize # type: ignore[import-not-found]
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
import ast
|
||||
import code
|
||||
from dataclasses import dataclass, field
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
||||
TYPE_CHECKING = False
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import IO
|
||||
from typing import Callable
|
||||
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
evt: str
|
||||
data: str
|
||||
raw: bytes = b""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Console(ABC):
|
||||
posxy: tuple[int, int]
|
||||
screen: list[str] = field(default_factory=list)
|
||||
height: int = 25
|
||||
width: int = 80
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
f_in: IO[bytes] | int = 0,
|
||||
f_out: IO[bytes] | int = 1,
|
||||
term: str = "",
|
||||
encoding: str = "",
|
||||
):
|
||||
self.encoding = encoding or sys.getdefaultencoding()
|
||||
|
||||
if isinstance(f_in, int):
|
||||
self.input_fd = f_in
|
||||
else:
|
||||
self.input_fd = f_in.fileno()
|
||||
|
||||
if isinstance(f_out, int):
|
||||
self.output_fd = f_out
|
||||
else:
|
||||
self.output_fd = f_out.fileno()
|
||||
|
||||
@abstractmethod
|
||||
def refresh(self, screen: list[str], xy: tuple[int, int]) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
def prepare(self) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
def restore(self) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
def move_cursor(self, x: int, y: int) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
def set_cursor_vis(self, visible: bool) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
def getheightwidth(self) -> tuple[int, int]:
|
||||
"""Return (height, width) where height and width are the height
|
||||
and width of the terminal window in characters."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def get_event(self, block: bool = True) -> Event | None:
|
||||
"""Return an Event instance. Returns None if |block| is false
|
||||
and there is no event pending, otherwise waits for the
|
||||
completion of an event."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def push_char(self, char: int | bytes) -> None:
|
||||
"""
|
||||
Push a character to the console event queue.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def beep(self) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
def clear(self) -> None:
|
||||
"""Wipe the screen"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def finish(self) -> None:
|
||||
"""Move the cursor to the end of the display and otherwise get
|
||||
ready for end. XXX could be merged with restore? Hmm."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def flushoutput(self) -> None:
|
||||
"""Flush all output to the screen (assuming there's some
|
||||
buffering going on somewhere)."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def forgetinput(self) -> None:
|
||||
"""Forget all pending, but not yet processed input."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def getpending(self) -> Event:
|
||||
"""Return the characters that have been typed but not yet
|
||||
processed."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def wait(self, timeout: float | None) -> bool:
|
||||
"""Wait for an event. The return value is True if an event is
|
||||
available, False if the timeout has been reached. If timeout is
|
||||
None, wait forever. The timeout is in milliseconds."""
|
||||
...
|
||||
|
||||
@property
|
||||
def input_hook(self) -> Callable[[], int] | None:
|
||||
"""Returns the current input hook."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def repaint(self) -> None: ...
|
||||
|
||||
|
||||
class InteractiveColoredConsole(code.InteractiveConsole):
|
||||
def __init__(
|
||||
self,
|
||||
locals: dict[str, object] | None = None,
|
||||
filename: str = "<console>",
|
||||
*,
|
||||
local_exit: bool = False,
|
||||
) -> None:
|
||||
super().__init__(locals=locals, filename=filename, local_exit=local_exit) # type: ignore[call-arg]
|
||||
self.can_colorize = _colorize.can_colorize()
|
||||
|
||||
def showsyntaxerror(self, filename=None, **kwargs):
|
||||
super().showsyntaxerror(filename=filename, **kwargs)
|
||||
|
||||
def _excepthook(self, typ, value, tb):
|
||||
import traceback
|
||||
lines = traceback.format_exception(
|
||||
typ, value, tb,
|
||||
colorize=self.can_colorize,
|
||||
limit=traceback.BUILTIN_EXCEPTION_LIMIT)
|
||||
self.write(''.join(lines))
|
||||
|
||||
def runsource(self, source, filename="<input>", symbol="single"):
|
||||
try:
|
||||
tree = self.compile.compiler(
|
||||
source,
|
||||
filename,
|
||||
"exec",
|
||||
ast.PyCF_ONLY_AST,
|
||||
incomplete_input=False,
|
||||
)
|
||||
except (SyntaxError, OverflowError, ValueError):
|
||||
self.showsyntaxerror(filename, source=source)
|
||||
return False
|
||||
if tree.body:
|
||||
*_, last_stmt = tree.body
|
||||
for stmt in tree.body:
|
||||
wrapper = ast.Interactive if stmt is last_stmt else ast.Module
|
||||
the_symbol = symbol if stmt is last_stmt else "exec"
|
||||
item = wrapper([stmt])
|
||||
try:
|
||||
code = self.compile.compiler(item, filename, the_symbol)
|
||||
except SyntaxError as e:
|
||||
if e.args[0] == "'await' outside function":
|
||||
python = os.path.basename(sys.executable)
|
||||
e.add_note(
|
||||
f"Try the asyncio REPL ({python} -m asyncio) to use"
|
||||
f" top-level 'await' and run background asyncio tasks."
|
||||
)
|
||||
self.showsyntaxerror(filename, source=source)
|
||||
return False
|
||||
except (OverflowError, ValueError):
|
||||
self.showsyntaxerror(filename, source=source)
|
||||
return False
|
||||
|
||||
if code is None:
|
||||
return True
|
||||
|
||||
self.runcode(code)
|
||||
return False
|
||||
33
Lib/_pyrepl/curses.py
vendored
Normal file
33
Lib/_pyrepl/curses.py
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
# Armin Rigo
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
try:
|
||||
import _curses
|
||||
except ImportError:
|
||||
try:
|
||||
import curses as _curses # type: ignore[no-redef]
|
||||
except ImportError:
|
||||
from . import _minimal_curses as _curses # type: ignore[no-redef]
|
||||
|
||||
setupterm = _curses.setupterm
|
||||
tigetstr = _curses.tigetstr
|
||||
tparm = _curses.tparm
|
||||
error = _curses.error
|
||||
76
Lib/_pyrepl/fancy_termios.py
vendored
Normal file
76
Lib/_pyrepl/fancy_termios.py
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import termios
|
||||
|
||||
|
||||
class TermState:
|
||||
def __init__(self, tuples):
|
||||
(
|
||||
self.iflag,
|
||||
self.oflag,
|
||||
self.cflag,
|
||||
self.lflag,
|
||||
self.ispeed,
|
||||
self.ospeed,
|
||||
self.cc,
|
||||
) = tuples
|
||||
|
||||
def as_list(self):
|
||||
return [
|
||||
self.iflag,
|
||||
self.oflag,
|
||||
self.cflag,
|
||||
self.lflag,
|
||||
self.ispeed,
|
||||
self.ospeed,
|
||||
# Always return a copy of the control characters list to ensure
|
||||
# there are not any additional references to self.cc
|
||||
self.cc[:],
|
||||
]
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self.as_list())
|
||||
|
||||
|
||||
def tcgetattr(fd):
|
||||
return TermState(termios.tcgetattr(fd))
|
||||
|
||||
|
||||
def tcsetattr(fd, when, attrs):
|
||||
termios.tcsetattr(fd, when, attrs.as_list())
|
||||
|
||||
|
||||
class Term(TermState):
|
||||
TS__init__ = TermState.__init__
|
||||
|
||||
def __init__(self, fd=0):
|
||||
self.TS__init__(termios.tcgetattr(fd))
|
||||
self.fd = fd
|
||||
self.stack = []
|
||||
|
||||
def save(self):
|
||||
self.stack.append(self.as_list())
|
||||
|
||||
def set(self, when=termios.TCSANOW):
|
||||
termios.tcsetattr(self.fd, when, self.as_list())
|
||||
|
||||
def restore(self):
|
||||
self.TS__init__(self.stack.pop())
|
||||
self.set()
|
||||
419
Lib/_pyrepl/historical_reader.py
vendored
Normal file
419
Lib/_pyrepl/historical_reader.py
vendored
Normal file
@@ -0,0 +1,419 @@
|
||||
# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from . import commands, input
|
||||
from .reader import Reader
|
||||
|
||||
|
||||
if False:
|
||||
from .types import SimpleContextManager, KeySpec, CommandName
|
||||
|
||||
|
||||
isearch_keymap: tuple[tuple[KeySpec, CommandName], ...] = tuple(
|
||||
[("\\%03o" % c, "isearch-end") for c in range(256) if chr(c) != "\\"]
|
||||
+ [(c, "isearch-add-character") for c in map(chr, range(32, 127)) if c != "\\"]
|
||||
+ [
|
||||
("\\%03o" % c, "isearch-add-character")
|
||||
for c in range(256)
|
||||
if chr(c).isalpha() and chr(c) != "\\"
|
||||
]
|
||||
+ [
|
||||
("\\\\", "self-insert"),
|
||||
(r"\C-r", "isearch-backwards"),
|
||||
(r"\C-s", "isearch-forwards"),
|
||||
(r"\C-c", "isearch-cancel"),
|
||||
(r"\C-g", "isearch-cancel"),
|
||||
(r"\<backspace>", "isearch-backspace"),
|
||||
]
|
||||
)
|
||||
|
||||
ISEARCH_DIRECTION_NONE = ""
|
||||
ISEARCH_DIRECTION_BACKWARDS = "r"
|
||||
ISEARCH_DIRECTION_FORWARDS = "f"
|
||||
|
||||
|
||||
class next_history(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
if r.historyi == len(r.history):
|
||||
r.error("end of history list")
|
||||
return
|
||||
r.select_item(r.historyi + 1)
|
||||
|
||||
|
||||
class previous_history(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
if r.historyi == 0:
|
||||
r.error("start of history list")
|
||||
return
|
||||
r.select_item(r.historyi - 1)
|
||||
|
||||
|
||||
class history_search_backward(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
r.search_next(forwards=False)
|
||||
|
||||
|
||||
class history_search_forward(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
r.search_next(forwards=True)
|
||||
|
||||
|
||||
class restore_history(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
if r.historyi != len(r.history):
|
||||
if r.get_unicode() != r.history[r.historyi]:
|
||||
r.buffer = list(r.history[r.historyi])
|
||||
r.pos = len(r.buffer)
|
||||
r.dirty = True
|
||||
|
||||
|
||||
class first_history(commands.Command):
|
||||
def do(self) -> None:
|
||||
self.reader.select_item(0)
|
||||
|
||||
|
||||
class last_history(commands.Command):
|
||||
def do(self) -> None:
|
||||
self.reader.select_item(len(self.reader.history))
|
||||
|
||||
|
||||
class operate_and_get_next(commands.FinishCommand):
|
||||
def do(self) -> None:
|
||||
self.reader.next_history = self.reader.historyi + 1
|
||||
|
||||
|
||||
class yank_arg(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
if r.last_command is self.__class__:
|
||||
r.yank_arg_i += 1
|
||||
else:
|
||||
r.yank_arg_i = 0
|
||||
if r.historyi < r.yank_arg_i:
|
||||
r.error("beginning of history list")
|
||||
return
|
||||
a = r.get_arg(-1)
|
||||
# XXX how to split?
|
||||
words = r.get_item(r.historyi - r.yank_arg_i - 1).split()
|
||||
if a < -len(words) or a >= len(words):
|
||||
r.error("no such arg")
|
||||
return
|
||||
w = words[a]
|
||||
b = r.buffer
|
||||
if r.yank_arg_i > 0:
|
||||
o = len(r.yank_arg_yanked)
|
||||
else:
|
||||
o = 0
|
||||
b[r.pos - o : r.pos] = list(w)
|
||||
r.yank_arg_yanked = w
|
||||
r.pos += len(w) - o
|
||||
r.dirty = True
|
||||
|
||||
|
||||
class forward_history_isearch(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
r.isearch_direction = ISEARCH_DIRECTION_FORWARDS
|
||||
r.isearch_start = r.historyi, r.pos
|
||||
r.isearch_term = ""
|
||||
r.dirty = True
|
||||
r.push_input_trans(r.isearch_trans)
|
||||
|
||||
|
||||
class reverse_history_isearch(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
r.isearch_direction = ISEARCH_DIRECTION_BACKWARDS
|
||||
r.dirty = True
|
||||
r.isearch_term = ""
|
||||
r.push_input_trans(r.isearch_trans)
|
||||
r.isearch_start = r.historyi, r.pos
|
||||
|
||||
|
||||
class isearch_cancel(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
r.isearch_direction = ISEARCH_DIRECTION_NONE
|
||||
r.pop_input_trans()
|
||||
r.select_item(r.isearch_start[0])
|
||||
r.pos = r.isearch_start[1]
|
||||
r.dirty = True
|
||||
|
||||
|
||||
class isearch_add_character(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
b = r.buffer
|
||||
r.isearch_term += self.event[-1]
|
||||
r.dirty = True
|
||||
p = r.pos + len(r.isearch_term) - 1
|
||||
if b[p : p + 1] != [r.isearch_term[-1]]:
|
||||
r.isearch_next()
|
||||
|
||||
|
||||
class isearch_backspace(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
if len(r.isearch_term) > 0:
|
||||
r.isearch_term = r.isearch_term[:-1]
|
||||
r.dirty = True
|
||||
else:
|
||||
r.error("nothing to rubout")
|
||||
|
||||
|
||||
class isearch_forwards(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
r.isearch_direction = ISEARCH_DIRECTION_FORWARDS
|
||||
r.isearch_next()
|
||||
|
||||
|
||||
class isearch_backwards(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
r.isearch_direction = ISEARCH_DIRECTION_BACKWARDS
|
||||
r.isearch_next()
|
||||
|
||||
|
||||
class isearch_end(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
r.isearch_direction = ISEARCH_DIRECTION_NONE
|
||||
r.console.forgetinput()
|
||||
r.pop_input_trans()
|
||||
r.dirty = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class HistoricalReader(Reader):
|
||||
"""Adds history support (with incremental history searching) to the
|
||||
Reader class.
|
||||
"""
|
||||
|
||||
history: list[str] = field(default_factory=list)
|
||||
historyi: int = 0
|
||||
next_history: int | None = None
|
||||
transient_history: dict[int, str] = field(default_factory=dict)
|
||||
isearch_term: str = ""
|
||||
isearch_direction: str = ISEARCH_DIRECTION_NONE
|
||||
isearch_start: tuple[int, int] = field(init=False)
|
||||
isearch_trans: input.KeymapTranslator = field(init=False)
|
||||
yank_arg_i: int = 0
|
||||
yank_arg_yanked: str = ""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
super().__post_init__()
|
||||
for c in [
|
||||
next_history,
|
||||
previous_history,
|
||||
restore_history,
|
||||
first_history,
|
||||
last_history,
|
||||
yank_arg,
|
||||
forward_history_isearch,
|
||||
reverse_history_isearch,
|
||||
isearch_end,
|
||||
isearch_add_character,
|
||||
isearch_cancel,
|
||||
isearch_add_character,
|
||||
isearch_backspace,
|
||||
isearch_forwards,
|
||||
isearch_backwards,
|
||||
operate_and_get_next,
|
||||
history_search_backward,
|
||||
history_search_forward,
|
||||
]:
|
||||
self.commands[c.__name__] = c
|
||||
self.commands[c.__name__.replace("_", "-")] = c
|
||||
self.isearch_start = self.historyi, self.pos
|
||||
self.isearch_trans = input.KeymapTranslator(
|
||||
isearch_keymap, invalid_cls=isearch_end, character_cls=isearch_add_character
|
||||
)
|
||||
|
||||
def collect_keymap(self) -> tuple[tuple[KeySpec, CommandName], ...]:
|
||||
return super().collect_keymap() + (
|
||||
(r"\C-n", "next-history"),
|
||||
(r"\C-p", "previous-history"),
|
||||
(r"\C-o", "operate-and-get-next"),
|
||||
(r"\C-r", "reverse-history-isearch"),
|
||||
(r"\C-s", "forward-history-isearch"),
|
||||
(r"\M-r", "restore-history"),
|
||||
(r"\M-.", "yank-arg"),
|
||||
(r"\<page down>", "history-search-forward"),
|
||||
(r"\x1b[6~", "history-search-forward"),
|
||||
(r"\<page up>", "history-search-backward"),
|
||||
(r"\x1b[5~", "history-search-backward"),
|
||||
)
|
||||
|
||||
def select_item(self, i: int) -> None:
|
||||
self.transient_history[self.historyi] = self.get_unicode()
|
||||
buf = self.transient_history.get(i)
|
||||
if buf is None:
|
||||
buf = self.history[i].rstrip()
|
||||
self.buffer = list(buf)
|
||||
self.historyi = i
|
||||
self.pos = len(self.buffer)
|
||||
self.dirty = True
|
||||
self.last_refresh_cache.invalidated = True
|
||||
|
||||
def get_item(self, i: int) -> str:
|
||||
if i != len(self.history):
|
||||
return self.transient_history.get(i, self.history[i])
|
||||
else:
|
||||
return self.transient_history.get(i, self.get_unicode())
|
||||
|
||||
@contextmanager
|
||||
def suspend(self) -> SimpleContextManager:
|
||||
with super().suspend(), self.suspend_history():
|
||||
yield
|
||||
|
||||
@contextmanager
|
||||
def suspend_history(self) -> SimpleContextManager:
|
||||
try:
|
||||
old_history = self.history[:]
|
||||
del self.history[:]
|
||||
yield
|
||||
finally:
|
||||
self.history[:] = old_history
|
||||
|
||||
def prepare(self) -> None:
|
||||
super().prepare()
|
||||
try:
|
||||
self.transient_history = {}
|
||||
if self.next_history is not None and self.next_history < len(self.history):
|
||||
self.historyi = self.next_history
|
||||
self.buffer[:] = list(self.history[self.next_history])
|
||||
self.pos = len(self.buffer)
|
||||
self.transient_history[len(self.history)] = ""
|
||||
else:
|
||||
self.historyi = len(self.history)
|
||||
self.next_history = None
|
||||
except:
|
||||
self.restore()
|
||||
raise
|
||||
|
||||
def get_prompt(self, lineno: int, cursor_on_line: bool) -> str:
|
||||
if cursor_on_line and self.isearch_direction != ISEARCH_DIRECTION_NONE:
|
||||
d = "rf"[self.isearch_direction == ISEARCH_DIRECTION_FORWARDS]
|
||||
return "(%s-search `%s') " % (d, self.isearch_term)
|
||||
else:
|
||||
return super().get_prompt(lineno, cursor_on_line)
|
||||
|
||||
def search_next(self, *, forwards: bool) -> None:
|
||||
"""Search history for the current line contents up to the cursor.
|
||||
|
||||
Selects the first item found. If nothing is under the cursor, any next
|
||||
item in history is selected.
|
||||
"""
|
||||
pos = self.pos
|
||||
s = self.get_unicode()
|
||||
history_index = self.historyi
|
||||
|
||||
# In multiline contexts, we're only interested in the current line.
|
||||
nl_index = s.rfind('\n', 0, pos)
|
||||
prefix = s[nl_index + 1:pos]
|
||||
pos = len(prefix)
|
||||
|
||||
match_prefix = len(prefix)
|
||||
len_item = 0
|
||||
if history_index < len(self.history):
|
||||
len_item = len(self.get_item(history_index))
|
||||
if len_item and pos == len_item:
|
||||
match_prefix = False
|
||||
elif not pos:
|
||||
match_prefix = False
|
||||
|
||||
while 1:
|
||||
if forwards:
|
||||
out_of_bounds = history_index >= len(self.history) - 1
|
||||
else:
|
||||
out_of_bounds = history_index == 0
|
||||
if out_of_bounds:
|
||||
if forwards and not match_prefix:
|
||||
self.pos = 0
|
||||
self.buffer = []
|
||||
self.dirty = True
|
||||
else:
|
||||
self.error("not found")
|
||||
return
|
||||
|
||||
history_index += 1 if forwards else -1
|
||||
s = self.get_item(history_index)
|
||||
|
||||
if not match_prefix:
|
||||
self.select_item(history_index)
|
||||
return
|
||||
|
||||
len_acc = 0
|
||||
for i, line in enumerate(s.splitlines(keepends=True)):
|
||||
if line.startswith(prefix):
|
||||
self.select_item(history_index)
|
||||
self.pos = pos + len_acc
|
||||
return
|
||||
len_acc += len(line)
|
||||
|
||||
def isearch_next(self) -> None:
|
||||
st = self.isearch_term
|
||||
p = self.pos
|
||||
i = self.historyi
|
||||
s = self.get_unicode()
|
||||
forwards = self.isearch_direction == ISEARCH_DIRECTION_FORWARDS
|
||||
while 1:
|
||||
if forwards:
|
||||
p = s.find(st, p + 1)
|
||||
else:
|
||||
p = s.rfind(st, 0, p + len(st) - 1)
|
||||
if p != -1:
|
||||
self.select_item(i)
|
||||
self.pos = p
|
||||
return
|
||||
elif (forwards and i >= len(self.history) - 1) or (not forwards and i == 0):
|
||||
self.error("not found")
|
||||
return
|
||||
else:
|
||||
if forwards:
|
||||
i += 1
|
||||
s = self.get_item(i)
|
||||
p = -1
|
||||
else:
|
||||
i -= 1
|
||||
s = self.get_item(i)
|
||||
p = len(s)
|
||||
|
||||
def finish(self) -> None:
|
||||
super().finish()
|
||||
ret = self.get_unicode()
|
||||
for i, t in self.transient_history.items():
|
||||
if i < len(self.history) and i != self.historyi:
|
||||
self.history[i] = t
|
||||
if ret and should_auto_add_history:
|
||||
self.history.append(ret)
|
||||
|
||||
|
||||
should_auto_add_history = True
|
||||
114
Lib/_pyrepl/input.py
vendored
Normal file
114
Lib/_pyrepl/input.py
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
# (naming modules after builtin functions is not such a hot idea...)
|
||||
|
||||
# an KeyTrans instance translates Event objects into Command objects
|
||||
|
||||
# hmm, at what level do we want [C-i] and [tab] to be equivalent?
|
||||
# [meta-a] and [esc a]? obviously, these are going to be equivalent
|
||||
# for the UnixConsole, but should they be for PygameConsole?
|
||||
|
||||
# it would in any situation seem to be a bad idea to bind, say, [tab]
|
||||
# and [C-i] to *different* things... but should binding one bind the
|
||||
# other?
|
||||
|
||||
# executive, temporary decision: [tab] and [C-i] are distinct, but
|
||||
# [meta-key] is identified with [esc key]. We demand that any console
|
||||
# class does quite a lot towards emulating a unix terminal.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
import unicodedata
|
||||
from collections import deque
|
||||
|
||||
|
||||
# types
|
||||
if False:
|
||||
from .types import EventTuple
|
||||
|
||||
|
||||
class InputTranslator(ABC):
|
||||
@abstractmethod
|
||||
def push(self, evt: EventTuple) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get(self) -> EventTuple | None:
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
def empty(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class KeymapTranslator(InputTranslator):
|
||||
def __init__(self, keymap, verbose=False, invalid_cls=None, character_cls=None):
|
||||
self.verbose = verbose
|
||||
from .keymap import compile_keymap, parse_keys
|
||||
|
||||
self.keymap = keymap
|
||||
self.invalid_cls = invalid_cls
|
||||
self.character_cls = character_cls
|
||||
d = {}
|
||||
for keyspec, command in keymap:
|
||||
keyseq = tuple(parse_keys(keyspec))
|
||||
d[keyseq] = command
|
||||
if self.verbose:
|
||||
print(d)
|
||||
self.k = self.ck = compile_keymap(d, ())
|
||||
self.results = deque()
|
||||
self.stack = []
|
||||
|
||||
def push(self, evt):
|
||||
if self.verbose:
|
||||
print("pushed", evt.data, end="")
|
||||
key = evt.data
|
||||
d = self.k.get(key)
|
||||
if isinstance(d, dict):
|
||||
if self.verbose:
|
||||
print("transition")
|
||||
self.stack.append(key)
|
||||
self.k = d
|
||||
else:
|
||||
if d is None:
|
||||
if self.verbose:
|
||||
print("invalid")
|
||||
if self.stack or len(key) > 1 or unicodedata.category(key) == "C":
|
||||
self.results.append((self.invalid_cls, self.stack + [key]))
|
||||
else:
|
||||
# small optimization:
|
||||
self.k[key] = self.character_cls
|
||||
self.results.append((self.character_cls, [key]))
|
||||
else:
|
||||
if self.verbose:
|
||||
print("matched", d)
|
||||
self.results.append((d, self.stack + [key]))
|
||||
self.stack = []
|
||||
self.k = self.ck
|
||||
|
||||
def get(self):
|
||||
if self.results:
|
||||
return self.results.popleft()
|
||||
else:
|
||||
return None
|
||||
|
||||
def empty(self) -> bool:
|
||||
return not self.results
|
||||
213
Lib/_pyrepl/keymap.py
vendored
Normal file
213
Lib/_pyrepl/keymap.py
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
# Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
# Armin Rigo
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""
|
||||
Keymap contains functions for parsing keyspecs and turning keyspecs into
|
||||
appropriate sequences.
|
||||
|
||||
A keyspec is a string representing a sequence of key presses that can
|
||||
be bound to a command. All characters other than the backslash represent
|
||||
themselves. In the traditional manner, a backslash introduces an escape
|
||||
sequence.
|
||||
|
||||
pyrepl uses its own keyspec format that is meant to be a strict superset of
|
||||
readline's KEYSEQ format. This means that if a spec is found that readline
|
||||
accepts that this doesn't, it should be logged as a bug. Note that this means
|
||||
we're using the `\\C-o' style of readline's keyspec, not the `Control-o' sort.
|
||||
|
||||
The extension to readline is that the sequence \\<KEY> denotes the
|
||||
sequence of characters produced by hitting KEY.
|
||||
|
||||
Examples:
|
||||
`a' - what you get when you hit the `a' key
|
||||
`\\EOA' - Escape - O - A (up, on my terminal)
|
||||
`\\<UP>' - the up arrow key
|
||||
`\\<up>' - ditto (keynames are case-insensitive)
|
||||
`\\C-o', `\\c-o' - control-o
|
||||
`\\M-.' - meta-period
|
||||
`\\E.' - ditto (that's how meta works for pyrepl)
|
||||
`\\<tab>', `\\<TAB>', `\\t', `\\011', '\\x09', '\\X09', '\\C-i', '\\C-I'
|
||||
- all of these are the tab character.
|
||||
"""
|
||||
|
||||
_escapes = {
|
||||
"\\": "\\",
|
||||
"'": "'",
|
||||
'"': '"',
|
||||
"a": "\a",
|
||||
"b": "\b",
|
||||
"e": "\033",
|
||||
"f": "\f",
|
||||
"n": "\n",
|
||||
"r": "\r",
|
||||
"t": "\t",
|
||||
"v": "\v",
|
||||
}
|
||||
|
||||
_keynames = {
|
||||
"backspace": "backspace",
|
||||
"delete": "delete",
|
||||
"down": "down",
|
||||
"end": "end",
|
||||
"enter": "\r",
|
||||
"escape": "\033",
|
||||
"f1": "f1",
|
||||
"f2": "f2",
|
||||
"f3": "f3",
|
||||
"f4": "f4",
|
||||
"f5": "f5",
|
||||
"f6": "f6",
|
||||
"f7": "f7",
|
||||
"f8": "f8",
|
||||
"f9": "f9",
|
||||
"f10": "f10",
|
||||
"f11": "f11",
|
||||
"f12": "f12",
|
||||
"f13": "f13",
|
||||
"f14": "f14",
|
||||
"f15": "f15",
|
||||
"f16": "f16",
|
||||
"f17": "f17",
|
||||
"f18": "f18",
|
||||
"f19": "f19",
|
||||
"f20": "f20",
|
||||
"home": "home",
|
||||
"insert": "insert",
|
||||
"left": "left",
|
||||
"page down": "page down",
|
||||
"page up": "page up",
|
||||
"return": "\r",
|
||||
"right": "right",
|
||||
"space": " ",
|
||||
"tab": "\t",
|
||||
"up": "up",
|
||||
}
|
||||
|
||||
|
||||
class KeySpecError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def parse_keys(keys: str) -> list[str]:
|
||||
"""Parse keys in keyspec format to a sequence of keys."""
|
||||
s = 0
|
||||
r: list[str] = []
|
||||
while s < len(keys):
|
||||
k, s = _parse_single_key_sequence(keys, s)
|
||||
r.extend(k)
|
||||
return r
|
||||
|
||||
|
||||
def _parse_single_key_sequence(key: str, s: int) -> tuple[list[str], int]:
|
||||
ctrl = 0
|
||||
meta = 0
|
||||
ret = ""
|
||||
while not ret and s < len(key):
|
||||
if key[s] == "\\":
|
||||
c = key[s + 1].lower()
|
||||
if c in _escapes:
|
||||
ret = _escapes[c]
|
||||
s += 2
|
||||
elif c == "c":
|
||||
if key[s + 2] != "-":
|
||||
raise KeySpecError(
|
||||
"\\C must be followed by `-' (char %d of %s)"
|
||||
% (s + 2, repr(key))
|
||||
)
|
||||
if ctrl:
|
||||
raise KeySpecError(
|
||||
"doubled \\C- (char %d of %s)" % (s + 1, repr(key))
|
||||
)
|
||||
ctrl = 1
|
||||
s += 3
|
||||
elif c == "m":
|
||||
if key[s + 2] != "-":
|
||||
raise KeySpecError(
|
||||
"\\M must be followed by `-' (char %d of %s)"
|
||||
% (s + 2, repr(key))
|
||||
)
|
||||
if meta:
|
||||
raise KeySpecError(
|
||||
"doubled \\M- (char %d of %s)" % (s + 1, repr(key))
|
||||
)
|
||||
meta = 1
|
||||
s += 3
|
||||
elif c.isdigit():
|
||||
n = key[s + 1 : s + 4]
|
||||
ret = chr(int(n, 8))
|
||||
s += 4
|
||||
elif c == "x":
|
||||
n = key[s + 2 : s + 4]
|
||||
ret = chr(int(n, 16))
|
||||
s += 4
|
||||
elif c == "<":
|
||||
t = key.find(">", s)
|
||||
if t == -1:
|
||||
raise KeySpecError(
|
||||
"unterminated \\< starting at char %d of %s"
|
||||
% (s + 1, repr(key))
|
||||
)
|
||||
ret = key[s + 2 : t].lower()
|
||||
if ret not in _keynames:
|
||||
raise KeySpecError(
|
||||
"unrecognised keyname `%s' at char %d of %s"
|
||||
% (ret, s + 2, repr(key))
|
||||
)
|
||||
ret = _keynames[ret]
|
||||
s = t + 1
|
||||
else:
|
||||
raise KeySpecError(
|
||||
"unknown backslash escape %s at char %d of %s"
|
||||
% (repr(c), s + 2, repr(key))
|
||||
)
|
||||
else:
|
||||
ret = key[s]
|
||||
s += 1
|
||||
if ctrl:
|
||||
if len(ret) == 1:
|
||||
ret = chr(ord(ret) & 0x1F) # curses.ascii.ctrl()
|
||||
elif ret in {"left", "right"}:
|
||||
ret = f"ctrl {ret}"
|
||||
else:
|
||||
raise KeySpecError("\\C- followed by invalid key")
|
||||
|
||||
result = [ret], s
|
||||
if meta:
|
||||
result[0].insert(0, "\033")
|
||||
return result
|
||||
|
||||
|
||||
def compile_keymap(keymap, empty=b""):
|
||||
r = {}
|
||||
for key, value in keymap.items():
|
||||
if isinstance(key, bytes):
|
||||
first = key[:1]
|
||||
else:
|
||||
first = key[0]
|
||||
r.setdefault(first, {})[key[1:]] = value
|
||||
for key, value in r.items():
|
||||
if empty in value:
|
||||
if len(value) != 1:
|
||||
raise KeySpecError("key definitions for %s clash" % (value.values(),))
|
||||
else:
|
||||
r[key] = value[empty]
|
||||
else:
|
||||
r[key] = compile_keymap(value, empty)
|
||||
return r
|
||||
59
Lib/_pyrepl/main.py
vendored
Normal file
59
Lib/_pyrepl/main.py
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import errno
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
CAN_USE_PYREPL: bool
|
||||
FAIL_REASON: str
|
||||
try:
|
||||
if sys.platform == "win32" and sys.getwindowsversion().build < 10586:
|
||||
raise RuntimeError("Windows 10 TH2 or later required")
|
||||
if not os.isatty(sys.stdin.fileno()):
|
||||
raise OSError(errno.ENOTTY, "tty required", "stdin")
|
||||
from .simple_interact import check
|
||||
if err := check():
|
||||
raise RuntimeError(err)
|
||||
except Exception as e:
|
||||
CAN_USE_PYREPL = False
|
||||
FAIL_REASON = f"warning: can't use pyrepl: {e}"
|
||||
else:
|
||||
CAN_USE_PYREPL = True
|
||||
FAIL_REASON = ""
|
||||
|
||||
|
||||
def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
|
||||
if not CAN_USE_PYREPL:
|
||||
if not os.getenv('PYTHON_BASIC_REPL') and FAIL_REASON:
|
||||
from .trace import trace
|
||||
trace(FAIL_REASON)
|
||||
print(FAIL_REASON, file=sys.stderr)
|
||||
return sys._baserepl()
|
||||
|
||||
if mainmodule:
|
||||
namespace = mainmodule.__dict__
|
||||
else:
|
||||
import __main__
|
||||
namespace = __main__.__dict__
|
||||
namespace.pop("__pyrepl_interactive_console", None)
|
||||
|
||||
# sys._baserepl() above does this internally, we do it here
|
||||
startup_path = os.getenv("PYTHONSTARTUP")
|
||||
if pythonstartup and startup_path:
|
||||
sys.audit("cpython.run_startup", startup_path)
|
||||
|
||||
import tokenize
|
||||
with tokenize.open(startup_path) as f:
|
||||
startup_code = compile(f.read(), startup_path, "exec")
|
||||
exec(startup_code, namespace)
|
||||
|
||||
# set sys.{ps1,ps2} just before invoking the interactive interpreter. This
|
||||
# mimics what CPython does in pythonrun.c
|
||||
if not hasattr(sys, "ps1"):
|
||||
sys.ps1 = ">>> "
|
||||
if not hasattr(sys, "ps2"):
|
||||
sys.ps2 = "... "
|
||||
|
||||
from .console import InteractiveColoredConsole
|
||||
from .simple_interact import run_multiline_interactive_console
|
||||
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||
run_multiline_interactive_console(console)
|
||||
24
Lib/_pyrepl/mypy.ini
vendored
Normal file
24
Lib/_pyrepl/mypy.ini
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Config file for running mypy on _pyrepl.
|
||||
# Run mypy by invoking `mypy --config-file Lib/_pyrepl/mypy.ini`
|
||||
# on the command-line from the repo root
|
||||
|
||||
[mypy]
|
||||
files = Lib/_pyrepl
|
||||
explicit_package_bases = True
|
||||
python_version = 3.12
|
||||
platform = linux
|
||||
pretty = True
|
||||
|
||||
# Enable most stricter settings
|
||||
enable_error_code = ignore-without-code,redundant-expr
|
||||
strict = True
|
||||
|
||||
# Various stricter settings that we can't yet enable
|
||||
# Try to enable these in the following order:
|
||||
disallow_untyped_calls = False
|
||||
disallow_untyped_defs = False
|
||||
check_untyped_defs = False
|
||||
|
||||
# Various internal modules that typeshed deliberately doesn't have stubs for:
|
||||
[mypy-_abc.*,_opcode.*,_overlapped.*,_testcapi.*,_testinternalcapi.*,test.*]
|
||||
ignore_missing_imports = True
|
||||
175
Lib/_pyrepl/pager.py
vendored
Normal file
175
Lib/_pyrepl/pager.py
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
# types
|
||||
if False:
|
||||
from typing import Protocol
|
||||
class Pager(Protocol):
|
||||
def __call__(self, text: str, title: str = "") -> None:
|
||||
...
|
||||
|
||||
|
||||
def get_pager() -> Pager:
|
||||
"""Decide what method to use for paging through text."""
|
||||
if not hasattr(sys.stdin, "isatty"):
|
||||
return plain_pager
|
||||
if not hasattr(sys.stdout, "isatty"):
|
||||
return plain_pager
|
||||
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
||||
return plain_pager
|
||||
if sys.platform == "emscripten":
|
||||
return plain_pager
|
||||
use_pager = os.environ.get('MANPAGER') or os.environ.get('PAGER')
|
||||
if use_pager:
|
||||
if sys.platform == 'win32': # pipes completely broken in Windows
|
||||
return lambda text, title='': tempfile_pager(plain(text), use_pager)
|
||||
elif os.environ.get('TERM') in ('dumb', 'emacs'):
|
||||
return lambda text, title='': pipe_pager(plain(text), use_pager, title)
|
||||
else:
|
||||
return lambda text, title='': pipe_pager(text, use_pager, title)
|
||||
if os.environ.get('TERM') in ('dumb', 'emacs'):
|
||||
return plain_pager
|
||||
if sys.platform == 'win32':
|
||||
return lambda text, title='': tempfile_pager(plain(text), 'more <')
|
||||
if hasattr(os, 'system') and os.system('(pager) 2>/dev/null') == 0:
|
||||
return lambda text, title='': pipe_pager(text, 'pager', title)
|
||||
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
|
||||
return lambda text, title='': pipe_pager(text, 'less', title)
|
||||
|
||||
import tempfile
|
||||
(fd, filename) = tempfile.mkstemp()
|
||||
os.close(fd)
|
||||
try:
|
||||
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
|
||||
return lambda text, title='': pipe_pager(text, 'more', title)
|
||||
else:
|
||||
return tty_pager
|
||||
finally:
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def escape_stdout(text: str) -> str:
|
||||
# Escape non-encodable characters to avoid encoding errors later
|
||||
encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8'
|
||||
return text.encode(encoding, 'backslashreplace').decode(encoding)
|
||||
|
||||
|
||||
def escape_less(s: str) -> str:
|
||||
return re.sub(r'([?:.%\\])', r'\\\1', s)
|
||||
|
||||
|
||||
def plain(text: str) -> str:
|
||||
"""Remove boldface formatting from text."""
|
||||
return re.sub('.\b', '', text)
|
||||
|
||||
|
||||
def tty_pager(text: str, title: str = '') -> None:
|
||||
"""Page through text on a text terminal."""
|
||||
lines = plain(escape_stdout(text)).split('\n')
|
||||
has_tty = False
|
||||
try:
|
||||
import tty
|
||||
import termios
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
tty.setcbreak(fd)
|
||||
has_tty = True
|
||||
|
||||
def getchar() -> str:
|
||||
return sys.stdin.read(1)
|
||||
|
||||
except (ImportError, AttributeError, io.UnsupportedOperation):
|
||||
def getchar() -> str:
|
||||
return sys.stdin.readline()[:-1][:1]
|
||||
|
||||
try:
|
||||
try:
|
||||
h = int(os.environ.get('LINES', 0))
|
||||
except ValueError:
|
||||
h = 0
|
||||
if h <= 1:
|
||||
h = 25
|
||||
r = inc = h - 1
|
||||
sys.stdout.write('\n'.join(lines[:inc]) + '\n')
|
||||
while lines[r:]:
|
||||
sys.stdout.write('-- more --')
|
||||
sys.stdout.flush()
|
||||
c = getchar()
|
||||
|
||||
if c in ('q', 'Q'):
|
||||
sys.stdout.write('\r \r')
|
||||
break
|
||||
elif c in ('\r', '\n'):
|
||||
sys.stdout.write('\r \r' + lines[r] + '\n')
|
||||
r = r + 1
|
||||
continue
|
||||
if c in ('b', 'B', '\x1b'):
|
||||
r = r - inc - inc
|
||||
if r < 0: r = 0
|
||||
sys.stdout.write('\n' + '\n'.join(lines[r:r+inc]) + '\n')
|
||||
r = r + inc
|
||||
|
||||
finally:
|
||||
if has_tty:
|
||||
termios.tcsetattr(fd, termios.TCSAFLUSH, old)
|
||||
|
||||
|
||||
def plain_pager(text: str, title: str = '') -> None:
|
||||
"""Simply print unformatted text. This is the ultimate fallback."""
|
||||
sys.stdout.write(plain(escape_stdout(text)))
|
||||
|
||||
|
||||
def pipe_pager(text: str, cmd: str, title: str = '') -> None:
|
||||
"""Page through text by feeding it to another program."""
|
||||
import subprocess
|
||||
env = os.environ.copy()
|
||||
if title:
|
||||
title += ' '
|
||||
esc_title = escape_less(title)
|
||||
prompt_string = (
|
||||
f' {esc_title}' +
|
||||
'?ltline %lt?L/%L.'
|
||||
':byte %bB?s/%s.'
|
||||
'.'
|
||||
'?e (END):?pB %pB\\%..'
|
||||
' (press h for help or q to quit)')
|
||||
env['LESS'] = '-RmPm{0}$PM{0}$'.format(prompt_string)
|
||||
proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
||||
errors='backslashreplace', env=env)
|
||||
assert proc.stdin is not None
|
||||
try:
|
||||
with proc.stdin as pipe:
|
||||
try:
|
||||
pipe.write(text)
|
||||
except KeyboardInterrupt:
|
||||
# We've hereby abandoned whatever text hasn't been written,
|
||||
# but the pager is still in control of the terminal.
|
||||
pass
|
||||
except OSError:
|
||||
pass # Ignore broken pipes caused by quitting the pager program.
|
||||
while True:
|
||||
try:
|
||||
proc.wait()
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
# Ignore ctl-c like the pager itself does. Otherwise the pager is
|
||||
# left running and the terminal is in raw mode and unusable.
|
||||
pass
|
||||
|
||||
|
||||
def tempfile_pager(text: str, cmd: str, title: str = '') -> None:
|
||||
"""Page through text by invoking a program on a temporary file."""
|
||||
import tempfile
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
filename = os.path.join(tempdir, 'pydoc.out')
|
||||
with open(filename, 'w', errors='backslashreplace',
|
||||
encoding=os.device_encoding(0) if
|
||||
sys.platform == 'win32' else None
|
||||
) as file:
|
||||
file.write(text)
|
||||
os.system(cmd + ' "' + filename + '"')
|
||||
816
Lib/_pyrepl/reader.py
vendored
Normal file
816
Lib/_pyrepl/reader.py
vendored
Normal file
@@ -0,0 +1,816 @@
|
||||
# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
# Antonio Cuni
|
||||
# Armin Rigo
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass, field, fields
|
||||
import unicodedata
|
||||
from _colorize import can_colorize, ANSIColors # type: ignore[import-not-found]
|
||||
|
||||
|
||||
from . import commands, console, input
|
||||
from .utils import ANSI_ESCAPE_SEQUENCE, wlen, str_width
|
||||
from .trace import trace
|
||||
|
||||
|
||||
# types
|
||||
Command = commands.Command
|
||||
from .types import Callback, SimpleContextManager, KeySpec, CommandName
|
||||
|
||||
|
||||
def disp_str(buffer: str) -> tuple[str, list[int]]:
|
||||
"""disp_str(buffer:string) -> (string, [int])
|
||||
|
||||
Return the string that should be the printed representation of
|
||||
|buffer| and a list detailing where the characters of |buffer|
|
||||
get used up. E.g.:
|
||||
|
||||
>>> disp_str(chr(3))
|
||||
('^C', [1, 0])
|
||||
|
||||
"""
|
||||
b: list[int] = []
|
||||
s: list[str] = []
|
||||
for c in buffer:
|
||||
if c == '\x1a':
|
||||
s.append(c)
|
||||
b.append(2)
|
||||
elif ord(c) < 128:
|
||||
s.append(c)
|
||||
b.append(1)
|
||||
elif unicodedata.category(c).startswith("C"):
|
||||
c = r"\u%04x" % ord(c)
|
||||
s.append(c)
|
||||
b.extend([0] * (len(c) - 1))
|
||||
else:
|
||||
s.append(c)
|
||||
b.append(str_width(c))
|
||||
return "".join(s), b
|
||||
|
||||
|
||||
# syntax classes:
|
||||
|
||||
SYNTAX_WHITESPACE, SYNTAX_WORD, SYNTAX_SYMBOL = range(3)
|
||||
|
||||
|
||||
def make_default_syntax_table() -> dict[str, int]:
|
||||
# XXX perhaps should use some unicodedata here?
|
||||
st: dict[str, int] = {}
|
||||
for c in map(chr, range(256)):
|
||||
st[c] = SYNTAX_SYMBOL
|
||||
for c in [a for a in map(chr, range(256)) if a.isalnum()]:
|
||||
st[c] = SYNTAX_WORD
|
||||
st["\n"] = st[" "] = SYNTAX_WHITESPACE
|
||||
return st
|
||||
|
||||
|
||||
def make_default_commands() -> dict[CommandName, type[Command]]:
|
||||
result: dict[CommandName, type[Command]] = {}
|
||||
for v in vars(commands).values():
|
||||
if isinstance(v, type) and issubclass(v, Command) and v.__name__[0].islower():
|
||||
result[v.__name__] = v
|
||||
result[v.__name__.replace("_", "-")] = v
|
||||
return result
|
||||
|
||||
|
||||
default_keymap: tuple[tuple[KeySpec, CommandName], ...] = tuple(
|
||||
[
|
||||
(r"\C-a", "beginning-of-line"),
|
||||
(r"\C-b", "left"),
|
||||
(r"\C-c", "interrupt"),
|
||||
(r"\C-d", "delete"),
|
||||
(r"\C-e", "end-of-line"),
|
||||
(r"\C-f", "right"),
|
||||
(r"\C-g", "cancel"),
|
||||
(r"\C-h", "backspace"),
|
||||
(r"\C-j", "accept"),
|
||||
(r"\<return>", "accept"),
|
||||
(r"\C-k", "kill-line"),
|
||||
(r"\C-l", "clear-screen"),
|
||||
(r"\C-m", "accept"),
|
||||
(r"\C-t", "transpose-characters"),
|
||||
(r"\C-u", "unix-line-discard"),
|
||||
(r"\C-w", "unix-word-rubout"),
|
||||
(r"\C-x\C-u", "upcase-region"),
|
||||
(r"\C-y", "yank"),
|
||||
*(() if sys.platform == "win32" else ((r"\C-z", "suspend"), )),
|
||||
(r"\M-b", "backward-word"),
|
||||
(r"\M-c", "capitalize-word"),
|
||||
(r"\M-d", "kill-word"),
|
||||
(r"\M-f", "forward-word"),
|
||||
(r"\M-l", "downcase-word"),
|
||||
(r"\M-t", "transpose-words"),
|
||||
(r"\M-u", "upcase-word"),
|
||||
(r"\M-y", "yank-pop"),
|
||||
(r"\M--", "digit-arg"),
|
||||
(r"\M-0", "digit-arg"),
|
||||
(r"\M-1", "digit-arg"),
|
||||
(r"\M-2", "digit-arg"),
|
||||
(r"\M-3", "digit-arg"),
|
||||
(r"\M-4", "digit-arg"),
|
||||
(r"\M-5", "digit-arg"),
|
||||
(r"\M-6", "digit-arg"),
|
||||
(r"\M-7", "digit-arg"),
|
||||
(r"\M-8", "digit-arg"),
|
||||
(r"\M-9", "digit-arg"),
|
||||
(r"\M-\n", "accept"),
|
||||
("\\\\", "self-insert"),
|
||||
(r"\x1b[200~", "enable_bracketed_paste"),
|
||||
(r"\x1b[201~", "disable_bracketed_paste"),
|
||||
(r"\x03", "ctrl-c"),
|
||||
]
|
||||
+ [(c, "self-insert") for c in map(chr, range(32, 127)) if c != "\\"]
|
||||
+ [(c, "self-insert") for c in map(chr, range(128, 256)) if c.isalpha()]
|
||||
+ [
|
||||
(r"\<up>", "up"),
|
||||
(r"\<down>", "down"),
|
||||
(r"\<left>", "left"),
|
||||
(r"\C-\<left>", "backward-word"),
|
||||
(r"\<right>", "right"),
|
||||
(r"\C-\<right>", "forward-word"),
|
||||
(r"\<delete>", "delete"),
|
||||
(r"\x1b[3~", "delete"),
|
||||
(r"\<backspace>", "backspace"),
|
||||
(r"\M-\<backspace>", "backward-kill-word"),
|
||||
(r"\<end>", "end-of-line"), # was 'end'
|
||||
(r"\<home>", "beginning-of-line"), # was 'home'
|
||||
(r"\<f1>", "help"),
|
||||
(r"\<f2>", "show-history"),
|
||||
(r"\<f3>", "paste-mode"),
|
||||
(r"\EOF", "end"), # the entries in the terminfo database for xterms
|
||||
(r"\EOH", "home"), # seem to be wrong. this is a less than ideal
|
||||
# workaround
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Reader:
|
||||
"""The Reader class implements the bare bones of a command reader,
|
||||
handling such details as editing and cursor motion. What it does
|
||||
not support are such things as completion or history support -
|
||||
these are implemented elsewhere.
|
||||
|
||||
Instance variables of note include:
|
||||
|
||||
* buffer:
|
||||
A *list* (*not* a string at the moment :-) containing all the
|
||||
characters that have been entered.
|
||||
* console:
|
||||
Hopefully encapsulates the OS dependent stuff.
|
||||
* pos:
|
||||
A 0-based index into `buffer' for where the insertion point
|
||||
is.
|
||||
* screeninfo:
|
||||
Ahem. This list contains some info needed to move the
|
||||
insertion point around reasonably efficiently.
|
||||
* cxy, lxy:
|
||||
the position of the insertion point in screen ...
|
||||
* syntax_table:
|
||||
Dictionary mapping characters to `syntax class'; read the
|
||||
emacs docs to see what this means :-)
|
||||
* commands:
|
||||
Dictionary mapping command names to command classes.
|
||||
* arg:
|
||||
The emacs-style prefix argument. It will be None if no such
|
||||
argument has been provided.
|
||||
* dirty:
|
||||
True if we need to refresh the display.
|
||||
* kill_ring:
|
||||
The emacs-style kill-ring; manipulated with yank & yank-pop
|
||||
* ps1, ps2, ps3, ps4:
|
||||
prompts. ps1 is the prompt for a one-line input; for a
|
||||
multiline input it looks like:
|
||||
ps2> first line of input goes here
|
||||
ps3> second and further
|
||||
ps3> lines get ps3
|
||||
...
|
||||
ps4> and the last one gets ps4
|
||||
As with the usual top-level, you can set these to instances if
|
||||
you like; str() will be called on them (once) at the beginning
|
||||
of each command. Don't put really long or newline containing
|
||||
strings here, please!
|
||||
This is just the default policy; you can change it freely by
|
||||
overriding get_prompt() (and indeed some standard subclasses
|
||||
do).
|
||||
* finished:
|
||||
handle1 will set this to a true value if a command signals
|
||||
that we're done.
|
||||
"""
|
||||
|
||||
console: console.Console
|
||||
|
||||
## state
|
||||
buffer: list[str] = field(default_factory=list)
|
||||
pos: int = 0
|
||||
ps1: str = "->> "
|
||||
ps2: str = "/>> "
|
||||
ps3: str = "|.. "
|
||||
ps4: str = R"\__ "
|
||||
kill_ring: list[list[str]] = field(default_factory=list)
|
||||
msg: str = ""
|
||||
arg: int | None = None
|
||||
dirty: bool = False
|
||||
finished: bool = False
|
||||
paste_mode: bool = False
|
||||
in_bracketed_paste: bool = False
|
||||
commands: dict[str, type[Command]] = field(default_factory=make_default_commands)
|
||||
last_command: type[Command] | None = None
|
||||
syntax_table: dict[str, int] = field(default_factory=make_default_syntax_table)
|
||||
keymap: tuple[tuple[str, str], ...] = ()
|
||||
input_trans: input.KeymapTranslator = field(init=False)
|
||||
input_trans_stack: list[input.KeymapTranslator] = field(default_factory=list)
|
||||
screen: list[str] = field(default_factory=list)
|
||||
screeninfo: list[tuple[int, list[int]]] = field(init=False)
|
||||
cxy: tuple[int, int] = field(init=False)
|
||||
lxy: tuple[int, int] = field(init=False)
|
||||
scheduled_commands: list[str] = field(default_factory=list)
|
||||
can_colorize: bool = False
|
||||
threading_hook: Callback | None = None
|
||||
|
||||
## cached metadata to speed up screen refreshes
|
||||
@dataclass
|
||||
class RefreshCache:
|
||||
in_bracketed_paste: bool = False
|
||||
screen: list[str] = field(default_factory=list)
|
||||
screeninfo: list[tuple[int, list[int]]] = field(init=False)
|
||||
line_end_offsets: list[int] = field(default_factory=list)
|
||||
pos: int = field(init=False)
|
||||
cxy: tuple[int, int] = field(init=False)
|
||||
dimensions: tuple[int, int] = field(init=False)
|
||||
invalidated: bool = False
|
||||
|
||||
def update_cache(self,
|
||||
reader: Reader,
|
||||
screen: list[str],
|
||||
screeninfo: list[tuple[int, list[int]]],
|
||||
) -> None:
|
||||
self.in_bracketed_paste = reader.in_bracketed_paste
|
||||
self.screen = screen.copy()
|
||||
self.screeninfo = screeninfo.copy()
|
||||
self.pos = reader.pos
|
||||
self.cxy = reader.cxy
|
||||
self.dimensions = reader.console.width, reader.console.height
|
||||
self.invalidated = False
|
||||
|
||||
def valid(self, reader: Reader) -> bool:
|
||||
if self.invalidated:
|
||||
return False
|
||||
dimensions = reader.console.width, reader.console.height
|
||||
dimensions_changed = dimensions != self.dimensions
|
||||
paste_changed = reader.in_bracketed_paste != self.in_bracketed_paste
|
||||
return not (dimensions_changed or paste_changed)
|
||||
|
||||
def get_cached_location(self, reader: Reader) -> tuple[int, int]:
|
||||
if self.invalidated:
|
||||
raise ValueError("Cache is invalidated")
|
||||
offset = 0
|
||||
earliest_common_pos = min(reader.pos, self.pos)
|
||||
num_common_lines = len(self.line_end_offsets)
|
||||
while num_common_lines > 0:
|
||||
offset = self.line_end_offsets[num_common_lines - 1]
|
||||
if earliest_common_pos > offset:
|
||||
break
|
||||
num_common_lines -= 1
|
||||
else:
|
||||
offset = 0
|
||||
return offset, num_common_lines
|
||||
|
||||
last_refresh_cache: RefreshCache = field(default_factory=RefreshCache)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
# Enable the use of `insert` without a `prepare` call - necessary to
|
||||
# facilitate the tab completion hack implemented for
|
||||
# <https://bugs.python.org/issue25660>.
|
||||
self.keymap = self.collect_keymap()
|
||||
self.input_trans = input.KeymapTranslator(
|
||||
self.keymap, invalid_cls="invalid-key", character_cls="self-insert"
|
||||
)
|
||||
self.screeninfo = [(0, [])]
|
||||
self.cxy = self.pos2xy()
|
||||
self.lxy = (self.pos, 0)
|
||||
self.can_colorize = can_colorize()
|
||||
|
||||
self.last_refresh_cache.screeninfo = self.screeninfo
|
||||
self.last_refresh_cache.pos = self.pos
|
||||
self.last_refresh_cache.cxy = self.cxy
|
||||
self.last_refresh_cache.dimensions = (0, 0)
|
||||
|
||||
def collect_keymap(self) -> tuple[tuple[KeySpec, CommandName], ...]:
|
||||
return default_keymap
|
||||
|
||||
def calc_screen(self) -> list[str]:
|
||||
"""Translate changes in self.buffer into changes in self.console.screen."""
|
||||
# Since the last call to calc_screen:
|
||||
# screen and screeninfo may differ due to a completion menu being shown
|
||||
# pos and cxy may differ due to edits, cursor movements, or completion menus
|
||||
|
||||
# Lines that are above both the old and new cursor position can't have changed,
|
||||
# unless the terminal has been resized (which might cause reflowing) or we've
|
||||
# entered or left paste mode (which changes prompts, causing reflowing).
|
||||
num_common_lines = 0
|
||||
offset = 0
|
||||
if self.last_refresh_cache.valid(self):
|
||||
offset, num_common_lines = self.last_refresh_cache.get_cached_location(self)
|
||||
|
||||
screen = self.last_refresh_cache.screen
|
||||
del screen[num_common_lines:]
|
||||
|
||||
screeninfo = self.last_refresh_cache.screeninfo
|
||||
del screeninfo[num_common_lines:]
|
||||
|
||||
last_refresh_line_end_offsets = self.last_refresh_cache.line_end_offsets
|
||||
del last_refresh_line_end_offsets[num_common_lines:]
|
||||
|
||||
pos = self.pos
|
||||
pos -= offset
|
||||
|
||||
prompt_from_cache = (offset and self.buffer[offset - 1] != "\n")
|
||||
|
||||
lines = "".join(self.buffer[offset:]).split("\n")
|
||||
|
||||
cursor_found = False
|
||||
lines_beyond_cursor = 0
|
||||
for ln, line in enumerate(lines, num_common_lines):
|
||||
ll = len(line)
|
||||
if 0 <= pos <= ll:
|
||||
self.lxy = pos, ln
|
||||
cursor_found = True
|
||||
elif cursor_found:
|
||||
lines_beyond_cursor += 1
|
||||
if lines_beyond_cursor > self.console.height:
|
||||
# No need to keep formatting lines.
|
||||
# The console can't show them.
|
||||
break
|
||||
if prompt_from_cache:
|
||||
# Only the first line's prompt can come from the cache
|
||||
prompt_from_cache = False
|
||||
prompt = ""
|
||||
else:
|
||||
prompt = self.get_prompt(ln, ll >= pos >= 0)
|
||||
while "\n" in prompt:
|
||||
pre_prompt, _, prompt = prompt.partition("\n")
|
||||
last_refresh_line_end_offsets.append(offset)
|
||||
screen.append(pre_prompt)
|
||||
screeninfo.append((0, []))
|
||||
pos -= ll + 1
|
||||
prompt, lp = self.process_prompt(prompt)
|
||||
l, l2 = disp_str(line)
|
||||
wrapcount = (wlen(l) + lp) // self.console.width
|
||||
if wrapcount == 0:
|
||||
offset += ll + 1 # Takes all of the line plus the newline
|
||||
last_refresh_line_end_offsets.append(offset)
|
||||
screen.append(prompt + l)
|
||||
screeninfo.append((lp, l2))
|
||||
else:
|
||||
i = 0
|
||||
while l:
|
||||
prelen = lp if i == 0 else 0
|
||||
index_to_wrap_before = 0
|
||||
column = 0
|
||||
for character_width in l2:
|
||||
if column + character_width >= self.console.width - prelen:
|
||||
break
|
||||
index_to_wrap_before += 1
|
||||
column += character_width
|
||||
pre = prompt if i == 0 else ""
|
||||
if len(l) > index_to_wrap_before:
|
||||
offset += index_to_wrap_before
|
||||
post = "\\"
|
||||
after = [1]
|
||||
else:
|
||||
offset += index_to_wrap_before + 1 # Takes the newline
|
||||
post = ""
|
||||
after = []
|
||||
last_refresh_line_end_offsets.append(offset)
|
||||
screen.append(pre + l[:index_to_wrap_before] + post)
|
||||
screeninfo.append((prelen, l2[:index_to_wrap_before] + after))
|
||||
l = l[index_to_wrap_before:]
|
||||
l2 = l2[index_to_wrap_before:]
|
||||
i += 1
|
||||
self.screeninfo = screeninfo
|
||||
self.cxy = self.pos2xy()
|
||||
if self.msg:
|
||||
for mline in self.msg.split("\n"):
|
||||
screen.append(mline)
|
||||
screeninfo.append((0, []))
|
||||
|
||||
self.last_refresh_cache.update_cache(self, screen, screeninfo)
|
||||
return screen
|
||||
|
||||
@staticmethod
|
||||
def process_prompt(prompt: str) -> tuple[str, int]:
|
||||
"""Process the prompt.
|
||||
|
||||
This means calculate the length of the prompt. The character \x01
|
||||
and \x02 are used to bracket ANSI control sequences and need to be
|
||||
excluded from the length calculation. So also a copy of the prompt
|
||||
is returned with these control characters removed."""
|
||||
|
||||
# The logic below also ignores the length of common escape
|
||||
# sequences if they were not explicitly within \x01...\x02.
|
||||
# They are CSI (or ANSI) sequences ( ESC [ ... LETTER )
|
||||
|
||||
# wlen from utils already excludes ANSI_ESCAPE_SEQUENCE chars,
|
||||
# which breaks the logic below so we redefine it here.
|
||||
def wlen(s: str) -> int:
|
||||
return sum(str_width(i) for i in s)
|
||||
|
||||
out_prompt = ""
|
||||
l = wlen(prompt)
|
||||
pos = 0
|
||||
while True:
|
||||
s = prompt.find("\x01", pos)
|
||||
if s == -1:
|
||||
break
|
||||
e = prompt.find("\x02", s)
|
||||
if e == -1:
|
||||
break
|
||||
# Found start and end brackets, subtract from string length
|
||||
l = l - (e - s + 1)
|
||||
keep = prompt[pos:s]
|
||||
l -= sum(map(wlen, ANSI_ESCAPE_SEQUENCE.findall(keep)))
|
||||
out_prompt += keep + prompt[s + 1 : e]
|
||||
pos = e + 1
|
||||
keep = prompt[pos:]
|
||||
l -= sum(map(wlen, ANSI_ESCAPE_SEQUENCE.findall(keep)))
|
||||
out_prompt += keep
|
||||
return out_prompt, l
|
||||
|
||||
def bow(self, p: int | None = None) -> int:
|
||||
"""Return the 0-based index of the word break preceding p most
|
||||
immediately.
|
||||
|
||||
p defaults to self.pos; word boundaries are determined using
|
||||
self.syntax_table."""
|
||||
if p is None:
|
||||
p = self.pos
|
||||
st = self.syntax_table
|
||||
b = self.buffer
|
||||
p -= 1
|
||||
while p >= 0 and st.get(b[p], SYNTAX_WORD) != SYNTAX_WORD:
|
||||
p -= 1
|
||||
while p >= 0 and st.get(b[p], SYNTAX_WORD) == SYNTAX_WORD:
|
||||
p -= 1
|
||||
return p + 1
|
||||
|
||||
def eow(self, p: int | None = None) -> int:
|
||||
"""Return the 0-based index of the word break following p most
|
||||
immediately.
|
||||
|
||||
p defaults to self.pos; word boundaries are determined using
|
||||
self.syntax_table."""
|
||||
if p is None:
|
||||
p = self.pos
|
||||
st = self.syntax_table
|
||||
b = self.buffer
|
||||
while p < len(b) and st.get(b[p], SYNTAX_WORD) != SYNTAX_WORD:
|
||||
p += 1
|
||||
while p < len(b) and st.get(b[p], SYNTAX_WORD) == SYNTAX_WORD:
|
||||
p += 1
|
||||
return p
|
||||
|
||||
def bol(self, p: int | None = None) -> int:
|
||||
"""Return the 0-based index of the line break preceding p most
|
||||
immediately.
|
||||
|
||||
p defaults to self.pos."""
|
||||
if p is None:
|
||||
p = self.pos
|
||||
b = self.buffer
|
||||
p -= 1
|
||||
while p >= 0 and b[p] != "\n":
|
||||
p -= 1
|
||||
return p + 1
|
||||
|
||||
def eol(self, p: int | None = None) -> int:
|
||||
"""Return the 0-based index of the line break following p most
|
||||
immediately.
|
||||
|
||||
p defaults to self.pos."""
|
||||
if p is None:
|
||||
p = self.pos
|
||||
b = self.buffer
|
||||
while p < len(b) and b[p] != "\n":
|
||||
p += 1
|
||||
return p
|
||||
|
||||
def max_column(self, y: int) -> int:
|
||||
"""Return the last x-offset for line y"""
|
||||
return self.screeninfo[y][0] + sum(self.screeninfo[y][1])
|
||||
|
||||
def max_row(self) -> int:
|
||||
return len(self.screeninfo) - 1
|
||||
|
||||
def get_arg(self, default: int = 1) -> int:
|
||||
"""Return any prefix argument that the user has supplied,
|
||||
returning `default' if there is None. Defaults to 1.
|
||||
"""
|
||||
if self.arg is None:
|
||||
return default
|
||||
return self.arg
|
||||
|
||||
def get_prompt(self, lineno: int, cursor_on_line: bool) -> str:
|
||||
"""Return what should be in the left-hand margin for line
|
||||
`lineno'."""
|
||||
if self.arg is not None and cursor_on_line:
|
||||
prompt = f"(arg: {self.arg}) "
|
||||
elif self.paste_mode and not self.in_bracketed_paste:
|
||||
prompt = "(paste) "
|
||||
elif "\n" in self.buffer:
|
||||
if lineno == 0:
|
||||
prompt = self.ps2
|
||||
elif self.ps4 and lineno == self.buffer.count("\n"):
|
||||
prompt = self.ps4
|
||||
else:
|
||||
prompt = self.ps3
|
||||
else:
|
||||
prompt = self.ps1
|
||||
|
||||
if self.can_colorize:
|
||||
prompt = f"{ANSIColors.BOLD_MAGENTA}{prompt}{ANSIColors.RESET}"
|
||||
return prompt
|
||||
|
||||
def push_input_trans(self, itrans: input.KeymapTranslator) -> None:
|
||||
self.input_trans_stack.append(self.input_trans)
|
||||
self.input_trans = itrans
|
||||
|
||||
def pop_input_trans(self) -> None:
|
||||
self.input_trans = self.input_trans_stack.pop()
|
||||
|
||||
def setpos_from_xy(self, x: int, y: int) -> None:
|
||||
"""Set pos according to coordinates x, y"""
|
||||
pos = 0
|
||||
i = 0
|
||||
while i < y:
|
||||
prompt_len, character_widths = self.screeninfo[i]
|
||||
offset = len(character_widths) - character_widths.count(0)
|
||||
in_wrapped_line = prompt_len + sum(character_widths) >= self.console.width
|
||||
if in_wrapped_line:
|
||||
pos += offset - 1 # -1 cause backslash is not in buffer
|
||||
else:
|
||||
pos += offset + 1 # +1 cause newline is in buffer
|
||||
i += 1
|
||||
|
||||
j = 0
|
||||
cur_x = self.screeninfo[i][0]
|
||||
while cur_x < x:
|
||||
if self.screeninfo[i][1][j] == 0:
|
||||
continue
|
||||
cur_x += self.screeninfo[i][1][j]
|
||||
j += 1
|
||||
pos += 1
|
||||
|
||||
self.pos = pos
|
||||
|
||||
def pos2xy(self) -> tuple[int, int]:
|
||||
"""Return the x, y coordinates of position 'pos'."""
|
||||
# this *is* incomprehensible, yes.
|
||||
p, y = 0, 0
|
||||
l2: list[int] = []
|
||||
pos = self.pos
|
||||
assert 0 <= pos <= len(self.buffer)
|
||||
if pos == len(self.buffer) and len(self.screeninfo) > 0:
|
||||
y = len(self.screeninfo) - 1
|
||||
p, l2 = self.screeninfo[y]
|
||||
return p + sum(l2) + l2.count(0), y
|
||||
|
||||
for p, l2 in self.screeninfo:
|
||||
l = len(l2) - l2.count(0)
|
||||
in_wrapped_line = p + sum(l2) >= self.console.width
|
||||
offset = l - 1 if in_wrapped_line else l # need to remove backslash
|
||||
if offset >= pos:
|
||||
break
|
||||
|
||||
if p + sum(l2) >= self.console.width:
|
||||
pos -= l - 1 # -1 cause backslash is not in buffer
|
||||
else:
|
||||
pos -= l + 1 # +1 cause newline is in buffer
|
||||
y += 1
|
||||
return p + sum(l2[:pos]), y
|
||||
|
||||
def insert(self, text: str | list[str]) -> None:
|
||||
"""Insert 'text' at the insertion point."""
|
||||
self.buffer[self.pos : self.pos] = list(text)
|
||||
self.pos += len(text)
|
||||
self.dirty = True
|
||||
|
||||
def update_cursor(self) -> None:
|
||||
"""Move the cursor to reflect changes in self.pos"""
|
||||
self.cxy = self.pos2xy()
|
||||
self.console.move_cursor(*self.cxy)
|
||||
|
||||
def after_command(self, cmd: Command) -> None:
|
||||
"""This function is called to allow post command cleanup."""
|
||||
if getattr(cmd, "kills_digit_arg", True):
|
||||
if self.arg is not None:
|
||||
self.dirty = True
|
||||
self.arg = None
|
||||
|
||||
def prepare(self) -> None:
|
||||
"""Get ready to run. Call restore when finished. You must not
|
||||
write to the console in between the calls to prepare and
|
||||
restore."""
|
||||
try:
|
||||
self.console.prepare()
|
||||
self.arg = None
|
||||
self.finished = False
|
||||
del self.buffer[:]
|
||||
self.pos = 0
|
||||
self.dirty = True
|
||||
self.last_command = None
|
||||
self.calc_screen()
|
||||
except BaseException:
|
||||
self.restore()
|
||||
raise
|
||||
|
||||
while self.scheduled_commands:
|
||||
cmd = self.scheduled_commands.pop()
|
||||
self.do_cmd((cmd, []))
|
||||
|
||||
def last_command_is(self, cls: type) -> bool:
|
||||
if not self.last_command:
|
||||
return False
|
||||
return issubclass(cls, self.last_command)
|
||||
|
||||
def restore(self) -> None:
|
||||
"""Clean up after a run."""
|
||||
self.console.restore()
|
||||
|
||||
@contextmanager
|
||||
def suspend(self) -> SimpleContextManager:
|
||||
"""A context manager to delegate to another reader."""
|
||||
prev_state = {f.name: getattr(self, f.name) for f in fields(self)}
|
||||
try:
|
||||
self.restore()
|
||||
yield
|
||||
finally:
|
||||
for arg in ("msg", "ps1", "ps2", "ps3", "ps4", "paste_mode"):
|
||||
setattr(self, arg, prev_state[arg])
|
||||
self.prepare()
|
||||
|
||||
def finish(self) -> None:
|
||||
"""Called when a command signals that we're finished."""
|
||||
pass
|
||||
|
||||
def error(self, msg: str = "none") -> None:
|
||||
self.msg = "! " + msg + " "
|
||||
self.dirty = True
|
||||
self.console.beep()
|
||||
|
||||
def update_screen(self) -> None:
|
||||
if self.dirty:
|
||||
self.refresh()
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Recalculate and refresh the screen."""
|
||||
if self.in_bracketed_paste and self.buffer and not self.buffer[-1] == "\n":
|
||||
return
|
||||
|
||||
# this call sets up self.cxy, so call it first.
|
||||
self.screen = self.calc_screen()
|
||||
self.console.refresh(self.screen, self.cxy)
|
||||
self.dirty = False
|
||||
|
||||
def do_cmd(self, cmd: tuple[str, list[str]]) -> None:
|
||||
"""`cmd` is a tuple of "event_name" and "event", which in the current
|
||||
implementation is always just the "buffer" which happens to be a list
|
||||
of single-character strings."""
|
||||
|
||||
trace("received command {cmd}", cmd=cmd)
|
||||
if isinstance(cmd[0], str):
|
||||
command_type = self.commands.get(cmd[0], commands.invalid_command)
|
||||
elif isinstance(cmd[0], type):
|
||||
command_type = cmd[0]
|
||||
else:
|
||||
return # nothing to do
|
||||
|
||||
command = command_type(self, *cmd) # type: ignore[arg-type]
|
||||
command.do()
|
||||
|
||||
self.after_command(command)
|
||||
|
||||
if self.dirty:
|
||||
self.refresh()
|
||||
else:
|
||||
self.update_cursor()
|
||||
|
||||
if not isinstance(cmd, commands.digit_arg):
|
||||
self.last_command = command_type
|
||||
|
||||
self.finished = bool(command.finish)
|
||||
if self.finished:
|
||||
self.console.finish()
|
||||
self.finish()
|
||||
|
||||
def run_hooks(self) -> None:
|
||||
threading_hook = self.threading_hook
|
||||
if threading_hook is None and 'threading' in sys.modules:
|
||||
from ._threading_handler import install_threading_hook
|
||||
install_threading_hook(self)
|
||||
if threading_hook is not None:
|
||||
try:
|
||||
threading_hook()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
input_hook = self.console.input_hook
|
||||
if input_hook:
|
||||
try:
|
||||
input_hook()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def handle1(self, block: bool = True) -> bool:
|
||||
"""Handle a single event. Wait as long as it takes if block
|
||||
is true (the default), otherwise return False if no event is
|
||||
pending."""
|
||||
|
||||
if self.msg:
|
||||
self.msg = ""
|
||||
self.dirty = True
|
||||
|
||||
while True:
|
||||
# We use the same timeout as in readline.c: 100ms
|
||||
self.run_hooks()
|
||||
self.console.wait(100)
|
||||
event = self.console.get_event(block=False)
|
||||
if not event:
|
||||
if block:
|
||||
continue
|
||||
return False
|
||||
|
||||
translate = True
|
||||
|
||||
if event.evt == "key":
|
||||
self.input_trans.push(event)
|
||||
elif event.evt == "scroll":
|
||||
self.refresh()
|
||||
elif event.evt == "resize":
|
||||
self.refresh()
|
||||
else:
|
||||
translate = False
|
||||
|
||||
if translate:
|
||||
cmd = self.input_trans.get()
|
||||
else:
|
||||
cmd = [event.evt, event.data]
|
||||
|
||||
if cmd is None:
|
||||
if block:
|
||||
continue
|
||||
return False
|
||||
|
||||
self.do_cmd(cmd)
|
||||
return True
|
||||
|
||||
def push_char(self, char: int | bytes) -> None:
|
||||
self.console.push_char(char)
|
||||
self.handle1(block=False)
|
||||
|
||||
def readline(self, startup_hook: Callback | None = None) -> str:
|
||||
"""Read a line. The implementation of this method also shows
|
||||
how to drive Reader if you want more control over the event
|
||||
loop."""
|
||||
self.prepare()
|
||||
try:
|
||||
if startup_hook is not None:
|
||||
startup_hook()
|
||||
self.refresh()
|
||||
while not self.finished:
|
||||
self.handle1()
|
||||
return self.get_unicode()
|
||||
|
||||
finally:
|
||||
self.restore()
|
||||
|
||||
def bind(self, spec: KeySpec, command: CommandName) -> None:
|
||||
self.keymap = self.keymap + ((spec, command),)
|
||||
self.input_trans = input.KeymapTranslator(
|
||||
self.keymap, invalid_cls="invalid-key", character_cls="self-insert"
|
||||
)
|
||||
|
||||
def get_unicode(self) -> str:
|
||||
"""Return the current buffer as a unicode string."""
|
||||
return "".join(self.buffer)
|
||||
598
Lib/_pyrepl/readline.py
vendored
Normal file
598
Lib/_pyrepl/readline.py
vendored
Normal file
@@ -0,0 +1,598 @@
|
||||
# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
# Alex Gaynor
|
||||
# Antonio Cuni
|
||||
# Armin Rigo
|
||||
# Holger Krekel
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""A compatibility wrapper reimplementing the 'readline' standard module
|
||||
on top of pyrepl. Not all functionalities are supported. Contains
|
||||
extensions for multiline input.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import os
|
||||
from site import gethistoryfile # type: ignore[attr-defined]
|
||||
import sys
|
||||
from rlcompleter import Completer as RLCompleter
|
||||
|
||||
from . import commands, historical_reader
|
||||
from .completing_reader import CompletingReader
|
||||
from .console import Console as ConsoleType
|
||||
|
||||
Console: type[ConsoleType]
|
||||
_error: tuple[type[Exception], ...] | type[Exception]
|
||||
try:
|
||||
from .unix_console import UnixConsole as Console, _error
|
||||
except ImportError:
|
||||
from .windows_console import WindowsConsole as Console, _error
|
||||
|
||||
ENCODING = sys.getdefaultencoding() or "latin1"
|
||||
|
||||
|
||||
# types
|
||||
Command = commands.Command
|
||||
from collections.abc import Callable, Collection
|
||||
from .types import Callback, Completer, KeySpec, CommandName
|
||||
|
||||
TYPE_CHECKING = False
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
MoreLinesCallable = Callable[[str], bool]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"add_history",
|
||||
"clear_history",
|
||||
"get_begidx",
|
||||
"get_completer",
|
||||
"get_completer_delims",
|
||||
"get_current_history_length",
|
||||
"get_endidx",
|
||||
"get_history_item",
|
||||
"get_history_length",
|
||||
"get_line_buffer",
|
||||
"insert_text",
|
||||
"parse_and_bind",
|
||||
"read_history_file",
|
||||
# "read_init_file",
|
||||
# "redisplay",
|
||||
"remove_history_item",
|
||||
"replace_history_item",
|
||||
"set_auto_history",
|
||||
"set_completer",
|
||||
"set_completer_delims",
|
||||
"set_history_length",
|
||||
# "set_pre_input_hook",
|
||||
"set_startup_hook",
|
||||
"write_history_file",
|
||||
# ---- multiline extensions ----
|
||||
"multiline_input",
|
||||
]
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
@dataclass
|
||||
class ReadlineConfig:
|
||||
readline_completer: Completer | None = None
|
||||
completer_delims: frozenset[str] = frozenset(" \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?")
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ReadlineAlikeReader(historical_reader.HistoricalReader, CompletingReader):
|
||||
# Class fields
|
||||
assume_immutable_completions = False
|
||||
use_brackets = False
|
||||
sort_in_column = True
|
||||
|
||||
# Instance fields
|
||||
config: ReadlineConfig
|
||||
more_lines: MoreLinesCallable | None = None
|
||||
last_used_indentation: str | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
super().__post_init__()
|
||||
self.commands["maybe_accept"] = maybe_accept
|
||||
self.commands["maybe-accept"] = maybe_accept
|
||||
self.commands["backspace_dedent"] = backspace_dedent
|
||||
self.commands["backspace-dedent"] = backspace_dedent
|
||||
|
||||
def error(self, msg: str = "none") -> None:
|
||||
pass # don't show error messages by default
|
||||
|
||||
def get_stem(self) -> str:
|
||||
b = self.buffer
|
||||
p = self.pos - 1
|
||||
completer_delims = self.config.completer_delims
|
||||
while p >= 0 and b[p] not in completer_delims:
|
||||
p -= 1
|
||||
return "".join(b[p + 1 : self.pos])
|
||||
|
||||
def get_completions(self, stem: str) -> list[str]:
|
||||
if len(stem) == 0 and self.more_lines is not None:
|
||||
b = self.buffer
|
||||
p = self.pos
|
||||
while p > 0 and b[p - 1] != "\n":
|
||||
p -= 1
|
||||
num_spaces = 4 - ((self.pos - p) % 4)
|
||||
return [" " * num_spaces]
|
||||
result = []
|
||||
function = self.config.readline_completer
|
||||
if function is not None:
|
||||
try:
|
||||
stem = str(stem) # rlcompleter.py seems to not like unicode
|
||||
except UnicodeEncodeError:
|
||||
pass # but feed unicode anyway if we have no choice
|
||||
state = 0
|
||||
while True:
|
||||
try:
|
||||
next = function(stem, state)
|
||||
except Exception:
|
||||
break
|
||||
if not isinstance(next, str):
|
||||
break
|
||||
result.append(next)
|
||||
state += 1
|
||||
# emulate the behavior of the standard readline that sorts
|
||||
# the completions before displaying them.
|
||||
result.sort()
|
||||
return result
|
||||
|
||||
def get_trimmed_history(self, maxlength: int) -> list[str]:
|
||||
if maxlength >= 0:
|
||||
cut = len(self.history) - maxlength
|
||||
if cut < 0:
|
||||
cut = 0
|
||||
else:
|
||||
cut = 0
|
||||
return self.history[cut:]
|
||||
|
||||
def update_last_used_indentation(self) -> None:
|
||||
indentation = _get_first_indentation(self.buffer)
|
||||
if indentation is not None:
|
||||
self.last_used_indentation = indentation
|
||||
|
||||
# --- simplified support for reading multiline Python statements ---
|
||||
|
||||
def collect_keymap(self) -> tuple[tuple[KeySpec, CommandName], ...]:
|
||||
return super().collect_keymap() + (
|
||||
(r"\n", "maybe-accept"),
|
||||
(r"\<backspace>", "backspace-dedent"),
|
||||
)
|
||||
|
||||
def after_command(self, cmd: Command) -> None:
|
||||
super().after_command(cmd)
|
||||
if self.more_lines is None:
|
||||
# Force single-line input if we are in raw_input() mode.
|
||||
# Although there is no direct way to add a \n in this mode,
|
||||
# multiline buffers can still show up using various
|
||||
# commands, e.g. navigating the history.
|
||||
try:
|
||||
index = self.buffer.index("\n")
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.buffer = self.buffer[:index]
|
||||
if self.pos > len(self.buffer):
|
||||
self.pos = len(self.buffer)
|
||||
|
||||
|
||||
def set_auto_history(_should_auto_add_history: bool) -> None:
|
||||
"""Enable or disable automatic history"""
|
||||
historical_reader.should_auto_add_history = bool(_should_auto_add_history)
|
||||
|
||||
|
||||
def _get_this_line_indent(buffer: list[str], pos: int) -> int:
|
||||
indent = 0
|
||||
while pos > 0 and buffer[pos - 1] in " \t":
|
||||
indent += 1
|
||||
pos -= 1
|
||||
if pos > 0 and buffer[pos - 1] == "\n":
|
||||
return indent
|
||||
return 0
|
||||
|
||||
|
||||
def _get_previous_line_indent(buffer: list[str], pos: int) -> tuple[int, int | None]:
|
||||
prevlinestart = pos
|
||||
while prevlinestart > 0 and buffer[prevlinestart - 1] != "\n":
|
||||
prevlinestart -= 1
|
||||
prevlinetext = prevlinestart
|
||||
while prevlinetext < pos and buffer[prevlinetext] in " \t":
|
||||
prevlinetext += 1
|
||||
if prevlinetext == pos:
|
||||
indent = None
|
||||
else:
|
||||
indent = prevlinetext - prevlinestart
|
||||
return prevlinestart, indent
|
||||
|
||||
|
||||
def _get_first_indentation(buffer: list[str]) -> str | None:
|
||||
indented_line_start = None
|
||||
for i in range(len(buffer)):
|
||||
if (i < len(buffer) - 1
|
||||
and buffer[i] == "\n"
|
||||
and buffer[i + 1] in " \t"
|
||||
):
|
||||
indented_line_start = i + 1
|
||||
elif indented_line_start is not None and buffer[i] not in " \t\n":
|
||||
return ''.join(buffer[indented_line_start : i])
|
||||
return None
|
||||
|
||||
|
||||
def _should_auto_indent(buffer: list[str], pos: int) -> bool:
|
||||
# check if last character before "pos" is a colon, ignoring
|
||||
# whitespaces and comments.
|
||||
last_char = None
|
||||
while pos > 0:
|
||||
pos -= 1
|
||||
if last_char is None:
|
||||
if buffer[pos] not in " \t\n#": # ignore whitespaces and comments
|
||||
last_char = buffer[pos]
|
||||
else:
|
||||
# even if we found a non-whitespace character before
|
||||
# original pos, we keep going back until newline is reached
|
||||
# to make sure we ignore comments
|
||||
if buffer[pos] == "\n":
|
||||
break
|
||||
if buffer[pos] == "#":
|
||||
last_char = None
|
||||
return last_char == ":"
|
||||
|
||||
|
||||
class maybe_accept(commands.Command):
|
||||
def do(self) -> None:
|
||||
r: ReadlineAlikeReader
|
||||
r = self.reader # type: ignore[assignment]
|
||||
r.dirty = True # this is needed to hide the completion menu, if visible
|
||||
|
||||
if self.reader.in_bracketed_paste:
|
||||
r.insert("\n")
|
||||
return
|
||||
|
||||
# if there are already several lines and the cursor
|
||||
# is not on the last one, always insert a new \n.
|
||||
text = r.get_unicode()
|
||||
|
||||
if "\n" in r.buffer[r.pos :] or (
|
||||
r.more_lines is not None and r.more_lines(text)
|
||||
):
|
||||
def _newline_before_pos():
|
||||
before_idx = r.pos - 1
|
||||
while before_idx > 0 and text[before_idx].isspace():
|
||||
before_idx -= 1
|
||||
return text[before_idx : r.pos].count("\n") > 0
|
||||
|
||||
# if there's already a new line before the cursor then
|
||||
# even if the cursor is followed by whitespace, we assume
|
||||
# the user is trying to terminate the block
|
||||
if _newline_before_pos() and text[r.pos:].isspace():
|
||||
self.finish = True
|
||||
return
|
||||
|
||||
# auto-indent the next line like the previous line
|
||||
prevlinestart, indent = _get_previous_line_indent(r.buffer, r.pos)
|
||||
r.insert("\n")
|
||||
if not self.reader.paste_mode:
|
||||
if indent:
|
||||
for i in range(prevlinestart, prevlinestart + indent):
|
||||
r.insert(r.buffer[i])
|
||||
r.update_last_used_indentation()
|
||||
if _should_auto_indent(r.buffer, r.pos):
|
||||
if r.last_used_indentation is not None:
|
||||
indentation = r.last_used_indentation
|
||||
else:
|
||||
# default
|
||||
indentation = " " * 4
|
||||
r.insert(indentation)
|
||||
elif not self.reader.paste_mode:
|
||||
self.finish = True
|
||||
else:
|
||||
r.insert("\n")
|
||||
|
||||
|
||||
class backspace_dedent(commands.Command):
|
||||
def do(self) -> None:
|
||||
r = self.reader
|
||||
b = r.buffer
|
||||
if r.pos > 0:
|
||||
repeat = 1
|
||||
if b[r.pos - 1] != "\n":
|
||||
indent = _get_this_line_indent(b, r.pos)
|
||||
if indent > 0:
|
||||
ls = r.pos - indent
|
||||
while ls > 0:
|
||||
ls, pi = _get_previous_line_indent(b, ls - 1)
|
||||
if pi is not None and pi < indent:
|
||||
repeat = indent - pi
|
||||
break
|
||||
r.pos -= repeat
|
||||
del b[r.pos : r.pos + repeat]
|
||||
r.dirty = True
|
||||
else:
|
||||
self.reader.error("can't backspace at start")
|
||||
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class _ReadlineWrapper:
|
||||
f_in: int = -1
|
||||
f_out: int = -1
|
||||
reader: ReadlineAlikeReader | None = field(default=None, repr=False)
|
||||
saved_history_length: int = -1
|
||||
startup_hook: Callback | None = None
|
||||
config: ReadlineConfig = field(default_factory=ReadlineConfig, repr=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.f_in == -1:
|
||||
self.f_in = os.dup(0)
|
||||
if self.f_out == -1:
|
||||
self.f_out = os.dup(1)
|
||||
|
||||
def get_reader(self) -> ReadlineAlikeReader:
|
||||
if self.reader is None:
|
||||
console = Console(self.f_in, self.f_out, encoding=ENCODING)
|
||||
self.reader = ReadlineAlikeReader(console=console, config=self.config)
|
||||
return self.reader
|
||||
|
||||
def input(self, prompt: object = "") -> str:
|
||||
try:
|
||||
reader = self.get_reader()
|
||||
except _error:
|
||||
assert raw_input is not None
|
||||
return raw_input(prompt)
|
||||
prompt_str = str(prompt)
|
||||
reader.ps1 = prompt_str
|
||||
sys.audit("builtins.input", prompt_str)
|
||||
result = reader.readline(startup_hook=self.startup_hook)
|
||||
sys.audit("builtins.input/result", result)
|
||||
return result
|
||||
|
||||
def multiline_input(self, more_lines: MoreLinesCallable, ps1: str, ps2: str) -> str:
|
||||
"""Read an input on possibly multiple lines, asking for more
|
||||
lines as long as 'more_lines(unicodetext)' returns an object whose
|
||||
boolean value is true.
|
||||
"""
|
||||
reader = self.get_reader()
|
||||
saved = reader.more_lines
|
||||
try:
|
||||
reader.more_lines = more_lines
|
||||
reader.ps1 = ps1
|
||||
reader.ps2 = ps1
|
||||
reader.ps3 = ps2
|
||||
reader.ps4 = ""
|
||||
with warnings.catch_warnings(action="ignore"):
|
||||
return reader.readline()
|
||||
finally:
|
||||
reader.more_lines = saved
|
||||
reader.paste_mode = False
|
||||
|
||||
def parse_and_bind(self, string: str) -> None:
|
||||
pass # XXX we don't support parsing GNU-readline-style init files
|
||||
|
||||
def set_completer(self, function: Completer | None = None) -> None:
|
||||
self.config.readline_completer = function
|
||||
|
||||
def get_completer(self) -> Completer | None:
|
||||
return self.config.readline_completer
|
||||
|
||||
def set_completer_delims(self, delimiters: Collection[str]) -> None:
|
||||
self.config.completer_delims = frozenset(delimiters)
|
||||
|
||||
def get_completer_delims(self) -> str:
|
||||
return "".join(sorted(self.config.completer_delims))
|
||||
|
||||
def _histline(self, line: str) -> str:
|
||||
line = line.rstrip("\n")
|
||||
return line
|
||||
|
||||
def get_history_length(self) -> int:
|
||||
return self.saved_history_length
|
||||
|
||||
def set_history_length(self, length: int) -> None:
|
||||
self.saved_history_length = length
|
||||
|
||||
def get_current_history_length(self) -> int:
|
||||
return len(self.get_reader().history)
|
||||
|
||||
def read_history_file(self, filename: str = gethistoryfile()) -> None:
|
||||
# multiline extension (really a hack) for the end of lines that
|
||||
# are actually continuations inside a single multiline_input()
|
||||
# history item: we use \r\n instead of just \n. If the history
|
||||
# file is passed to GNU readline, the extra \r are just ignored.
|
||||
history = self.get_reader().history
|
||||
|
||||
with open(os.path.expanduser(filename), 'rb') as f:
|
||||
is_editline = f.readline().startswith(b"_HiStOrY_V2_")
|
||||
if is_editline:
|
||||
encoding = "unicode-escape"
|
||||
else:
|
||||
f.seek(0)
|
||||
encoding = "utf-8"
|
||||
|
||||
lines = [line.decode(encoding, errors='replace') for line in f.read().split(b'\n')]
|
||||
buffer = []
|
||||
for line in lines:
|
||||
if line.endswith("\r"):
|
||||
buffer.append(line+'\n')
|
||||
else:
|
||||
line = self._histline(line)
|
||||
if buffer:
|
||||
line = self._histline("".join(buffer).replace("\r", "") + line)
|
||||
del buffer[:]
|
||||
if line:
|
||||
history.append(line)
|
||||
|
||||
def write_history_file(self, filename: str = gethistoryfile()) -> None:
|
||||
maxlength = self.saved_history_length
|
||||
history = self.get_reader().get_trimmed_history(maxlength)
|
||||
f = open(os.path.expanduser(filename), "w",
|
||||
encoding="utf-8", newline="\n")
|
||||
with f:
|
||||
for entry in history:
|
||||
entry = entry.replace("\n", "\r\n") # multiline history support
|
||||
f.write(entry + "\n")
|
||||
|
||||
def clear_history(self) -> None:
|
||||
del self.get_reader().history[:]
|
||||
|
||||
def get_history_item(self, index: int) -> str | None:
|
||||
history = self.get_reader().history
|
||||
if 1 <= index <= len(history):
|
||||
return history[index - 1]
|
||||
else:
|
||||
return None # like readline.c
|
||||
|
||||
def remove_history_item(self, index: int) -> None:
|
||||
history = self.get_reader().history
|
||||
if 0 <= index < len(history):
|
||||
del history[index]
|
||||
else:
|
||||
raise ValueError("No history item at position %d" % index)
|
||||
# like readline.c
|
||||
|
||||
def replace_history_item(self, index: int, line: str) -> None:
|
||||
history = self.get_reader().history
|
||||
if 0 <= index < len(history):
|
||||
history[index] = self._histline(line)
|
||||
else:
|
||||
raise ValueError("No history item at position %d" % index)
|
||||
# like readline.c
|
||||
|
||||
def add_history(self, line: str) -> None:
|
||||
self.get_reader().history.append(self._histline(line))
|
||||
|
||||
def set_startup_hook(self, function: Callback | None = None) -> None:
|
||||
self.startup_hook = function
|
||||
|
||||
def get_line_buffer(self) -> str:
|
||||
return self.get_reader().get_unicode()
|
||||
|
||||
def _get_idxs(self) -> tuple[int, int]:
|
||||
start = cursor = self.get_reader().pos
|
||||
buf = self.get_line_buffer()
|
||||
for i in range(cursor - 1, -1, -1):
|
||||
if buf[i] in self.get_completer_delims():
|
||||
break
|
||||
start = i
|
||||
return start, cursor
|
||||
|
||||
def get_begidx(self) -> int:
|
||||
return self._get_idxs()[0]
|
||||
|
||||
def get_endidx(self) -> int:
|
||||
return self._get_idxs()[1]
|
||||
|
||||
def insert_text(self, text: str) -> None:
|
||||
self.get_reader().insert(text)
|
||||
|
||||
|
||||
_wrapper = _ReadlineWrapper()
|
||||
|
||||
# ____________________________________________________________
|
||||
# Public API
|
||||
|
||||
parse_and_bind = _wrapper.parse_and_bind
|
||||
set_completer = _wrapper.set_completer
|
||||
get_completer = _wrapper.get_completer
|
||||
set_completer_delims = _wrapper.set_completer_delims
|
||||
get_completer_delims = _wrapper.get_completer_delims
|
||||
get_history_length = _wrapper.get_history_length
|
||||
set_history_length = _wrapper.set_history_length
|
||||
get_current_history_length = _wrapper.get_current_history_length
|
||||
read_history_file = _wrapper.read_history_file
|
||||
write_history_file = _wrapper.write_history_file
|
||||
clear_history = _wrapper.clear_history
|
||||
get_history_item = _wrapper.get_history_item
|
||||
remove_history_item = _wrapper.remove_history_item
|
||||
replace_history_item = _wrapper.replace_history_item
|
||||
add_history = _wrapper.add_history
|
||||
set_startup_hook = _wrapper.set_startup_hook
|
||||
get_line_buffer = _wrapper.get_line_buffer
|
||||
get_begidx = _wrapper.get_begidx
|
||||
get_endidx = _wrapper.get_endidx
|
||||
insert_text = _wrapper.insert_text
|
||||
|
||||
# Extension
|
||||
multiline_input = _wrapper.multiline_input
|
||||
|
||||
# Internal hook
|
||||
_get_reader = _wrapper.get_reader
|
||||
|
||||
# ____________________________________________________________
|
||||
# Stubs
|
||||
|
||||
|
||||
def _make_stub(_name: str, _ret: object) -> None:
|
||||
def stub(*args: object, **kwds: object) -> None:
|
||||
import warnings
|
||||
|
||||
warnings.warn("readline.%s() not implemented" % _name, stacklevel=2)
|
||||
|
||||
stub.__name__ = _name
|
||||
globals()[_name] = stub
|
||||
|
||||
|
||||
for _name, _ret in [
|
||||
("read_init_file", None),
|
||||
("redisplay", None),
|
||||
("set_pre_input_hook", None),
|
||||
]:
|
||||
assert _name not in globals(), _name
|
||||
_make_stub(_name, _ret)
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
|
||||
def _setup(namespace: Mapping[str, Any]) -> None:
|
||||
global raw_input
|
||||
if raw_input is not None:
|
||||
return # don't run _setup twice
|
||||
|
||||
try:
|
||||
f_in = sys.stdin.fileno()
|
||||
f_out = sys.stdout.fileno()
|
||||
except (AttributeError, ValueError):
|
||||
return
|
||||
if not os.isatty(f_in) or not os.isatty(f_out):
|
||||
return
|
||||
|
||||
_wrapper.f_in = f_in
|
||||
_wrapper.f_out = f_out
|
||||
|
||||
# set up namespace in rlcompleter, which requires it to be a bona fide dict
|
||||
if not isinstance(namespace, dict):
|
||||
namespace = dict(namespace)
|
||||
_wrapper.config.readline_completer = RLCompleter(namespace).complete
|
||||
|
||||
# this is not really what readline.c does. Better than nothing I guess
|
||||
import builtins
|
||||
raw_input = builtins.input
|
||||
builtins.input = _wrapper.input
|
||||
|
||||
|
||||
raw_input: Callable[[object], str] | None = None
|
||||
167
Lib/_pyrepl/simple_interact.py
vendored
Normal file
167
Lib/_pyrepl/simple_interact.py
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
# Armin Rigo
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""This is an alternative to python_reader which tries to emulate
|
||||
the CPython prompt as closely as possible, with the exception of
|
||||
allowing multiline input and multiline history entries.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import _sitebuiltins
|
||||
import linecache
|
||||
import functools
|
||||
import os
|
||||
import sys
|
||||
import code
|
||||
|
||||
from .readline import _get_reader, multiline_input
|
||||
|
||||
TYPE_CHECKING = False
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
_error: tuple[type[Exception], ...] | type[Exception]
|
||||
try:
|
||||
from .unix_console import _error
|
||||
except ModuleNotFoundError:
|
||||
from .windows_console import _error
|
||||
|
||||
def check() -> str:
|
||||
"""Returns the error message if there is a problem initializing the state."""
|
||||
try:
|
||||
_get_reader()
|
||||
except _error as e:
|
||||
if term := os.environ.get("TERM", ""):
|
||||
term = f"; TERM={term}"
|
||||
return str(str(e) or repr(e) or "unknown error") + term
|
||||
return ""
|
||||
|
||||
|
||||
def _strip_final_indent(text: str) -> str:
|
||||
# kill spaces and tabs at the end, but only if they follow '\n'.
|
||||
# meant to remove the auto-indentation only (although it would of
|
||||
# course also remove explicitly-added indentation).
|
||||
short = text.rstrip(" \t")
|
||||
n = len(short)
|
||||
if n > 0 and text[n - 1] == "\n":
|
||||
return short
|
||||
return text
|
||||
|
||||
|
||||
def _clear_screen():
|
||||
reader = _get_reader()
|
||||
reader.scheduled_commands.append("clear_screen")
|
||||
|
||||
|
||||
REPL_COMMANDS = {
|
||||
"exit": _sitebuiltins.Quitter('exit', ''),
|
||||
"quit": _sitebuiltins.Quitter('quit' ,''),
|
||||
"copyright": _sitebuiltins._Printer('copyright', sys.copyright),
|
||||
"help": _sitebuiltins._Helper(),
|
||||
"clear": _clear_screen,
|
||||
"\x1a": _sitebuiltins.Quitter('\x1a', ''),
|
||||
}
|
||||
|
||||
|
||||
def _more_lines(console: code.InteractiveConsole, unicodetext: str) -> bool:
|
||||
# ooh, look at the hack:
|
||||
src = _strip_final_indent(unicodetext)
|
||||
try:
|
||||
code = console.compile(src, "<stdin>", "single")
|
||||
except (OverflowError, SyntaxError, ValueError):
|
||||
lines = src.splitlines(keepends=True)
|
||||
if len(lines) == 1:
|
||||
return False
|
||||
|
||||
last_line = lines[-1]
|
||||
was_indented = last_line.startswith((" ", "\t"))
|
||||
not_empty = last_line.strip() != ""
|
||||
incomplete = not last_line.endswith("\n")
|
||||
return (was_indented or not_empty) and incomplete
|
||||
else:
|
||||
return code is None
|
||||
|
||||
|
||||
def run_multiline_interactive_console(
|
||||
console: code.InteractiveConsole,
|
||||
*,
|
||||
future_flags: int = 0,
|
||||
) -> None:
|
||||
from .readline import _setup
|
||||
_setup(console.locals)
|
||||
if future_flags:
|
||||
console.compile.compiler.flags |= future_flags
|
||||
|
||||
more_lines = functools.partial(_more_lines, console)
|
||||
input_n = 0
|
||||
|
||||
def maybe_run_command(statement: str) -> bool:
|
||||
statement = statement.strip()
|
||||
if statement in console.locals or statement not in REPL_COMMANDS:
|
||||
return False
|
||||
|
||||
reader = _get_reader()
|
||||
reader.history.pop() # skip internal commands in history
|
||||
command = REPL_COMMANDS[statement]
|
||||
if callable(command):
|
||||
# Make sure that history does not change because of commands
|
||||
with reader.suspend_history():
|
||||
command()
|
||||
return True
|
||||
return False
|
||||
|
||||
while 1:
|
||||
try:
|
||||
try:
|
||||
sys.stdout.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ps1 = getattr(sys, "ps1", ">>> ")
|
||||
ps2 = getattr(sys, "ps2", "... ")
|
||||
try:
|
||||
statement = multiline_input(more_lines, ps1, ps2)
|
||||
except EOFError:
|
||||
break
|
||||
|
||||
if maybe_run_command(statement):
|
||||
continue
|
||||
|
||||
input_name = f"<python-input-{input_n}>"
|
||||
linecache._register_code(input_name, statement, "<stdin>") # type: ignore[attr-defined]
|
||||
more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg]
|
||||
assert not more
|
||||
input_n += 1
|
||||
except KeyboardInterrupt:
|
||||
r = _get_reader()
|
||||
if r.input_trans is r.isearch_trans:
|
||||
r.do_cmd(("isearch-end", [""]))
|
||||
r.pos = len(r.get_unicode())
|
||||
r.dirty = True
|
||||
r.refresh()
|
||||
r.in_bracketed_paste = False
|
||||
console.write("\nKeyboardInterrupt\n")
|
||||
console.resetbuffer()
|
||||
except MemoryError:
|
||||
console.write("\nMemoryError\n")
|
||||
console.resetbuffer()
|
||||
21
Lib/_pyrepl/trace.py
vendored
Normal file
21
Lib/_pyrepl/trace.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
# types
|
||||
if False:
|
||||
from typing import IO
|
||||
|
||||
|
||||
trace_file: IO[str] | None = None
|
||||
if trace_filename := os.environ.get("PYREPL_TRACE"):
|
||||
trace_file = open(trace_filename, "a")
|
||||
|
||||
|
||||
def trace(line: str, *k: object, **kw: object) -> None:
|
||||
if trace_file is None:
|
||||
return
|
||||
if k or kw:
|
||||
line = line.format(*k, **kw)
|
||||
trace_file.write(line + "\n")
|
||||
trace_file.flush()
|
||||
8
Lib/_pyrepl/types.py
vendored
Normal file
8
Lib/_pyrepl/types.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
from collections.abc import Callable, Iterator
|
||||
|
||||
Callback = Callable[[], object]
|
||||
SimpleContextManager = Iterator[None]
|
||||
KeySpec = str # like r"\C-c"
|
||||
CommandName = str # like "interrupt"
|
||||
EventTuple = tuple[CommandName, str]
|
||||
Completer = Callable[[str, int], str | None]
|
||||
810
Lib/_pyrepl/unix_console.py
vendored
Normal file
810
Lib/_pyrepl/unix_console.py
vendored
Normal file
@@ -0,0 +1,810 @@
|
||||
# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
# Antonio Cuni
|
||||
# Armin Rigo
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import errno
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import signal
|
||||
import struct
|
||||
import termios
|
||||
import time
|
||||
import platform
|
||||
from fcntl import ioctl
|
||||
|
||||
from . import curses
|
||||
from .console import Console, Event
|
||||
from .fancy_termios import tcgetattr, tcsetattr
|
||||
from .trace import trace
|
||||
from .unix_eventqueue import EventQueue
|
||||
from .utils import wlen
|
||||
|
||||
|
||||
TYPE_CHECKING = False
|
||||
|
||||
# types
|
||||
if TYPE_CHECKING:
|
||||
from typing import IO, Literal, overload
|
||||
else:
|
||||
overload = lambda func: None
|
||||
|
||||
|
||||
class InvalidTerminal(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
_error = (termios.error, curses.error, InvalidTerminal)
|
||||
|
||||
SIGWINCH_EVENT = "repaint"
|
||||
|
||||
FIONREAD = getattr(termios, "FIONREAD", None)
|
||||
TIOCGWINSZ = getattr(termios, "TIOCGWINSZ", None)
|
||||
|
||||
# ------------ start of baudrate definitions ------------
|
||||
|
||||
# Add (possibly) missing baudrates (check termios man page) to termios
|
||||
|
||||
|
||||
def add_baudrate_if_supported(dictionary: dict[int, int], rate: int) -> None:
|
||||
baudrate_name = "B%d" % rate
|
||||
if hasattr(termios, baudrate_name):
|
||||
dictionary[getattr(termios, baudrate_name)] = rate
|
||||
|
||||
|
||||
# Check the termios man page (Line speed) to know where these
|
||||
# values come from.
|
||||
potential_baudrates = [
|
||||
0,
|
||||
110,
|
||||
115200,
|
||||
1200,
|
||||
134,
|
||||
150,
|
||||
1800,
|
||||
19200,
|
||||
200,
|
||||
230400,
|
||||
2400,
|
||||
300,
|
||||
38400,
|
||||
460800,
|
||||
4800,
|
||||
50,
|
||||
57600,
|
||||
600,
|
||||
75,
|
||||
9600,
|
||||
]
|
||||
|
||||
ratedict: dict[int, int] = {}
|
||||
for rate in potential_baudrates:
|
||||
add_baudrate_if_supported(ratedict, rate)
|
||||
|
||||
# Clean up variables to avoid unintended usage
|
||||
del rate, add_baudrate_if_supported
|
||||
|
||||
# ------------ end of baudrate definitions ------------
|
||||
|
||||
delayprog = re.compile(b"\\$<([0-9]+)((?:/|\\*){0,2})>")
|
||||
|
||||
try:
|
||||
poll: type[select.poll] = select.poll
|
||||
except AttributeError:
|
||||
# this is exactly the minumum necessary to support what we
|
||||
# do with poll objects
|
||||
class MinimalPoll:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def register(self, fd, flag):
|
||||
self.fd = fd
|
||||
# note: The 'timeout' argument is received as *milliseconds*
|
||||
def poll(self, timeout: float | None = None) -> list[int]:
|
||||
if timeout is None:
|
||||
r, w, e = select.select([self.fd], [], [])
|
||||
else:
|
||||
r, w, e = select.select([self.fd], [], [], timeout/1000)
|
||||
return r
|
||||
|
||||
poll = MinimalPoll # type: ignore[assignment]
|
||||
|
||||
|
||||
class UnixConsole(Console):
|
||||
def __init__(
|
||||
self,
|
||||
f_in: IO[bytes] | int = 0,
|
||||
f_out: IO[bytes] | int = 1,
|
||||
term: str = "",
|
||||
encoding: str = "",
|
||||
):
|
||||
"""
|
||||
Initialize the UnixConsole.
|
||||
|
||||
Parameters:
|
||||
- f_in (int or file-like object): Input file descriptor or object.
|
||||
- f_out (int or file-like object): Output file descriptor or object.
|
||||
- term (str): Terminal name.
|
||||
- encoding (str): Encoding to use for I/O operations.
|
||||
"""
|
||||
super().__init__(f_in, f_out, term, encoding)
|
||||
|
||||
self.pollob = poll()
|
||||
self.pollob.register(self.input_fd, select.POLLIN)
|
||||
self.input_buffer = b""
|
||||
self.input_buffer_pos = 0
|
||||
curses.setupterm(term or None, self.output_fd)
|
||||
self.term = term
|
||||
|
||||
@overload
|
||||
def _my_getstr(cap: str, optional: Literal[False] = False) -> bytes: ...
|
||||
|
||||
@overload
|
||||
def _my_getstr(cap: str, optional: bool) -> bytes | None: ...
|
||||
|
||||
def _my_getstr(cap: str, optional: bool = False) -> bytes | None:
|
||||
r = curses.tigetstr(cap)
|
||||
if not optional and r is None:
|
||||
raise InvalidTerminal(
|
||||
f"terminal doesn't have the required {cap} capability"
|
||||
)
|
||||
return r
|
||||
|
||||
self._bel = _my_getstr("bel")
|
||||
self._civis = _my_getstr("civis", optional=True)
|
||||
self._clear = _my_getstr("clear")
|
||||
self._cnorm = _my_getstr("cnorm", optional=True)
|
||||
self._cub = _my_getstr("cub", optional=True)
|
||||
self._cub1 = _my_getstr("cub1", optional=True)
|
||||
self._cud = _my_getstr("cud", optional=True)
|
||||
self._cud1 = _my_getstr("cud1", optional=True)
|
||||
self._cuf = _my_getstr("cuf", optional=True)
|
||||
self._cuf1 = _my_getstr("cuf1", optional=True)
|
||||
self._cup = _my_getstr("cup")
|
||||
self._cuu = _my_getstr("cuu", optional=True)
|
||||
self._cuu1 = _my_getstr("cuu1", optional=True)
|
||||
self._dch1 = _my_getstr("dch1", optional=True)
|
||||
self._dch = _my_getstr("dch", optional=True)
|
||||
self._el = _my_getstr("el")
|
||||
self._hpa = _my_getstr("hpa", optional=True)
|
||||
self._ich = _my_getstr("ich", optional=True)
|
||||
self._ich1 = _my_getstr("ich1", optional=True)
|
||||
self._ind = _my_getstr("ind", optional=True)
|
||||
self._pad = _my_getstr("pad", optional=True)
|
||||
self._ri = _my_getstr("ri", optional=True)
|
||||
self._rmkx = _my_getstr("rmkx", optional=True)
|
||||
self._smkx = _my_getstr("smkx", optional=True)
|
||||
|
||||
self.__setup_movement()
|
||||
|
||||
self.event_queue = EventQueue(self.input_fd, self.encoding)
|
||||
self.cursor_visible = 1
|
||||
|
||||
def more_in_buffer(self) -> bool:
|
||||
return bool(
|
||||
self.input_buffer
|
||||
and self.input_buffer_pos < len(self.input_buffer)
|
||||
)
|
||||
|
||||
def __read(self, n: int) -> bytes:
|
||||
if not self.more_in_buffer():
|
||||
self.input_buffer = os.read(self.input_fd, 10000)
|
||||
|
||||
ret = self.input_buffer[self.input_buffer_pos : self.input_buffer_pos + n]
|
||||
self.input_buffer_pos += len(ret)
|
||||
if self.input_buffer_pos >= len(self.input_buffer):
|
||||
self.input_buffer = b""
|
||||
self.input_buffer_pos = 0
|
||||
return ret
|
||||
|
||||
|
||||
def change_encoding(self, encoding: str) -> None:
|
||||
"""
|
||||
Change the encoding used for I/O operations.
|
||||
|
||||
Parameters:
|
||||
- encoding (str): New encoding to use.
|
||||
"""
|
||||
self.encoding = encoding
|
||||
|
||||
def refresh(self, screen, c_xy):
|
||||
"""
|
||||
Refresh the console screen.
|
||||
|
||||
Parameters:
|
||||
- screen (list): List of strings representing the screen contents.
|
||||
- c_xy (tuple): Cursor position (x, y) on the screen.
|
||||
"""
|
||||
cx, cy = c_xy
|
||||
if not self.__gone_tall:
|
||||
while len(self.screen) < min(len(screen), self.height):
|
||||
self.__hide_cursor()
|
||||
self.__move(0, len(self.screen) - 1)
|
||||
self.__write("\n")
|
||||
self.posxy = 0, len(self.screen)
|
||||
self.screen.append("")
|
||||
else:
|
||||
while len(self.screen) < len(screen):
|
||||
self.screen.append("")
|
||||
|
||||
if len(screen) > self.height:
|
||||
self.__gone_tall = 1
|
||||
self.__move = self.__move_tall
|
||||
|
||||
px, py = self.posxy
|
||||
old_offset = offset = self.__offset
|
||||
height = self.height
|
||||
|
||||
# we make sure the cursor is on the screen, and that we're
|
||||
# using all of the screen if we can
|
||||
if cy < offset:
|
||||
offset = cy
|
||||
elif cy >= offset + height:
|
||||
offset = cy - height + 1
|
||||
elif offset > 0 and len(screen) < offset + height:
|
||||
offset = max(len(screen) - height, 0)
|
||||
screen.append("")
|
||||
|
||||
oldscr = self.screen[old_offset : old_offset + height]
|
||||
newscr = screen[offset : offset + height]
|
||||
|
||||
# use hardware scrolling if we have it.
|
||||
if old_offset > offset and self._ri:
|
||||
self.__hide_cursor()
|
||||
self.__write_code(self._cup, 0, 0)
|
||||
self.posxy = 0, old_offset
|
||||
for i in range(old_offset - offset):
|
||||
self.__write_code(self._ri)
|
||||
oldscr.pop(-1)
|
||||
oldscr.insert(0, "")
|
||||
elif old_offset < offset and self._ind:
|
||||
self.__hide_cursor()
|
||||
self.__write_code(self._cup, self.height - 1, 0)
|
||||
self.posxy = 0, old_offset + self.height - 1
|
||||
for i in range(offset - old_offset):
|
||||
self.__write_code(self._ind)
|
||||
oldscr.pop(0)
|
||||
oldscr.append("")
|
||||
|
||||
self.__offset = offset
|
||||
|
||||
for (
|
||||
y,
|
||||
oldline,
|
||||
newline,
|
||||
) in zip(range(offset, offset + height), oldscr, newscr):
|
||||
if oldline != newline:
|
||||
self.__write_changed_line(y, oldline, newline, px)
|
||||
|
||||
y = len(newscr)
|
||||
while y < len(oldscr):
|
||||
self.__hide_cursor()
|
||||
self.__move(0, y)
|
||||
self.posxy = 0, y
|
||||
self.__write_code(self._el)
|
||||
y += 1
|
||||
|
||||
self.__show_cursor()
|
||||
|
||||
self.screen = screen.copy()
|
||||
self.move_cursor(cx, cy)
|
||||
self.flushoutput()
|
||||
|
||||
def move_cursor(self, x, y):
|
||||
"""
|
||||
Move the cursor to the specified position on the screen.
|
||||
|
||||
Parameters:
|
||||
- x (int): X coordinate.
|
||||
- y (int): Y coordinate.
|
||||
"""
|
||||
if y < self.__offset or y >= self.__offset + self.height:
|
||||
self.event_queue.insert(Event("scroll", None))
|
||||
else:
|
||||
self.__move(x, y)
|
||||
self.posxy = x, y
|
||||
self.flushoutput()
|
||||
|
||||
def prepare(self):
|
||||
"""
|
||||
Prepare the console for input/output operations.
|
||||
"""
|
||||
self.__svtermstate = tcgetattr(self.input_fd)
|
||||
raw = self.__svtermstate.copy()
|
||||
raw.iflag &= ~(termios.INPCK | termios.ISTRIP | termios.IXON)
|
||||
raw.oflag &= ~(termios.OPOST)
|
||||
raw.cflag &= ~(termios.CSIZE | termios.PARENB)
|
||||
raw.cflag |= termios.CS8
|
||||
raw.iflag |= termios.BRKINT
|
||||
raw.lflag &= ~(termios.ICANON | termios.ECHO | termios.IEXTEN)
|
||||
raw.lflag |= termios.ISIG
|
||||
raw.cc[termios.VMIN] = 1
|
||||
raw.cc[termios.VTIME] = 0
|
||||
tcsetattr(self.input_fd, termios.TCSADRAIN, raw)
|
||||
|
||||
# In macOS terminal we need to deactivate line wrap via ANSI escape code
|
||||
if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal":
|
||||
os.write(self.output_fd, b"\033[?7l")
|
||||
|
||||
self.screen = []
|
||||
self.height, self.width = self.getheightwidth()
|
||||
|
||||
self.__buffer = []
|
||||
|
||||
self.posxy = 0, 0
|
||||
self.__gone_tall = 0
|
||||
self.__move = self.__move_short
|
||||
self.__offset = 0
|
||||
|
||||
self.__maybe_write_code(self._smkx)
|
||||
|
||||
try:
|
||||
self.old_sigwinch = signal.signal(signal.SIGWINCH, self.__sigwinch)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
self.__enable_bracketed_paste()
|
||||
|
||||
def restore(self):
|
||||
"""
|
||||
Restore the console to the default state
|
||||
"""
|
||||
self.__disable_bracketed_paste()
|
||||
self.__maybe_write_code(self._rmkx)
|
||||
self.flushoutput()
|
||||
tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate)
|
||||
|
||||
if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal":
|
||||
os.write(self.output_fd, b"\033[?7h")
|
||||
|
||||
if hasattr(self, "old_sigwinch"):
|
||||
signal.signal(signal.SIGWINCH, self.old_sigwinch)
|
||||
del self.old_sigwinch
|
||||
|
||||
def push_char(self, char: int | bytes) -> None:
|
||||
"""
|
||||
Push a character to the console event queue.
|
||||
"""
|
||||
trace("push char {char!r}", char=char)
|
||||
self.event_queue.push(char)
|
||||
|
||||
def get_event(self, block: bool = True) -> Event | None:
|
||||
"""
|
||||
Get an event from the console event queue.
|
||||
|
||||
Parameters:
|
||||
- block (bool): Whether to block until an event is available.
|
||||
|
||||
Returns:
|
||||
- Event: Event object from the event queue.
|
||||
"""
|
||||
if not block and not self.wait(timeout=0):
|
||||
return None
|
||||
|
||||
while self.event_queue.empty():
|
||||
while True:
|
||||
try:
|
||||
self.push_char(self.__read(1))
|
||||
except OSError as err:
|
||||
if err.errno == errno.EINTR:
|
||||
if not self.event_queue.empty():
|
||||
return self.event_queue.get()
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
break
|
||||
return self.event_queue.get()
|
||||
|
||||
def wait(self, timeout: float | None = None) -> bool:
|
||||
"""
|
||||
Wait for events on the console.
|
||||
"""
|
||||
return (
|
||||
not self.event_queue.empty()
|
||||
or self.more_in_buffer()
|
||||
or bool(self.pollob.poll(timeout))
|
||||
)
|
||||
|
||||
def set_cursor_vis(self, visible):
|
||||
"""
|
||||
Set the visibility of the cursor.
|
||||
|
||||
Parameters:
|
||||
- visible (bool): Visibility flag.
|
||||
"""
|
||||
if visible:
|
||||
self.__show_cursor()
|
||||
else:
|
||||
self.__hide_cursor()
|
||||
|
||||
if TIOCGWINSZ:
|
||||
|
||||
def getheightwidth(self):
|
||||
"""
|
||||
Get the height and width of the console.
|
||||
|
||||
Returns:
|
||||
- tuple: Height and width of the console.
|
||||
"""
|
||||
try:
|
||||
return int(os.environ["LINES"]), int(os.environ["COLUMNS"])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
try:
|
||||
size = ioctl(self.input_fd, TIOCGWINSZ, b"\000" * 8)
|
||||
except OSError:
|
||||
return 25, 80
|
||||
height, width = struct.unpack("hhhh", size)[0:2]
|
||||
if not height:
|
||||
return 25, 80
|
||||
return height, width
|
||||
|
||||
else:
|
||||
|
||||
def getheightwidth(self):
|
||||
"""
|
||||
Get the height and width of the console.
|
||||
|
||||
Returns:
|
||||
- tuple: Height and width of the console.
|
||||
"""
|
||||
try:
|
||||
return int(os.environ["LINES"]), int(os.environ["COLUMNS"])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return 25, 80
|
||||
|
||||
def forgetinput(self):
|
||||
"""
|
||||
Discard any pending input on the console.
|
||||
"""
|
||||
termios.tcflush(self.input_fd, termios.TCIFLUSH)
|
||||
|
||||
def flushoutput(self):
|
||||
"""
|
||||
Flush the output buffer.
|
||||
"""
|
||||
for text, iscode in self.__buffer:
|
||||
if iscode:
|
||||
self.__tputs(text)
|
||||
else:
|
||||
os.write(self.output_fd, text.encode(self.encoding, "replace"))
|
||||
del self.__buffer[:]
|
||||
|
||||
def finish(self):
|
||||
"""
|
||||
Finish console operations and flush the output buffer.
|
||||
"""
|
||||
y = len(self.screen) - 1
|
||||
while y >= 0 and not self.screen[y]:
|
||||
y -= 1
|
||||
self.__move(0, min(y, self.height + self.__offset - 1))
|
||||
self.__write("\n\r")
|
||||
self.flushoutput()
|
||||
|
||||
def beep(self):
|
||||
"""
|
||||
Emit a beep sound.
|
||||
"""
|
||||
self.__maybe_write_code(self._bel)
|
||||
self.flushoutput()
|
||||
|
||||
if FIONREAD:
|
||||
|
||||
def getpending(self):
|
||||
"""
|
||||
Get pending events from the console event queue.
|
||||
|
||||
Returns:
|
||||
- Event: Pending event from the event queue.
|
||||
"""
|
||||
e = Event("key", "", b"")
|
||||
|
||||
while not self.event_queue.empty():
|
||||
e2 = self.event_queue.get()
|
||||
e.data += e2.data
|
||||
e.raw += e.raw
|
||||
|
||||
amount = struct.unpack("i", ioctl(self.input_fd, FIONREAD, b"\0\0\0\0"))[0]
|
||||
raw = self.__read(amount)
|
||||
data = str(raw, self.encoding, "replace")
|
||||
e.data += data
|
||||
e.raw += raw
|
||||
return e
|
||||
|
||||
else:
|
||||
|
||||
def getpending(self):
|
||||
"""
|
||||
Get pending events from the console event queue.
|
||||
|
||||
Returns:
|
||||
- Event: Pending event from the event queue.
|
||||
"""
|
||||
e = Event("key", "", b"")
|
||||
|
||||
while not self.event_queue.empty():
|
||||
e2 = self.event_queue.get()
|
||||
e.data += e2.data
|
||||
e.raw += e.raw
|
||||
|
||||
amount = 10000
|
||||
raw = self.__read(amount)
|
||||
data = str(raw, self.encoding, "replace")
|
||||
e.data += data
|
||||
e.raw += raw
|
||||
return e
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clear the console screen.
|
||||
"""
|
||||
self.__write_code(self._clear)
|
||||
self.__gone_tall = 1
|
||||
self.__move = self.__move_tall
|
||||
self.posxy = 0, 0
|
||||
self.screen = []
|
||||
|
||||
@property
|
||||
def input_hook(self):
|
||||
try:
|
||||
import posix
|
||||
except ImportError:
|
||||
return None
|
||||
if posix._is_inputhook_installed():
|
||||
return posix._inputhook
|
||||
|
||||
def __enable_bracketed_paste(self) -> None:
|
||||
os.write(self.output_fd, b"\x1b[?2004h")
|
||||
|
||||
def __disable_bracketed_paste(self) -> None:
|
||||
os.write(self.output_fd, b"\x1b[?2004l")
|
||||
|
||||
def __setup_movement(self):
|
||||
"""
|
||||
Set up the movement functions based on the terminal capabilities.
|
||||
"""
|
||||
if 0 and self._hpa: # hpa don't work in windows telnet :-(
|
||||
self.__move_x = self.__move_x_hpa
|
||||
elif self._cub and self._cuf:
|
||||
self.__move_x = self.__move_x_cub_cuf
|
||||
elif self._cub1 and self._cuf1:
|
||||
self.__move_x = self.__move_x_cub1_cuf1
|
||||
else:
|
||||
raise RuntimeError("insufficient terminal (horizontal)")
|
||||
|
||||
if self._cuu and self._cud:
|
||||
self.__move_y = self.__move_y_cuu_cud
|
||||
elif self._cuu1 and self._cud1:
|
||||
self.__move_y = self.__move_y_cuu1_cud1
|
||||
else:
|
||||
raise RuntimeError("insufficient terminal (vertical)")
|
||||
|
||||
if self._dch1:
|
||||
self.dch1 = self._dch1
|
||||
elif self._dch:
|
||||
self.dch1 = curses.tparm(self._dch, 1)
|
||||
else:
|
||||
self.dch1 = None
|
||||
|
||||
if self._ich1:
|
||||
self.ich1 = self._ich1
|
||||
elif self._ich:
|
||||
self.ich1 = curses.tparm(self._ich, 1)
|
||||
else:
|
||||
self.ich1 = None
|
||||
|
||||
self.__move = self.__move_short
|
||||
|
||||
def __write_changed_line(self, y, oldline, newline, px_coord):
|
||||
# this is frustrating; there's no reason to test (say)
|
||||
# self.dch1 inside the loop -- but alternative ways of
|
||||
# structuring this function are equally painful (I'm trying to
|
||||
# avoid writing code generators these days...)
|
||||
minlen = min(wlen(oldline), wlen(newline))
|
||||
x_pos = 0
|
||||
x_coord = 0
|
||||
|
||||
px_pos = 0
|
||||
j = 0
|
||||
for c in oldline:
|
||||
if j >= px_coord:
|
||||
break
|
||||
j += wlen(c)
|
||||
px_pos += 1
|
||||
|
||||
# reuse the oldline as much as possible, but stop as soon as we
|
||||
# encounter an ESCAPE, because it might be the start of an escape
|
||||
# sequene
|
||||
while (
|
||||
x_coord < minlen
|
||||
and oldline[x_pos] == newline[x_pos]
|
||||
and newline[x_pos] != "\x1b"
|
||||
):
|
||||
x_coord += wlen(newline[x_pos])
|
||||
x_pos += 1
|
||||
|
||||
# if we need to insert a single character right after the first detected change
|
||||
if oldline[x_pos:] == newline[x_pos + 1 :] and self.ich1:
|
||||
if (
|
||||
y == self.posxy[1]
|
||||
and x_coord > self.posxy[0]
|
||||
and oldline[px_pos:x_pos] == newline[px_pos + 1 : x_pos + 1]
|
||||
):
|
||||
x_pos = px_pos
|
||||
x_coord = px_coord
|
||||
character_width = wlen(newline[x_pos])
|
||||
self.__move(x_coord, y)
|
||||
self.__write_code(self.ich1)
|
||||
self.__write(newline[x_pos])
|
||||
self.posxy = x_coord + character_width, y
|
||||
|
||||
# if it's a single character change in the middle of the line
|
||||
elif (
|
||||
x_coord < minlen
|
||||
and oldline[x_pos + 1 :] == newline[x_pos + 1 :]
|
||||
and wlen(oldline[x_pos]) == wlen(newline[x_pos])
|
||||
):
|
||||
character_width = wlen(newline[x_pos])
|
||||
self.__move(x_coord, y)
|
||||
self.__write(newline[x_pos])
|
||||
self.posxy = x_coord + character_width, y
|
||||
|
||||
# if this is the last character to fit in the line and we edit in the middle of the line
|
||||
elif (
|
||||
self.dch1
|
||||
and self.ich1
|
||||
and wlen(newline) == self.width
|
||||
and x_coord < wlen(newline) - 2
|
||||
and newline[x_pos + 1 : -1] == oldline[x_pos:-2]
|
||||
):
|
||||
self.__hide_cursor()
|
||||
self.__move(self.width - 2, y)
|
||||
self.posxy = self.width - 2, y
|
||||
self.__write_code(self.dch1)
|
||||
|
||||
character_width = wlen(newline[x_pos])
|
||||
self.__move(x_coord, y)
|
||||
self.__write_code(self.ich1)
|
||||
self.__write(newline[x_pos])
|
||||
self.posxy = character_width + 1, y
|
||||
|
||||
else:
|
||||
self.__hide_cursor()
|
||||
self.__move(x_coord, y)
|
||||
if wlen(oldline) > wlen(newline):
|
||||
self.__write_code(self._el)
|
||||
self.__write(newline[x_pos:])
|
||||
self.posxy = wlen(newline), y
|
||||
|
||||
if "\x1b" in newline:
|
||||
# ANSI escape characters are present, so we can't assume
|
||||
# anything about the position of the cursor. Moving the cursor
|
||||
# to the left margin should work to get to a known position.
|
||||
self.move_cursor(0, y)
|
||||
|
||||
def __write(self, text):
|
||||
self.__buffer.append((text, 0))
|
||||
|
||||
def __write_code(self, fmt, *args):
|
||||
self.__buffer.append((curses.tparm(fmt, *args), 1))
|
||||
|
||||
def __maybe_write_code(self, fmt, *args):
|
||||
if fmt:
|
||||
self.__write_code(fmt, *args)
|
||||
|
||||
def __move_y_cuu1_cud1(self, y):
|
||||
assert self._cud1 is not None
|
||||
assert self._cuu1 is not None
|
||||
dy = y - self.posxy[1]
|
||||
if dy > 0:
|
||||
self.__write_code(dy * self._cud1)
|
||||
elif dy < 0:
|
||||
self.__write_code((-dy) * self._cuu1)
|
||||
|
||||
def __move_y_cuu_cud(self, y):
|
||||
dy = y - self.posxy[1]
|
||||
if dy > 0:
|
||||
self.__write_code(self._cud, dy)
|
||||
elif dy < 0:
|
||||
self.__write_code(self._cuu, -dy)
|
||||
|
||||
def __move_x_hpa(self, x: int) -> None:
|
||||
if x != self.posxy[0]:
|
||||
self.__write_code(self._hpa, x)
|
||||
|
||||
def __move_x_cub1_cuf1(self, x: int) -> None:
|
||||
assert self._cuf1 is not None
|
||||
assert self._cub1 is not None
|
||||
dx = x - self.posxy[0]
|
||||
if dx > 0:
|
||||
self.__write_code(self._cuf1 * dx)
|
||||
elif dx < 0:
|
||||
self.__write_code(self._cub1 * (-dx))
|
||||
|
||||
def __move_x_cub_cuf(self, x: int) -> None:
|
||||
dx = x - self.posxy[0]
|
||||
if dx > 0:
|
||||
self.__write_code(self._cuf, dx)
|
||||
elif dx < 0:
|
||||
self.__write_code(self._cub, -dx)
|
||||
|
||||
def __move_short(self, x, y):
|
||||
self.__move_x(x)
|
||||
self.__move_y(y)
|
||||
|
||||
def __move_tall(self, x, y):
|
||||
assert 0 <= y - self.__offset < self.height, y - self.__offset
|
||||
self.__write_code(self._cup, y - self.__offset, x)
|
||||
|
||||
def __sigwinch(self, signum, frame):
|
||||
self.height, self.width = self.getheightwidth()
|
||||
self.event_queue.insert(Event("resize", None))
|
||||
|
||||
def __hide_cursor(self):
|
||||
if self.cursor_visible:
|
||||
self.__maybe_write_code(self._civis)
|
||||
self.cursor_visible = 0
|
||||
|
||||
def __show_cursor(self):
|
||||
if not self.cursor_visible:
|
||||
self.__maybe_write_code(self._cnorm)
|
||||
self.cursor_visible = 1
|
||||
|
||||
def repaint(self):
|
||||
if not self.__gone_tall:
|
||||
self.posxy = 0, self.posxy[1]
|
||||
self.__write("\r")
|
||||
ns = len(self.screen) * ["\000" * self.width]
|
||||
self.screen = ns
|
||||
else:
|
||||
self.posxy = 0, self.__offset
|
||||
self.__move(0, self.__offset)
|
||||
ns = self.height * ["\000" * self.width]
|
||||
self.screen = ns
|
||||
|
||||
def __tputs(self, fmt, prog=delayprog):
|
||||
"""A Python implementation of the curses tputs function; the
|
||||
curses one can't really be wrapped in a sane manner.
|
||||
|
||||
I have the strong suspicion that this is complexity that
|
||||
will never do anyone any good."""
|
||||
# using .get() means that things will blow up
|
||||
# only if the bps is actually needed (which I'm
|
||||
# betting is pretty unlkely)
|
||||
bps = ratedict.get(self.__svtermstate.ospeed)
|
||||
while 1:
|
||||
m = prog.search(fmt)
|
||||
if not m:
|
||||
os.write(self.output_fd, fmt)
|
||||
break
|
||||
x, y = m.span()
|
||||
os.write(self.output_fd, fmt[:x])
|
||||
fmt = fmt[y:]
|
||||
delay = int(m.group(1))
|
||||
if b"*" in m.group(2):
|
||||
delay *= self.height
|
||||
if self._pad and bps is not None:
|
||||
nchars = (bps * delay) / 1000
|
||||
os.write(self.output_fd, self._pad * nchars)
|
||||
else:
|
||||
time.sleep(float(delay) / 1000.0)
|
||||
152
Lib/_pyrepl/unix_eventqueue.py
vendored
Normal file
152
Lib/_pyrepl/unix_eventqueue.py
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
# Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
# Armin Rigo
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from collections import deque
|
||||
|
||||
from . import keymap
|
||||
from .console import Event
|
||||
from . import curses
|
||||
from .trace import trace
|
||||
from termios import tcgetattr, VERASE
|
||||
import os
|
||||
|
||||
|
||||
# Mapping of human-readable key names to their terminal-specific codes
|
||||
TERMINAL_KEYNAMES = {
|
||||
"delete": "kdch1",
|
||||
"down": "kcud1",
|
||||
"end": "kend",
|
||||
"enter": "kent",
|
||||
"home": "khome",
|
||||
"insert": "kich1",
|
||||
"left": "kcub1",
|
||||
"page down": "knp",
|
||||
"page up": "kpp",
|
||||
"right": "kcuf1",
|
||||
"up": "kcuu1",
|
||||
}
|
||||
|
||||
|
||||
# Function keys F1-F20 mapping
|
||||
TERMINAL_KEYNAMES.update(("f%d" % i, "kf%d" % i) for i in range(1, 21))
|
||||
|
||||
# Known CTRL-arrow keycodes
|
||||
CTRL_ARROW_KEYCODES= {
|
||||
# for xterm, gnome-terminal, xfce terminal, etc.
|
||||
b'\033[1;5D': 'ctrl left',
|
||||
b'\033[1;5C': 'ctrl right',
|
||||
# for rxvt
|
||||
b'\033Od': 'ctrl left',
|
||||
b'\033Oc': 'ctrl right',
|
||||
}
|
||||
|
||||
def get_terminal_keycodes() -> dict[bytes, str]:
|
||||
"""
|
||||
Generates a dictionary mapping terminal keycodes to human-readable names.
|
||||
"""
|
||||
keycodes = {}
|
||||
for key, terminal_code in TERMINAL_KEYNAMES.items():
|
||||
keycode = curses.tigetstr(terminal_code)
|
||||
trace('key {key} tiname {terminal_code} keycode {keycode!r}', **locals())
|
||||
if keycode:
|
||||
keycodes[keycode] = key
|
||||
keycodes.update(CTRL_ARROW_KEYCODES)
|
||||
return keycodes
|
||||
|
||||
class EventQueue:
|
||||
def __init__(self, fd: int, encoding: str) -> None:
|
||||
self.keycodes = get_terminal_keycodes()
|
||||
if os.isatty(fd):
|
||||
backspace = tcgetattr(fd)[6][VERASE]
|
||||
self.keycodes[backspace] = "backspace"
|
||||
self.compiled_keymap = keymap.compile_keymap(self.keycodes)
|
||||
self.keymap = self.compiled_keymap
|
||||
trace("keymap {k!r}", k=self.keymap)
|
||||
self.encoding = encoding
|
||||
self.events: deque[Event] = deque()
|
||||
self.buf = bytearray()
|
||||
|
||||
def get(self) -> Event | None:
|
||||
"""
|
||||
Retrieves the next event from the queue.
|
||||
"""
|
||||
if self.events:
|
||||
return self.events.popleft()
|
||||
else:
|
||||
return None
|
||||
|
||||
def empty(self) -> bool:
|
||||
"""
|
||||
Checks if the queue is empty.
|
||||
"""
|
||||
return not self.events
|
||||
|
||||
def flush_buf(self) -> bytearray:
|
||||
"""
|
||||
Flushes the buffer and returns its contents.
|
||||
"""
|
||||
old = self.buf
|
||||
self.buf = bytearray()
|
||||
return old
|
||||
|
||||
def insert(self, event: Event) -> None:
|
||||
"""
|
||||
Inserts an event into the queue.
|
||||
"""
|
||||
trace('added event {event}', event=event)
|
||||
self.events.append(event)
|
||||
|
||||
def push(self, char: int | bytes) -> None:
|
||||
"""
|
||||
Processes a character by updating the buffer and handling special key mappings.
|
||||
"""
|
||||
ord_char = char if isinstance(char, int) else ord(char)
|
||||
char = bytes(bytearray((ord_char,)))
|
||||
self.buf.append(ord_char)
|
||||
if char in self.keymap:
|
||||
if self.keymap is self.compiled_keymap:
|
||||
#sanity check, buffer is empty when a special key comes
|
||||
assert len(self.buf) == 1
|
||||
k = self.keymap[char]
|
||||
trace('found map {k!r}', k=k)
|
||||
if isinstance(k, dict):
|
||||
self.keymap = k
|
||||
else:
|
||||
self.insert(Event('key', k, self.flush_buf()))
|
||||
self.keymap = self.compiled_keymap
|
||||
|
||||
elif self.buf and self.buf[0] == 27: # escape
|
||||
# escape sequence not recognized by our keymap: propagate it
|
||||
# outside so that i can be recognized as an M-... key (see also
|
||||
# the docstring in keymap.py
|
||||
trace('unrecognized escape sequence, propagating...')
|
||||
self.keymap = self.compiled_keymap
|
||||
self.insert(Event('key', '\033', bytearray(b'\033')))
|
||||
for _c in self.flush_buf()[1:]:
|
||||
self.push(_c)
|
||||
|
||||
else:
|
||||
try:
|
||||
decoded = bytes(self.buf).decode(self.encoding)
|
||||
except UnicodeError:
|
||||
return
|
||||
else:
|
||||
self.insert(Event('key', decoded, self.flush_buf()))
|
||||
self.keymap = self.compiled_keymap
|
||||
25
Lib/_pyrepl/utils.py
vendored
Normal file
25
Lib/_pyrepl/utils.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import re
|
||||
import unicodedata
|
||||
import functools
|
||||
|
||||
ANSI_ESCAPE_SEQUENCE = re.compile(r"\x1b\[[ -@]*[A-~]")
|
||||
|
||||
|
||||
@functools.cache
|
||||
def str_width(c: str) -> int:
|
||||
if ord(c) < 128:
|
||||
return 1
|
||||
w = unicodedata.east_asian_width(c)
|
||||
if w in ('N', 'Na', 'H', 'A'):
|
||||
return 1
|
||||
return 2
|
||||
|
||||
|
||||
def wlen(s: str) -> int:
|
||||
if len(s) == 1 and s != '\x1a':
|
||||
return str_width(s)
|
||||
length = sum(str_width(i) for i in s)
|
||||
# remove lengths of any escape sequences
|
||||
sequence = ANSI_ESCAPE_SEQUENCE.findall(s)
|
||||
ctrl_z_cnt = s.count('\x1a')
|
||||
return length - sum(len(i) for i in sequence) + ctrl_z_cnt
|
||||
618
Lib/_pyrepl/windows_console.py
vendored
Normal file
618
Lib/_pyrepl/windows_console.py
vendored
Normal file
@@ -0,0 +1,618 @@
|
||||
# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com>
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose is hereby granted without fee,
|
||||
# provided that the above copyright notice appear in all copies and
|
||||
# that both that copyright notice and this permission notice appear in
|
||||
# supporting documentation.
|
||||
#
|
||||
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import msvcrt
|
||||
|
||||
from collections import deque
|
||||
import ctypes
|
||||
from ctypes.wintypes import (
|
||||
_COORD,
|
||||
WORD,
|
||||
SMALL_RECT,
|
||||
BOOL,
|
||||
HANDLE,
|
||||
CHAR,
|
||||
DWORD,
|
||||
WCHAR,
|
||||
SHORT,
|
||||
)
|
||||
from ctypes import Structure, POINTER, Union
|
||||
from .console import Event, Console
|
||||
from .trace import trace
|
||||
from .utils import wlen
|
||||
|
||||
try:
|
||||
from ctypes import GetLastError, WinDLL, windll, WinError # type: ignore[attr-defined]
|
||||
except:
|
||||
# Keep MyPy happy off Windows
|
||||
from ctypes import CDLL as WinDLL, cdll as windll
|
||||
|
||||
def GetLastError() -> int:
|
||||
return 42
|
||||
|
||||
class WinError(OSError): # type: ignore[no-redef]
|
||||
def __init__(self, err: int | None, descr: str | None = None) -> None:
|
||||
self.err = err
|
||||
self.descr = descr
|
||||
|
||||
|
||||
TYPE_CHECKING = False
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import IO
|
||||
|
||||
# Virtual-Key Codes: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
VK_MAP: dict[int, str] = {
|
||||
0x23: "end", # VK_END
|
||||
0x24: "home", # VK_HOME
|
||||
0x25: "left", # VK_LEFT
|
||||
0x26: "up", # VK_UP
|
||||
0x27: "right", # VK_RIGHT
|
||||
0x28: "down", # VK_DOWN
|
||||
0x2E: "delete", # VK_DELETE
|
||||
0x70: "f1", # VK_F1
|
||||
0x71: "f2", # VK_F2
|
||||
0x72: "f3", # VK_F3
|
||||
0x73: "f4", # VK_F4
|
||||
0x74: "f5", # VK_F5
|
||||
0x75: "f6", # VK_F6
|
||||
0x76: "f7", # VK_F7
|
||||
0x77: "f8", # VK_F8
|
||||
0x78: "f9", # VK_F9
|
||||
0x79: "f10", # VK_F10
|
||||
0x7A: "f11", # VK_F11
|
||||
0x7B: "f12", # VK_F12
|
||||
0x7C: "f13", # VK_F13
|
||||
0x7D: "f14", # VK_F14
|
||||
0x7E: "f15", # VK_F15
|
||||
0x7F: "f16", # VK_F16
|
||||
0x80: "f17", # VK_F17
|
||||
0x81: "f18", # VK_F18
|
||||
0x82: "f19", # VK_F19
|
||||
0x83: "f20", # VK_F20
|
||||
}
|
||||
|
||||
# Console escape codes: https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
|
||||
ERASE_IN_LINE = "\x1b[K"
|
||||
MOVE_LEFT = "\x1b[{}D"
|
||||
MOVE_RIGHT = "\x1b[{}C"
|
||||
MOVE_UP = "\x1b[{}A"
|
||||
MOVE_DOWN = "\x1b[{}B"
|
||||
CLEAR = "\x1b[H\x1b[J"
|
||||
|
||||
|
||||
class _error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class WindowsConsole(Console):
|
||||
def __init__(
|
||||
self,
|
||||
f_in: IO[bytes] | int = 0,
|
||||
f_out: IO[bytes] | int = 1,
|
||||
term: str = "",
|
||||
encoding: str = "",
|
||||
):
|
||||
super().__init__(f_in, f_out, term, encoding)
|
||||
|
||||
SetConsoleMode(
|
||||
OutHandle,
|
||||
ENABLE_WRAP_AT_EOL_OUTPUT
|
||||
| ENABLE_PROCESSED_OUTPUT
|
||||
| ENABLE_VIRTUAL_TERMINAL_PROCESSING,
|
||||
)
|
||||
self.screen: list[str] = []
|
||||
self.width = 80
|
||||
self.height = 25
|
||||
self.__offset = 0
|
||||
self.event_queue: deque[Event] = deque()
|
||||
try:
|
||||
self.out = io._WindowsConsoleIO(self.output_fd, "w") # type: ignore[attr-defined]
|
||||
except ValueError:
|
||||
# Console I/O is redirected, fallback...
|
||||
self.out = None
|
||||
|
||||
def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:
|
||||
"""
|
||||
Refresh the console screen.
|
||||
|
||||
Parameters:
|
||||
- screen (list): List of strings representing the screen contents.
|
||||
- c_xy (tuple): Cursor position (x, y) on the screen.
|
||||
"""
|
||||
cx, cy = c_xy
|
||||
|
||||
while len(self.screen) < min(len(screen), self.height):
|
||||
self._hide_cursor()
|
||||
self._move_relative(0, len(self.screen) - 1)
|
||||
self.__write("\n")
|
||||
self.posxy = 0, len(self.screen)
|
||||
self.screen.append("")
|
||||
|
||||
px, py = self.posxy
|
||||
old_offset = offset = self.__offset
|
||||
height = self.height
|
||||
|
||||
# we make sure the cursor is on the screen, and that we're
|
||||
# using all of the screen if we can
|
||||
if cy < offset:
|
||||
offset = cy
|
||||
elif cy >= offset + height:
|
||||
offset = cy - height + 1
|
||||
scroll_lines = offset - old_offset
|
||||
|
||||
# Scrolling the buffer as the current input is greater than the visible
|
||||
# portion of the window. We need to scroll the visible portion and the
|
||||
# entire history
|
||||
self._scroll(scroll_lines, self._getscrollbacksize())
|
||||
self.posxy = self.posxy[0], self.posxy[1] + scroll_lines
|
||||
self.__offset += scroll_lines
|
||||
|
||||
for i in range(scroll_lines):
|
||||
self.screen.append("")
|
||||
elif offset > 0 and len(screen) < offset + height:
|
||||
offset = max(len(screen) - height, 0)
|
||||
screen.append("")
|
||||
|
||||
oldscr = self.screen[old_offset : old_offset + height]
|
||||
newscr = screen[offset : offset + height]
|
||||
|
||||
self.__offset = offset
|
||||
|
||||
self._hide_cursor()
|
||||
for (
|
||||
y,
|
||||
oldline,
|
||||
newline,
|
||||
) in zip(range(offset, offset + height), oldscr, newscr):
|
||||
if oldline != newline:
|
||||
self.__write_changed_line(y, oldline, newline, px)
|
||||
|
||||
y = len(newscr)
|
||||
while y < len(oldscr):
|
||||
self._move_relative(0, y)
|
||||
self.posxy = 0, y
|
||||
self._erase_to_end()
|
||||
y += 1
|
||||
|
||||
self._show_cursor()
|
||||
|
||||
self.screen = screen
|
||||
self.move_cursor(cx, cy)
|
||||
|
||||
@property
|
||||
def input_hook(self):
|
||||
try:
|
||||
import nt
|
||||
except ImportError:
|
||||
return None
|
||||
if nt._is_inputhook_installed():
|
||||
return nt._inputhook
|
||||
|
||||
def __write_changed_line(
|
||||
self, y: int, oldline: str, newline: str, px_coord: int
|
||||
) -> None:
|
||||
# this is frustrating; there's no reason to test (say)
|
||||
# self.dch1 inside the loop -- but alternative ways of
|
||||
# structuring this function are equally painful (I'm trying to
|
||||
# avoid writing code generators these days...)
|
||||
minlen = min(wlen(oldline), wlen(newline))
|
||||
x_pos = 0
|
||||
x_coord = 0
|
||||
|
||||
px_pos = 0
|
||||
j = 0
|
||||
for c in oldline:
|
||||
if j >= px_coord:
|
||||
break
|
||||
j += wlen(c)
|
||||
px_pos += 1
|
||||
|
||||
# reuse the oldline as much as possible, but stop as soon as we
|
||||
# encounter an ESCAPE, because it might be the start of an escape
|
||||
# sequene
|
||||
while (
|
||||
x_coord < minlen
|
||||
and oldline[x_pos] == newline[x_pos]
|
||||
and newline[x_pos] != "\x1b"
|
||||
):
|
||||
x_coord += wlen(newline[x_pos])
|
||||
x_pos += 1
|
||||
|
||||
self._hide_cursor()
|
||||
self._move_relative(x_coord, y)
|
||||
if wlen(oldline) > wlen(newline):
|
||||
self._erase_to_end()
|
||||
|
||||
self.__write(newline[x_pos:])
|
||||
if wlen(newline) == self.width:
|
||||
# If we wrapped we want to start at the next line
|
||||
self._move_relative(0, y + 1)
|
||||
self.posxy = 0, y + 1
|
||||
else:
|
||||
self.posxy = wlen(newline), y
|
||||
|
||||
if "\x1b" in newline or y != self.posxy[1] or '\x1a' in newline:
|
||||
# ANSI escape characters are present, so we can't assume
|
||||
# anything about the position of the cursor. Moving the cursor
|
||||
# to the left margin should work to get to a known position.
|
||||
self.move_cursor(0, y)
|
||||
|
||||
def _scroll(
|
||||
self, top: int, bottom: int, left: int | None = None, right: int | None = None
|
||||
) -> None:
|
||||
scroll_rect = SMALL_RECT()
|
||||
scroll_rect.Top = SHORT(top)
|
||||
scroll_rect.Bottom = SHORT(bottom)
|
||||
scroll_rect.Left = SHORT(0 if left is None else left)
|
||||
scroll_rect.Right = SHORT(
|
||||
self.getheightwidth()[1] - 1 if right is None else right
|
||||
)
|
||||
destination_origin = _COORD()
|
||||
fill_info = CHAR_INFO()
|
||||
fill_info.UnicodeChar = " "
|
||||
|
||||
if not ScrollConsoleScreenBuffer(
|
||||
OutHandle, scroll_rect, None, destination_origin, fill_info
|
||||
):
|
||||
raise WinError(GetLastError())
|
||||
|
||||
def _hide_cursor(self):
|
||||
self.__write("\x1b[?25l")
|
||||
|
||||
def _show_cursor(self):
|
||||
self.__write("\x1b[?25h")
|
||||
|
||||
def _enable_blinking(self):
|
||||
self.__write("\x1b[?12h")
|
||||
|
||||
def _disable_blinking(self):
|
||||
self.__write("\x1b[?12l")
|
||||
|
||||
def __write(self, text: str) -> None:
|
||||
if "\x1a" in text:
|
||||
text = ''.join(["^Z" if x == '\x1a' else x for x in text])
|
||||
|
||||
if self.out is not None:
|
||||
self.out.write(text.encode(self.encoding, "replace"))
|
||||
self.out.flush()
|
||||
else:
|
||||
os.write(self.output_fd, text.encode(self.encoding, "replace"))
|
||||
|
||||
@property
|
||||
def screen_xy(self) -> tuple[int, int]:
|
||||
info = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
if not GetConsoleScreenBufferInfo(OutHandle, info):
|
||||
raise WinError(GetLastError())
|
||||
return info.dwCursorPosition.X, info.dwCursorPosition.Y
|
||||
|
||||
def _erase_to_end(self) -> None:
|
||||
self.__write(ERASE_IN_LINE)
|
||||
|
||||
def prepare(self) -> None:
|
||||
trace("prepare")
|
||||
self.screen = []
|
||||
self.height, self.width = self.getheightwidth()
|
||||
|
||||
self.posxy = 0, 0
|
||||
self.__gone_tall = 0
|
||||
self.__offset = 0
|
||||
|
||||
def restore(self) -> None:
|
||||
pass
|
||||
|
||||
def _move_relative(self, x: int, y: int) -> None:
|
||||
"""Moves relative to the current posxy"""
|
||||
dx = x - self.posxy[0]
|
||||
dy = y - self.posxy[1]
|
||||
if dx < 0:
|
||||
self.__write(MOVE_LEFT.format(-dx))
|
||||
elif dx > 0:
|
||||
self.__write(MOVE_RIGHT.format(dx))
|
||||
|
||||
if dy < 0:
|
||||
self.__write(MOVE_UP.format(-dy))
|
||||
elif dy > 0:
|
||||
self.__write(MOVE_DOWN.format(dy))
|
||||
|
||||
def move_cursor(self, x: int, y: int) -> None:
|
||||
if x < 0 or y < 0:
|
||||
raise ValueError(f"Bad cursor position {x}, {y}")
|
||||
|
||||
if y < self.__offset or y >= self.__offset + self.height:
|
||||
self.event_queue.insert(0, Event("scroll", ""))
|
||||
else:
|
||||
self._move_relative(x, y)
|
||||
self.posxy = x, y
|
||||
|
||||
def set_cursor_vis(self, visible: bool) -> None:
|
||||
if visible:
|
||||
self._show_cursor()
|
||||
else:
|
||||
self._hide_cursor()
|
||||
|
||||
def getheightwidth(self) -> tuple[int, int]:
|
||||
"""Return (height, width) where height and width are the height
|
||||
and width of the terminal window in characters."""
|
||||
info = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
if not GetConsoleScreenBufferInfo(OutHandle, info):
|
||||
raise WinError(GetLastError())
|
||||
return (
|
||||
info.srWindow.Bottom - info.srWindow.Top + 1,
|
||||
info.srWindow.Right - info.srWindow.Left + 1,
|
||||
)
|
||||
|
||||
def _getscrollbacksize(self) -> int:
|
||||
info = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
if not GetConsoleScreenBufferInfo(OutHandle, info):
|
||||
raise WinError(GetLastError())
|
||||
|
||||
return info.srWindow.Bottom # type: ignore[no-any-return]
|
||||
|
||||
def _read_input(self, block: bool = True) -> INPUT_RECORD | None:
|
||||
if not block:
|
||||
events = DWORD()
|
||||
if not GetNumberOfConsoleInputEvents(InHandle, events):
|
||||
raise WinError(GetLastError())
|
||||
if not events.value:
|
||||
return None
|
||||
|
||||
rec = INPUT_RECORD()
|
||||
read = DWORD()
|
||||
if not ReadConsoleInput(InHandle, rec, 1, read):
|
||||
raise WinError(GetLastError())
|
||||
|
||||
return rec
|
||||
|
||||
def get_event(self, block: bool = True) -> Event | None:
|
||||
"""Return an Event instance. Returns None if |block| is false
|
||||
and there is no event pending, otherwise waits for the
|
||||
completion of an event."""
|
||||
if self.event_queue:
|
||||
return self.event_queue.pop()
|
||||
|
||||
while True:
|
||||
rec = self._read_input(block)
|
||||
if rec is None:
|
||||
return None
|
||||
|
||||
if rec.EventType == WINDOW_BUFFER_SIZE_EVENT:
|
||||
return Event("resize", "")
|
||||
|
||||
if rec.EventType != KEY_EVENT or not rec.Event.KeyEvent.bKeyDown:
|
||||
# Only process keys and keydown events
|
||||
if block:
|
||||
continue
|
||||
return None
|
||||
|
||||
key = rec.Event.KeyEvent.uChar.UnicodeChar
|
||||
|
||||
if rec.Event.KeyEvent.uChar.UnicodeChar == "\r":
|
||||
# Make enter make unix-like
|
||||
return Event(evt="key", data="\n", raw=b"\n")
|
||||
elif rec.Event.KeyEvent.wVirtualKeyCode == 8:
|
||||
# Turn backspace directly into the command
|
||||
return Event(
|
||||
evt="key",
|
||||
data="backspace",
|
||||
raw=rec.Event.KeyEvent.uChar.UnicodeChar,
|
||||
)
|
||||
elif rec.Event.KeyEvent.uChar.UnicodeChar == "\x00":
|
||||
# Handle special keys like arrow keys and translate them into the appropriate command
|
||||
code = VK_MAP.get(rec.Event.KeyEvent.wVirtualKeyCode)
|
||||
if code:
|
||||
return Event(
|
||||
evt="key", data=code, raw=rec.Event.KeyEvent.uChar.UnicodeChar
|
||||
)
|
||||
if block:
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
return Event(evt="key", data=key, raw=rec.Event.KeyEvent.uChar.UnicodeChar)
|
||||
|
||||
def push_char(self, char: int | bytes) -> None:
|
||||
"""
|
||||
Push a character to the console event queue.
|
||||
"""
|
||||
raise NotImplementedError("push_char not supported on Windows")
|
||||
|
||||
def beep(self) -> None:
|
||||
self.__write("\x07")
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Wipe the screen"""
|
||||
self.__write(CLEAR)
|
||||
self.posxy = 0, 0
|
||||
self.screen = [""]
|
||||
|
||||
def finish(self) -> None:
|
||||
"""Move the cursor to the end of the display and otherwise get
|
||||
ready for end. XXX could be merged with restore? Hmm."""
|
||||
y = len(self.screen) - 1
|
||||
while y >= 0 and not self.screen[y]:
|
||||
y -= 1
|
||||
self._move_relative(0, min(y, self.height + self.__offset - 1))
|
||||
self.__write("\r\n")
|
||||
|
||||
def flushoutput(self) -> None:
|
||||
"""Flush all output to the screen (assuming there's some
|
||||
buffering going on somewhere).
|
||||
|
||||
All output on Windows is unbuffered so this is a nop"""
|
||||
pass
|
||||
|
||||
def forgetinput(self) -> None:
|
||||
"""Forget all pending, but not yet processed input."""
|
||||
if not FlushConsoleInputBuffer(InHandle):
|
||||
raise WinError(GetLastError())
|
||||
|
||||
def getpending(self) -> Event:
|
||||
"""Return the characters that have been typed but not yet
|
||||
processed."""
|
||||
return Event("key", "", b"")
|
||||
|
||||
def wait(self, timeout: float | None) -> bool:
|
||||
"""Wait for an event."""
|
||||
# Poor man's Windows select loop
|
||||
start_time = time.time()
|
||||
while True:
|
||||
if msvcrt.kbhit(): # type: ignore[attr-defined]
|
||||
return True
|
||||
if timeout and time.time() - start_time > timeout / 1000:
|
||||
return False
|
||||
time.sleep(0.01)
|
||||
|
||||
def repaint(self) -> None:
|
||||
raise NotImplementedError("No repaint support")
|
||||
|
||||
|
||||
# Windows interop
|
||||
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
|
||||
_fields_ = [
|
||||
("dwSize", _COORD),
|
||||
("dwCursorPosition", _COORD),
|
||||
("wAttributes", WORD),
|
||||
("srWindow", SMALL_RECT),
|
||||
("dwMaximumWindowSize", _COORD),
|
||||
]
|
||||
|
||||
|
||||
class CONSOLE_CURSOR_INFO(Structure):
|
||||
_fields_ = [
|
||||
("dwSize", DWORD),
|
||||
("bVisible", BOOL),
|
||||
]
|
||||
|
||||
|
||||
class CHAR_INFO(Structure):
|
||||
_fields_ = [
|
||||
("UnicodeChar", WCHAR),
|
||||
("Attributes", WORD),
|
||||
]
|
||||
|
||||
|
||||
class Char(Union):
|
||||
_fields_ = [
|
||||
("UnicodeChar", WCHAR),
|
||||
("Char", CHAR),
|
||||
]
|
||||
|
||||
|
||||
class KeyEvent(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("bKeyDown", BOOL),
|
||||
("wRepeatCount", WORD),
|
||||
("wVirtualKeyCode", WORD),
|
||||
("wVirtualScanCode", WORD),
|
||||
("uChar", Char),
|
||||
("dwControlKeyState", DWORD),
|
||||
]
|
||||
|
||||
|
||||
class WindowsBufferSizeEvent(ctypes.Structure):
|
||||
_fields_ = [("dwSize", _COORD)]
|
||||
|
||||
|
||||
class ConsoleEvent(ctypes.Union):
|
||||
_fields_ = [
|
||||
("KeyEvent", KeyEvent),
|
||||
("WindowsBufferSizeEvent", WindowsBufferSizeEvent),
|
||||
]
|
||||
|
||||
|
||||
class INPUT_RECORD(Structure):
|
||||
_fields_ = [("EventType", WORD), ("Event", ConsoleEvent)]
|
||||
|
||||
|
||||
KEY_EVENT = 0x01
|
||||
FOCUS_EVENT = 0x10
|
||||
MENU_EVENT = 0x08
|
||||
MOUSE_EVENT = 0x02
|
||||
WINDOW_BUFFER_SIZE_EVENT = 0x04
|
||||
|
||||
ENABLE_PROCESSED_OUTPUT = 0x01
|
||||
ENABLE_WRAP_AT_EOL_OUTPUT = 0x02
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x04
|
||||
|
||||
STD_INPUT_HANDLE = -10
|
||||
STD_OUTPUT_HANDLE = -11
|
||||
|
||||
if sys.platform == "win32":
|
||||
_KERNEL32 = WinDLL("kernel32", use_last_error=True)
|
||||
|
||||
GetStdHandle = windll.kernel32.GetStdHandle
|
||||
GetStdHandle.argtypes = [DWORD]
|
||||
GetStdHandle.restype = HANDLE
|
||||
|
||||
GetConsoleScreenBufferInfo = _KERNEL32.GetConsoleScreenBufferInfo
|
||||
GetConsoleScreenBufferInfo.argtypes = [
|
||||
HANDLE,
|
||||
ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO),
|
||||
]
|
||||
GetConsoleScreenBufferInfo.restype = BOOL
|
||||
|
||||
ScrollConsoleScreenBuffer = _KERNEL32.ScrollConsoleScreenBufferW
|
||||
ScrollConsoleScreenBuffer.argtypes = [
|
||||
HANDLE,
|
||||
POINTER(SMALL_RECT),
|
||||
POINTER(SMALL_RECT),
|
||||
_COORD,
|
||||
POINTER(CHAR_INFO),
|
||||
]
|
||||
ScrollConsoleScreenBuffer.restype = BOOL
|
||||
|
||||
SetConsoleMode = _KERNEL32.SetConsoleMode
|
||||
SetConsoleMode.argtypes = [HANDLE, DWORD]
|
||||
SetConsoleMode.restype = BOOL
|
||||
|
||||
ReadConsoleInput = _KERNEL32.ReadConsoleInputW
|
||||
ReadConsoleInput.argtypes = [HANDLE, POINTER(INPUT_RECORD), DWORD, POINTER(DWORD)]
|
||||
ReadConsoleInput.restype = BOOL
|
||||
|
||||
GetNumberOfConsoleInputEvents = _KERNEL32.GetNumberOfConsoleInputEvents
|
||||
GetNumberOfConsoleInputEvents.argtypes = [HANDLE, POINTER(DWORD)]
|
||||
GetNumberOfConsoleInputEvents.restype = BOOL
|
||||
|
||||
FlushConsoleInputBuffer = _KERNEL32.FlushConsoleInputBuffer
|
||||
FlushConsoleInputBuffer.argtypes = [HANDLE]
|
||||
FlushConsoleInputBuffer.restype = BOOL
|
||||
|
||||
OutHandle = GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
InHandle = GetStdHandle(STD_INPUT_HANDLE)
|
||||
else:
|
||||
|
||||
def _win_only(*args, **kwargs):
|
||||
raise NotImplementedError("Windows only")
|
||||
|
||||
GetStdHandle = _win_only
|
||||
GetConsoleScreenBufferInfo = _win_only
|
||||
ScrollConsoleScreenBuffer = _win_only
|
||||
SetConsoleMode = _win_only
|
||||
ReadConsoleInput = _win_only
|
||||
GetNumberOfConsoleInputEvents = _win_only
|
||||
FlushConsoleInputBuffer = _win_only
|
||||
OutHandle = 0
|
||||
InHandle = 0
|
||||
3
Lib/_sitebuiltins.py
vendored
3
Lib/_sitebuiltins.py
vendored
@@ -10,7 +10,6 @@ The objects used by the site module to add custom builtins.
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
class Quitter(object):
|
||||
def __init__(self, name, eof):
|
||||
self.name = name
|
||||
@@ -48,7 +47,7 @@ class _Printer(object):
|
||||
data = None
|
||||
for filename in self.__filenames:
|
||||
try:
|
||||
with open(filename, "r") as fp:
|
||||
with open(filename, encoding='utf-8') as fp:
|
||||
data = fp.read()
|
||||
break
|
||||
except OSError:
|
||||
|
||||
822
Lib/_strptime.py
vendored
Normal file
822
Lib/_strptime.py
vendored
Normal file
@@ -0,0 +1,822 @@
|
||||
"""Strptime-related classes and functions.
|
||||
|
||||
CLASSES:
|
||||
LocaleTime -- Discovers and stores locale-specific time information
|
||||
TimeRE -- Creates regexes for pattern matching a string of text containing
|
||||
time information
|
||||
|
||||
FUNCTIONS:
|
||||
_getlang -- Figure out what language is being used for the locale
|
||||
strptime -- Calculates the time struct represented by the passed-in string
|
||||
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
import locale
|
||||
import calendar
|
||||
import re
|
||||
from re import compile as re_compile
|
||||
from re import sub as re_sub
|
||||
from re import IGNORECASE
|
||||
from re import escape as re_escape
|
||||
from datetime import (date as datetime_date,
|
||||
timedelta as datetime_timedelta,
|
||||
timezone as datetime_timezone)
|
||||
from _thread import allocate_lock as _thread_allocate_lock
|
||||
|
||||
__all__ = []
|
||||
|
||||
def _getlang():
|
||||
# Figure out what the current language is set to.
|
||||
return locale.getlocale(locale.LC_TIME)
|
||||
|
||||
def _findall(haystack, needle):
|
||||
# Find all positions of needle in haystack.
|
||||
if not needle:
|
||||
return
|
||||
i = 0
|
||||
while True:
|
||||
i = haystack.find(needle, i)
|
||||
if i < 0:
|
||||
break
|
||||
yield i
|
||||
i += len(needle)
|
||||
|
||||
def _fixmonths(months):
|
||||
yield from months
|
||||
# The lower case of 'İ' ('\u0130') is 'i\u0307'.
|
||||
# The re module only supports 1-to-1 character matching in
|
||||
# case-insensitive mode.
|
||||
for s in months:
|
||||
if 'i\u0307' in s:
|
||||
yield s.replace('i\u0307', '\u0130')
|
||||
|
||||
lzh_TW_alt_digits = (
|
||||
# 〇:一:二:三:四:五:六:七:八:九
|
||||
'\u3007', '\u4e00', '\u4e8c', '\u4e09', '\u56db',
|
||||
'\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d',
|
||||
# 十:十一:十二:十三:十四:十五:十六:十七:十八:十九
|
||||
'\u5341', '\u5341\u4e00', '\u5341\u4e8c', '\u5341\u4e09', '\u5341\u56db',
|
||||
'\u5341\u4e94', '\u5341\u516d', '\u5341\u4e03', '\u5341\u516b', '\u5341\u4e5d',
|
||||
# 廿:廿一:廿二:廿三:廿四:廿五:廿六:廿七:廿八:廿九
|
||||
'\u5eff', '\u5eff\u4e00', '\u5eff\u4e8c', '\u5eff\u4e09', '\u5eff\u56db',
|
||||
'\u5eff\u4e94', '\u5eff\u516d', '\u5eff\u4e03', '\u5eff\u516b', '\u5eff\u4e5d',
|
||||
# 卅:卅一
|
||||
'\u5345', '\u5345\u4e00')
|
||||
|
||||
|
||||
class LocaleTime(object):
|
||||
"""Stores and handles locale-specific information related to time.
|
||||
|
||||
ATTRIBUTES:
|
||||
f_weekday -- full weekday names (7-item list)
|
||||
a_weekday -- abbreviated weekday names (7-item list)
|
||||
f_month -- full month names (13-item list; dummy value in [0], which
|
||||
is added by code)
|
||||
a_month -- abbreviated month names (13-item list, dummy value in
|
||||
[0], which is added by code)
|
||||
am_pm -- AM/PM representation (2-item list)
|
||||
LC_date_time -- format string for date/time representation (string)
|
||||
LC_date -- format string for date representation (string)
|
||||
LC_time -- format string for time representation (string)
|
||||
timezone -- daylight- and non-daylight-savings timezone representation
|
||||
(2-item list of sets)
|
||||
lang -- Language used by instance (2-item tuple)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Set all attributes.
|
||||
|
||||
Order of methods called matters for dependency reasons.
|
||||
|
||||
The locale language is set at the offset and then checked again before
|
||||
exiting. This is to make sure that the attributes were not set with a
|
||||
mix of information from more than one locale. This would most likely
|
||||
happen when using threads where one thread calls a locale-dependent
|
||||
function while another thread changes the locale while the function in
|
||||
the other thread is still running. Proper coding would call for
|
||||
locks to prevent changing the locale while locale-dependent code is
|
||||
running. The check here is done in case someone does not think about
|
||||
doing this.
|
||||
|
||||
Only other possible issue is if someone changed the timezone and did
|
||||
not call tz.tzset . That is an issue for the programmer, though,
|
||||
since changing the timezone is worthless without that call.
|
||||
|
||||
"""
|
||||
self.lang = _getlang()
|
||||
self.__calc_weekday()
|
||||
self.__calc_month()
|
||||
self.__calc_am_pm()
|
||||
self.__calc_alt_digits()
|
||||
self.__calc_timezone()
|
||||
self.__calc_date_time()
|
||||
if _getlang() != self.lang:
|
||||
raise ValueError("locale changed during initialization")
|
||||
if time.tzname != self.tzname or time.daylight != self.daylight:
|
||||
raise ValueError("timezone changed during initialization")
|
||||
|
||||
def __calc_weekday(self):
|
||||
# Set self.a_weekday and self.f_weekday using the calendar
|
||||
# module.
|
||||
a_weekday = [calendar.day_abbr[i].lower() for i in range(7)]
|
||||
f_weekday = [calendar.day_name[i].lower() for i in range(7)]
|
||||
self.a_weekday = a_weekday
|
||||
self.f_weekday = f_weekday
|
||||
|
||||
def __calc_month(self):
|
||||
# Set self.f_month and self.a_month using the calendar module.
|
||||
a_month = [calendar.month_abbr[i].lower() for i in range(13)]
|
||||
f_month = [calendar.month_name[i].lower() for i in range(13)]
|
||||
self.a_month = a_month
|
||||
self.f_month = f_month
|
||||
|
||||
def __calc_am_pm(self):
|
||||
# Set self.am_pm by using time.strftime().
|
||||
|
||||
# The magic date (1999,3,17,hour,44,55,2,76,0) is not really that
|
||||
# magical; just happened to have used it everywhere else where a
|
||||
# static date was needed.
|
||||
am_pm = []
|
||||
for hour in (1, 22):
|
||||
time_tuple = time.struct_time((1999,3,17,hour,44,55,2,76,0))
|
||||
# br_FR has AM/PM info (' ',' ').
|
||||
am_pm.append(time.strftime("%p", time_tuple).lower().strip())
|
||||
self.am_pm = am_pm
|
||||
|
||||
def __calc_alt_digits(self):
|
||||
# Set self.LC_alt_digits by using time.strftime().
|
||||
|
||||
# The magic data should contain all decimal digits.
|
||||
time_tuple = time.struct_time((1998, 1, 27, 10, 43, 56, 1, 27, 0))
|
||||
s = time.strftime("%x%X", time_tuple)
|
||||
if s.isascii():
|
||||
# Fast path -- all digits are ASCII.
|
||||
self.LC_alt_digits = ()
|
||||
return
|
||||
|
||||
digits = ''.join(sorted(set(re.findall(r'\d', s))))
|
||||
if len(digits) == 10 and ord(digits[-1]) == ord(digits[0]) + 9:
|
||||
# All 10 decimal digits from the same set.
|
||||
if digits.isascii():
|
||||
# All digits are ASCII.
|
||||
self.LC_alt_digits = ()
|
||||
return
|
||||
|
||||
self.LC_alt_digits = [a + b for a in digits for b in digits]
|
||||
# Test whether the numbers contain leading zero.
|
||||
time_tuple2 = time.struct_time((2000, 1, 1, 1, 1, 1, 5, 1, 0))
|
||||
if self.LC_alt_digits[1] not in time.strftime("%x %X", time_tuple2):
|
||||
self.LC_alt_digits[:10] = digits
|
||||
return
|
||||
|
||||
# Either non-Gregorian calendar or non-decimal numbers.
|
||||
if {'\u4e00', '\u4e03', '\u4e5d', '\u5341', '\u5eff'}.issubset(s):
|
||||
# lzh_TW
|
||||
self.LC_alt_digits = lzh_TW_alt_digits
|
||||
return
|
||||
|
||||
self.LC_alt_digits = None
|
||||
|
||||
def __calc_date_time(self):
|
||||
# Set self.LC_date_time, self.LC_date, self.LC_time and
|
||||
# self.LC_time_ampm by using time.strftime().
|
||||
|
||||
# Use (1999,3,17,22,44,55,2,76,0) for magic date because the amount of
|
||||
# overloaded numbers is minimized. The order in which searches for
|
||||
# values within the format string is very important; it eliminates
|
||||
# possible ambiguity for what something represents.
|
||||
time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0))
|
||||
time_tuple2 = time.struct_time((1999,1,3,1,1,1,6,3,0))
|
||||
replacement_pairs = []
|
||||
|
||||
# Non-ASCII digits
|
||||
if self.LC_alt_digits or self.LC_alt_digits is None:
|
||||
for n, d in [(19, '%OC'), (99, '%Oy'), (22, '%OH'),
|
||||
(44, '%OM'), (55, '%OS'), (17, '%Od'),
|
||||
(3, '%Om'), (2, '%Ow'), (10, '%OI')]:
|
||||
if self.LC_alt_digits is None:
|
||||
s = chr(0x660 + n // 10) + chr(0x660 + n % 10)
|
||||
replacement_pairs.append((s, d))
|
||||
if n < 10:
|
||||
replacement_pairs.append((s[1], d))
|
||||
elif len(self.LC_alt_digits) > n:
|
||||
replacement_pairs.append((self.LC_alt_digits[n], d))
|
||||
else:
|
||||
replacement_pairs.append((time.strftime(d, time_tuple), d))
|
||||
replacement_pairs += [
|
||||
('1999', '%Y'), ('99', '%y'), ('22', '%H'),
|
||||
('44', '%M'), ('55', '%S'), ('76', '%j'),
|
||||
('17', '%d'), ('03', '%m'), ('3', '%m'),
|
||||
# '3' needed for when no leading zero.
|
||||
('2', '%w'), ('10', '%I'),
|
||||
]
|
||||
|
||||
date_time = []
|
||||
for directive in ('%c', '%x', '%X', '%r'):
|
||||
current_format = time.strftime(directive, time_tuple).lower()
|
||||
current_format = current_format.replace('%', '%%')
|
||||
# The month and the day of the week formats are treated specially
|
||||
# because of a possible ambiguity in some locales where the full
|
||||
# and abbreviated names are equal or names of different types
|
||||
# are equal. See doc of __find_month_format for more details.
|
||||
lst, fmt = self.__find_weekday_format(directive)
|
||||
if lst:
|
||||
current_format = current_format.replace(lst[2], fmt, 1)
|
||||
lst, fmt = self.__find_month_format(directive)
|
||||
if lst:
|
||||
current_format = current_format.replace(lst[3], fmt, 1)
|
||||
if self.am_pm[1]:
|
||||
# Must deal with possible lack of locale info
|
||||
# manifesting itself as the empty string (e.g., Swedish's
|
||||
# lack of AM/PM info) or a platform returning a tuple of empty
|
||||
# strings (e.g., MacOS 9 having timezone as ('','')).
|
||||
current_format = current_format.replace(self.am_pm[1], '%p')
|
||||
for tz_values in self.timezone:
|
||||
for tz in tz_values:
|
||||
if tz:
|
||||
current_format = current_format.replace(tz, "%Z")
|
||||
# Transform all non-ASCII digits to digits in range U+0660 to U+0669.
|
||||
if not current_format.isascii() and self.LC_alt_digits is None:
|
||||
current_format = re_sub(r'\d(?<![0-9])',
|
||||
lambda m: chr(0x0660 + int(m[0])),
|
||||
current_format)
|
||||
for old, new in replacement_pairs:
|
||||
current_format = current_format.replace(old, new)
|
||||
# If %W is used, then Sunday, 2005-01-03 will fall on week 0 since
|
||||
# 2005-01-03 occurs before the first Monday of the year. Otherwise
|
||||
# %U is used.
|
||||
if '00' in time.strftime(directive, time_tuple2):
|
||||
U_W = '%W'
|
||||
else:
|
||||
U_W = '%U'
|
||||
current_format = current_format.replace('11', U_W)
|
||||
date_time.append(current_format)
|
||||
self.LC_date_time = date_time[0]
|
||||
self.LC_date = date_time[1]
|
||||
self.LC_time = date_time[2]
|
||||
self.LC_time_ampm = date_time[3]
|
||||
|
||||
def __find_month_format(self, directive):
|
||||
"""Find the month format appropriate for the current locale.
|
||||
|
||||
In some locales (for example French and Hebrew), the default month
|
||||
used in __calc_date_time has the same name in full and abbreviated
|
||||
form. Also, the month name can by accident match other part of the
|
||||
representation: the day of the week name (for example in Morisyen)
|
||||
or the month number (for example in Japanese). Thus, cycle months
|
||||
of the year and find all positions that match the month name for
|
||||
each month, If no common positions are found, the representation
|
||||
does not use the month name.
|
||||
"""
|
||||
full_indices = abbr_indices = None
|
||||
for m in range(1, 13):
|
||||
time_tuple = time.struct_time((1999, m, 17, 22, 44, 55, 2, 76, 0))
|
||||
datetime = time.strftime(directive, time_tuple).lower()
|
||||
indices = set(_findall(datetime, self.f_month[m]))
|
||||
if full_indices is None:
|
||||
full_indices = indices
|
||||
else:
|
||||
full_indices &= indices
|
||||
indices = set(_findall(datetime, self.a_month[m]))
|
||||
if abbr_indices is None:
|
||||
abbr_indices = set(indices)
|
||||
else:
|
||||
abbr_indices &= indices
|
||||
if not full_indices and not abbr_indices:
|
||||
return None, None
|
||||
if full_indices:
|
||||
return self.f_month, '%B'
|
||||
if abbr_indices:
|
||||
return self.a_month, '%b'
|
||||
return None, None
|
||||
|
||||
def __find_weekday_format(self, directive):
|
||||
"""Find the day of the week format appropriate for the current locale.
|
||||
|
||||
Similar to __find_month_format().
|
||||
"""
|
||||
full_indices = abbr_indices = None
|
||||
for wd in range(7):
|
||||
time_tuple = time.struct_time((1999, 3, 17, 22, 44, 55, wd, 76, 0))
|
||||
datetime = time.strftime(directive, time_tuple).lower()
|
||||
indices = set(_findall(datetime, self.f_weekday[wd]))
|
||||
if full_indices is None:
|
||||
full_indices = indices
|
||||
else:
|
||||
full_indices &= indices
|
||||
if self.f_weekday[wd] != self.a_weekday[wd]:
|
||||
indices = set(_findall(datetime, self.a_weekday[wd]))
|
||||
if abbr_indices is None:
|
||||
abbr_indices = set(indices)
|
||||
else:
|
||||
abbr_indices &= indices
|
||||
if not full_indices and not abbr_indices:
|
||||
return None, None
|
||||
if full_indices:
|
||||
return self.f_weekday, '%A'
|
||||
if abbr_indices:
|
||||
return self.a_weekday, '%a'
|
||||
return None, None
|
||||
|
||||
def __calc_timezone(self):
|
||||
# Set self.timezone by using time.tzname.
|
||||
# Do not worry about possibility of time.tzname[0] == time.tzname[1]
|
||||
# and time.daylight; handle that in strptime.
|
||||
try:
|
||||
time.tzset()
|
||||
except AttributeError:
|
||||
pass
|
||||
self.tzname = time.tzname
|
||||
self.daylight = time.daylight
|
||||
no_saving = frozenset({"utc", "gmt", self.tzname[0].lower()})
|
||||
if self.daylight:
|
||||
has_saving = frozenset({self.tzname[1].lower()})
|
||||
else:
|
||||
has_saving = frozenset()
|
||||
self.timezone = (no_saving, has_saving)
|
||||
|
||||
|
||||
class TimeRE(dict):
|
||||
"""Handle conversion from format directives to regexes."""
|
||||
|
||||
def __init__(self, locale_time=None):
|
||||
"""Create keys/values.
|
||||
|
||||
Order of execution is important for dependency reasons.
|
||||
|
||||
"""
|
||||
if locale_time:
|
||||
self.locale_time = locale_time
|
||||
else:
|
||||
self.locale_time = LocaleTime()
|
||||
base = super()
|
||||
mapping = {
|
||||
# The " [1-9]" part of the regex is to make %c from ANSI C work
|
||||
'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
|
||||
'f': r"(?P<f>[0-9]{1,6})",
|
||||
'H': r"(?P<H>2[0-3]|[0-1]\d|\d| \d)",
|
||||
'k': r"(?P<H>2[0-3]|[0-1]\d|\d| \d)",
|
||||
'I': r"(?P<I>1[0-2]|0[1-9]|[1-9]| [1-9])",
|
||||
'l': r"(?P<I>1[0-2]|0[1-9]|[1-9]| [1-9])",
|
||||
'G': r"(?P<G>\d\d\d\d)",
|
||||
'j': r"(?P<j>36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])",
|
||||
'm': r"(?P<m>1[0-2]|0[1-9]|[1-9])",
|
||||
'M': r"(?P<M>[0-5]\d|\d)",
|
||||
'S': r"(?P<S>6[0-1]|[0-5]\d|\d)",
|
||||
'U': r"(?P<U>5[0-3]|[0-4]\d|\d)",
|
||||
'w': r"(?P<w>[0-6])",
|
||||
'u': r"(?P<u>[1-7])",
|
||||
'V': r"(?P<V>5[0-3]|0[1-9]|[1-4]\d|\d)",
|
||||
# W is set below by using 'U'
|
||||
'y': r"(?P<y>\d\d)",
|
||||
'Y': r"(?P<Y>\d\d\d\d)",
|
||||
'z': r"(?P<z>[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|(?-i:Z))",
|
||||
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
|
||||
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
|
||||
'B': self.__seqToRE(_fixmonths(self.locale_time.f_month[1:]), 'B'),
|
||||
'b': self.__seqToRE(_fixmonths(self.locale_time.a_month[1:]), 'b'),
|
||||
'p': self.__seqToRE(self.locale_time.am_pm, 'p'),
|
||||
'Z': self.__seqToRE((tz for tz_names in self.locale_time.timezone
|
||||
for tz in tz_names),
|
||||
'Z'),
|
||||
'%': '%'}
|
||||
if self.locale_time.LC_alt_digits is None:
|
||||
for d in 'dmyCHIMS':
|
||||
mapping['O' + d] = r'(?P<%s>\d\d|\d| \d)' % d
|
||||
mapping['Ow'] = r'(?P<w>\d)'
|
||||
else:
|
||||
mapping.update({
|
||||
'Od': self.__seqToRE(self.locale_time.LC_alt_digits[1:32], 'd',
|
||||
'3[0-1]|[1-2][0-9]|0[1-9]|[1-9]'),
|
||||
'Om': self.__seqToRE(self.locale_time.LC_alt_digits[1:13], 'm',
|
||||
'1[0-2]|0[1-9]|[1-9]'),
|
||||
'Ow': self.__seqToRE(self.locale_time.LC_alt_digits[:7], 'w',
|
||||
'[0-6]'),
|
||||
'Oy': self.__seqToRE(self.locale_time.LC_alt_digits, 'y',
|
||||
'[0-9][0-9]'),
|
||||
'OC': self.__seqToRE(self.locale_time.LC_alt_digits, 'C',
|
||||
'[0-9][0-9]'),
|
||||
'OH': self.__seqToRE(self.locale_time.LC_alt_digits[:24], 'H',
|
||||
'2[0-3]|[0-1][0-9]|[0-9]'),
|
||||
'OI': self.__seqToRE(self.locale_time.LC_alt_digits[1:13], 'I',
|
||||
'1[0-2]|0[1-9]|[1-9]'),
|
||||
'OM': self.__seqToRE(self.locale_time.LC_alt_digits[:60], 'M',
|
||||
'[0-5][0-9]|[0-9]'),
|
||||
'OS': self.__seqToRE(self.locale_time.LC_alt_digits[:62], 'S',
|
||||
'6[0-1]|[0-5][0-9]|[0-9]'),
|
||||
})
|
||||
mapping.update({
|
||||
'e': mapping['d'],
|
||||
'Oe': mapping['Od'],
|
||||
'P': mapping['p'],
|
||||
'Op': mapping['p'],
|
||||
'W': mapping['U'].replace('U', 'W'),
|
||||
})
|
||||
mapping['W'] = mapping['U'].replace('U', 'W')
|
||||
|
||||
base.__init__(mapping)
|
||||
base.__setitem__('T', self.pattern('%H:%M:%S'))
|
||||
base.__setitem__('R', self.pattern('%H:%M'))
|
||||
base.__setitem__('r', self.pattern(self.locale_time.LC_time_ampm))
|
||||
base.__setitem__('X', self.pattern(self.locale_time.LC_time))
|
||||
base.__setitem__('x', self.pattern(self.locale_time.LC_date))
|
||||
base.__setitem__('c', self.pattern(self.locale_time.LC_date_time))
|
||||
|
||||
def __seqToRE(self, to_convert, directive, altregex=None):
|
||||
"""Convert a list to a regex string for matching a directive.
|
||||
|
||||
Want possible matching values to be from longest to shortest. This
|
||||
prevents the possibility of a match occurring for a value that also
|
||||
a substring of a larger value that should have matched (e.g., 'abc'
|
||||
matching when 'abcdef' should have been the match).
|
||||
|
||||
"""
|
||||
to_convert = sorted(to_convert, key=len, reverse=True)
|
||||
for value in to_convert:
|
||||
if value != '':
|
||||
break
|
||||
else:
|
||||
return ''
|
||||
regex = '|'.join(re_escape(stuff) for stuff in to_convert)
|
||||
if altregex is not None:
|
||||
regex += '|' + altregex
|
||||
return '(?P<%s>%s)' % (directive, regex)
|
||||
|
||||
def pattern(self, format):
|
||||
"""Return regex pattern for the format string.
|
||||
|
||||
Need to make sure that any characters that might be interpreted as
|
||||
regex syntax are escaped.
|
||||
|
||||
"""
|
||||
# The sub() call escapes all characters that might be misconstrued
|
||||
# as regex syntax. Cannot use re.escape since we have to deal with
|
||||
# format directives (%m, etc.).
|
||||
format = re_sub(r"([\\.^$*+?\(\){}\[\]|])", r"\\\1", format)
|
||||
format = re_sub(r'\s+', r'\\s+', format)
|
||||
format = re_sub(r"'", "['\u02bc]", format) # needed for br_FR
|
||||
year_in_format = False
|
||||
day_of_month_in_format = False
|
||||
def repl(m):
|
||||
format_char = m[1]
|
||||
match format_char:
|
||||
case 'Y' | 'y' | 'G':
|
||||
nonlocal year_in_format
|
||||
year_in_format = True
|
||||
case 'd':
|
||||
nonlocal day_of_month_in_format
|
||||
day_of_month_in_format = True
|
||||
return self[format_char]
|
||||
format = re_sub(r'%[-_0^#]*[0-9]*([OE]?\\?.?)', repl, format)
|
||||
if day_of_month_in_format and not year_in_format:
|
||||
import warnings
|
||||
warnings.warn("""\
|
||||
Parsing dates involving a day of month without a year specified is ambiguous
|
||||
and fails to parse leap day. The default behavior will change in Python 3.15
|
||||
to either always raise an exception or to use a different default year (TBD).
|
||||
To avoid trouble, add a specific year to the input & format.
|
||||
See https://github.com/python/cpython/issues/70647.""",
|
||||
DeprecationWarning,
|
||||
skip_file_prefixes=(os.path.dirname(__file__),))
|
||||
return format
|
||||
|
||||
def compile(self, format):
|
||||
"""Return a compiled re object for the format string."""
|
||||
return re_compile(self.pattern(format), IGNORECASE)
|
||||
|
||||
_cache_lock = _thread_allocate_lock()
|
||||
# DO NOT modify _TimeRE_cache or _regex_cache without acquiring the cache lock
|
||||
# first!
|
||||
_TimeRE_cache = TimeRE()
|
||||
_CACHE_MAX_SIZE = 5 # Max number of regexes stored in _regex_cache
|
||||
_regex_cache = {}
|
||||
|
||||
def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
|
||||
"""Calculate the Julian day based on the year, week of the year, and day of
|
||||
the week, with week_start_day representing whether the week of the year
|
||||
assumes the week starts on Sunday or Monday (6 or 0)."""
|
||||
first_weekday = datetime_date(year, 1, 1).weekday()
|
||||
# If we are dealing with the %U directive (week starts on Sunday), it's
|
||||
# easier to just shift the view to Sunday being the first day of the
|
||||
# week.
|
||||
if not week_starts_Mon:
|
||||
first_weekday = (first_weekday + 1) % 7
|
||||
day_of_week = (day_of_week + 1) % 7
|
||||
# Need to watch out for a week 0 (when the first day of the year is not
|
||||
# the same as that specified by %U or %W).
|
||||
week_0_length = (7 - first_weekday) % 7
|
||||
if week_of_year == 0:
|
||||
return 1 + day_of_week - first_weekday
|
||||
else:
|
||||
days_to_week = week_0_length + (7 * (week_of_year - 1))
|
||||
return 1 + days_to_week + day_of_week
|
||||
|
||||
|
||||
def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
|
||||
"""Return a 2-tuple consisting of a time struct and an int containing
|
||||
the number of microseconds based on the input string and the
|
||||
format string."""
|
||||
|
||||
for index, arg in enumerate([data_string, format]):
|
||||
if not isinstance(arg, str):
|
||||
msg = "strptime() argument {} must be str, not {}"
|
||||
raise TypeError(msg.format(index, type(arg)))
|
||||
|
||||
global _TimeRE_cache, _regex_cache
|
||||
with _cache_lock:
|
||||
locale_time = _TimeRE_cache.locale_time
|
||||
if (_getlang() != locale_time.lang or
|
||||
time.tzname != locale_time.tzname or
|
||||
time.daylight != locale_time.daylight):
|
||||
_TimeRE_cache = TimeRE()
|
||||
_regex_cache.clear()
|
||||
locale_time = _TimeRE_cache.locale_time
|
||||
if len(_regex_cache) > _CACHE_MAX_SIZE:
|
||||
_regex_cache.clear()
|
||||
format_regex = _regex_cache.get(format)
|
||||
if not format_regex:
|
||||
try:
|
||||
format_regex = _TimeRE_cache.compile(format)
|
||||
# KeyError raised when a bad format is found; can be specified as
|
||||
# \\, in which case it was a stray % but with a space after it
|
||||
except KeyError as err:
|
||||
bad_directive = err.args[0]
|
||||
del err
|
||||
bad_directive = bad_directive.replace('\\s', '')
|
||||
if not bad_directive:
|
||||
raise ValueError("stray %% in format '%s'" % format) from None
|
||||
bad_directive = bad_directive.replace('\\', '', 1)
|
||||
raise ValueError("'%s' is a bad directive in format '%s'" %
|
||||
(bad_directive, format)) from None
|
||||
_regex_cache[format] = format_regex
|
||||
found = format_regex.match(data_string)
|
||||
if not found:
|
||||
raise ValueError("time data %r does not match format %r" %
|
||||
(data_string, format))
|
||||
if len(data_string) != found.end():
|
||||
raise ValueError("unconverted data remains: %s" %
|
||||
data_string[found.end():])
|
||||
|
||||
iso_year = year = None
|
||||
month = day = 1
|
||||
hour = minute = second = fraction = 0
|
||||
tz = -1
|
||||
gmtoff = None
|
||||
gmtoff_fraction = 0
|
||||
iso_week = week_of_year = None
|
||||
week_of_year_start = None
|
||||
# weekday and julian defaulted to None so as to signal need to calculate
|
||||
# values
|
||||
weekday = julian = None
|
||||
found_dict = found.groupdict()
|
||||
if locale_time.LC_alt_digits:
|
||||
def parse_int(s):
|
||||
try:
|
||||
return locale_time.LC_alt_digits.index(s)
|
||||
except ValueError:
|
||||
return int(s)
|
||||
else:
|
||||
parse_int = int
|
||||
|
||||
for group_key in found_dict.keys():
|
||||
# Directives not explicitly handled below:
|
||||
# c, x, X
|
||||
# handled by making out of other directives
|
||||
# U, W
|
||||
# worthless without day of the week
|
||||
if group_key == 'y':
|
||||
year = parse_int(found_dict['y'])
|
||||
if 'C' in found_dict:
|
||||
century = parse_int(found_dict['C'])
|
||||
year += century * 100
|
||||
else:
|
||||
# Open Group specification for strptime() states that a %y
|
||||
#value in the range of [00, 68] is in the century 2000, while
|
||||
#[69,99] is in the century 1900
|
||||
if year <= 68:
|
||||
year += 2000
|
||||
else:
|
||||
year += 1900
|
||||
elif group_key == 'Y':
|
||||
year = int(found_dict['Y'])
|
||||
elif group_key == 'G':
|
||||
iso_year = int(found_dict['G'])
|
||||
elif group_key == 'm':
|
||||
month = parse_int(found_dict['m'])
|
||||
elif group_key == 'B':
|
||||
month = locale_time.f_month.index(found_dict['B'].lower())
|
||||
elif group_key == 'b':
|
||||
month = locale_time.a_month.index(found_dict['b'].lower())
|
||||
elif group_key == 'd':
|
||||
day = parse_int(found_dict['d'])
|
||||
elif group_key == 'H':
|
||||
hour = parse_int(found_dict['H'])
|
||||
elif group_key == 'I':
|
||||
hour = parse_int(found_dict['I'])
|
||||
ampm = found_dict.get('p', '').lower()
|
||||
# If there was no AM/PM indicator, we'll treat this like AM
|
||||
if ampm in ('', locale_time.am_pm[0]):
|
||||
# We're in AM so the hour is correct unless we're
|
||||
# looking at 12 midnight.
|
||||
# 12 midnight == 12 AM == hour 0
|
||||
if hour == 12:
|
||||
hour = 0
|
||||
elif ampm == locale_time.am_pm[1]:
|
||||
# We're in PM so we need to add 12 to the hour unless
|
||||
# we're looking at 12 noon.
|
||||
# 12 noon == 12 PM == hour 12
|
||||
if hour != 12:
|
||||
hour += 12
|
||||
elif group_key == 'M':
|
||||
minute = parse_int(found_dict['M'])
|
||||
elif group_key == 'S':
|
||||
second = parse_int(found_dict['S'])
|
||||
elif group_key == 'f':
|
||||
s = found_dict['f']
|
||||
# Pad to always return microseconds.
|
||||
s += "0" * (6 - len(s))
|
||||
fraction = int(s)
|
||||
elif group_key == 'A':
|
||||
weekday = locale_time.f_weekday.index(found_dict['A'].lower())
|
||||
elif group_key == 'a':
|
||||
weekday = locale_time.a_weekday.index(found_dict['a'].lower())
|
||||
elif group_key == 'w':
|
||||
weekday = int(found_dict['w'])
|
||||
if weekday == 0:
|
||||
weekday = 6
|
||||
else:
|
||||
weekday -= 1
|
||||
elif group_key == 'u':
|
||||
weekday = int(found_dict['u'])
|
||||
weekday -= 1
|
||||
elif group_key == 'j':
|
||||
julian = int(found_dict['j'])
|
||||
elif group_key in ('U', 'W'):
|
||||
week_of_year = int(found_dict[group_key])
|
||||
if group_key == 'U':
|
||||
# U starts week on Sunday.
|
||||
week_of_year_start = 6
|
||||
else:
|
||||
# W starts week on Monday.
|
||||
week_of_year_start = 0
|
||||
elif group_key == 'V':
|
||||
iso_week = int(found_dict['V'])
|
||||
elif group_key == 'z':
|
||||
z = found_dict['z']
|
||||
if z == 'Z':
|
||||
gmtoff = 0
|
||||
else:
|
||||
if z[3] == ':':
|
||||
z = z[:3] + z[4:]
|
||||
if len(z) > 5:
|
||||
if z[5] != ':':
|
||||
msg = f"Inconsistent use of : in {found_dict['z']}"
|
||||
raise ValueError(msg)
|
||||
z = z[:5] + z[6:]
|
||||
hours = int(z[1:3])
|
||||
minutes = int(z[3:5])
|
||||
seconds = int(z[5:7] or 0)
|
||||
gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds
|
||||
gmtoff_remainder = z[8:]
|
||||
# Pad to always return microseconds.
|
||||
gmtoff_remainder_padding = "0" * (6 - len(gmtoff_remainder))
|
||||
gmtoff_fraction = int(gmtoff_remainder + gmtoff_remainder_padding)
|
||||
if z.startswith("-"):
|
||||
gmtoff = -gmtoff
|
||||
gmtoff_fraction = -gmtoff_fraction
|
||||
elif group_key == 'Z':
|
||||
# Since -1 is default value only need to worry about setting tz if
|
||||
# it can be something other than -1.
|
||||
found_zone = found_dict['Z'].lower()
|
||||
for value, tz_values in enumerate(locale_time.timezone):
|
||||
if found_zone in tz_values:
|
||||
# Deal with bad locale setup where timezone names are the
|
||||
# same and yet time.daylight is true; too ambiguous to
|
||||
# be able to tell what timezone has daylight savings
|
||||
if (time.tzname[0] == time.tzname[1] and
|
||||
time.daylight and found_zone not in ("utc", "gmt")):
|
||||
break
|
||||
else:
|
||||
tz = value
|
||||
break
|
||||
|
||||
# Deal with the cases where ambiguities arise
|
||||
# don't assume default values for ISO week/year
|
||||
if iso_year is not None:
|
||||
if julian is not None:
|
||||
raise ValueError("Day of the year directive '%j' is not "
|
||||
"compatible with ISO year directive '%G'. "
|
||||
"Use '%Y' instead.")
|
||||
elif iso_week is None or weekday is None:
|
||||
raise ValueError("ISO year directive '%G' must be used with "
|
||||
"the ISO week directive '%V' and a weekday "
|
||||
"directive ('%A', '%a', '%w', or '%u').")
|
||||
elif iso_week is not None:
|
||||
if year is None or weekday is None:
|
||||
raise ValueError("ISO week directive '%V' must be used with "
|
||||
"the ISO year directive '%G' and a weekday "
|
||||
"directive ('%A', '%a', '%w', or '%u').")
|
||||
else:
|
||||
raise ValueError("ISO week directive '%V' is incompatible with "
|
||||
"the year directive '%Y'. Use the ISO year '%G' "
|
||||
"instead.")
|
||||
|
||||
leap_year_fix = False
|
||||
if year is None:
|
||||
if month == 2 and day == 29:
|
||||
year = 1904 # 1904 is first leap year of 20th century
|
||||
leap_year_fix = True
|
||||
else:
|
||||
year = 1900
|
||||
|
||||
# If we know the week of the year and what day of that week, we can figure
|
||||
# out the Julian day of the year.
|
||||
if julian is None and weekday is not None:
|
||||
if week_of_year is not None:
|
||||
week_starts_Mon = True if week_of_year_start == 0 else False
|
||||
julian = _calc_julian_from_U_or_W(year, week_of_year, weekday,
|
||||
week_starts_Mon)
|
||||
elif iso_year is not None and iso_week is not None:
|
||||
datetime_result = datetime_date.fromisocalendar(iso_year, iso_week, weekday + 1)
|
||||
year = datetime_result.year
|
||||
month = datetime_result.month
|
||||
day = datetime_result.day
|
||||
if julian is not None and julian <= 0:
|
||||
year -= 1
|
||||
yday = 366 if calendar.isleap(year) else 365
|
||||
julian += yday
|
||||
|
||||
if julian is None:
|
||||
# Cannot pre-calculate datetime_date() since can change in Julian
|
||||
# calculation and thus could have different value for the day of
|
||||
# the week calculation.
|
||||
# Need to add 1 to result since first day of the year is 1, not 0.
|
||||
julian = datetime_date(year, month, day).toordinal() - \
|
||||
datetime_date(year, 1, 1).toordinal() + 1
|
||||
else: # Assume that if they bothered to include Julian day (or if it was
|
||||
# calculated above with year/week/weekday) it will be accurate.
|
||||
datetime_result = datetime_date.fromordinal(
|
||||
(julian - 1) +
|
||||
datetime_date(year, 1, 1).toordinal())
|
||||
year = datetime_result.year
|
||||
month = datetime_result.month
|
||||
day = datetime_result.day
|
||||
if weekday is None:
|
||||
weekday = datetime_date(year, month, day).weekday()
|
||||
# Add timezone info
|
||||
tzname = found_dict.get("Z")
|
||||
|
||||
if leap_year_fix:
|
||||
# the caller didn't supply a year but asked for Feb 29th. We couldn't
|
||||
# use the default of 1900 for computations. We set it back to ensure
|
||||
# that February 29th is smaller than March 1st.
|
||||
year = 1900
|
||||
|
||||
return (year, month, day,
|
||||
hour, minute, second,
|
||||
weekday, julian, tz, tzname, gmtoff), fraction, gmtoff_fraction
|
||||
|
||||
def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
|
||||
"""Return a time struct based on the input string and the
|
||||
format string."""
|
||||
tt = _strptime(data_string, format)[0]
|
||||
return time.struct_time(tt[:time._STRUCT_TM_ITEMS])
|
||||
|
||||
def _strptime_datetime_date(cls, data_string, format="%a %b %d %Y"):
|
||||
"""Return a date instance based on the input string and the
|
||||
format string."""
|
||||
tt, _, _ = _strptime(data_string, format)
|
||||
args = tt[:3]
|
||||
return cls(*args)
|
||||
|
||||
def _parse_tz(tzname, gmtoff, gmtoff_fraction):
|
||||
tzdelta = datetime_timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)
|
||||
if tzname:
|
||||
return datetime_timezone(tzdelta, tzname)
|
||||
else:
|
||||
return datetime_timezone(tzdelta)
|
||||
|
||||
def _strptime_datetime_time(cls, data_string, format="%H:%M:%S"):
|
||||
"""Return a time instance based on the input string and the
|
||||
format string."""
|
||||
tt, fraction, gmtoff_fraction = _strptime(data_string, format)
|
||||
tzname, gmtoff = tt[-2:]
|
||||
args = tt[3:6] + (fraction,)
|
||||
if gmtoff is None:
|
||||
return cls(*args)
|
||||
else:
|
||||
tz = _parse_tz(tzname, gmtoff, gmtoff_fraction)
|
||||
return cls(*args, tz)
|
||||
|
||||
def _strptime_datetime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
|
||||
"""Return a datetime instance based on the input string and the
|
||||
format string."""
|
||||
tt, fraction, gmtoff_fraction = _strptime(data_string, format)
|
||||
tzname, gmtoff = tt[-2:]
|
||||
args = tt[:6] + (fraction,)
|
||||
if gmtoff is None:
|
||||
return cls(*args)
|
||||
else:
|
||||
tz = _parse_tz(tzname, gmtoff, gmtoff_fraction)
|
||||
return cls(*args, tz)
|
||||
131
Lib/_threading_local.py
vendored
131
Lib/_threading_local.py
vendored
@@ -4,133 +4,6 @@
|
||||
class. Depending on the version of Python you're using, there may be a
|
||||
faster one available. You should always import the `local` class from
|
||||
`threading`.)
|
||||
|
||||
Thread-local objects support the management of thread-local data.
|
||||
If you have data that you want to be local to a thread, simply create
|
||||
a thread-local object and use its attributes:
|
||||
|
||||
>>> mydata = local()
|
||||
>>> mydata.number = 42
|
||||
>>> mydata.number
|
||||
42
|
||||
|
||||
You can also access the local-object's dictionary:
|
||||
|
||||
>>> mydata.__dict__
|
||||
{'number': 42}
|
||||
>>> mydata.__dict__.setdefault('widgets', [])
|
||||
[]
|
||||
>>> mydata.widgets
|
||||
[]
|
||||
|
||||
What's important about thread-local objects is that their data are
|
||||
local to a thread. If we access the data in a different thread:
|
||||
|
||||
>>> log = []
|
||||
>>> def f():
|
||||
... items = sorted(mydata.__dict__.items())
|
||||
... log.append(items)
|
||||
... mydata.number = 11
|
||||
... log.append(mydata.number)
|
||||
|
||||
>>> import threading
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
>>> log
|
||||
[[], 11]
|
||||
|
||||
we get different data. Furthermore, changes made in the other thread
|
||||
don't affect data seen in this thread:
|
||||
|
||||
>>> mydata.number
|
||||
42
|
||||
|
||||
Of course, values you get from a local object, including a __dict__
|
||||
attribute, are for whatever thread was current at the time the
|
||||
attribute was read. For that reason, you generally don't want to save
|
||||
these values across threads, as they apply only to the thread they
|
||||
came from.
|
||||
|
||||
You can create custom local objects by subclassing the local class:
|
||||
|
||||
>>> class MyLocal(local):
|
||||
... number = 2
|
||||
... initialized = False
|
||||
... def __init__(self, **kw):
|
||||
... if self.initialized:
|
||||
... raise SystemError('__init__ called too many times')
|
||||
... self.initialized = True
|
||||
... self.__dict__.update(kw)
|
||||
... def squared(self):
|
||||
... return self.number ** 2
|
||||
|
||||
This can be useful to support default values, methods and
|
||||
initialization. Note that if you define an __init__ method, it will be
|
||||
called each time the local object is used in a separate thread. This
|
||||
is necessary to initialize each thread's dictionary.
|
||||
|
||||
Now if we create a local object:
|
||||
|
||||
>>> mydata = MyLocal(color='red')
|
||||
|
||||
Now we have a default number:
|
||||
|
||||
>>> mydata.number
|
||||
2
|
||||
|
||||
an initial color:
|
||||
|
||||
>>> mydata.color
|
||||
'red'
|
||||
>>> del mydata.color
|
||||
|
||||
And a method that operates on the data:
|
||||
|
||||
>>> mydata.squared()
|
||||
4
|
||||
|
||||
As before, we can access the data in a separate thread:
|
||||
|
||||
>>> log = []
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
>>> log
|
||||
[[('color', 'red'), ('initialized', True)], 11]
|
||||
|
||||
without affecting this thread's data:
|
||||
|
||||
>>> mydata.number
|
||||
2
|
||||
>>> mydata.color
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'MyLocal' object has no attribute 'color'
|
||||
|
||||
Note that subclasses can define slots, but they are not thread
|
||||
local. They are shared across threads:
|
||||
|
||||
>>> class MyLocal(local):
|
||||
... __slots__ = 'number'
|
||||
|
||||
>>> mydata = MyLocal()
|
||||
>>> mydata.number = 42
|
||||
>>> mydata.color = 'red'
|
||||
|
||||
So, the separate thread:
|
||||
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
|
||||
affects what we see:
|
||||
|
||||
>>> # TODO: RUSTPYTHON, __slots__
|
||||
>>> mydata.number #doctest: +SKIP
|
||||
11
|
||||
|
||||
>>> del mydata
|
||||
"""
|
||||
|
||||
from weakref import ref
|
||||
@@ -194,7 +67,6 @@ class _localimpl:
|
||||
|
||||
@contextmanager
|
||||
def _patch(self):
|
||||
old = object.__getattribute__(self, '__dict__')
|
||||
impl = object.__getattribute__(self, '_local__impl')
|
||||
try:
|
||||
dct = impl.get_dict()
|
||||
@@ -205,13 +77,12 @@ def _patch(self):
|
||||
with impl.locallock:
|
||||
object.__setattr__(self, '__dict__', dct)
|
||||
yield
|
||||
object.__setattr__(self, '__dict__', old)
|
||||
|
||||
|
||||
class local:
|
||||
__slots__ = '_local__impl', '__dict__'
|
||||
|
||||
def __new__(cls, *args, **kw):
|
||||
def __new__(cls, /, *args, **kw):
|
||||
if (args or kw) and (cls.__init__ is object.__init__):
|
||||
raise TypeError("Initialization arguments are not supported")
|
||||
self = object.__new__(cls)
|
||||
|
||||
81
Lib/_weakrefset.py
vendored
81
Lib/_weakrefset.py
vendored
@@ -8,69 +8,29 @@ from types import GenericAlias
|
||||
__all__ = ['WeakSet']
|
||||
|
||||
|
||||
class _IterationGuard:
|
||||
# This context manager registers itself in the current iterators of the
|
||||
# weak container, such as to delay all removals until the context manager
|
||||
# exits.
|
||||
# This technique should be relatively thread-safe (since sets are).
|
||||
|
||||
def __init__(self, weakcontainer):
|
||||
# Don't create cycles
|
||||
self.weakcontainer = ref(weakcontainer)
|
||||
|
||||
def __enter__(self):
|
||||
w = self.weakcontainer()
|
||||
if w is not None:
|
||||
w._iterating.add(self)
|
||||
return self
|
||||
|
||||
def __exit__(self, e, t, b):
|
||||
w = self.weakcontainer()
|
||||
if w is not None:
|
||||
s = w._iterating
|
||||
s.remove(self)
|
||||
if not s:
|
||||
w._commit_removals()
|
||||
|
||||
|
||||
class WeakSet:
|
||||
def __init__(self, data=None):
|
||||
self.data = set()
|
||||
|
||||
def _remove(item, selfref=ref(self)):
|
||||
self = selfref()
|
||||
if self is not None:
|
||||
if self._iterating:
|
||||
self._pending_removals.append(item)
|
||||
else:
|
||||
self.data.discard(item)
|
||||
self.data.discard(item)
|
||||
|
||||
self._remove = _remove
|
||||
# A list of keys to be removed
|
||||
self._pending_removals = []
|
||||
self._iterating = set()
|
||||
if data is not None:
|
||||
self.update(data)
|
||||
|
||||
def _commit_removals(self):
|
||||
pop = self._pending_removals.pop
|
||||
discard = self.data.discard
|
||||
while True:
|
||||
try:
|
||||
item = pop()
|
||||
except IndexError:
|
||||
return
|
||||
discard(item)
|
||||
|
||||
def __iter__(self):
|
||||
with _IterationGuard(self):
|
||||
for itemref in self.data:
|
||||
item = itemref()
|
||||
if item is not None:
|
||||
# Caveat: the iterator will keep a strong reference to
|
||||
# `item` until it is resumed or closed.
|
||||
yield item
|
||||
for itemref in self.data.copy():
|
||||
item = itemref()
|
||||
if item is not None:
|
||||
# Caveat: the iterator will keep a strong reference to
|
||||
# `item` until it is resumed or closed.
|
||||
yield item
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data) - len(self._pending_removals)
|
||||
return len(self.data)
|
||||
|
||||
def __contains__(self, item):
|
||||
try:
|
||||
@@ -80,25 +40,18 @@ class WeakSet:
|
||||
return wr in self.data
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, (list(self),),
|
||||
getattr(self, '__dict__', None))
|
||||
return self.__class__, (list(self),), self.__getstate__()
|
||||
|
||||
def add(self, item):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
self.data.add(ref(item, self._remove))
|
||||
|
||||
def clear(self):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
self.data.clear()
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
|
||||
def pop(self):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
while True:
|
||||
try:
|
||||
itemref = self.data.pop()
|
||||
@@ -109,18 +62,12 @@ class WeakSet:
|
||||
return item
|
||||
|
||||
def remove(self, item):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
self.data.remove(ref(item))
|
||||
|
||||
def discard(self, item):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
self.data.discard(ref(item))
|
||||
|
||||
def update(self, other):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
for element in other:
|
||||
self.add(element)
|
||||
|
||||
@@ -137,8 +84,6 @@ class WeakSet:
|
||||
def difference_update(self, other):
|
||||
self.__isub__(other)
|
||||
def __isub__(self, other):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
if self is other:
|
||||
self.data.clear()
|
||||
else:
|
||||
@@ -152,8 +97,6 @@ class WeakSet:
|
||||
def intersection_update(self, other):
|
||||
self.__iand__(other)
|
||||
def __iand__(self, other):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
self.data.intersection_update(ref(item) for item in other)
|
||||
return self
|
||||
|
||||
@@ -185,8 +128,6 @@ class WeakSet:
|
||||
def symmetric_difference_update(self, other):
|
||||
self.__ixor__(other)
|
||||
def __ixor__(self, other):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
if self is other:
|
||||
self.data.clear()
|
||||
else:
|
||||
|
||||
4
Lib/abc.py
vendored
4
Lib/abc.py
vendored
@@ -85,10 +85,6 @@ try:
|
||||
from _abc import (get_cache_token, _abc_init, _abc_register,
|
||||
_abc_instancecheck, _abc_subclasscheck, _get_dump,
|
||||
_reset_registry, _reset_caches)
|
||||
# TODO: RUSTPYTHON missing _abc module implementation.
|
||||
except ModuleNotFoundError:
|
||||
from _py_abc import ABCMeta, get_cache_token
|
||||
ABCMeta.__module__ = 'abc'
|
||||
except ImportError:
|
||||
from _py_abc import ABCMeta, get_cache_token
|
||||
ABCMeta.__module__ = 'abc'
|
||||
|
||||
984
Lib/aifc.py
vendored
984
Lib/aifc.py
vendored
@@ -1,984 +0,0 @@
|
||||
"""Stuff to parse AIFF-C and AIFF files.
|
||||
|
||||
Unless explicitly stated otherwise, the description below is true
|
||||
both for AIFF-C files and AIFF files.
|
||||
|
||||
An AIFF-C file has the following structure.
|
||||
|
||||
+-----------------+
|
||||
| FORM |
|
||||
+-----------------+
|
||||
| <size> |
|
||||
+----+------------+
|
||||
| | AIFC |
|
||||
| +------------+
|
||||
| | <chunks> |
|
||||
| | . |
|
||||
| | . |
|
||||
| | . |
|
||||
+----+------------+
|
||||
|
||||
An AIFF file has the string "AIFF" instead of "AIFC".
|
||||
|
||||
A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
|
||||
big endian order), followed by the data. The size field does not include
|
||||
the size of the 8 byte header.
|
||||
|
||||
The following chunk types are recognized.
|
||||
|
||||
FVER
|
||||
<version number of AIFF-C defining document> (AIFF-C only).
|
||||
MARK
|
||||
<# of markers> (2 bytes)
|
||||
list of markers:
|
||||
<marker ID> (2 bytes, must be > 0)
|
||||
<position> (4 bytes)
|
||||
<marker name> ("pstring")
|
||||
COMM
|
||||
<# of channels> (2 bytes)
|
||||
<# of sound frames> (4 bytes)
|
||||
<size of the samples> (2 bytes)
|
||||
<sampling frequency> (10 bytes, IEEE 80-bit extended
|
||||
floating point)
|
||||
in AIFF-C files only:
|
||||
<compression type> (4 bytes)
|
||||
<human-readable version of compression type> ("pstring")
|
||||
SSND
|
||||
<offset> (4 bytes, not used by this program)
|
||||
<blocksize> (4 bytes, not used by this program)
|
||||
<sound data>
|
||||
|
||||
A pstring consists of 1 byte length, a string of characters, and 0 or 1
|
||||
byte pad to make the total length even.
|
||||
|
||||
Usage.
|
||||
|
||||
Reading AIFF files:
|
||||
f = aifc.open(file, 'r')
|
||||
where file is either the name of a file or an open file pointer.
|
||||
The open file pointer must have methods read(), seek(), and close().
|
||||
In some types of audio files, if the setpos() method is not used,
|
||||
the seek() method is not necessary.
|
||||
|
||||
This returns an instance of a class with the following public methods:
|
||||
getnchannels() -- returns number of audio channels (1 for
|
||||
mono, 2 for stereo)
|
||||
getsampwidth() -- returns sample width in bytes
|
||||
getframerate() -- returns sampling frequency
|
||||
getnframes() -- returns number of audio frames
|
||||
getcomptype() -- returns compression type ('NONE' for AIFF files)
|
||||
getcompname() -- returns human-readable version of
|
||||
compression type ('not compressed' for AIFF files)
|
||||
getparams() -- returns a namedtuple consisting of all of the
|
||||
above in the above order
|
||||
getmarkers() -- get the list of marks in the audio file or None
|
||||
if there are no marks
|
||||
getmark(id) -- get mark with the specified id (raises an error
|
||||
if the mark does not exist)
|
||||
readframes(n) -- returns at most n frames of audio
|
||||
rewind() -- rewind to the beginning of the audio stream
|
||||
setpos(pos) -- seek to the specified position
|
||||
tell() -- return the current position
|
||||
close() -- close the instance (make it unusable)
|
||||
The position returned by tell(), the position given to setpos() and
|
||||
the position of marks are all compatible and have nothing to do with
|
||||
the actual position in the file.
|
||||
The close() method is called automatically when the class instance
|
||||
is destroyed.
|
||||
|
||||
Writing AIFF files:
|
||||
f = aifc.open(file, 'w')
|
||||
where file is either the name of a file or an open file pointer.
|
||||
The open file pointer must have methods write(), tell(), seek(), and
|
||||
close().
|
||||
|
||||
This returns an instance of a class with the following public methods:
|
||||
aiff() -- create an AIFF file (AIFF-C default)
|
||||
aifc() -- create an AIFF-C file
|
||||
setnchannels(n) -- set the number of channels
|
||||
setsampwidth(n) -- set the sample width
|
||||
setframerate(n) -- set the frame rate
|
||||
setnframes(n) -- set the number of frames
|
||||
setcomptype(type, name)
|
||||
-- set the compression type and the
|
||||
human-readable compression type
|
||||
setparams(tuple)
|
||||
-- set all parameters at once
|
||||
setmark(id, pos, name)
|
||||
-- add specified mark to the list of marks
|
||||
tell() -- return current position in output file (useful
|
||||
in combination with setmark())
|
||||
writeframesraw(data)
|
||||
-- write audio frames without pathing up the
|
||||
file header
|
||||
writeframes(data)
|
||||
-- write audio frames and patch up the file header
|
||||
close() -- patch up the file header and close the
|
||||
output file
|
||||
You should set the parameters before the first writeframesraw or
|
||||
writeframes. The total number of frames does not need to be set,
|
||||
but when it is set to the correct value, the header does not have to
|
||||
be patched up.
|
||||
It is best to first set all parameters, perhaps possibly the
|
||||
compression type, and then write audio frames using writeframesraw.
|
||||
When all frames have been written, either call writeframes(b'') or
|
||||
close() to patch up the sizes in the header.
|
||||
Marks can be added anytime. If there are any marks, you must call
|
||||
close() after all frames have been written.
|
||||
The close() method is called automatically when the class instance
|
||||
is destroyed.
|
||||
|
||||
When a file is opened with the extension '.aiff', an AIFF file is
|
||||
written, otherwise an AIFF-C file is written. This default can be
|
||||
changed by calling aiff() or aifc() before the first writeframes or
|
||||
writeframesraw.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import builtins
|
||||
import warnings
|
||||
|
||||
__all__ = ["Error", "open"]
|
||||
|
||||
|
||||
warnings._deprecated(__name__, remove=(3, 13))
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
_AIFC_version = 0xA2805140 # Version 1 of AIFF-C
|
||||
|
||||
def _read_long(file):
|
||||
try:
|
||||
return struct.unpack('>l', file.read(4))[0]
|
||||
except struct.error:
|
||||
raise EOFError from None
|
||||
|
||||
def _read_ulong(file):
|
||||
try:
|
||||
return struct.unpack('>L', file.read(4))[0]
|
||||
except struct.error:
|
||||
raise EOFError from None
|
||||
|
||||
def _read_short(file):
|
||||
try:
|
||||
return struct.unpack('>h', file.read(2))[0]
|
||||
except struct.error:
|
||||
raise EOFError from None
|
||||
|
||||
def _read_ushort(file):
|
||||
try:
|
||||
return struct.unpack('>H', file.read(2))[0]
|
||||
except struct.error:
|
||||
raise EOFError from None
|
||||
|
||||
def _read_string(file):
|
||||
length = ord(file.read(1))
|
||||
if length == 0:
|
||||
data = b''
|
||||
else:
|
||||
data = file.read(length)
|
||||
if length & 1 == 0:
|
||||
dummy = file.read(1)
|
||||
return data
|
||||
|
||||
_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
|
||||
|
||||
def _read_float(f): # 10 bytes
|
||||
expon = _read_short(f) # 2 bytes
|
||||
sign = 1
|
||||
if expon < 0:
|
||||
sign = -1
|
||||
expon = expon + 0x8000
|
||||
himant = _read_ulong(f) # 4 bytes
|
||||
lomant = _read_ulong(f) # 4 bytes
|
||||
if expon == himant == lomant == 0:
|
||||
f = 0.0
|
||||
elif expon == 0x7FFF:
|
||||
f = _HUGE_VAL
|
||||
else:
|
||||
expon = expon - 16383
|
||||
f = (himant * 0x100000000 + lomant) * pow(2.0, expon - 63)
|
||||
return sign * f
|
||||
|
||||
def _write_short(f, x):
|
||||
f.write(struct.pack('>h', x))
|
||||
|
||||
def _write_ushort(f, x):
|
||||
f.write(struct.pack('>H', x))
|
||||
|
||||
def _write_long(f, x):
|
||||
f.write(struct.pack('>l', x))
|
||||
|
||||
def _write_ulong(f, x):
|
||||
f.write(struct.pack('>L', x))
|
||||
|
||||
def _write_string(f, s):
|
||||
if len(s) > 255:
|
||||
raise ValueError("string exceeds maximum pstring length")
|
||||
f.write(struct.pack('B', len(s)))
|
||||
f.write(s)
|
||||
if len(s) & 1 == 0:
|
||||
f.write(b'\x00')
|
||||
|
||||
def _write_float(f, x):
|
||||
import math
|
||||
if x < 0:
|
||||
sign = 0x8000
|
||||
x = x * -1
|
||||
else:
|
||||
sign = 0
|
||||
if x == 0:
|
||||
expon = 0
|
||||
himant = 0
|
||||
lomant = 0
|
||||
else:
|
||||
fmant, expon = math.frexp(x)
|
||||
if expon > 16384 or fmant >= 1 or fmant != fmant: # Infinity or NaN
|
||||
expon = sign|0x7FFF
|
||||
himant = 0
|
||||
lomant = 0
|
||||
else: # Finite
|
||||
expon = expon + 16382
|
||||
if expon < 0: # denormalized
|
||||
fmant = math.ldexp(fmant, expon)
|
||||
expon = 0
|
||||
expon = expon | sign
|
||||
fmant = math.ldexp(fmant, 32)
|
||||
fsmant = math.floor(fmant)
|
||||
himant = int(fsmant)
|
||||
fmant = math.ldexp(fmant - fsmant, 32)
|
||||
fsmant = math.floor(fmant)
|
||||
lomant = int(fsmant)
|
||||
_write_ushort(f, expon)
|
||||
_write_ulong(f, himant)
|
||||
_write_ulong(f, lomant)
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", DeprecationWarning)
|
||||
from chunk import Chunk
|
||||
from collections import namedtuple
|
||||
|
||||
_aifc_params = namedtuple('_aifc_params',
|
||||
'nchannels sampwidth framerate nframes comptype compname')
|
||||
|
||||
_aifc_params.nchannels.__doc__ = 'Number of audio channels (1 for mono, 2 for stereo)'
|
||||
_aifc_params.sampwidth.__doc__ = 'Sample width in bytes'
|
||||
_aifc_params.framerate.__doc__ = 'Sampling frequency'
|
||||
_aifc_params.nframes.__doc__ = 'Number of audio frames'
|
||||
_aifc_params.comptype.__doc__ = 'Compression type ("NONE" for AIFF files)'
|
||||
_aifc_params.compname.__doc__ = ("""\
|
||||
A human-readable version of the compression type
|
||||
('not compressed' for AIFF files)""")
|
||||
|
||||
|
||||
class Aifc_read:
|
||||
# Variables used in this class:
|
||||
#
|
||||
# These variables are available to the user though appropriate
|
||||
# methods of this class:
|
||||
# _file -- the open file with methods read(), close(), and seek()
|
||||
# set through the __init__() method
|
||||
# _nchannels -- the number of audio channels
|
||||
# available through the getnchannels() method
|
||||
# _nframes -- the number of audio frames
|
||||
# available through the getnframes() method
|
||||
# _sampwidth -- the number of bytes per audio sample
|
||||
# available through the getsampwidth() method
|
||||
# _framerate -- the sampling frequency
|
||||
# available through the getframerate() method
|
||||
# _comptype -- the AIFF-C compression type ('NONE' if AIFF)
|
||||
# available through the getcomptype() method
|
||||
# _compname -- the human-readable AIFF-C compression type
|
||||
# available through the getcomptype() method
|
||||
# _markers -- the marks in the audio file
|
||||
# available through the getmarkers() and getmark()
|
||||
# methods
|
||||
# _soundpos -- the position in the audio stream
|
||||
# available through the tell() method, set through the
|
||||
# setpos() method
|
||||
#
|
||||
# These variables are used internally only:
|
||||
# _version -- the AIFF-C version number
|
||||
# _decomp -- the decompressor from builtin module cl
|
||||
# _comm_chunk_read -- 1 iff the COMM chunk has been read
|
||||
# _aifc -- 1 iff reading an AIFF-C file
|
||||
# _ssnd_seek_needed -- 1 iff positioned correctly in audio
|
||||
# file for readframes()
|
||||
# _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
|
||||
# _framesize -- size of one frame in the file
|
||||
|
||||
_file = None # Set here since __del__ checks it
|
||||
|
||||
def initfp(self, file):
|
||||
self._version = 0
|
||||
self._convert = None
|
||||
self._markers = []
|
||||
self._soundpos = 0
|
||||
self._file = file
|
||||
chunk = Chunk(file)
|
||||
if chunk.getname() != b'FORM':
|
||||
raise Error('file does not start with FORM id')
|
||||
formdata = chunk.read(4)
|
||||
if formdata == b'AIFF':
|
||||
self._aifc = 0
|
||||
elif formdata == b'AIFC':
|
||||
self._aifc = 1
|
||||
else:
|
||||
raise Error('not an AIFF or AIFF-C file')
|
||||
self._comm_chunk_read = 0
|
||||
self._ssnd_chunk = None
|
||||
while 1:
|
||||
self._ssnd_seek_needed = 1
|
||||
try:
|
||||
chunk = Chunk(self._file)
|
||||
except EOFError:
|
||||
break
|
||||
chunkname = chunk.getname()
|
||||
if chunkname == b'COMM':
|
||||
self._read_comm_chunk(chunk)
|
||||
self._comm_chunk_read = 1
|
||||
elif chunkname == b'SSND':
|
||||
self._ssnd_chunk = chunk
|
||||
dummy = chunk.read(8)
|
||||
self._ssnd_seek_needed = 0
|
||||
elif chunkname == b'FVER':
|
||||
self._version = _read_ulong(chunk)
|
||||
elif chunkname == b'MARK':
|
||||
self._readmark(chunk)
|
||||
chunk.skip()
|
||||
if not self._comm_chunk_read or not self._ssnd_chunk:
|
||||
raise Error('COMM chunk and/or SSND chunk missing')
|
||||
|
||||
def __init__(self, f):
|
||||
if isinstance(f, str):
|
||||
file_object = builtins.open(f, 'rb')
|
||||
try:
|
||||
self.initfp(file_object)
|
||||
except:
|
||||
file_object.close()
|
||||
raise
|
||||
else:
|
||||
# assume it is an open file object already
|
||||
self.initfp(f)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
#
|
||||
# User visible methods.
|
||||
#
|
||||
def getfp(self):
|
||||
return self._file
|
||||
|
||||
def rewind(self):
|
||||
self._ssnd_seek_needed = 1
|
||||
self._soundpos = 0
|
||||
|
||||
def close(self):
|
||||
file = self._file
|
||||
if file is not None:
|
||||
self._file = None
|
||||
file.close()
|
||||
|
||||
def tell(self):
|
||||
return self._soundpos
|
||||
|
||||
def getnchannels(self):
|
||||
return self._nchannels
|
||||
|
||||
def getnframes(self):
|
||||
return self._nframes
|
||||
|
||||
def getsampwidth(self):
|
||||
return self._sampwidth
|
||||
|
||||
def getframerate(self):
|
||||
return self._framerate
|
||||
|
||||
def getcomptype(self):
|
||||
return self._comptype
|
||||
|
||||
def getcompname(self):
|
||||
return self._compname
|
||||
|
||||
## def getversion(self):
|
||||
## return self._version
|
||||
|
||||
def getparams(self):
|
||||
return _aifc_params(self.getnchannels(), self.getsampwidth(),
|
||||
self.getframerate(), self.getnframes(),
|
||||
self.getcomptype(), self.getcompname())
|
||||
|
||||
def getmarkers(self):
|
||||
if len(self._markers) == 0:
|
||||
return None
|
||||
return self._markers
|
||||
|
||||
def getmark(self, id):
|
||||
for marker in self._markers:
|
||||
if id == marker[0]:
|
||||
return marker
|
||||
raise Error('marker {0!r} does not exist'.format(id))
|
||||
|
||||
def setpos(self, pos):
|
||||
if pos < 0 or pos > self._nframes:
|
||||
raise Error('position not in range')
|
||||
self._soundpos = pos
|
||||
self._ssnd_seek_needed = 1
|
||||
|
||||
def readframes(self, nframes):
|
||||
if self._ssnd_seek_needed:
|
||||
self._ssnd_chunk.seek(0)
|
||||
dummy = self._ssnd_chunk.read(8)
|
||||
pos = self._soundpos * self._framesize
|
||||
if pos:
|
||||
self._ssnd_chunk.seek(pos + 8)
|
||||
self._ssnd_seek_needed = 0
|
||||
if nframes == 0:
|
||||
return b''
|
||||
data = self._ssnd_chunk.read(nframes * self._framesize)
|
||||
if self._convert and data:
|
||||
data = self._convert(data)
|
||||
self._soundpos = self._soundpos + len(data) // (self._nchannels
|
||||
* self._sampwidth)
|
||||
return data
|
||||
|
||||
#
|
||||
# Internal methods.
|
||||
#
|
||||
|
||||
def _alaw2lin(self, data):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||
import audioop
|
||||
return audioop.alaw2lin(data, 2)
|
||||
|
||||
def _ulaw2lin(self, data):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||
import audioop
|
||||
return audioop.ulaw2lin(data, 2)
|
||||
|
||||
def _adpcm2lin(self, data):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||
import audioop
|
||||
if not hasattr(self, '_adpcmstate'):
|
||||
# first time
|
||||
self._adpcmstate = None
|
||||
data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
|
||||
return data
|
||||
|
||||
def _sowt2lin(self, data):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||
import audioop
|
||||
return audioop.byteswap(data, 2)
|
||||
|
||||
def _read_comm_chunk(self, chunk):
|
||||
self._nchannels = _read_short(chunk)
|
||||
self._nframes = _read_long(chunk)
|
||||
self._sampwidth = (_read_short(chunk) + 7) // 8
|
||||
self._framerate = int(_read_float(chunk))
|
||||
if self._sampwidth <= 0:
|
||||
raise Error('bad sample width')
|
||||
if self._nchannels <= 0:
|
||||
raise Error('bad # of channels')
|
||||
self._framesize = self._nchannels * self._sampwidth
|
||||
if self._aifc:
|
||||
#DEBUG: SGI's soundeditor produces a bad size :-(
|
||||
kludge = 0
|
||||
if chunk.chunksize == 18:
|
||||
kludge = 1
|
||||
warnings.warn('Warning: bad COMM chunk size')
|
||||
chunk.chunksize = 23
|
||||
#DEBUG end
|
||||
self._comptype = chunk.read(4)
|
||||
#DEBUG start
|
||||
if kludge:
|
||||
length = ord(chunk.file.read(1))
|
||||
if length & 1 == 0:
|
||||
length = length + 1
|
||||
chunk.chunksize = chunk.chunksize + length
|
||||
chunk.file.seek(-1, 1)
|
||||
#DEBUG end
|
||||
self._compname = _read_string(chunk)
|
||||
if self._comptype != b'NONE':
|
||||
if self._comptype == b'G722':
|
||||
self._convert = self._adpcm2lin
|
||||
elif self._comptype in (b'ulaw', b'ULAW'):
|
||||
self._convert = self._ulaw2lin
|
||||
elif self._comptype in (b'alaw', b'ALAW'):
|
||||
self._convert = self._alaw2lin
|
||||
elif self._comptype in (b'sowt', b'SOWT'):
|
||||
self._convert = self._sowt2lin
|
||||
else:
|
||||
raise Error('unsupported compression type')
|
||||
self._sampwidth = 2
|
||||
else:
|
||||
self._comptype = b'NONE'
|
||||
self._compname = b'not compressed'
|
||||
|
||||
def _readmark(self, chunk):
|
||||
nmarkers = _read_short(chunk)
|
||||
# Some files appear to contain invalid counts.
|
||||
# Cope with this by testing for EOF.
|
||||
try:
|
||||
for i in range(nmarkers):
|
||||
id = _read_short(chunk)
|
||||
pos = _read_long(chunk)
|
||||
name = _read_string(chunk)
|
||||
if pos or name:
|
||||
# some files appear to have
|
||||
# dummy markers consisting of
|
||||
# a position 0 and name ''
|
||||
self._markers.append((id, pos, name))
|
||||
except EOFError:
|
||||
w = ('Warning: MARK chunk contains only %s marker%s instead of %s' %
|
||||
(len(self._markers), '' if len(self._markers) == 1 else 's',
|
||||
nmarkers))
|
||||
warnings.warn(w)
|
||||
|
||||
class Aifc_write:
|
||||
# Variables used in this class:
|
||||
#
|
||||
# These variables are user settable through appropriate methods
|
||||
# of this class:
|
||||
# _file -- the open file with methods write(), close(), tell(), seek()
|
||||
# set through the __init__() method
|
||||
# _comptype -- the AIFF-C compression type ('NONE' in AIFF)
|
||||
# set through the setcomptype() or setparams() method
|
||||
# _compname -- the human-readable AIFF-C compression type
|
||||
# set through the setcomptype() or setparams() method
|
||||
# _nchannels -- the number of audio channels
|
||||
# set through the setnchannels() or setparams() method
|
||||
# _sampwidth -- the number of bytes per audio sample
|
||||
# set through the setsampwidth() or setparams() method
|
||||
# _framerate -- the sampling frequency
|
||||
# set through the setframerate() or setparams() method
|
||||
# _nframes -- the number of audio frames written to the header
|
||||
# set through the setnframes() or setparams() method
|
||||
# _aifc -- whether we're writing an AIFF-C file or an AIFF file
|
||||
# set through the aifc() method, reset through the
|
||||
# aiff() method
|
||||
#
|
||||
# These variables are used internally only:
|
||||
# _version -- the AIFF-C version number
|
||||
# _comp -- the compressor from builtin module cl
|
||||
# _nframeswritten -- the number of audio frames actually written
|
||||
# _datalength -- the size of the audio samples written to the header
|
||||
# _datawritten -- the size of the audio samples actually written
|
||||
|
||||
_file = None # Set here since __del__ checks it
|
||||
|
||||
def __init__(self, f):
|
||||
if isinstance(f, str):
|
||||
file_object = builtins.open(f, 'wb')
|
||||
try:
|
||||
self.initfp(file_object)
|
||||
except:
|
||||
file_object.close()
|
||||
raise
|
||||
|
||||
# treat .aiff file extensions as non-compressed audio
|
||||
if f.endswith('.aiff'):
|
||||
self._aifc = 0
|
||||
else:
|
||||
# assume it is an open file object already
|
||||
self.initfp(f)
|
||||
|
||||
def initfp(self, file):
|
||||
self._file = file
|
||||
self._version = _AIFC_version
|
||||
self._comptype = b'NONE'
|
||||
self._compname = b'not compressed'
|
||||
self._convert = None
|
||||
self._nchannels = 0
|
||||
self._sampwidth = 0
|
||||
self._framerate = 0
|
||||
self._nframes = 0
|
||||
self._nframeswritten = 0
|
||||
self._datawritten = 0
|
||||
self._datalength = 0
|
||||
self._markers = []
|
||||
self._marklength = 0
|
||||
self._aifc = 1 # AIFF-C is default
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
#
|
||||
# User visible methods.
|
||||
#
|
||||
def aiff(self):
|
||||
if self._nframeswritten:
|
||||
raise Error('cannot change parameters after starting to write')
|
||||
self._aifc = 0
|
||||
|
||||
def aifc(self):
|
||||
if self._nframeswritten:
|
||||
raise Error('cannot change parameters after starting to write')
|
||||
self._aifc = 1
|
||||
|
||||
def setnchannels(self, nchannels):
|
||||
if self._nframeswritten:
|
||||
raise Error('cannot change parameters after starting to write')
|
||||
if nchannels < 1:
|
||||
raise Error('bad # of channels')
|
||||
self._nchannels = nchannels
|
||||
|
||||
def getnchannels(self):
|
||||
if not self._nchannels:
|
||||
raise Error('number of channels not set')
|
||||
return self._nchannels
|
||||
|
||||
def setsampwidth(self, sampwidth):
|
||||
if self._nframeswritten:
|
||||
raise Error('cannot change parameters after starting to write')
|
||||
if sampwidth < 1 or sampwidth > 4:
|
||||
raise Error('bad sample width')
|
||||
self._sampwidth = sampwidth
|
||||
|
||||
def getsampwidth(self):
|
||||
if not self._sampwidth:
|
||||
raise Error('sample width not set')
|
||||
return self._sampwidth
|
||||
|
||||
def setframerate(self, framerate):
|
||||
if self._nframeswritten:
|
||||
raise Error('cannot change parameters after starting to write')
|
||||
if framerate <= 0:
|
||||
raise Error('bad frame rate')
|
||||
self._framerate = framerate
|
||||
|
||||
def getframerate(self):
|
||||
if not self._framerate:
|
||||
raise Error('frame rate not set')
|
||||
return self._framerate
|
||||
|
||||
def setnframes(self, nframes):
|
||||
if self._nframeswritten:
|
||||
raise Error('cannot change parameters after starting to write')
|
||||
self._nframes = nframes
|
||||
|
||||
def getnframes(self):
|
||||
return self._nframeswritten
|
||||
|
||||
def setcomptype(self, comptype, compname):
|
||||
if self._nframeswritten:
|
||||
raise Error('cannot change parameters after starting to write')
|
||||
if comptype not in (b'NONE', b'ulaw', b'ULAW',
|
||||
b'alaw', b'ALAW', b'G722', b'sowt', b'SOWT'):
|
||||
raise Error('unsupported compression type')
|
||||
self._comptype = comptype
|
||||
self._compname = compname
|
||||
|
||||
def getcomptype(self):
|
||||
return self._comptype
|
||||
|
||||
def getcompname(self):
|
||||
return self._compname
|
||||
|
||||
## def setversion(self, version):
|
||||
## if self._nframeswritten:
|
||||
## raise Error, 'cannot change parameters after starting to write'
|
||||
## self._version = version
|
||||
|
||||
def setparams(self, params):
|
||||
nchannels, sampwidth, framerate, nframes, comptype, compname = params
|
||||
if self._nframeswritten:
|
||||
raise Error('cannot change parameters after starting to write')
|
||||
if comptype not in (b'NONE', b'ulaw', b'ULAW',
|
||||
b'alaw', b'ALAW', b'G722', b'sowt', b'SOWT'):
|
||||
raise Error('unsupported compression type')
|
||||
self.setnchannels(nchannels)
|
||||
self.setsampwidth(sampwidth)
|
||||
self.setframerate(framerate)
|
||||
self.setnframes(nframes)
|
||||
self.setcomptype(comptype, compname)
|
||||
|
||||
def getparams(self):
|
||||
if not self._nchannels or not self._sampwidth or not self._framerate:
|
||||
raise Error('not all parameters set')
|
||||
return _aifc_params(self._nchannels, self._sampwidth, self._framerate,
|
||||
self._nframes, self._comptype, self._compname)
|
||||
|
||||
def setmark(self, id, pos, name):
|
||||
if id <= 0:
|
||||
raise Error('marker ID must be > 0')
|
||||
if pos < 0:
|
||||
raise Error('marker position must be >= 0')
|
||||
if not isinstance(name, bytes):
|
||||
raise Error('marker name must be bytes')
|
||||
for i in range(len(self._markers)):
|
||||
if id == self._markers[i][0]:
|
||||
self._markers[i] = id, pos, name
|
||||
return
|
||||
self._markers.append((id, pos, name))
|
||||
|
||||
def getmark(self, id):
|
||||
for marker in self._markers:
|
||||
if id == marker[0]:
|
||||
return marker
|
||||
raise Error('marker {0!r} does not exist'.format(id))
|
||||
|
||||
def getmarkers(self):
|
||||
if len(self._markers) == 0:
|
||||
return None
|
||||
return self._markers
|
||||
|
||||
def tell(self):
|
||||
return self._nframeswritten
|
||||
|
||||
def writeframesraw(self, data):
|
||||
if not isinstance(data, (bytes, bytearray)):
|
||||
data = memoryview(data).cast('B')
|
||||
self._ensure_header_written(len(data))
|
||||
nframes = len(data) // (self._sampwidth * self._nchannels)
|
||||
if self._convert:
|
||||
data = self._convert(data)
|
||||
self._file.write(data)
|
||||
self._nframeswritten = self._nframeswritten + nframes
|
||||
self._datawritten = self._datawritten + len(data)
|
||||
|
||||
def writeframes(self, data):
|
||||
self.writeframesraw(data)
|
||||
if self._nframeswritten != self._nframes or \
|
||||
self._datalength != self._datawritten:
|
||||
self._patchheader()
|
||||
|
||||
def close(self):
|
||||
if self._file is None:
|
||||
return
|
||||
try:
|
||||
self._ensure_header_written(0)
|
||||
if self._datawritten & 1:
|
||||
# quick pad to even size
|
||||
self._file.write(b'\x00')
|
||||
self._datawritten = self._datawritten + 1
|
||||
self._writemarkers()
|
||||
if self._nframeswritten != self._nframes or \
|
||||
self._datalength != self._datawritten or \
|
||||
self._marklength:
|
||||
self._patchheader()
|
||||
finally:
|
||||
# Prevent ref cycles
|
||||
self._convert = None
|
||||
f = self._file
|
||||
self._file = None
|
||||
f.close()
|
||||
|
||||
#
|
||||
# Internal methods.
|
||||
#
|
||||
|
||||
def _lin2alaw(self, data):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||
import audioop
|
||||
return audioop.lin2alaw(data, 2)
|
||||
|
||||
def _lin2ulaw(self, data):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||
import audioop
|
||||
return audioop.lin2ulaw(data, 2)
|
||||
|
||||
def _lin2adpcm(self, data):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||
import audioop
|
||||
if not hasattr(self, '_adpcmstate'):
|
||||
self._adpcmstate = None
|
||||
data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
|
||||
return data
|
||||
|
||||
def _lin2sowt(self, data):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||
import audioop
|
||||
return audioop.byteswap(data, 2)
|
||||
|
||||
def _ensure_header_written(self, datasize):
|
||||
if not self._nframeswritten:
|
||||
if self._comptype in (b'ULAW', b'ulaw',
|
||||
b'ALAW', b'alaw', b'G722',
|
||||
b'sowt', b'SOWT'):
|
||||
if not self._sampwidth:
|
||||
self._sampwidth = 2
|
||||
if self._sampwidth != 2:
|
||||
raise Error('sample width must be 2 when compressing '
|
||||
'with ulaw/ULAW, alaw/ALAW, sowt/SOWT '
|
||||
'or G7.22 (ADPCM)')
|
||||
if not self._nchannels:
|
||||
raise Error('# channels not specified')
|
||||
if not self._sampwidth:
|
||||
raise Error('sample width not specified')
|
||||
if not self._framerate:
|
||||
raise Error('sampling rate not specified')
|
||||
self._write_header(datasize)
|
||||
|
||||
def _init_compression(self):
|
||||
if self._comptype == b'G722':
|
||||
self._convert = self._lin2adpcm
|
||||
elif self._comptype in (b'ulaw', b'ULAW'):
|
||||
self._convert = self._lin2ulaw
|
||||
elif self._comptype in (b'alaw', b'ALAW'):
|
||||
self._convert = self._lin2alaw
|
||||
elif self._comptype in (b'sowt', b'SOWT'):
|
||||
self._convert = self._lin2sowt
|
||||
|
||||
def _write_header(self, initlength):
|
||||
if self._aifc and self._comptype != b'NONE':
|
||||
self._init_compression()
|
||||
self._file.write(b'FORM')
|
||||
if not self._nframes:
|
||||
self._nframes = initlength // (self._nchannels * self._sampwidth)
|
||||
self._datalength = self._nframes * self._nchannels * self._sampwidth
|
||||
if self._datalength & 1:
|
||||
self._datalength = self._datalength + 1
|
||||
if self._aifc:
|
||||
if self._comptype in (b'ulaw', b'ULAW', b'alaw', b'ALAW'):
|
||||
self._datalength = self._datalength // 2
|
||||
if self._datalength & 1:
|
||||
self._datalength = self._datalength + 1
|
||||
elif self._comptype == b'G722':
|
||||
self._datalength = (self._datalength + 3) // 4
|
||||
if self._datalength & 1:
|
||||
self._datalength = self._datalength + 1
|
||||
try:
|
||||
self._form_length_pos = self._file.tell()
|
||||
except (AttributeError, OSError):
|
||||
self._form_length_pos = None
|
||||
commlength = self._write_form_length(self._datalength)
|
||||
if self._aifc:
|
||||
self._file.write(b'AIFC')
|
||||
self._file.write(b'FVER')
|
||||
_write_ulong(self._file, 4)
|
||||
_write_ulong(self._file, self._version)
|
||||
else:
|
||||
self._file.write(b'AIFF')
|
||||
self._file.write(b'COMM')
|
||||
_write_ulong(self._file, commlength)
|
||||
_write_short(self._file, self._nchannels)
|
||||
if self._form_length_pos is not None:
|
||||
self._nframes_pos = self._file.tell()
|
||||
_write_ulong(self._file, self._nframes)
|
||||
if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
|
||||
_write_short(self._file, 8)
|
||||
else:
|
||||
_write_short(self._file, self._sampwidth * 8)
|
||||
_write_float(self._file, self._framerate)
|
||||
if self._aifc:
|
||||
self._file.write(self._comptype)
|
||||
_write_string(self._file, self._compname)
|
||||
self._file.write(b'SSND')
|
||||
if self._form_length_pos is not None:
|
||||
self._ssnd_length_pos = self._file.tell()
|
||||
_write_ulong(self._file, self._datalength + 8)
|
||||
_write_ulong(self._file, 0)
|
||||
_write_ulong(self._file, 0)
|
||||
|
||||
def _write_form_length(self, datalength):
|
||||
if self._aifc:
|
||||
commlength = 18 + 5 + len(self._compname)
|
||||
if commlength & 1:
|
||||
commlength = commlength + 1
|
||||
verslength = 12
|
||||
else:
|
||||
commlength = 18
|
||||
verslength = 0
|
||||
_write_ulong(self._file, 4 + verslength + self._marklength + \
|
||||
8 + commlength + 16 + datalength)
|
||||
return commlength
|
||||
|
||||
def _patchheader(self):
|
||||
curpos = self._file.tell()
|
||||
if self._datawritten & 1:
|
||||
datalength = self._datawritten + 1
|
||||
self._file.write(b'\x00')
|
||||
else:
|
||||
datalength = self._datawritten
|
||||
if datalength == self._datalength and \
|
||||
self._nframes == self._nframeswritten and \
|
||||
self._marklength == 0:
|
||||
self._file.seek(curpos, 0)
|
||||
return
|
||||
self._file.seek(self._form_length_pos, 0)
|
||||
dummy = self._write_form_length(datalength)
|
||||
self._file.seek(self._nframes_pos, 0)
|
||||
_write_ulong(self._file, self._nframeswritten)
|
||||
self._file.seek(self._ssnd_length_pos, 0)
|
||||
_write_ulong(self._file, datalength + 8)
|
||||
self._file.seek(curpos, 0)
|
||||
self._nframes = self._nframeswritten
|
||||
self._datalength = datalength
|
||||
|
||||
def _writemarkers(self):
|
||||
if len(self._markers) == 0:
|
||||
return
|
||||
self._file.write(b'MARK')
|
||||
length = 2
|
||||
for marker in self._markers:
|
||||
id, pos, name = marker
|
||||
length = length + len(name) + 1 + 6
|
||||
if len(name) & 1 == 0:
|
||||
length = length + 1
|
||||
_write_ulong(self._file, length)
|
||||
self._marklength = length + 8
|
||||
_write_short(self._file, len(self._markers))
|
||||
for marker in self._markers:
|
||||
id, pos, name = marker
|
||||
_write_short(self._file, id)
|
||||
_write_ulong(self._file, pos)
|
||||
_write_string(self._file, name)
|
||||
|
||||
def open(f, mode=None):
|
||||
if mode is None:
|
||||
if hasattr(f, 'mode'):
|
||||
mode = f.mode
|
||||
else:
|
||||
mode = 'rb'
|
||||
if mode in ('r', 'rb'):
|
||||
return Aifc_read(f)
|
||||
elif mode in ('w', 'wb'):
|
||||
return Aifc_write(f)
|
||||
else:
|
||||
raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if not sys.argv[1:]:
|
||||
sys.argv.append('/usr/demos/data/audio/bach.aiff')
|
||||
fn = sys.argv[1]
|
||||
with open(fn, 'r') as f:
|
||||
print("Reading", fn)
|
||||
print("nchannels =", f.getnchannels())
|
||||
print("nframes =", f.getnframes())
|
||||
print("sampwidth =", f.getsampwidth())
|
||||
print("framerate =", f.getframerate())
|
||||
print("comptype =", f.getcomptype())
|
||||
print("compname =", f.getcompname())
|
||||
if sys.argv[2:]:
|
||||
gn = sys.argv[2]
|
||||
print("Writing", gn)
|
||||
with open(gn, 'w') as g:
|
||||
g.setparams(f.getparams())
|
||||
while 1:
|
||||
data = f.readframes(1024)
|
||||
if not data:
|
||||
break
|
||||
g.writeframes(data)
|
||||
print("Done.")
|
||||
1197
Lib/annotationlib.py
vendored
Normal file
1197
Lib/annotationlib.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1051
Lib/argparse.py
vendored
1051
Lib/argparse.py
vendored
File diff suppressed because it is too large
Load Diff
1489
Lib/ast.py
vendored
1489
Lib/ast.py
vendored
File diff suppressed because it is too large
Load Diff
307
Lib/asynchat.py
vendored
307
Lib/asynchat.py
vendored
@@ -1,307 +0,0 @@
|
||||
# -*- Mode: Python; tab-width: 4 -*-
|
||||
# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
|
||||
# Author: Sam Rushing <rushing@nightmare.com>
|
||||
|
||||
# ======================================================================
|
||||
# Copyright 1996 by Sam Rushing
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose and without fee is hereby
|
||||
# granted, provided that the above copyright notice appear in all
|
||||
# copies and that both that copyright notice and this permission
|
||||
# notice appear in supporting documentation, and that the name of Sam
|
||||
# Rushing not be used in advertising or publicity pertaining to
|
||||
# distribution of the software without specific, written prior
|
||||
# permission.
|
||||
#
|
||||
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
||||
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
# ======================================================================
|
||||
|
||||
r"""A class supporting chat-style (command/response) protocols.
|
||||
|
||||
This class adds support for 'chat' style protocols - where one side
|
||||
sends a 'command', and the other sends a response (examples would be
|
||||
the common internet protocols - smtp, nntp, ftp, etc..).
|
||||
|
||||
The handle_read() method looks at the input stream for the current
|
||||
'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
|
||||
for multi-line output), calling self.found_terminator() on its
|
||||
receipt.
|
||||
|
||||
for example:
|
||||
Say you build an async nntp client using this class. At the start
|
||||
of the connection, you'll have self.terminator set to '\r\n', in
|
||||
order to process the single-line greeting. Just before issuing a
|
||||
'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
|
||||
command will be accumulated (using your own 'collect_incoming_data'
|
||||
method) up to the terminator, and then control will be returned to
|
||||
you - by calling your self.found_terminator() method.
|
||||
"""
|
||||
import asyncore
|
||||
from collections import deque
|
||||
|
||||
|
||||
class async_chat(asyncore.dispatcher):
|
||||
"""This is an abstract class. You must derive from this class, and add
|
||||
the two methods collect_incoming_data() and found_terminator()"""
|
||||
|
||||
# these are overridable defaults
|
||||
|
||||
ac_in_buffer_size = 65536
|
||||
ac_out_buffer_size = 65536
|
||||
|
||||
# we don't want to enable the use of encoding by default, because that is a
|
||||
# sign of an application bug that we don't want to pass silently
|
||||
|
||||
use_encoding = 0
|
||||
encoding = 'latin-1'
|
||||
|
||||
def __init__(self, sock=None, map=None):
|
||||
# for string terminator matching
|
||||
self.ac_in_buffer = b''
|
||||
|
||||
# we use a list here rather than io.BytesIO for a few reasons...
|
||||
# del lst[:] is faster than bio.truncate(0)
|
||||
# lst = [] is faster than bio.truncate(0)
|
||||
self.incoming = []
|
||||
|
||||
# we toss the use of the "simple producer" and replace it with
|
||||
# a pure deque, which the original fifo was a wrapping of
|
||||
self.producer_fifo = deque()
|
||||
asyncore.dispatcher.__init__(self, sock, map)
|
||||
|
||||
def collect_incoming_data(self, data):
|
||||
raise NotImplementedError("must be implemented in subclass")
|
||||
|
||||
def _collect_incoming_data(self, data):
|
||||
self.incoming.append(data)
|
||||
|
||||
def _get_data(self):
|
||||
d = b''.join(self.incoming)
|
||||
del self.incoming[:]
|
||||
return d
|
||||
|
||||
def found_terminator(self):
|
||||
raise NotImplementedError("must be implemented in subclass")
|
||||
|
||||
def set_terminator(self, term):
|
||||
"""Set the input delimiter.
|
||||
|
||||
Can be a fixed string of any length, an integer, or None.
|
||||
"""
|
||||
if isinstance(term, str) and self.use_encoding:
|
||||
term = bytes(term, self.encoding)
|
||||
elif isinstance(term, int) and term < 0:
|
||||
raise ValueError('the number of received bytes must be positive')
|
||||
self.terminator = term
|
||||
|
||||
def get_terminator(self):
|
||||
return self.terminator
|
||||
|
||||
# grab some more data from the socket,
|
||||
# throw it to the collector method,
|
||||
# check for the terminator,
|
||||
# if found, transition to the next state.
|
||||
|
||||
def handle_read(self):
|
||||
|
||||
try:
|
||||
data = self.recv(self.ac_in_buffer_size)
|
||||
except BlockingIOError:
|
||||
return
|
||||
except OSError as why:
|
||||
self.handle_error()
|
||||
return
|
||||
|
||||
if isinstance(data, str) and self.use_encoding:
|
||||
data = bytes(str, self.encoding)
|
||||
self.ac_in_buffer = self.ac_in_buffer + data
|
||||
|
||||
# Continue to search for self.terminator in self.ac_in_buffer,
|
||||
# while calling self.collect_incoming_data. The while loop
|
||||
# is necessary because we might read several data+terminator
|
||||
# combos with a single recv(4096).
|
||||
|
||||
while self.ac_in_buffer:
|
||||
lb = len(self.ac_in_buffer)
|
||||
terminator = self.get_terminator()
|
||||
if not terminator:
|
||||
# no terminator, collect it all
|
||||
self.collect_incoming_data(self.ac_in_buffer)
|
||||
self.ac_in_buffer = b''
|
||||
elif isinstance(terminator, int):
|
||||
# numeric terminator
|
||||
n = terminator
|
||||
if lb < n:
|
||||
self.collect_incoming_data(self.ac_in_buffer)
|
||||
self.ac_in_buffer = b''
|
||||
self.terminator = self.terminator - lb
|
||||
else:
|
||||
self.collect_incoming_data(self.ac_in_buffer[:n])
|
||||
self.ac_in_buffer = self.ac_in_buffer[n:]
|
||||
self.terminator = 0
|
||||
self.found_terminator()
|
||||
else:
|
||||
# 3 cases:
|
||||
# 1) end of buffer matches terminator exactly:
|
||||
# collect data, transition
|
||||
# 2) end of buffer matches some prefix:
|
||||
# collect data to the prefix
|
||||
# 3) end of buffer does not match any prefix:
|
||||
# collect data
|
||||
terminator_len = len(terminator)
|
||||
index = self.ac_in_buffer.find(terminator)
|
||||
if index != -1:
|
||||
# we found the terminator
|
||||
if index > 0:
|
||||
# don't bother reporting the empty string
|
||||
# (source of subtle bugs)
|
||||
self.collect_incoming_data(self.ac_in_buffer[:index])
|
||||
self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
|
||||
# This does the Right Thing if the terminator
|
||||
# is changed here.
|
||||
self.found_terminator()
|
||||
else:
|
||||
# check for a prefix of the terminator
|
||||
index = find_prefix_at_end(self.ac_in_buffer, terminator)
|
||||
if index:
|
||||
if index != lb:
|
||||
# we found a prefix, collect up to the prefix
|
||||
self.collect_incoming_data(self.ac_in_buffer[:-index])
|
||||
self.ac_in_buffer = self.ac_in_buffer[-index:]
|
||||
break
|
||||
else:
|
||||
# no prefix, collect it all
|
||||
self.collect_incoming_data(self.ac_in_buffer)
|
||||
self.ac_in_buffer = b''
|
||||
|
||||
def handle_write(self):
|
||||
self.initiate_send()
|
||||
|
||||
def handle_close(self):
|
||||
self.close()
|
||||
|
||||
def push(self, data):
|
||||
if not isinstance(data, (bytes, bytearray, memoryview)):
|
||||
raise TypeError('data argument must be byte-ish (%r)',
|
||||
type(data))
|
||||
sabs = self.ac_out_buffer_size
|
||||
if len(data) > sabs:
|
||||
for i in range(0, len(data), sabs):
|
||||
self.producer_fifo.append(data[i:i+sabs])
|
||||
else:
|
||||
self.producer_fifo.append(data)
|
||||
self.initiate_send()
|
||||
|
||||
def push_with_producer(self, producer):
|
||||
self.producer_fifo.append(producer)
|
||||
self.initiate_send()
|
||||
|
||||
def readable(self):
|
||||
"predicate for inclusion in the readable for select()"
|
||||
# cannot use the old predicate, it violates the claim of the
|
||||
# set_terminator method.
|
||||
|
||||
# return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
|
||||
return 1
|
||||
|
||||
def writable(self):
|
||||
"predicate for inclusion in the writable for select()"
|
||||
return self.producer_fifo or (not self.connected)
|
||||
|
||||
def close_when_done(self):
|
||||
"automatically close this channel once the outgoing queue is empty"
|
||||
self.producer_fifo.append(None)
|
||||
|
||||
def initiate_send(self):
|
||||
while self.producer_fifo and self.connected:
|
||||
first = self.producer_fifo[0]
|
||||
# handle empty string/buffer or None entry
|
||||
if not first:
|
||||
del self.producer_fifo[0]
|
||||
if first is None:
|
||||
self.handle_close()
|
||||
return
|
||||
|
||||
# handle classic producer behavior
|
||||
obs = self.ac_out_buffer_size
|
||||
try:
|
||||
data = first[:obs]
|
||||
except TypeError:
|
||||
data = first.more()
|
||||
if data:
|
||||
self.producer_fifo.appendleft(data)
|
||||
else:
|
||||
del self.producer_fifo[0]
|
||||
continue
|
||||
|
||||
if isinstance(data, str) and self.use_encoding:
|
||||
data = bytes(data, self.encoding)
|
||||
|
||||
# send the data
|
||||
try:
|
||||
num_sent = self.send(data)
|
||||
except OSError:
|
||||
self.handle_error()
|
||||
return
|
||||
|
||||
if num_sent:
|
||||
if num_sent < len(data) or obs < len(first):
|
||||
self.producer_fifo[0] = first[num_sent:]
|
||||
else:
|
||||
del self.producer_fifo[0]
|
||||
# we tried to send some actual data
|
||||
return
|
||||
|
||||
def discard_buffers(self):
|
||||
# Emergencies only!
|
||||
self.ac_in_buffer = b''
|
||||
del self.incoming[:]
|
||||
self.producer_fifo.clear()
|
||||
|
||||
|
||||
class simple_producer:
|
||||
|
||||
def __init__(self, data, buffer_size=512):
|
||||
self.data = data
|
||||
self.buffer_size = buffer_size
|
||||
|
||||
def more(self):
|
||||
if len(self.data) > self.buffer_size:
|
||||
result = self.data[:self.buffer_size]
|
||||
self.data = self.data[self.buffer_size:]
|
||||
return result
|
||||
else:
|
||||
result = self.data
|
||||
self.data = b''
|
||||
return result
|
||||
|
||||
|
||||
# Given 'haystack', see if any prefix of 'needle' is at its end. This
|
||||
# assumes an exact match has already been checked. Return the number of
|
||||
# characters matched.
|
||||
# for example:
|
||||
# f_p_a_e("qwerty\r", "\r\n") => 1
|
||||
# f_p_a_e("qwertydkjf", "\r\n") => 0
|
||||
# f_p_a_e("qwerty\r\n", "\r\n") => <undefined>
|
||||
|
||||
# this could maybe be made faster with a computed regex?
|
||||
# [answer: no; circa Python-2.0, Jan 2001]
|
||||
# new python: 28961/s
|
||||
# old python: 18307/s
|
||||
# re: 12820/s
|
||||
# regex: 14035/s
|
||||
|
||||
def find_prefix_at_end(haystack, needle):
|
||||
l = len(needle) - 1
|
||||
while l and not haystack.endswith(needle[:l]):
|
||||
l -= 1
|
||||
return l
|
||||
46
Lib/asyncio/__init__.py
vendored
46
Lib/asyncio/__init__.py
vendored
@@ -1,23 +1,16 @@
|
||||
"""The asyncio package, tracking PEP 3156."""
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
import sys
|
||||
|
||||
import selectors
|
||||
# XXX RustPython TODO: _overlapped
|
||||
if sys.platform == 'win32' and False:
|
||||
# Similar thing for _overlapped.
|
||||
try:
|
||||
from . import _overlapped
|
||||
except ImportError:
|
||||
import _overlapped # Will also be exported.
|
||||
|
||||
|
||||
# This relies on each of the submodules having an __all__ variable.
|
||||
from .base_events import *
|
||||
from .coroutines import *
|
||||
from .events import *
|
||||
from .exceptions import *
|
||||
from .futures import *
|
||||
from .graph import *
|
||||
from .locks import *
|
||||
from .protocols import *
|
||||
from .runners import *
|
||||
@@ -25,12 +18,17 @@ from .queues import *
|
||||
from .streams import *
|
||||
from .subprocess import *
|
||||
from .tasks import *
|
||||
from .taskgroups import *
|
||||
from .timeouts import *
|
||||
from .threads import *
|
||||
from .transports import *
|
||||
|
||||
__all__ = (base_events.__all__ +
|
||||
coroutines.__all__ +
|
||||
events.__all__ +
|
||||
exceptions.__all__ +
|
||||
futures.__all__ +
|
||||
graph.__all__ +
|
||||
locks.__all__ +
|
||||
protocols.__all__ +
|
||||
runners.__all__ +
|
||||
@@ -38,6 +36,9 @@ __all__ = (base_events.__all__ +
|
||||
streams.__all__ +
|
||||
subprocess.__all__ +
|
||||
tasks.__all__ +
|
||||
taskgroups.__all__ +
|
||||
threads.__all__ +
|
||||
timeouts.__all__ +
|
||||
transports.__all__)
|
||||
|
||||
if sys.platform == 'win32': # pragma: no cover
|
||||
@@ -46,3 +47,28 @@ if sys.platform == 'win32': # pragma: no cover
|
||||
else:
|
||||
from .unix_events import * # pragma: no cover
|
||||
__all__ += unix_events.__all__
|
||||
|
||||
def __getattr__(name: str):
|
||||
import warnings
|
||||
|
||||
match name:
|
||||
case "AbstractEventLoopPolicy":
|
||||
warnings._deprecated(f"asyncio.{name}", remove=(3, 16))
|
||||
return events._AbstractEventLoopPolicy
|
||||
case "DefaultEventLoopPolicy":
|
||||
warnings._deprecated(f"asyncio.{name}", remove=(3, 16))
|
||||
if sys.platform == 'win32':
|
||||
return windows_events._DefaultEventLoopPolicy
|
||||
return unix_events._DefaultEventLoopPolicy
|
||||
case "WindowsSelectorEventLoopPolicy":
|
||||
if sys.platform == 'win32':
|
||||
warnings._deprecated(f"asyncio.{name}", remove=(3, 16))
|
||||
return windows_events._WindowsSelectorEventLoopPolicy
|
||||
# Else fall through to the AttributeError below.
|
||||
case "WindowsProactorEventLoopPolicy":
|
||||
if sys.platform == 'win32':
|
||||
warnings._deprecated(f"asyncio.{name}", remove=(3, 16))
|
||||
return windows_events._WindowsProactorEventLoopPolicy
|
||||
# Else fall through to the AttributeError below.
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
239
Lib/asyncio/__main__.py
vendored
Normal file
239
Lib/asyncio/__main__.py
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
import argparse
|
||||
import ast
|
||||
import asyncio
|
||||
import asyncio.tools
|
||||
import concurrent.futures
|
||||
import contextvars
|
||||
import inspect
|
||||
import os
|
||||
import site
|
||||
import sys
|
||||
import threading
|
||||
import types
|
||||
import warnings
|
||||
|
||||
from _colorize import get_theme
|
||||
from _pyrepl.console import InteractiveColoredConsole
|
||||
|
||||
from . import futures
|
||||
|
||||
|
||||
class AsyncIOInteractiveConsole(InteractiveColoredConsole):
|
||||
|
||||
def __init__(self, locals, loop):
|
||||
super().__init__(locals, filename="<stdin>")
|
||||
self.compile.compiler.flags |= ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
|
||||
|
||||
self.loop = loop
|
||||
self.context = contextvars.copy_context()
|
||||
|
||||
def runcode(self, code):
|
||||
global return_code
|
||||
future = concurrent.futures.Future()
|
||||
|
||||
def callback():
|
||||
global return_code
|
||||
global repl_future
|
||||
global keyboard_interrupted
|
||||
|
||||
repl_future = None
|
||||
keyboard_interrupted = False
|
||||
|
||||
func = types.FunctionType(code, self.locals)
|
||||
try:
|
||||
coro = func()
|
||||
except SystemExit as se:
|
||||
return_code = se.code
|
||||
self.loop.stop()
|
||||
return
|
||||
except KeyboardInterrupt as ex:
|
||||
keyboard_interrupted = True
|
||||
future.set_exception(ex)
|
||||
return
|
||||
except BaseException as ex:
|
||||
future.set_exception(ex)
|
||||
return
|
||||
|
||||
if not inspect.iscoroutine(coro):
|
||||
future.set_result(coro)
|
||||
return
|
||||
|
||||
try:
|
||||
repl_future = self.loop.create_task(coro, context=self.context)
|
||||
futures._chain_future(repl_future, future)
|
||||
except BaseException as exc:
|
||||
future.set_exception(exc)
|
||||
|
||||
self.loop.call_soon_threadsafe(callback, context=self.context)
|
||||
|
||||
try:
|
||||
return future.result()
|
||||
except SystemExit as se:
|
||||
return_code = se.code
|
||||
self.loop.stop()
|
||||
return
|
||||
except BaseException:
|
||||
if keyboard_interrupted:
|
||||
if not CAN_USE_PYREPL:
|
||||
self.write("\nKeyboardInterrupt\n")
|
||||
else:
|
||||
self.showtraceback()
|
||||
return self.STATEMENT_FAILED
|
||||
|
||||
class REPLThread(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
global return_code
|
||||
|
||||
try:
|
||||
banner = (
|
||||
f'asyncio REPL {sys.version} on {sys.platform}\n'
|
||||
f'Use "await" directly instead of "asyncio.run()".\n'
|
||||
f'Type "help", "copyright", "credits" or "license" '
|
||||
f'for more information.\n'
|
||||
)
|
||||
|
||||
console.write(banner)
|
||||
|
||||
if startup_path := os.getenv("PYTHONSTARTUP"):
|
||||
sys.audit("cpython.run_startup", startup_path)
|
||||
|
||||
import tokenize
|
||||
with tokenize.open(startup_path) as f:
|
||||
startup_code = compile(f.read(), startup_path, "exec")
|
||||
exec(startup_code, console.locals)
|
||||
|
||||
ps1 = getattr(sys, "ps1", ">>> ")
|
||||
if CAN_USE_PYREPL:
|
||||
theme = get_theme().syntax
|
||||
ps1 = f"{theme.prompt}{ps1}{theme.reset}"
|
||||
console.write(f"{ps1}import asyncio\n")
|
||||
|
||||
if CAN_USE_PYREPL:
|
||||
from _pyrepl.simple_interact import (
|
||||
run_multiline_interactive_console,
|
||||
)
|
||||
try:
|
||||
run_multiline_interactive_console(console)
|
||||
except SystemExit:
|
||||
# expected via the `exit` and `quit` commands
|
||||
pass
|
||||
except BaseException:
|
||||
# unexpected issue
|
||||
console.showtraceback()
|
||||
console.write("Internal error, ")
|
||||
return_code = 1
|
||||
else:
|
||||
console.interact(banner="", exitmsg="")
|
||||
finally:
|
||||
warnings.filterwarnings(
|
||||
'ignore',
|
||||
message=r'^coroutine .* was never awaited$',
|
||||
category=RuntimeWarning)
|
||||
|
||||
loop.call_soon_threadsafe(loop.stop)
|
||||
|
||||
def interrupt(self) -> None:
|
||||
if not CAN_USE_PYREPL:
|
||||
return
|
||||
|
||||
from _pyrepl.simple_interact import _get_reader
|
||||
r = _get_reader()
|
||||
if r.threading_hook is not None:
|
||||
r.threading_hook.add("") # type: ignore
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="python3 -m asyncio",
|
||||
description="Interactive asyncio shell and CLI tools",
|
||||
color=True,
|
||||
)
|
||||
subparsers = parser.add_subparsers(help="sub-commands", dest="command")
|
||||
ps = subparsers.add_parser(
|
||||
"ps", help="Display a table of all pending tasks in a process"
|
||||
)
|
||||
ps.add_argument("pid", type=int, help="Process ID to inspect")
|
||||
pstree = subparsers.add_parser(
|
||||
"pstree", help="Display a tree of all pending tasks in a process"
|
||||
)
|
||||
pstree.add_argument("pid", type=int, help="Process ID to inspect")
|
||||
args = parser.parse_args()
|
||||
match args.command:
|
||||
case "ps":
|
||||
asyncio.tools.display_awaited_by_tasks_table(args.pid)
|
||||
sys.exit(0)
|
||||
case "pstree":
|
||||
asyncio.tools.display_awaited_by_tasks_tree(args.pid)
|
||||
sys.exit(0)
|
||||
case None:
|
||||
pass # continue to the interactive shell
|
||||
case _:
|
||||
# shouldn't happen as an invalid command-line wouldn't parse
|
||||
# but let's keep it for the next person adding a command
|
||||
print(f"error: unhandled command {args.command}", file=sys.stderr)
|
||||
parser.print_usage(file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
sys.audit("cpython.run_stdin")
|
||||
|
||||
if os.getenv('PYTHON_BASIC_REPL'):
|
||||
CAN_USE_PYREPL = False
|
||||
else:
|
||||
from _pyrepl.main import CAN_USE_PYREPL
|
||||
|
||||
return_code = 0
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
repl_locals = {'asyncio': asyncio}
|
||||
for key in {'__name__', '__package__',
|
||||
'__loader__', '__spec__',
|
||||
'__builtins__', '__file__'}:
|
||||
repl_locals[key] = locals()[key]
|
||||
|
||||
console = AsyncIOInteractiveConsole(repl_locals, loop)
|
||||
|
||||
repl_future = None
|
||||
keyboard_interrupted = False
|
||||
|
||||
try:
|
||||
import readline # NoQA
|
||||
except ImportError:
|
||||
readline = None
|
||||
|
||||
interactive_hook = getattr(sys, "__interactivehook__", None)
|
||||
|
||||
if interactive_hook is not None:
|
||||
sys.audit("cpython.run_interactivehook", interactive_hook)
|
||||
interactive_hook()
|
||||
|
||||
if interactive_hook is site.register_readline:
|
||||
# Fix the completer function to use the interactive console locals
|
||||
try:
|
||||
import rlcompleter
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if readline is not None:
|
||||
completer = rlcompleter.Completer(console.locals)
|
||||
readline.set_completer(completer.complete)
|
||||
|
||||
repl_thread = REPLThread(name="Interactive thread")
|
||||
repl_thread.daemon = True
|
||||
repl_thread.start()
|
||||
|
||||
while True:
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
keyboard_interrupted = True
|
||||
if repl_future and not repl_future.done():
|
||||
repl_future.cancel()
|
||||
repl_thread.interrupt()
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
console.write('exiting asyncio REPL...\n')
|
||||
sys.exit(return_code)
|
||||
1459
Lib/asyncio/base_events.py
vendored
1459
Lib/asyncio/base_events.py
vendored
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user