mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Compare commits
655 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a3e2834b6 | ||
|
|
c0d45d368b | ||
|
|
f18ec3d20a | ||
|
|
b0377cc7ab | ||
|
|
96e671b55f | ||
|
|
40e99abbdf | ||
|
|
8b6b055681 | ||
|
|
8e5605fa42 | ||
|
|
06e1fdecc2 | ||
|
|
a82e8334d6 | ||
|
|
539bc2ae0e | ||
|
|
0711acd30e | ||
|
|
1476131ab4 | ||
|
|
89902a440c | ||
|
|
156c23d550 | ||
|
|
30396ba79a | ||
|
|
a4343c62ca | ||
|
|
4b89c84692 | ||
|
|
df68449b82 | ||
|
|
48e3383f66 | ||
|
|
e750fa7393 | ||
|
|
5a15199a3a | ||
|
|
1801472fc4 | ||
|
|
ab15ac37ff | ||
|
|
0955a6be49 | ||
|
|
d58237ea15 | ||
|
|
2d50ca86a6 | ||
|
|
f1a46be738 | ||
|
|
3e2a67d434 | ||
|
|
aef028be6e | ||
|
|
c8ec29a3d8 | ||
|
|
e81830a2ea | ||
|
|
54df7171a2 | ||
|
|
b31af823d1 | ||
|
|
72f266532b | ||
|
|
d9bf5cae12 | ||
|
|
cd95a42e5e | ||
|
|
e67eb06d8b | ||
|
|
28d37cdead | ||
|
|
13604e0a47 | ||
|
|
aeb6f1a755 | ||
|
|
92e6f711b7 | ||
|
|
a24113f42b | ||
|
|
7a6f8ab3ad | ||
|
|
6dd242f3ce | ||
|
|
88fa82c61a | ||
|
|
2299ba5f61 | ||
|
|
117df6ca38 | ||
|
|
4256a81653 | ||
|
|
d5b6935c0b | ||
|
|
b4503ef729 | ||
|
|
a00a6750b4 | ||
|
|
a08f891b20 | ||
|
|
bc1cac9c41 | ||
|
|
50f7ab0f34 | ||
|
|
fdc35ce3ed | ||
|
|
5c4e400d32 | ||
|
|
7a23e355b9 | ||
|
|
dffac642a1 | ||
|
|
97699eaded | ||
|
|
c6aaacdbf1 | ||
|
|
abfc68765f | ||
|
|
3ac2ac0982 | ||
|
|
b9a1227e47 | ||
|
|
801c63947a | ||
|
|
ffee4add4a | ||
|
|
f0be7ef418 | ||
|
|
e4eedd80bc | ||
|
|
c9e7fe16e4 | ||
|
|
5079dd19cb | ||
|
|
b4c686f411 | ||
|
|
287d0fa1af | ||
|
|
b78455c4c1 | ||
|
|
312b6b0706 | ||
|
|
924e530096 | ||
|
|
ef8918f3a7 | ||
|
|
91ae242e49 | ||
|
|
fd307e52ae | ||
|
|
a68967c773 | ||
|
|
52da45bb9c | ||
|
|
ad0dde3f17 | ||
|
|
8f3c36deea | ||
|
|
23e1ab81b3 | ||
|
|
77b40aa348 | ||
|
|
f6decfd93d | ||
|
|
e8d5138cfa | ||
|
|
a088fbd6fb | ||
|
|
19214901f9 | ||
|
|
c330a623b2 | ||
|
|
f77241e977 | ||
|
|
ed6de66c08 | ||
|
|
5191c45113 | ||
|
|
840bc803b7 | ||
|
|
00fdc73015 | ||
|
|
9660976d1d | ||
|
|
7f666dc6a0 | ||
|
|
e2a2292a6f | ||
|
|
4d89cbde01 | ||
|
|
d8e1cb8b0f | ||
|
|
3aef5a99dc | ||
|
|
7994207c78 | ||
|
|
f376097a15 | ||
|
|
2a2ff4066d | ||
|
|
32c3fb72cc | ||
|
|
e44e18114d | ||
|
|
7d2df4895e | ||
|
|
59db56feec | ||
|
|
fd60b4789a | ||
|
|
0696e4682d | ||
|
|
d56eeb59ed | ||
|
|
1d015c7534 | ||
|
|
264675d0c3 | ||
|
|
37d4cb7c48 | ||
|
|
cabb1c72b6 | ||
|
|
489a2bb20e | ||
|
|
d5f42e57ce | ||
|
|
94b0bf4131 | ||
|
|
12428c0617 | ||
|
|
ef44df5dda | ||
|
|
da3b43abdd | ||
|
|
4cc9647dc6 | ||
|
|
74cd7c840a | ||
|
|
0f2deeb71a | ||
|
|
93539c9b5a | ||
|
|
e48e6276e1 | ||
|
|
75a57a49f5 | ||
|
|
8a1db288fc | ||
|
|
84dcde188b | ||
|
|
27c91e9703 | ||
|
|
b5a0cd4057 | ||
|
|
77d8fe3562 | ||
|
|
a484aff457 | ||
|
|
c96f5912df | ||
|
|
8a01a56e51 | ||
|
|
2774e49ab9 | ||
|
|
26e7a54f1f | ||
|
|
f0e69cbc36 | ||
|
|
413428f535 | ||
|
|
0c54036466 | ||
|
|
2555833831 | ||
|
|
7e0aceced1 | ||
|
|
77234f6df3 | ||
|
|
45af96aad4 | ||
|
|
184d29055e | ||
|
|
9e73181816 | ||
|
|
0b0e03456c | ||
|
|
c6b5ce7f55 | ||
|
|
a14e701be4 | ||
|
|
7813c3f03f | ||
|
|
3a3cb7b11d | ||
|
|
d7b0731385 | ||
|
|
df8973736f | ||
|
|
9121071ba3 | ||
|
|
bf6470c046 | ||
|
|
3b7099cd3d | ||
|
|
f6dfc5361e | ||
|
|
0a7e1ce0d7 | ||
|
|
d6b1c393f6 | ||
|
|
bccd5e3750 | ||
|
|
6df5905b2b | ||
|
|
6284c02032 | ||
|
|
db27d52352 | ||
|
|
8ba28989fb | ||
|
|
da544929ac | ||
|
|
bb364b0524 | ||
|
|
818614b798 | ||
|
|
50b1a1d7c5 | ||
|
|
7d3b792a79 | ||
|
|
af72e232c3 | ||
|
|
0cdbfbeb30 | ||
|
|
339e40063a | ||
|
|
4467898473 | ||
|
|
17d16b987f | ||
|
|
8e86daac71 | ||
|
|
856720da49 | ||
|
|
8f2c150d1e | ||
|
|
7d8b4c980a | ||
|
|
932756c7a0 | ||
|
|
538aac9a28 | ||
|
|
856bf8f5fb | ||
|
|
e1758ae2e2 | ||
|
|
61b3154461 | ||
|
|
fb9b30d144 | ||
|
|
b0df96b13f | ||
|
|
a469062a32 | ||
|
|
89d5d5c7db | ||
|
|
b8c2d6b05d | ||
|
|
b247864414 | ||
|
|
d3bcd87cfa | ||
|
|
82e5b64bad | ||
|
|
73e0271c23 | ||
|
|
a2dabee0e9 | ||
|
|
6a27c6d9f2 | ||
|
|
213ced0c7f | ||
|
|
5086c23d47 | ||
|
|
ee345a5206 | ||
|
|
f74cddc3b1 | ||
|
|
5b986b8b26 | ||
|
|
14887b9814 | ||
|
|
ecc40315b3 | ||
|
|
e7aed7fcf0 | ||
|
|
cd1aa948f9 | ||
|
|
82613d016a | ||
|
|
3a66be585f | ||
|
|
0a4e36ae09 | ||
|
|
92643539cf | ||
|
|
a1281d1331 | ||
|
|
074ca0ef8f | ||
|
|
464a9633dc | ||
|
|
fc2d91c5bb | ||
|
|
d68169bffb | ||
|
|
7efdb04e1e | ||
|
|
0155e122fd | ||
|
|
eb03f16a77 | ||
|
|
5ac39641ab | ||
|
|
8d1e48e400 | ||
|
|
0021ccb49f | ||
|
|
8590c7e5b8 | ||
|
|
8c5475f78f | ||
|
|
dfa116eb70 | ||
|
|
3a9fd3c074 | ||
|
|
5a92ef3c11 | ||
|
|
d3902f5c93 | ||
|
|
c886f887ae | ||
|
|
fc5089ac59 | ||
|
|
e3602f464b | ||
|
|
f3db6a339c | ||
|
|
c05195c045 | ||
|
|
af981fc719 | ||
|
|
088a264910 | ||
|
|
d7e80ad51b | ||
|
|
b53ddd401f | ||
|
|
e9122bca9d | ||
|
|
b61e8435d1 | ||
|
|
146afb6532 | ||
|
|
854e9d1378 | ||
|
|
689878ce32 | ||
|
|
d7ab177cc5 | ||
|
|
f4c6093c47 | ||
|
|
9fedfe3699 | ||
|
|
26f07246e1 | ||
|
|
3ae4b3c4de | ||
|
|
c8f9f16791 | ||
|
|
88f0738500 | ||
|
|
03c79d5f2f | ||
|
|
e7c3b7bcfe | ||
|
|
c8becca044 | ||
|
|
543a27271f | ||
|
|
a62aba83a0 | ||
|
|
53c6cf5f45 | ||
|
|
89842e20da | ||
|
|
ef793aecf3 | ||
|
|
51d51409d3 | ||
|
|
371b5eac45 | ||
|
|
5319bd13d5 | ||
|
|
e10d055453 | ||
|
|
716254e655 | ||
|
|
4c00b1683f | ||
|
|
37c9db09c6 | ||
|
|
653e2c9be4 | ||
|
|
a2a9613da1 | ||
|
|
e8d92d0d34 | ||
|
|
755b98a8c0 | ||
|
|
13e9252260 | ||
|
|
6a9c27325a | ||
|
|
a1cb78eb85 | ||
|
|
716b57ebd3 | ||
|
|
8e231313b8 | ||
|
|
84e4e361c5 | ||
|
|
41a8d804e3 | ||
|
|
03e798a079 | ||
|
|
34a0205757 | ||
|
|
ba145f04ea | ||
|
|
22fd023635 | ||
|
|
08f34f748b | ||
|
|
7ffe6a598e | ||
|
|
71d24a445e | ||
|
|
6bcbbfb085 | ||
|
|
04fe1348d8 | ||
|
|
3033c779b0 | ||
|
|
4483f0db0f | ||
|
|
727267ae22 | ||
|
|
b5d15c2f7e | ||
|
|
589c614e57 | ||
|
|
4588e90226 | ||
|
|
8665a14dec | ||
|
|
43d598d951 | ||
|
|
68018cf078 | ||
|
|
ef4ab0d7a8 | ||
|
|
e66a2702df | ||
|
|
c57d4a7054 | ||
|
|
a36f08f0f1 | ||
|
|
760a8c75a5 | ||
|
|
740fd921e1 | ||
|
|
065c697070 | ||
|
|
e2c2459290 | ||
|
|
11c79a5344 | ||
|
|
429fe4c356 | ||
|
|
a18b4edfc0 | ||
|
|
b14a2bba5f | ||
|
|
1f825edc28 | ||
|
|
6ed834807a | ||
|
|
9a908e5fd0 | ||
|
|
4c30359b71 | ||
|
|
34dfe2d80b | ||
|
|
25bcff10b7 | ||
|
|
81268d0545 | ||
|
|
8f0a7706d7 | ||
|
|
46150f9b80 | ||
|
|
247745b7e7 | ||
|
|
94cc09b610 | ||
|
|
a210b2d5f5 | ||
|
|
12bf6db331 | ||
|
|
697ac9de9a | ||
|
|
4124bb5edc | ||
|
|
d55340a817 | ||
|
|
0de8cd9ab7 | ||
|
|
4e8281c749 | ||
|
|
357fbc644d | ||
|
|
7947a8a2dc | ||
|
|
35de3aa154 | ||
|
|
1ea687beb8 | ||
|
|
bb5c59307a | ||
|
|
5a3c414c8f | ||
|
|
cc4b460183 | ||
|
|
470c3489dd | ||
|
|
e1b4415193 | ||
|
|
77d98a565e | ||
|
|
412da2de08 | ||
|
|
dbdcd0b3d0 | ||
|
|
5c67384fbf | ||
|
|
35b0f9d377 | ||
|
|
95783bc284 | ||
|
|
4b840f7cbd | ||
|
|
f73d6cd9f2 | ||
|
|
15bb8f03ea | ||
|
|
059dbc88c9 | ||
|
|
c0f36aa047 | ||
|
|
d4120d2af3 | ||
|
|
dd1c008447 | ||
|
|
3721d2cd72 | ||
|
|
e0dda0e547 | ||
|
|
3c7568c72c | ||
|
|
6be1758548 | ||
|
|
25809660ef | ||
|
|
6b9eff45bb | ||
|
|
08e83feaf5 | ||
|
|
4f05b5afc6 | ||
|
|
9a5bf9918e | ||
|
|
1c7cf0ba7d | ||
|
|
ce2d1a4513 | ||
|
|
7d50d7eea0 | ||
|
|
e9411dc796 | ||
|
|
5cf2de16d1 | ||
|
|
e53bcf15a9 | ||
|
|
bec70b60b8 | ||
|
|
8b7fb89c68 | ||
|
|
b2bbdda73d | ||
|
|
ee2f46cfb9 | ||
|
|
4337e6833a | ||
|
|
a73c73b814 | ||
|
|
e8318a98f0 | ||
|
|
94f2ac6204 | ||
|
|
cc6cb4ded0 | ||
|
|
c61d4191c3 | ||
|
|
e284da7c09 | ||
|
|
9992096654 | ||
|
|
c696d92f40 | ||
|
|
af60299324 | ||
|
|
5aa9135a34 | ||
|
|
72e23ac86f | ||
|
|
33d49ad87d | ||
|
|
aa2335ca2e | ||
|
|
b31428006c | ||
|
|
dc1d583791 | ||
|
|
4299a74e40 | ||
|
|
3e408b7baa | ||
|
|
446c131ccb | ||
|
|
b062efcf17 | ||
|
|
30e31a86ef | ||
|
|
182272e8c7 | ||
|
|
06df21e8e3 | ||
|
|
cafebd68f2 | ||
|
|
061d4b3f72 | ||
|
|
6586e79d5e | ||
|
|
cda6c6bc7e | ||
|
|
a628026838 | ||
|
|
6700856b9f | ||
|
|
411aa0bbed | ||
|
|
0d79d31b96 | ||
|
|
cb05a9b067 | ||
|
|
536f359fb9 | ||
|
|
56e888ed33 | ||
|
|
687b93d148 | ||
|
|
0e1c396d7c | ||
|
|
7e24289703 | ||
|
|
0b23310a06 | ||
|
|
41ebaaf366 | ||
|
|
b79ceea7a8 | ||
|
|
b990bcb67a | ||
|
|
3f0f2d9910 | ||
|
|
4333f5f979 | ||
|
|
07e75293b8 | ||
|
|
a9ca7106cb | ||
|
|
da2728e6df | ||
|
|
adfa9a9b05 | ||
|
|
be9b9f66d3 | ||
|
|
9521bc7175 | ||
|
|
b445f8a834 | ||
|
|
3c3dffd5ed | ||
|
|
4c8443fd00 | ||
|
|
06a5a54103 | ||
|
|
0d3c3eef4e | ||
|
|
f0a6fb913f | ||
|
|
5f0c508fed | ||
|
|
16d9657982 | ||
|
|
40d098310e | ||
|
|
1345449d57 | ||
|
|
515858f313 | ||
|
|
2f452e9dc7 | ||
|
|
66119157a7 | ||
|
|
5b671dd1d0 | ||
|
|
1017362eec | ||
|
|
f67b8e0285 | ||
|
|
4c635fe84c | ||
|
|
68e463493e | ||
|
|
f1979d60b7 | ||
|
|
9150ebafec | ||
|
|
9543019336 | ||
|
|
1c53d91c6b | ||
|
|
4850f39b5a | ||
|
|
ab085c2d92 | ||
|
|
bf4d835948 | ||
|
|
214e39537b | ||
|
|
2d33afc195 | ||
|
|
87ea24ebd4 | ||
|
|
5380f8b9b3 | ||
|
|
00121ff8ba | ||
|
|
80e5d20e37 | ||
|
|
58f7c2137d | ||
|
|
f9194cc833 | ||
|
|
d9b8b48972 | ||
|
|
aa85f5f596 | ||
|
|
5341a0be4a | ||
|
|
0cfe20ca65 | ||
|
|
c352b502c4 | ||
|
|
58b4df6b3d | ||
|
|
29ba9436c8 | ||
|
|
2a044e88ad | ||
|
|
63092f9d72 | ||
|
|
0209324d57 | ||
|
|
1587273868 | ||
|
|
beb3aa1574 | ||
|
|
fe708c9fb4 | ||
|
|
e45d8bf973 | ||
|
|
b184c92f01 | ||
|
|
6c8afb05a7 | ||
|
|
d3dd4573cf | ||
|
|
d5cf68391a | ||
|
|
4e54e93450 | ||
|
|
e4f6387f18 | ||
|
|
54cb35b60a | ||
|
|
18ede2b729 | ||
|
|
f138b5a4f4 | ||
|
|
11a517bba4 | ||
|
|
66b57bf812 | ||
|
|
a9357bd97e | ||
|
|
d7c6d42c3d | ||
|
|
4dd1dc28b1 | ||
|
|
1e05ff7c95 | ||
|
|
e8e2e65584 | ||
|
|
3727e60152 | ||
|
|
0254012db6 | ||
|
|
8b97e4757f | ||
|
|
c70e121078 | ||
|
|
a4f97e6e46 | ||
|
|
800145a83c | ||
|
|
c75f885cb4 | ||
|
|
4011a51013 | ||
|
|
f4165dabcf | ||
|
|
de6c26eb05 | ||
|
|
5f319452d5 | ||
|
|
60d505d2d1 | ||
|
|
f64cc4dcae | ||
|
|
60e6f4293a | ||
|
|
00ab9a8d02 | ||
|
|
7d5f6c9ead | ||
|
|
a295edf19d | ||
|
|
b674515d06 | ||
|
|
d033ab04da | ||
|
|
304d76d088 | ||
|
|
978afdad97 | ||
|
|
d4e41e679d | ||
|
|
146264ff12 | ||
|
|
dcb107ae65 | ||
|
|
c236269d13 | ||
|
|
8f658e6d85 | ||
|
|
d203b60f44 | ||
|
|
a1a16aba74 | ||
|
|
6ded003447 | ||
|
|
4841e29fc6 | ||
|
|
0b014eea56 | ||
|
|
1c0be16f30 | ||
|
|
27ba8bea2f | ||
|
|
c566977749 | ||
|
|
5c1b785b4b | ||
|
|
8657dfb5da | ||
|
|
dfa837754e | ||
|
|
0a7df78770 | ||
|
|
066ecbe022 | ||
|
|
6c80db810f | ||
|
|
6023c413ab | ||
|
|
7910d040b6 | ||
|
|
5bd99f5224 | ||
|
|
f3157b377f | ||
|
|
e31e03afde | ||
|
|
eddde7c94c | ||
|
|
7be72ee4c1 | ||
|
|
6731467514 | ||
|
|
8dd699d235 | ||
|
|
6cb81b5c3d | ||
|
|
17187ba3ec | ||
|
|
531ee928b0 | ||
|
|
db806a5df9 | ||
|
|
9de154595a | ||
|
|
9e4cb79679 | ||
|
|
b7834073b8 | ||
|
|
b0e56577b5 | ||
|
|
ccb0e6b269 | ||
|
|
edfd4baa1f | ||
|
|
0f50f4a9fd | ||
|
|
47494e62a7 | ||
|
|
7aa25712d9 | ||
|
|
1db155570d | ||
|
|
1054e8e644 | ||
|
|
aa429f34d8 | ||
|
|
e351889811 | ||
|
|
24a70a8273 | ||
|
|
3f26657116 | ||
|
|
d41669af8b | ||
|
|
56466c2a00 | ||
|
|
fa7a97ca30 | ||
|
|
8aba271a42 | ||
|
|
8275aa2810 | ||
|
|
410ddf314c | ||
|
|
fa217bee20 | ||
|
|
817d0edc69 | ||
|
|
513dfe0b42 | ||
|
|
bd7a20309b | ||
|
|
a726be3c7c | ||
|
|
10f2054e9a | ||
|
|
2fa47f310d | ||
|
|
5b927a70c2 | ||
|
|
2a59ff8e68 | ||
|
|
e4d1befcdb | ||
|
|
844e04ff96 | ||
|
|
cc05a98b0e | ||
|
|
006d161a32 | ||
|
|
a4839db79a | ||
|
|
87e3b5b1dc | ||
|
|
faa900d502 | ||
|
|
a5275db3ec | ||
|
|
77e017a574 | ||
|
|
8ed8ddbf76 | ||
|
|
eb31978488 | ||
|
|
677d708588 | ||
|
|
ade0dca8f9 | ||
|
|
8e1cd0b268 | ||
|
|
9102768366 | ||
|
|
0c722b9164 | ||
|
|
b6f514451a | ||
|
|
c49fdfc56c | ||
|
|
676e04b28e | ||
|
|
6aa864a351 | ||
|
|
72acb4826c | ||
|
|
734be5f355 | ||
|
|
cede06ae19 | ||
|
|
19491d8010 | ||
|
|
c580aac991 | ||
|
|
032d1aaad7 | ||
|
|
afa216dc5e | ||
|
|
69339fe3de | ||
|
|
571bb2b294 | ||
|
|
91a09a09f7 | ||
|
|
9b3433f6ae | ||
|
|
ee9b0960f7 | ||
|
|
506ac2574f | ||
|
|
dc84d7c1b5 | ||
|
|
fcaa57307f | ||
|
|
d25e754beb | ||
|
|
7f6f411ea8 | ||
|
|
96a73e31f3 | ||
|
|
c7942d7d8f | ||
|
|
479348eec9 | ||
|
|
ebfed27630 | ||
|
|
8923485169 | ||
|
|
d62de26683 | ||
|
|
1dd9c5b009 | ||
|
|
01b64e18ab | ||
|
|
fa61339a49 | ||
|
|
deb2eee3ad | ||
|
|
829cc9f6f9 | ||
|
|
1c7ef6622b | ||
|
|
f54ae52b2a | ||
|
|
171ae3cabe | ||
|
|
f3888df88c | ||
|
|
9274f9ec08 | ||
|
|
6822105c70 | ||
|
|
d5d855e4c3 | ||
|
|
1e09ac4fd3 | ||
|
|
bc513400c5 | ||
|
|
715ceb9ed4 | ||
|
|
0115b152a8 | ||
|
|
e1622a56fa | ||
|
|
03fee56a54 | ||
|
|
d00171a629 | ||
|
|
fa28b218ec | ||
|
|
eb882170d5 | ||
|
|
48a565a51d | ||
|
|
f60dd6a788 | ||
|
|
d6a88d4c9e | ||
|
|
4b4ff08131 | ||
|
|
698478ef95 | ||
|
|
4ab9ab51f6 | ||
|
|
bf3995d45c | ||
|
|
4fe603bf96 | ||
|
|
0639827d00 | ||
|
|
eaacd45672 | ||
|
|
5d733ab915 | ||
|
|
1bf6cc0e72 | ||
|
|
a9470ed9c1 | ||
|
|
2c5ef95027 | ||
|
|
f712b1369a | ||
|
|
2f03b18619 | ||
|
|
8cd5ba6361 | ||
|
|
c951961a92 | ||
|
|
3b68e1b27d | ||
|
|
48e6b1a84e | ||
|
|
ee46b46234 | ||
|
|
db49fd498a | ||
|
|
e95b90363e | ||
|
|
82c0d741e4 | ||
|
|
87b150d539 | ||
|
|
508a7d8605 | ||
|
|
9119939c3d | ||
|
|
59fc667651 | ||
|
|
0d946f853f | ||
|
|
b767a78b05 | ||
|
|
39774a83c5 | ||
|
|
c04015899b | ||
|
|
6898daf0ce | ||
|
|
eb3a31a698 | ||
|
|
0476627f34 | ||
|
|
eba42ad9b4 | ||
|
|
ca909b4f6b |
63
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
63
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
labels: ["type: bug", "needs-triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for helping PyScript! 🐍
|
||||||
|
|
||||||
|
Going through bugs and issues takes up a lot of time, so please be so kind and take a few minutes to fill out all the areas to the best of your ability.
|
||||||
|
|
||||||
|
There will always be more issues than there is time to do them, and so we will need to selectively close issues that don't provide enough information, so we can focus our time on helping people like you who fill out the issue form completely. Thank you for your collaboration!
|
||||||
|
|
||||||
|
There are also already a lot of open issues, so please take 2 minutes and search through existing ones to see if what you are experiencing already exists
|
||||||
|
|
||||||
|
Thanks for helping PyScript be amazing. We are nothing without people like you helping build a better community 💐!
|
||||||
|
- type: checkboxes
|
||||||
|
id: checks
|
||||||
|
attributes:
|
||||||
|
label: Checklist
|
||||||
|
description: Please confirm and check all the following options.
|
||||||
|
options:
|
||||||
|
- label: I added a descriptive title
|
||||||
|
required: true
|
||||||
|
- label: I searched for other issues and couldn't find a solution or duplication
|
||||||
|
required: true
|
||||||
|
- label: I already searched in Google and didn't find any good information or help
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: And what should have happened instead? This really helps everyone review quicker and greatly increases the chance that someone can get around to solve your issue
|
||||||
|
placeholder: Tell us what you see!
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: browsers
|
||||||
|
attributes:
|
||||||
|
label: What browsers are you seeing the problem on? (if applicable)
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Firefox
|
||||||
|
- Chrome
|
||||||
|
- Safari
|
||||||
|
- Microsoft Edge
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: list
|
||||||
|
attributes:
|
||||||
|
label: Console info
|
||||||
|
description: |
|
||||||
|
If there are errors in your browser console then its helpful to be able to troubleshoot.
|
||||||
|
- Chrome , Firefox, and Edge: Right-click on the page and select *Inspect*. Alternatively you can press F12 on your keyboard.
|
||||||
|
- Safari: Find instructions [here](https://support.apple.com/guide/safari/use-the-developer-tools-in-the-develop-menu-sfri20948/mac).
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: Add any additional context information or screenshots you think are useful.
|
||||||
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: "[BUG]"
|
|
||||||
labels: needs-triage
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior: either a code snippet or a link to an HTML page which shows the bug.
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Feature Proposals
|
||||||
|
url: https://github.com/pyscript/pyscript/discussions/new?category=proposals
|
||||||
|
about: Create a feature request to make PyScript even better
|
||||||
|
- name: Questions
|
||||||
|
url: https://github.com/pyscript/pyscript/discussions/new?category=q-a
|
||||||
|
about: For questions or discussions about pyscript
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: "[FEATURE]"
|
|
||||||
labels: needs-triage
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. As a user, I'd like to [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you expect to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
## Description
|
||||||
|
|
||||||
|
<!--Please describe the changes in your pull request in few words here. -->
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
<!-- List the changes done to fix a bug or introduce a new feature.Please note both user-facing changes and changes to internal API's here -->
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
<!-- Note: Only user-facing changes require a changelog entry. Internal-only API changes do not require a changelog entry. Changes in documentation do not require a changelog entry. -->
|
||||||
|
|
||||||
|
- [ ] All tests pass locally
|
||||||
|
- [ ] I have updated `CHANGELOG.md`
|
||||||
|
- [ ] I have created documentation for this(if applicable)
|
||||||
5
.github/release.yml
vendored
Normal file
5
.github/release.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
changelog:
|
||||||
|
categories:
|
||||||
|
- title: New Features
|
||||||
|
- title: Breaking Changes
|
||||||
|
- title: Known Issues
|
||||||
56
.github/workflows/build-alpha.yml
vendored
56
.github/workflows/build-alpha.yml
vendored
@@ -1,56 +0,0 @@
|
|||||||
name: '[CI] Build Alpha'
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
tags:
|
|
||||||
- '**' # Currently any tag, need to slim down
|
|
||||||
paths:
|
|
||||||
- pyscriptjs/**
|
|
||||||
- .github/workflows/build-alpha.yml # Test that workflow works when changed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
id-token: write
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: pyscriptjs
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Install node
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 18.x
|
|
||||||
- name: Cache node modules
|
|
||||||
uses: actions/cache@v3
|
|
||||||
env:
|
|
||||||
cache-name: cache-node-modules
|
|
||||||
with:
|
|
||||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
|
||||||
path: ~/.npm
|
|
||||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
${{ runner.os }}-build-
|
|
||||||
${{ runner.os }}-
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
npm install
|
|
||||||
- name: Build pyscript
|
|
||||||
run: |
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Deploy to S3
|
|
||||||
- name: Configure AWS credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
|
||||||
with:
|
|
||||||
aws-region: ${{secrets.AWS_REGION}}
|
|
||||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
|
||||||
- name: Sync to S3
|
|
||||||
run: aws s3 sync --quiet ./examples/build/ s3://pyscript.net/alpha/
|
|
||||||
63
.github/workflows/build-latest.yml
vendored
63
.github/workflows/build-latest.yml
vendored
@@ -1,63 +0,0 @@
|
|||||||
name: '[CI] Build Latest'
|
|
||||||
|
|
||||||
on:
|
|
||||||
push: # Only run on merges into main that modify files under pyscriptjs/
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- pyscriptjs/**
|
|
||||||
- .github/workflows/build-latest.yml # Test that workflow works when changed
|
|
||||||
|
|
||||||
pull_request: # Run on any PR that modifies files in pyscriptjs/
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- pyscriptjs/**
|
|
||||||
- .github/workflows/build-latest.yml # Test that workflow works when changed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
id-token: write
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: pyscriptjs
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Install node
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 18.x
|
|
||||||
- name: Cache node modules
|
|
||||||
uses: actions/cache@v3
|
|
||||||
env:
|
|
||||||
cache-name: cache-node-modules
|
|
||||||
with:
|
|
||||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
|
||||||
path: ~/.npm
|
|
||||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
${{ runner.os }}-build-
|
|
||||||
${{ runner.os }}-
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
npm install
|
|
||||||
- name: Build pyscript
|
|
||||||
run: |
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Deploy to S3
|
|
||||||
- name: Configure AWS credentials
|
|
||||||
if: github.ref == 'refs/heads/main' # Only deploy on merge into main
|
|
||||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
|
||||||
with:
|
|
||||||
aws-region: ${{secrets.AWS_REGION}}
|
|
||||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
|
||||||
- name: Sync to S3
|
|
||||||
if: github.ref == 'refs/heads/main'
|
|
||||||
run: aws s3 sync --quiet ./examples/build/ s3://pyscript.net/unstable
|
|
||||||
56
.github/workflows/prepare-release.yml
vendored
Normal file
56
.github/workflows/prepare-release.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: "Prepare Release"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9]+" # YYYY.MM.MICRO
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./pyscript.core
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18.x
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v3
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules
|
||||||
|
with:
|
||||||
|
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||||
|
${{ runner.os }}-build-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: NPM Install
|
||||||
|
run: npm install && npx playwright install
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Generate index.html
|
||||||
|
working-directory: .
|
||||||
|
run: sed 's#_PATH_#./#' ./public/index.html > ./pyscript.core/dist/index.html
|
||||||
|
|
||||||
|
- name: Zip dist folder
|
||||||
|
run: zip -r -q ./build.zip ./dist
|
||||||
|
|
||||||
|
- name: Prepare Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
prerelease: true
|
||||||
|
generate_release_notes: true
|
||||||
|
files: ./build.zip
|
||||||
59
.github/workflows/publish-release.yml
vendored
Normal file
59
.github/workflows/publish-release.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
name: "Publish Release"
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./pyscript.core
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18.x
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v3
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules
|
||||||
|
with:
|
||||||
|
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||||
|
${{ runner.os }}-build-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: npm install
|
||||||
|
run: npm install && npx playwright install
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Generate index.html in snapshot
|
||||||
|
working-directory: .
|
||||||
|
run: sed 's#_PATH_#https://pyscript.net/releases/${{ github.ref_name }}/#' ./public/index.html > ./pyscript.core/dist/index.html
|
||||||
|
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
with:
|
||||||
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
|
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||||
|
|
||||||
|
- name: Sync to S3
|
||||||
|
run:
|
||||||
|
| # Update /latest and create an explicitly versioned directory under releases/YYYY.MM.MICRO/
|
||||||
|
aws s3 sync --quiet ./dist/ s3://pyscript.net/latest/
|
||||||
|
aws s3 sync --quiet ./dist/ s3://pyscript.net/releases/${{ github.ref_name }}/
|
||||||
61
.github/workflows/publish-snapshot.yml
vendored
Normal file
61
.github/workflows/publish-snapshot.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: "Publish Snapshot"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
snapshot_version:
|
||||||
|
description: "The calver version of this snapshot: 2022.09.1 or 2022.09.1.RC1"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./pyscript.core
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-snapshot:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18.x
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v3
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules
|
||||||
|
with:
|
||||||
|
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||||
|
${{ runner.os }}-build-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm install && npx playwright install
|
||||||
|
|
||||||
|
- name: Build Pyscript.core
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
with:
|
||||||
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
|
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||||
|
|
||||||
|
- name: Generate index.html in snapshot
|
||||||
|
working-directory: .
|
||||||
|
run: sed 's#_PATH_#https://pyscript.net/snapshots/${{ inputs.snapshot_version }}/#' ./public/index.html > ./pyscript.core/dist/index.html
|
||||||
|
|
||||||
|
- name: Copy to Snapshot
|
||||||
|
run: >
|
||||||
|
aws s3 sync ./dist/ s3://pyscript.net/snapshots/${{ inputs.snapshot_version }}/
|
||||||
61
.github/workflows/publish-unstable.yml
vendored
Normal file
61
.github/workflows/publish-unstable.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: "Publish Unstable"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push: # Only run on merges into main that modify files under pyscript.core/ and examples/
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- pyscript.core/**
|
||||||
|
- examples/**
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-unstable:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./pyscript.core
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18.x
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v3
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules
|
||||||
|
with:
|
||||||
|
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||||
|
${{ runner.os }}-build-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: NPM Install
|
||||||
|
run: npm install && npx playwright install
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Generate index.html in snapshot
|
||||||
|
working-directory: .
|
||||||
|
run: sed 's#_PATH_#https://pyscript.net/unstable/#' ./public/index.html > ./pyscript.core/dist/index.html
|
||||||
|
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
with:
|
||||||
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
|
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||||
|
|
||||||
|
- name: Sync to S3
|
||||||
|
run: aws s3 sync --quiet ./dist/ s3://pyscript.net/unstable/
|
||||||
33
.github/workflows/sync-examples.yml
vendored
33
.github/workflows/sync-examples.yml
vendored
@@ -1,33 +0,0 @@
|
|||||||
name: '[CI] Sync Examples'
|
|
||||||
|
|
||||||
on:
|
|
||||||
push: # Only run on merges into main that modify files under pyscriptjs/examples/
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- pyscriptjs/examples/**
|
|
||||||
- .github/workflows/sync-examples.yml # Test that workflow works when changed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
id-token: write
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: pyscriptjs/examples
|
|
||||||
|
|
||||||
steps:
|
|
||||||
|
|
||||||
# Deploy to S3
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Configure AWS credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v1.6.1
|
|
||||||
with:
|
|
||||||
aws-region: ${{secrets.AWS_REGION}}
|
|
||||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
|
||||||
- name: Sync to S3
|
|
||||||
# Sync outdated or new files, delete ones no longer in source
|
|
||||||
run: aws s3 sync --quiet --delete . s3://pyscript.net/examples/ # Sync directory, delete what is not in source
|
|
||||||
92
.github/workflows/test.yml
vendored
Normal file
92
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
name: "[CI] Test"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push: # Only run on merges into main that modify certain files
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- pyscript.core/**
|
||||||
|
- .github/workflows/test.yml
|
||||||
|
|
||||||
|
pull_request: # Only run on merges into main that modify certain files
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- pyscript.core/**
|
||||||
|
- .github/workflows/test.yml
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
BuildAndTest:
|
||||||
|
runs-on: ubuntu-latest-8core
|
||||||
|
env:
|
||||||
|
MINICONDA_PYTHON_VERSION: py38
|
||||||
|
MINICONDA_VERSION: 4.11.0
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 3
|
||||||
|
|
||||||
|
# display a git log: when you run CI on PRs, github automatically
|
||||||
|
# merges the PR into main and run the CI on that commit. The idea
|
||||||
|
# here is to show enough of git log to understand what is the
|
||||||
|
# actual commit (in the PR) that we are using. See also
|
||||||
|
# 'fetch-depth: 3' above.
|
||||||
|
- name: git log
|
||||||
|
run: git log --graph -3
|
||||||
|
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 20.x
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v3
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules
|
||||||
|
with:
|
||||||
|
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||||
|
${{ runner.os }}-build-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: setup Miniconda
|
||||||
|
uses: conda-incubator/setup-miniconda@v2
|
||||||
|
|
||||||
|
- name: Create and activate virtual environment
|
||||||
|
run: |
|
||||||
|
python3 -m venv test_venv
|
||||||
|
source test_venv/bin/activate
|
||||||
|
echo PATH=$PATH >> $GITHUB_ENV
|
||||||
|
echo VIRTUAL_ENV=$VIRTUAL_ENV >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup dependencies in virtual environment
|
||||||
|
run: |
|
||||||
|
make setup
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: make build
|
||||||
|
|
||||||
|
- name: Integration Tests
|
||||||
|
#run: make test-integration-parallel
|
||||||
|
run: |
|
||||||
|
make test-integration
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: pyscript
|
||||||
|
path: |
|
||||||
|
pyscript.core/dist/
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: success() || failure()
|
||||||
|
with:
|
||||||
|
name: test_results
|
||||||
|
path: test_results/
|
||||||
|
if-no-files-found: error
|
||||||
16
.github/workflows/test_report.yml
vendored
Normal file
16
.github/workflows/test_report.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: Test Report
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ['\[CI\] Test']
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
jobs:
|
||||||
|
report:
|
||||||
|
runs-on: ubuntu-latest-8core
|
||||||
|
steps:
|
||||||
|
- uses: dorny/test-reporter@v1.6.0
|
||||||
|
with:
|
||||||
|
artifact: test_results
|
||||||
|
name: Test reports
|
||||||
|
path: "*.xml"
|
||||||
|
reporter: java-junit
|
||||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -71,6 +71,7 @@ instance/
|
|||||||
|
|
||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
docs/_env/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
target/
|
target/
|
||||||
@@ -134,3 +135,17 @@ dmypy.json
|
|||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# junit xml for test results
|
||||||
|
test_results
|
||||||
|
|
||||||
|
# @pyscript/core npm artifacts
|
||||||
|
pyscript.core/core.*
|
||||||
|
pyscript.core/dist
|
||||||
|
pyscript.core/dist.zip
|
||||||
|
pyscript.core/src/plugins.js
|
||||||
|
pyscript.core/src/stdlib/pyscript.js
|
||||||
|
pyscript.core/src/3rd-party/*
|
||||||
|
!pyscript.core/src/3rd-party/READMEmd
|
||||||
|
|||||||
@@ -1,77 +1,53 @@
|
|||||||
# This is the configuration for pre-commit, a local framework for managing pre-commit hooks
|
# This is the configuration for pre-commit, a local framework for managing pre-commit hooks
|
||||||
# Check out the docs at: https://pre-commit.com/
|
# Check out the docs at: https://pre-commit.com/
|
||||||
|
ci:
|
||||||
|
#skip: [eslint]
|
||||||
|
autoupdate_schedule: monthly
|
||||||
|
|
||||||
default_stages: [commit]
|
default_stages: [commit]
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.2.0
|
rev: v4.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-builtin-literals
|
- id: check-builtin-literals
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
- id: check-docstring-first
|
- id: check-docstring-first
|
||||||
- id: check-executables-have-shebangs
|
- id: check-executables-have-shebangs
|
||||||
- id: check-json
|
- id: check-json
|
||||||
exclude: tsconfig.json
|
exclude: tsconfig\.json
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
- id: check-xml
|
exclude: bad\.toml
|
||||||
- id: check-yaml
|
- id: check-xml
|
||||||
- id: detect-private-key
|
- id: check-yaml
|
||||||
- id: end-of-file-fixer
|
- id: detect-private-key
|
||||||
exclude: \.min\.js$
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
exclude: pyscript\.core/dist|\.min\.js$
|
||||||
|
- id: trailing-whitespace
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/bandit
|
- repo: https://github.com/psf/black
|
||||||
rev: 1.7.4
|
rev: 23.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: bandit
|
- id: black
|
||||||
args:
|
exclude: pyscript\.core/src/stdlib/pyscript/__init__\.py
|
||||||
- --skip=B201
|
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: 22.3.0
|
rev: v2.2.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: codespell # See 'pyproject.toml' for args
|
||||||
|
exclude: \.js\.map$
|
||||||
|
additional_dependencies:
|
||||||
|
- tomli
|
||||||
|
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/hoodmane/pyscript-prettier-precommit
|
||||||
rev: v2.1.0
|
rev: "v3.0.0-alpha.6"
|
||||||
hooks:
|
hooks:
|
||||||
- id: codespell # See 'setup.cfg' for args
|
- id: prettier
|
||||||
|
exclude: pyscript\.core/test|pyscript\.core/dist|pyscript\.core/types|pyscript.core/src/stdlib/pyscript.js|pyscript\.sw/|pyscript.core/src/3rd-party
|
||||||
|
args: [--tab-width, "4"]
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/pycqa/isort
|
||||||
rev: 4.0.1
|
rev: 5.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8 # See 'setup.cfg' for args
|
- id: isort
|
||||||
additional_dependencies: [flake8-bugbear, flake8-comprehensions]
|
name: isort (python)
|
||||||
|
args: [--profile, black]
|
||||||
- repo: https://github.com/pycqa/isort
|
|
||||||
rev: 5.10.1
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
name: isort (python)
|
|
||||||
args: [--profile, black]
|
|
||||||
|
|
||||||
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
|
|
||||||
rev: v2.3.0
|
|
||||||
hooks:
|
|
||||||
- id: pretty-format-yaml
|
|
||||||
args: [--autofix, --indent, '4']
|
|
||||||
|
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
|
||||||
rev: v2.32.1
|
|
||||||
hooks:
|
|
||||||
- id: pyupgrade
|
|
||||||
args:
|
|
||||||
- --py310-plus
|
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
|
||||||
rev: v8.15.0
|
|
||||||
hooks:
|
|
||||||
- id: eslint
|
|
||||||
files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
|
|
||||||
types: [file]
|
|
||||||
additional_dependencies:
|
|
||||||
- eslint
|
|
||||||
- eslint-plugin-svelte3
|
|
||||||
- typescript
|
|
||||||
- '@typescript-eslint/eslint-plugin'
|
|
||||||
- '@typescript-eslint/parser'
|
|
||||||
|
|||||||
5
.prettierignore
Normal file
5
.prettierignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ISSUE_TEMPLATE
|
||||||
|
*.min.*
|
||||||
|
package-lock.json
|
||||||
|
docs
|
||||||
|
examples/panel.html
|
||||||
28
.readthedocs.yml
Normal file
28
.readthedocs.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# .readthedocs.yaml
|
||||||
|
# Read the Docs configuration file
|
||||||
|
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||||
|
|
||||||
|
# Required
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
# Set the version of Python and other tools you might need
|
||||||
|
build:
|
||||||
|
os: ubuntu-20.04
|
||||||
|
tools:
|
||||||
|
python: miniconda3-4.7
|
||||||
|
|
||||||
|
# Build documentation in the docs/ directory with Sphinx
|
||||||
|
sphinx:
|
||||||
|
configuration: docs/conf.py
|
||||||
|
|
||||||
|
conda:
|
||||||
|
environment: docs/environment.yml
|
||||||
|
|
||||||
|
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
||||||
|
# formats:
|
||||||
|
# - pdf
|
||||||
|
|
||||||
|
# Optionally declare the Python requirements required to build your docs
|
||||||
|
python:
|
||||||
|
install:
|
||||||
|
- requirements: docs/requirements.txt
|
||||||
87
CHANGELOG.md
Normal file
87
CHANGELOG.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Release Notes
|
||||||
|
|
||||||
|
## 2023.05.01
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Added the `xterm` attribute to `py-config`. When set to `True` or `xterm`, an (output-only) [xterm.js](http://xtermjs.org/) terminal will be used in place of the default py-terminal.
|
||||||
|
- The default version of Pyodide is now `0.23.2`. See the [Pyodide Changelog](https://pyodide.org/en/stable/project/changelog.html#version-0-23-2) for a detailed list of changes.
|
||||||
|
- Added the `@when` decorator for attaching Python functions as event handlers
|
||||||
|
- The `py-mount` attribute on HTML elements has been deprecated, and will be removed in a future release.
|
||||||
|
|
||||||
|
#### Runtime py- attributes
|
||||||
|
|
||||||
|
- Added logic to react to `py-*` attributes changes, removal, `py-*` attributes added to already live nodes but also `py-*` attributes added or defined via injected nodes (either appended or via `innerHTML` operations). ([#1435](https://github.com/pyscript/pyscript/pull/1435))
|
||||||
|
|
||||||
|
#### <script type="py">
|
||||||
|
|
||||||
|
- Added the ability to optionally use `<script type="py">`, `<script type="pyscript">` or `<script type="py-script">` instead of a `<py-script>` custom element, in order to tackle cases where the content of the `<py-script>` tag, inevitably parsed by browsers, could accidentally contain _HTML_ able to break the surrounding page layout. ([#1396](https://github.com/pyscript/pyscript/pull/1396))
|
||||||
|
|
||||||
|
#### <py-terminal>
|
||||||
|
|
||||||
|
- Added a `docked` field and attribute for the `<py-terminal>` custom element, enabled by default when the terminal is in `auto` mode, and able to dock the terminal at the bottom of the page with auto scroll on new code execution.
|
||||||
|
|
||||||
|
#### <py-script>
|
||||||
|
|
||||||
|
- Restored the `output` attribute of `py-script` tags to route `sys.stdout` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
|
||||||
|
- Added a `stderr` attribute of `py-script` tags to route `sys.stderr` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
|
||||||
|
|
||||||
|
#### <py-repl>
|
||||||
|
|
||||||
|
- The `output` attribute of `py-repl` tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()`
|
||||||
|
- Added a `stderr` attribute of `py-repl` tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
|
||||||
|
- Resored the `output-mode` attribute of `py-repl` tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results.
|
||||||
|
- Load code from the attribute src of py-repl and preload it into the corresponding py-repl tag by use the attribute `str` in your `py-repl` tag([#1292](https://github.com/pyscript/pyscript/pull/1292))
|
||||||
|
- <py-repl> elements now have a `getPySrc()` method, which returns the code inside the REPL as a string.([#1516](https://github.com/pyscript/pyscript/pull/1292))
|
||||||
|
|
||||||
|
#### Plugins
|
||||||
|
|
||||||
|
- Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a `py-repl` tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
|
||||||
|
|
||||||
|
#### Web worker support
|
||||||
|
|
||||||
|
- introduced the new experimental `execution_thread` config option: if you set `execution_thread = "worker"`, the python interpreter runs inside a web worker
|
||||||
|
- worker support is still **very** experimental: not everything works, use it at your own risk
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fixes [#1280](https://github.com/pyscript/pyscript/issues/1280), which describes the errors on the PyRepl tests related to having auto-gen tags that shouldn't be there.
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
- Py-REPL tests now run on both osx and non osx OSs
|
||||||
|
- migrated from _rollup_ to _esbuild_ to create artifacts
|
||||||
|
- updated `@codemirror` dependency to its latest
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
- Add docs for event handlers
|
||||||
|
|
||||||
|
## 2023.03.1
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fixed an issue where `pyscript` would not be available when using the minified version of PyScript. ([#1054](https://github.com/pyscript/pyscript/pull/1054))
|
||||||
|
- Fixed missing closing tag when rendering an image with `display`. ([#1058](https://github.com/pyscript/pyscript/pull/1058))
|
||||||
|
- Fixed a bug where Python plugins methods were being executed twice. ([#1064](https://github.com/pyscript/pyscript/pull/1064))
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
- When adding a `py-` attribute to an element but didn't added an `id` attribute, PyScript will now generate a random ID for the element instead of throwing an error which caused the splash screen to not shutdown. ([#1122](https://github.com/pyscript/pyscript/pull/1122))
|
||||||
|
- You can now disable the splashscreen by setting `enabled = false` in your `py-config` under the `[splashscreen]` configuration section. ([#1138](https://github.com/pyscript/pyscript/pull/1138))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Fixed 'Direct usage of document is deprecated' warning in the getting started guide. ([#1052](https://github.com/pyscript/pyscript/pull/1052))
|
||||||
|
- Added reference documentation for the `py-splashscreen` plugin ([#1138](https://github.com/pyscript/pyscript/pull/1138))
|
||||||
|
- Adds doc for installing tests ([#1156](https://github.com/pyscript/pyscript/pull/1156))
|
||||||
|
- Adds docs for custom Pyscript attributes (`py-*`) that allow you to add event listeners to an element ([#1125](https://github.com/pyscript/pyscript/pull/1125))
|
||||||
|
|
||||||
|
### Deprecations and Removals
|
||||||
|
|
||||||
|
- The py-config `runtimes` to specify an interpreter has been deprecated. The `interpreters` config should be used instead. ([#1082](https://github.com/pyscript/pyscript/pull/1082))
|
||||||
|
- The attributes `pys-onClick` and `pys-onKeyDown` have been deprecated, but the warning was only shown in the console. An alert banner will now be shown on the page if the attributes are used. They will be removed in the next release. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
|
||||||
|
- The pyscript elements `py-button`, `py-inputbox`, `py-box` and `py-title` have now completed their deprecation cycle and have been removed. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
|
||||||
|
- The attributes `pys-onClick` and `pys-onKeyDown` have been removed. Use `py-click` and `py-keydown` instead ([#1361](https://github.com/pyscript/pyscript/pull/1361))
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Code of Conduct
|
# Code of Conduct
|
||||||
|
|
||||||
The Code of Conduct is available in the pyscript Governance repo.
|
The Code of Conduct is available in the PyScript Governance repo.
|
||||||
See https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md
|
See https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md
|
||||||
|
|||||||
@@ -4,81 +4,64 @@ Thank you for wanting to contribute to the PyScript project!
|
|||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
* [Code of Conduct](#code-of-conduct)
|
- [Contributing to PyScript](#contributing-to-pyscript)
|
||||||
* [Contributing](#contributing)
|
- [Table of contents](#table-of-contents)
|
||||||
* [Reporting bugs](#reporting-bugs)
|
- [Code of Conduct](#code-of-conduct)
|
||||||
* [Reporting security issues](#reporting-security-issues)
|
- [Contributing](#contributing)
|
||||||
* [Asking questions](#asking-questions)
|
- [Reporting bugs](#reporting-bugs)
|
||||||
* [Setting up your environment](#setting-up-your-environment)
|
- [Creating useful issues](#creating-useful-issues)
|
||||||
* [Places to start](#places-to-start)
|
- [Reporting security issues](#reporting-security-issues)
|
||||||
* [Submitting a change](#submitting-a-change)
|
- [Asking questions](#asking-questions)
|
||||||
* [License terms for contributions](#license-terms-for-contributions)
|
- [Setting up your local environment and developing](#setting-up-your-local-environment-and-developing)
|
||||||
* [Becoming a maintainer](#becoming-a-maintainer)
|
- [Developing](#developing)
|
||||||
* [Trademarks](#trademarks)
|
- [Rebasing changes](#rebasing-changes)
|
||||||
|
- [Building the docs](#building-the-docs)
|
||||||
|
- [Places to start](#places-to-start)
|
||||||
|
- [Setting up your local environment and developing](#setting-up-your-local-environment-and-developing)
|
||||||
|
- [Submitting a change](#submitting-a-change)
|
||||||
|
- [License terms for contributions](#license-terms-for-contributions)
|
||||||
|
- [Becoming a maintainer](#becoming-a-maintainer)
|
||||||
|
- [Trademarks](#trademarks)
|
||||||
|
|
||||||
## Code of Conduct
|
# Code of Conduct
|
||||||
|
|
||||||
The [PyScript Code of Conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md) governs the project and everyone participating in it. By participating, you are expected to uphold this code. Please report unacceptable behavior to the maintainers or administrators as described in that document.
|
The [PyScript Code of Conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md) governs the project and everyone participating in it. By participating, you are expected to uphold this code. Please report unacceptable behavior to the maintainers or administrators as described in that document.
|
||||||
|
|
||||||
## Contributing
|
# Contributing
|
||||||
|
|
||||||
### Reporting bugs
|
## Reporting bugs
|
||||||
|
|
||||||
Bugs are tracked on the [project issues page](https://github.com/pyscript/pyscript/issues). Please check if your issue has already been filed by someone else by searching the existing issues before filing a new one. Once your issue is filed, it will be triaged by another contributor or maintainer. If there are questions raised about your issue, please respond promptly.
|
Bugs are tracked on the [project issues page](https://github.com/pyscript/pyscript/issues). Please check if your issue has already been filed by someone else by searching the existing issues before filing a new one. Once your issue is filed, it will be triaged by another contributor or maintainer. If there are questions raised about your issue, please respond promptly.
|
||||||
|
|
||||||
#### Creating useful issues
|
## Creating useful issues
|
||||||
|
|
||||||
* Use a clear and descriptive title.
|
- Use a clear and descriptive title.
|
||||||
* Describe the specific steps that reproduce the problem with as many details as possible so that someone can verify the issue.
|
- Describe the specific steps that reproduce the problem with as many details as possible so that someone can verify the issue.
|
||||||
* Describe the behavior you observed, and the behavior you had expected.
|
- Describe the behavior you observed, and the behavior you had expected.
|
||||||
* Include screenshots if they help make the issue clear.
|
- Include screenshots if they help make the issue clear.
|
||||||
|
|
||||||
### Reporting security issues
|
## Reporting security issues
|
||||||
|
|
||||||
If you aren't confident that it is appropriate to submit a security issue using the above process, you can e-mail it to security@pyscript.net
|
If you aren't confident that it is appropriate to submit a security issue using the above process, you can e-mail it to security@pyscript.net
|
||||||
|
|
||||||
### Asking questions
|
## Asking questions
|
||||||
|
|
||||||
If you have questions about the project, using PyScript, or anything else, please ask in the [PyScript forum](https://community.anaconda.cloud/c/tech-topics/pyscript).
|
If you have questions about the project, using PyScript, or anything else, please ask in the [PyScript forum](https://community.anaconda.cloud/c/tech-topics/pyscript).
|
||||||
|
|
||||||
### Setting up your environment
|
## Places to start
|
||||||
|
|
||||||
* clone the repo
|
If you would like to contribute to PyScript, but you aren't sure where to begin, here are some suggestions:
|
||||||
```
|
|
||||||
git clone https://github.com/pyscript/pyscript
|
|
||||||
```
|
|
||||||
* cd into the main project folder
|
|
||||||
```
|
|
||||||
cd pyscript/pyscriptjs
|
|
||||||
```
|
|
||||||
* install the dependencies with npm install - make sure to use nodejs version >= 16
|
|
||||||
```
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
* run npm run dev to build and run the dev server. This will also watch for changes and rebuild when a file is saved.
|
|
||||||
```
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Places to start
|
- **Read over the existing documentation.** Are there things missing, or could they be clearer? Make some changes/additions to those documents.
|
||||||
|
- **Review the open issues.** Are they clear? Can you reproduce them? You can add comments, clarifications, or additions to those issues. If you think you have an idea of how to address the issue, submit a fix!
|
||||||
|
- **Look over the open pull requests.** Do you have comments or suggestions for the proposed changes? Add them.
|
||||||
|
- **Check out the examples.** Is there a use case that would be good to have sample code for? Create an example for it.
|
||||||
|
|
||||||
If you would like to contribute to PyScript, but you aren't sure where to begin, here are some suggestions.
|
## Setting up your local environment and developing
|
||||||
|
|
||||||
* **Read over the existing documentation.** Are there things missing, or could they be clearer? Make some changes/additions to those documents.
|
If you would like to contribute to PyScript, you will need to set up a local development environment. The [following instructions](https://pyscript.github.io/docs/latest/development/setting-up-environment.html) will help you get started.
|
||||||
* **Review the open issues.** Are they clear? Can you reproduce them? You can add comments, clarifications, or additions to those issues. If you think you have an idea of how to address the issue, submit a fix!
|
|
||||||
* **Look over the open pull requests.** Do you have comments or suggestions for the proposed changes? Add them.
|
|
||||||
* **Check out the examples.** Is there a use case that would be good to have sample code for? Create an example for it.
|
|
||||||
|
|
||||||
### Submitting a change
|
You can also read about PyScript's [development process](https://pyscript.github.io/docs/latest/development/developing.html) to learn how to contribute code to PyScript, how to run tests and what's the PR etiquette of the community!
|
||||||
|
|
||||||
All contributions must be licensed Apache 2.0, and all files must have a copy of the boilerplate license comment (can be copied from an existing file).
|
|
||||||
|
|
||||||
To create a change for PyScript, you can follow the process described [here](https://docs.github.com/en/get-started/quickstart/contributing-to-projects).
|
|
||||||
|
|
||||||
* Fork a personal copy of the PyScript project.
|
|
||||||
* Make the changes you would like (don't forget to test them!)
|
|
||||||
* Please squash all commits for a change into a single commit (this can be done using "git rebase -i"). Do your best to have a well-formed commit message for the change.
|
|
||||||
* Open a pull request back to the PyScript project and address any comments/questions from the maintainers and other contributors.
|
|
||||||
|
|
||||||
## License terms for contributions
|
## License terms for contributions
|
||||||
|
|
||||||
@@ -86,12 +69,13 @@ This Project welcomes contributions, suggestions, and feedback. All contribution
|
|||||||
|
|
||||||
## Becoming a maintainer
|
## Becoming a maintainer
|
||||||
|
|
||||||
Contributors are invited to be maintainers to the project by demonstrating good decision making in their contributions, a commitment to the goals of the project, and consistent adherence to the [code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md). New maintainers are invited by a 3/4 vote of the existing maintainers.
|
Contributors are invited to be maintainers of the project by demonstrating good decision making in their contributions, a commitment to the goals of the project, and consistent adherence to the [code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md). New maintainers are invited by a 3/4 vote of the existing maintainers.
|
||||||
|
|
||||||
## Trademarks
|
## Trademarks
|
||||||
|
|
||||||
The Project abides by the Organization's [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md).
|
The Project abides by the Organization's [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Part of MVG-0.1-beta.
|
Part of MVG-0.1-beta.
|
||||||
Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/).
|
Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||||
|
|||||||
@@ -1,210 +0,0 @@
|
|||||||
# Getting started with PyScript
|
|
||||||
|
|
||||||
This page will guide you through getting started with PyScript.
|
|
||||||
|
|
||||||
## Development setup
|
|
||||||
|
|
||||||
PyScript does not require any development environment other
|
|
||||||
than a web browser. We recommend using [Chrome](https://www.google.com/chrome/).
|
|
||||||
|
|
||||||
If you're using [VSCode](https://code.visualstudio.com/), the
|
|
||||||
[Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
|
|
||||||
can be used to reload the page as you edit the HTML file.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
There is no installation required. In this document we'll use
|
|
||||||
the PyScript assets served on https://pyscript.net.
|
|
||||||
|
|
||||||
If you want to download the source and build it yourself, follow
|
|
||||||
the instructions in the README.md file.
|
|
||||||
|
|
||||||
## Your first PyScript HTML file
|
|
||||||
|
|
||||||
Here's a "Hello, world!" example using PyScript.
|
|
||||||
|
|
||||||
Using your favorite editor create a new file called `hello.html` in
|
|
||||||
the same directory as your PyScript, JavaScript, and CSS files with the
|
|
||||||
following content, and open the file in your web browser. You can typically
|
|
||||||
open an HTML by double clicking it in your file explorer.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
|
||||||
</head>
|
|
||||||
<body> <py-script> print('Hello, World!') </py-script> </body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice the use of the `<py-script>` tag in the HTML body. This
|
|
||||||
is where you'll write your Python code. In the following sections we'll
|
|
||||||
introduce the 8 tags provided by PyScript.
|
|
||||||
|
|
||||||
## The py-script tag
|
|
||||||
|
|
||||||
The `<py-script>` tag lets you execute multi-line Python scripts and
|
|
||||||
print back onto the page. For
|
|
||||||
example, we can compute π.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<py-script>
|
|
||||||
print("Let's compute π:")
|
|
||||||
def compute_pi(n):
|
|
||||||
pi = 2
|
|
||||||
for i in range(1,n):
|
|
||||||
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
|
|
||||||
return pi
|
|
||||||
|
|
||||||
pi = compute_pi(100000)
|
|
||||||
s = f"π is approximately {pi:.3f}"
|
|
||||||
print(s)
|
|
||||||
</py-script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Writing into labeled elements
|
|
||||||
|
|
||||||
In the example above we had a single `<py-script>` tag and it printed
|
|
||||||
one or more lines onto the page in order. Within the `<py-script>` you
|
|
||||||
have access to the `pyscript` module, which provides a `.write()` method
|
|
||||||
to send strings into labeled elements on the page.
|
|
||||||
|
|
||||||
For example we'll add some style elements and provide place holders for
|
|
||||||
the `<py-script>` tag write to.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<b><p>Today is <u><label id='today'></label></u></p></b>
|
|
||||||
<br>
|
|
||||||
<div id="pi" class="alert alert-primary"></div>
|
|
||||||
<py-script>
|
|
||||||
import datetime as dt
|
|
||||||
pyscript.write('today', dt.date.today().strftime('%A %B %d, %Y'))
|
|
||||||
|
|
||||||
def compute_pi(n):
|
|
||||||
pi = 2
|
|
||||||
for i in range(1,n):
|
|
||||||
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
|
|
||||||
return pi
|
|
||||||
|
|
||||||
pi = compute_pi(100000)
|
|
||||||
pyscript.write('pi', f'π is approximately {pi:.3f}')
|
|
||||||
</py-script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Packages and modules
|
|
||||||
|
|
||||||
In addition to the [Python Standard Library](https://docs.python.org/3/library/) and
|
|
||||||
the `pyscript` module, many 3rd-party OSS packages will work out-of-the-box with PyScript.
|
|
||||||
|
|
||||||
In order to use them you will need to declare the dependencies using the `<py-env>` in the
|
|
||||||
HTML head. You can also link to `.whl` files directly on disk like in our [toga example](https://github.com/pyscript/pyscript/blob/main/pyscriptjs/examples/toga/freedom.html)
|
|
||||||
|
|
||||||
```
|
|
||||||
<py-env>
|
|
||||||
- './static/wheels/travertino-0.1.3-py3-none-any.whl'
|
|
||||||
</py-env>
|
|
||||||
```
|
|
||||||
|
|
||||||
If your `.whl` is not a pure Python wheel, then open a PR or issue with [pyodide](https://github.com/pyodide/pyodide) to get it added [here](https://github.com/pyodide/pyodide/tree/main/packages).
|
|
||||||
If there's enough popular demand the pyodide team will likely work on supporting your package, regardless things will likely move faster if you make the PR and consult with the team to get unblocked.
|
|
||||||
|
|
||||||
For example, NumPy and Matplotlib are available. Notice here we're using `<py-script output="plot">`
|
|
||||||
as a shortcut, which takes the expression on the last line of the script and runs `pyscript.write('plot', fig)`.
|
|
||||||
|
|
||||||
|
|
||||||
```html
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
|
||||||
<py-env>
|
|
||||||
- numpy
|
|
||||||
- matplotlib
|
|
||||||
</py-env>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>Let's plot random numbers</h1>
|
|
||||||
<div id="plot"></div>
|
|
||||||
<py-script output="plot">
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
x = np.random.randn(1000)
|
|
||||||
y = np.random.randn(1000)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
|
||||||
ax.scatter(x, y)
|
|
||||||
fig
|
|
||||||
</py-script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Local modules
|
|
||||||
|
|
||||||
In addition to packages you can declare local Python modules that will
|
|
||||||
be imported in the `<py-script>` tag. For example, we can place the random
|
|
||||||
number generation steps in a function in the file `data.py`.
|
|
||||||
|
|
||||||
```python
|
|
||||||
# data.py
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
def make_x_and_y(n):
|
|
||||||
x = np.random.randn(n)
|
|
||||||
y = np.random.randn(n)
|
|
||||||
return x, y
|
|
||||||
```
|
|
||||||
|
|
||||||
In the HTML tag `<py-env>` paths to local modules are provided in the
|
|
||||||
`paths:` key.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
|
||||||
<py-env>
|
|
||||||
- numpy
|
|
||||||
- matplotlib
|
|
||||||
- paths:
|
|
||||||
- /data.py
|
|
||||||
</py-env>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>Let's plot random numbers</h1>
|
|
||||||
<div id="plot"></div>
|
|
||||||
<py-script output="plot">
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
from data import make_x_and_y
|
|
||||||
|
|
||||||
x, y = make_x_and_y(n=1000)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
|
||||||
ax.scatter(x, y)
|
|
||||||
fig
|
|
||||||
</py-script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Governance Policy
|
# Governance Policy
|
||||||
|
|
||||||
This document provides the governance policy for the Project. Maintainers agree to this policy and to abide by all Project polices, including the [code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md), [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md), and [antitrust policy](https://github.com/pyscript/governance/blob/main/ANTITRUST.md) by adding their name to the [maintainers.md file](./MAINTAINERS.md).
|
This document provides the governance policy for the Project. Maintainers agree to this policy and to abide by all Project policies, including the [code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md), [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md), and [antitrust policy](https://github.com/pyscript/governance/blob/main/ANTITRUST.md) by adding their name to the [maintainers.md file](https://github.com/pyscript/pyscript/blob/main/MAINTAINERS.md).
|
||||||
|
|
||||||
## 1. Roles.
|
## 1. Roles.
|
||||||
|
|
||||||
@@ -41,5 +41,6 @@ Any names, trademarks, logos, or goodwill developed by and associated with the P
|
|||||||
Amendments to this governance policy may be made by affirmative vote of 2/3 of all Maintainers, with approval by the Organization's Steering Committee.
|
Amendments to this governance policy may be made by affirmative vote of 2/3 of all Maintainers, with approval by the Organization's Steering Committee.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Part of MVG-0.1-beta.
|
Part of MVG-0.1-beta.
|
||||||
Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/).
|
Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
# Maintainers
|
# Maintainers
|
||||||
|
|
||||||
This document lists the Maintainers of the Project. Maintainers may be added once approved by the existing maintainers as described in the [Governance document](./GOVERNANCE.md). By adding your name to this list you are agreeing to abide by the Project governance documents and to abide by all of the Organization's polices, including the [code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md), [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md), and [antitrust policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md). If you are participating because of your affiliation with another organization (designated below), you represent that you have the authority to bind that organization to these policies.
|
This document lists the Maintainers of the Project. Maintainers may be added once approved by the existing maintainers as described in the [Governance document](https://github.com/pyscript/pyscript/blob/main/GOVERNANCE.md). By adding your name to this list you are agreeing to abide by the Project governance documents and to abide by all of the Organization's polices, including the [code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md), [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md), and [antitrust policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md). If you are participating because of your affiliation with another organization (designated below), you represent that you have the authority to bind that organization to these policies.
|
||||||
|
|
||||||
| **NAME** | **Organization** |
|
| **NAME** | **Organization** |
|
||||||
| --- | --- |
|
| -------------------- | ---------------- |
|
||||||
| Fabio Pliger | Anaconda, Inc |
|
| Fabio Pliger | Anaconda, Inc |
|
||||||
| Antonio Cuni | Anaconda, Inc |
|
| Antonio Cuni | Anaconda, Inc |
|
||||||
| Philipp Rudiger | Anaconda, Inc |
|
| Philipp Rudiger | Anaconda, Inc |
|
||||||
| Peter Wang | Anaconda, Inc |
|
| Peter Wang | Anaconda, Inc |
|
||||||
| Kevin Goldsmith | Anaconda, Inc |
|
| Kevin Goldsmith | Anaconda, Inc |
|
||||||
| Mariana Meireles | Anaconda, Inc |
|
| Mariana Meireles | |
|
||||||
| --- | --- |
|
| Nicholas H.Tollervey | Anaconda, Inc |
|
||||||
|
| Madhur Tandon | Anaconda, Inc |
|
||||||
|
| Ted Patrick | Anaconda, Inc |
|
||||||
|
| Jeff Glass | |
|
||||||
|
| Paul Everitt | |
|
||||||
|
| Fabio Rosado | Anaconda, Inc |
|
||||||
|
| Andrea Giammarchi | Anaconda, Inc |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Part of MVG-0.1-beta.
|
Part of MVG-0.1-beta.
|
||||||
Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/).
|
Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||||
|
|||||||
92
Makefile
Normal file
92
Makefile
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
MIN_NODE_VER := 20
|
||||||
|
MIN_NPM_VER := 6
|
||||||
|
MIN_PY3_VER := 8
|
||||||
|
NODE_VER := $(shell node -v | cut -d. -f1 | sed 's/^v\(.*\)/\1/')
|
||||||
|
NPM_VER := $(shell npm -v | cut -d. -f1)
|
||||||
|
PY3_VER := $(shell python3 -c "import sys;t='{v[1]}'.format(v=list(sys.version_info[:2]));print(t)")
|
||||||
|
PY_OK := $(shell python3 -c "print(int($(PY3_VER) >= $(MIN_PY3_VER)))")
|
||||||
|
|
||||||
|
all:
|
||||||
|
@echo "\nThere is no default Makefile target right now. Try:\n"
|
||||||
|
@echo "make setup - check your environment and install the dependencies."
|
||||||
|
@echo "make clean - clean up auto-generated assets."
|
||||||
|
@echo "make build - build PyScript."
|
||||||
|
@echo "make precommit-check - run the precommit checks (run eslint)."
|
||||||
|
@echo "make test-integration - run all integration tests sequentially."
|
||||||
|
@echo "make fmt - format the code."
|
||||||
|
@echo "make fmt-check - check the code formatting.\n"
|
||||||
|
|
||||||
|
.PHONY: check-node
|
||||||
|
check-node:
|
||||||
|
@if [ $(NODE_VER) -lt $(MIN_NODE_VER) ]; then \
|
||||||
|
echo "\033[0;31mBuild requires Node $(MIN_NODE_VER).x or higher: $(NODE_VER) detected.\033[0m"; \
|
||||||
|
false; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: check-npm
|
||||||
|
check-npm:
|
||||||
|
@if [ $(NPM_VER) -lt $(MIN_NPM_VER) ]; then \
|
||||||
|
echo "\033[0;31mBuild requires Node $(MIN_NPM_VER).x or higher: $(NPM_VER) detected.\033[0m"; \
|
||||||
|
false; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: check-python
|
||||||
|
check-python:
|
||||||
|
@if [ $(PY_OK) -eq 0 ]; then \
|
||||||
|
echo "\033[0;31mRequires Python 3.$(MIN_PY3_VER).x or higher: 3.$(PY3_VER) detected.\033[0m"; \
|
||||||
|
false; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check the environment, install the dependencies.
|
||||||
|
setup: check-node check-npm check-python
|
||||||
|
cd pyscript.core && npm install && cd ..
|
||||||
|
ifeq ($(VIRTUAL_ENV),)
|
||||||
|
echo "\n\n\033[0;31mCannot install Python dependencies. Your virtualenv is not activated.\033[0m"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
python -m pip install -r requirements.txt
|
||||||
|
playwright install
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Clean up generated assets.
|
||||||
|
clean:
|
||||||
|
find . -name \*.py[cod] -delete
|
||||||
|
rm -rf $(env) *.egg-info
|
||||||
|
rm -rf .pytest_cache .coverage coverage.xml
|
||||||
|
|
||||||
|
# Build PyScript.
|
||||||
|
build:
|
||||||
|
cd pyscript.core && npx playwright install && npm run build
|
||||||
|
|
||||||
|
# Run the precommit checks (run eslint).
|
||||||
|
precommit-check:
|
||||||
|
pre-commit run --all-files
|
||||||
|
|
||||||
|
# Run all integration tests sequentially.
|
||||||
|
test-integration:
|
||||||
|
mkdir -p test_results
|
||||||
|
pytest -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
||||||
|
|
||||||
|
# Run all integration tests in parallel.
|
||||||
|
test-integration-parallel:
|
||||||
|
mkdir -p test_results
|
||||||
|
pytest --numprocesses auto -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
||||||
|
|
||||||
|
# Format the code.
|
||||||
|
fmt: fmt-py
|
||||||
|
@echo "Format completed"
|
||||||
|
|
||||||
|
# Check the code formatting.
|
||||||
|
fmt-check: fmt-py-check
|
||||||
|
@echo "Format check completed"
|
||||||
|
|
||||||
|
# Format Python code.
|
||||||
|
fmt-py:
|
||||||
|
black -l 88 --skip-string-normalization .
|
||||||
|
isort --profile black .
|
||||||
|
|
||||||
|
# Check the format of Python code.
|
||||||
|
fmt-py-check:
|
||||||
|
black -l 88 --check .
|
||||||
|
|
||||||
|
.PHONY: $(MAKECMDGOALS)
|
||||||
72
README.md
72
README.md
@@ -3,43 +3,73 @@
|
|||||||
## What is PyScript
|
## What is PyScript
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
PyScript is a Pythonic alternative to Scratch, JSFiddle, and other "easy to use" programming frameworks, with the goal of making the web a friendly, hackable place where anyone can author interesting and interactive applications.
|
|
||||||
|
|
||||||
To get started see [GETTING-STARTED](GETTING-STARTED.md).
|
PyScript is a framework that allows users to create rich Python applications in the browser using HTML's interface and the power of [Pyodide](https://pyodide.org/en/stable/), [MicroPython](https://micropython.org/) and [WASM](https://webassembly.org/), and modern web technologies.
|
||||||
|
|
||||||
For examples see [the pyscript folder](pyscriptjs).
|
To get started see the [Beginning PyScript tutorial](https://docs.pyscript.net/latest/beginning-pyscript/).
|
||||||
|
|
||||||
|
For examples see [here](https://pyscript.com/@examples).
|
||||||
|
|
||||||
|
Other useful resources:
|
||||||
|
|
||||||
|
- The [official technical docs](https://docs.pyscript.net/).
|
||||||
|
- Our current [Home Page](https://pyscript.net/) on the web.
|
||||||
|
- A free-to-use [online editor](https://pyscript.com/) for trying PyScript.
|
||||||
|
- Our community [Discord Channel](https://discord.gg/BYB2kvyFwm), to keep in touch .
|
||||||
|
|
||||||
|
Every Tuesday at 15:30 UTC there is the _PyScript Community Call_ on zoom, where we can talk about PyScript development in the open. Most of the maintainers regularly participate in the call, and everybody is welcome to join.
|
||||||
|
|
||||||
|
Every other Thursday at 16:00 UTC there is the _PyScript FUN_ call: this is a call in which everybody is encouraged to show what they did with PyScript.
|
||||||
|
|
||||||
|
For more details on how to join the calls and up to date schedule, consult the official calendar:
|
||||||
|
|
||||||
|
- [Google calendar](https://calendar.google.com/calendar/u/0/embed?src=d3afdd81f9c132a8c8f3290f5cc5966adebdf61017fca784eef0f6be9fd519e0@group.calendar.google.com&ctz=UTC) in UTC time;
|
||||||
|
- [iCal format](https://calendar.google.com/calendar/ical/d3afdd81f9c132a8c8f3290f5cc5966adebdf61017fca784eef0f6be9fd519e0%40group.calendar.google.com/public/basic.ics).
|
||||||
|
|
||||||
### Longer Version
|
### Longer Version
|
||||||
|
|
||||||
PyScript is a meta project that aims to combine multiple open technologies into a framework that allows users to create sophisticated browser applications with Python. It integrates seamlessly with the way the DOM works in the browser and allows users to add Python logic in a way that feels natural both to web and Python developers.
|
PyScript is a meta project that aims to combine multiple open technologies into a framework that allows users to create sophisticated browser applications with Python. It integrates seamlessly with the way the DOM works in the browser and allows users to add Python logic in a way that feels natural both to web and Python developers.
|
||||||
|
|
||||||
## Try PyScript
|
## Try PyScript
|
||||||
|
|
||||||
To try PyScript, import the appropriate pyscript files to your html page with:
|
To try PyScript, import the appropriate pyscript files into the `<head>` tag of your html page:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
|
<head>
|
||||||
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://pyscript.net/releases/2023.11.2/core.css"
|
||||||
|
/>
|
||||||
|
<script
|
||||||
|
type="module"
|
||||||
|
src="https://pyscript.net/releases/2023.11.2/core.js"
|
||||||
|
></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py" terminal>
|
||||||
|
from pyscript import display
|
||||||
|
display("Hello World!") # this goes to the DOM
|
||||||
|
print("Hello terminal") # this goes to the terminal
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
```
|
```
|
||||||
You can then use PyScript components in your html page. PyScript currently implements the following elements:
|
|
||||||
|
|
||||||
* `<py-script>`: can be used to define python code that is executable within the web page. The element itself is not rendered to the page and is only used to add logic
|
You can then use PyScript components in your html page. PyScript currently offers various ways of running Python code:
|
||||||
* `<py-repl>`: creates a REPL component that is rendered to the page as a code editor and allows users to write executable code
|
|
||||||
|
|
||||||
Check out the [pyscriptjs/examples](pyscriptjs/examples) folder for more examples on how to use it, all you need to do is open them in Chrome.
|
- `<script type="py">`: can be used to define python code that is executable within the web page.
|
||||||
|
- `<script type="py" src="hello.py">`: same as above, but the python source is fetched from the given URL.
|
||||||
|
- `<script type="py" terminal>`: same as above, but also creates a terminal where to display stdout and stderr (e.g., the output of `print()`); `input()` does not work.
|
||||||
|
- `<script type="py" terminal worker>`: run Python inside a web worker: the terminal is fully functional and `input()` works.
|
||||||
|
- `<py-script>`: same as `<script type="py">`, but it is not recommended because if the code contains HTML tags, they could be parsed wrongly.
|
||||||
|
- `<script type="mpy">`: same as above but use MicroPython instead of Python.
|
||||||
|
|
||||||
|
Check out the [official docs](https://docs.pyscript.net/) for more detailed documentation.
|
||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
To contribute see the [CONTRIBUTING](CONTRIBUTING.md) document.
|
Read the [contributing guide](CONTRIBUTING.md) to learn about our development process, reporting bugs and improvements, creating issues and asking questions.
|
||||||
|
|
||||||
## Resources
|
Check out the [developing process](https://pyscript.github.io/docs/latest/contributing) documentation for more information on how to setup your development environment.
|
||||||
|
|
||||||
* [Discussion board](https://community.anaconda.cloud/c/tech-topics/pyscript)
|
|
||||||
* [Home Page](https://pyscript.net/)
|
|
||||||
* [Blog Post](https://engineering.anaconda.com/2022/04/welcome-pyscript.html)
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
* This is an extremely experimental project, so expect things to break!
|
|
||||||
* PyScript has been only tested on Chrome at the moment.
|
|
||||||
|
|
||||||
## Governance
|
## Governance
|
||||||
|
|
||||||
|
|||||||
19
TROUBLESHOOTING.md
Normal file
19
TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
This page is meant for troubleshooting common problems with PyScript.
|
||||||
|
|
||||||
|
## Table of contents:
|
||||||
|
|
||||||
|
- [Make Setup](#make-setup)
|
||||||
|
|
||||||
|
## Make setup
|
||||||
|
|
||||||
|
A lot of problems related to `make setup` are related to node and npm being outdated. Once npm and node are updated, `make setup` should work. You can follow the steps on the [npm documentation](https://docs.npmjs.com/try-the-latest-stable-version-of-npm) to update npm (the update command for Linux should work for Mac as well). Once npm has been updated you can continue to the instructions to update node below.
|
||||||
|
|
||||||
|
To update Node run the following commands in order (Most likely you'll be prompted for your user password, this is normal):
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo npm cache clean -f
|
||||||
|
sudo npm install -g n
|
||||||
|
sudo n stable
|
||||||
|
```
|
||||||
54
public/index.html
Normal file
54
public/index.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css" />
|
||||||
|
<link rel="stylesheet" href="_PATH_core.css" />
|
||||||
|
<script type="module" src="_PATH_core.js"></script>
|
||||||
|
<title>PyScript</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1><py-script></h1>
|
||||||
|
<ul>
|
||||||
|
<li><a href="core.js">core.js</a></li>
|
||||||
|
<li><a href="core.js.map">core.js.map</a></li>
|
||||||
|
<li><a href="core.css">core.css</a></li>
|
||||||
|
</ul>
|
||||||
|
<div id="out"></div>
|
||||||
|
<py-script>
|
||||||
|
from pyscript import display
|
||||||
|
import sys
|
||||||
|
display(sys.version)
|
||||||
|
</py-script>
|
||||||
|
|
||||||
|
<h2>Example</h2>
|
||||||
|
<pre style="padding: 1em; border: 1px solid #000000">
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>PyScript Hello World</title>
|
||||||
|
<link rel="stylesheet" href="_PATH_core.css" />
|
||||||
|
<script type="module" src="_PATH_core.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
Hello world! <br>
|
||||||
|
This is the current date and time, as computed by Python:
|
||||||
|
<py-script>
|
||||||
|
from pyscript import display
|
||||||
|
from datetime import datetime
|
||||||
|
now = datetime.now()
|
||||||
|
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
|
||||||
|
</py-script>
|
||||||
|
</body>
|
||||||
|
</html></pre
|
||||||
|
>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
pyproject.toml
Normal file
13
pyproject.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.2"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
dynamic = ["version"]
|
||||||
|
|
||||||
|
[tool.codespell]
|
||||||
|
ignore-words-list = "afterall"
|
||||||
|
skip = "*.js,*.json"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
include-package-data = false
|
||||||
26
pyscript.core/.eslintrc.cjs
Normal file
26
pyscript.core/.eslintrc.cjs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
},
|
||||||
|
extends: "eslint:recommended",
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
files: [".eslintrc.{js,cjs}"],
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: "script",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
ignorePatterns: ["3rd-party"],
|
||||||
|
rules: {
|
||||||
|
"no-implicit-globals": ["error"],
|
||||||
|
},
|
||||||
|
};
|
||||||
10
pyscript.core/.npmignore
Normal file
10
pyscript.core/.npmignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.eslintrc.cjs
|
||||||
|
.pytest_cache/
|
||||||
|
node_modules/
|
||||||
|
rollup/
|
||||||
|
test/
|
||||||
|
tests/
|
||||||
|
src/stdlib/_pyscript
|
||||||
|
src/stdlib/pyscript.py
|
||||||
|
package-lock.json
|
||||||
|
tsconfig.json
|
||||||
203
pyscript.core/LICENSE
Normal file
203
pyscript.core/LICENSE
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright (c) 2022-present, PyScript Development Team
|
||||||
|
|
||||||
|
Originated at Anaconda, Inc. in 2022
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
61
pyscript.core/README.md
Normal file
61
pyscript.core/README.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# @pyscript/core
|
||||||
|
|
||||||
|
We have moved and renamed previous _core_ module as [polyscript](https://github.com/pyscript/polyscript/#readme), which is the base module used in here to build up _PyScript Next_, now hosted in this folder.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Please read [core documentation](./docs/README.md) to know more about this project.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Clone this repository then run `npm install` within its folder.
|
||||||
|
|
||||||
|
Use `npm run build` to create all artifacts and _dist_ files.
|
||||||
|
|
||||||
|
Use `npm run server` to test locally, via the `http://localhost:8080/test/` url, smoke tests or to test manually anything you'd like to check.
|
||||||
|
|
||||||
|
### Artifacts
|
||||||
|
|
||||||
|
There are two main artifacts in this project:
|
||||||
|
|
||||||
|
- **stdlib** and its content, where `src/stdlib/pyscript.js` exposes as object literal all the _Python_ content within the folder (recursively)
|
||||||
|
- **plugins** and its content, where `src/plugins.js` exposes all available _dynamic imports_, able to instrument the bundler to create files a part within the _dist/_ folder, so that by default _core_ remains as small as possible
|
||||||
|
|
||||||
|
Accordingly, whenever a file contains this warning at its first line, please do not change such file directly before submitting a merge request, as that file will be overwritten at the next `npm run build` command, either here or in _CI_:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ⚠️ This file is an artifact: DO NOT MODIFY
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running tests
|
||||||
|
|
||||||
|
Before running the tests, we need to create a tests environment first. To do so run the following command from the root folder of the project:
|
||||||
|
|
||||||
|
```
|
||||||
|
make setup
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a tests environment [in the root of the project, named `./env`]and install all the dependencies needed to run the tests.
|
||||||
|
|
||||||
|
After the command has completed and the tests environment has been created, you can run the **integration tests** with
|
||||||
|
the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
make test-integration
|
||||||
|
```
|
||||||
|
|
||||||
|
## `pyscript` python package
|
||||||
|
|
||||||
|
The `pyscript` package available in _Python_ lives in the folder `src/stdlib/pyscript/`.
|
||||||
|
|
||||||
|
All _Python_ files will be embedded automatically whenever `npm run build` happens and reflected into the `src/stdlib/pyscript.js` file.
|
||||||
|
|
||||||
|
It is _core_ responsibility to ensure those files will be available through the Filesystem in either the _main_ thread, or any _worker_.
|
||||||
|
|
||||||
|
## JS plugins
|
||||||
|
|
||||||
|
While community or third party plugins don't need to be part of this repository and can be added just importing `@pyscript/core` as module, there are a few plugins that we would like to make available by default and these are considered _core plugins_.
|
||||||
|
|
||||||
|
To add a _core plugin_ to this project you can define your plugin entry-point and name in the `src/plugins` folder (see the `error.js` example) and create, if necessary, a folder with the same name where extra files or dependencies can be added.
|
||||||
|
|
||||||
|
The _build_ command will bring plugins by name as artifact so that the bundler can create ad-hoc files within the `dist/` folder.
|
||||||
31
pyscript.core/dev.cjs
Normal file
31
pyscript.core/dev.cjs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
let queue = Promise.resolve();
|
||||||
|
|
||||||
|
const { exec } = require("node:child_process");
|
||||||
|
|
||||||
|
const build = (fileName) => {
|
||||||
|
if (fileName) console.log(fileName, "changed");
|
||||||
|
else console.log("building without optimizations");
|
||||||
|
queue = queue.then(
|
||||||
|
() =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
exec(
|
||||||
|
"npm run build:stdlib && npm run build:plugins && npm run build:core",
|
||||||
|
{ cwd: __dirname, env: { ...process.env, NO_MIN: true } },
|
||||||
|
(error) => {
|
||||||
|
if (error) console.error(error);
|
||||||
|
else console.log(fileName || "", "build completed");
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
ignored: /\/(?:toml|plugins|pyscript)\.[mc]?js$/,
|
||||||
|
persistent: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
require("chokidar").watch("./src", options).on("change", build);
|
||||||
|
|
||||||
|
build();
|
||||||
271
pyscript.core/docs/README.md
Normal file
271
pyscript.core/docs/README.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
# PyScript Next
|
||||||
|
|
||||||
|
<sup>A summary of <code>@pyscript/core</code> features</sup>
|
||||||
|
|
||||||
|
### Getting started
|
||||||
|
|
||||||
|
Differently from [pyscript classic](https://github.com/pyscript/pyscript), where "*classic*" is the disambiguation name we use to describe the two versions of the project, `@pyscript/core` is an *ECMAScript Module* with the follow benefits:
|
||||||
|
|
||||||
|
* it doesn't block the page like a regular script, without a `deferred` attribute, would
|
||||||
|
* it allows modularity in the future
|
||||||
|
* it bootstraps itself once but it allows exports via the module
|
||||||
|
|
||||||
|
Accordingly, this is the bare minimum required output to bootstrap *PyScript Next* in your page via a CDN:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Option 1: based on esm.sh which in turns is jsdlvr -->
|
||||||
|
<script type="module" src="https://cdn.jsdelivr.net/npm/@pyscript/core"></script>
|
||||||
|
|
||||||
|
<!-- Option 2: based on unpkg.com -->
|
||||||
|
<script type="module" src="https://unpkg.com/@pyscript/core"></script>
|
||||||
|
|
||||||
|
<!-- Option X: any CDN that uses npmjs registry should work -->
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the module is loaded, any `<script type="py"></script>` on the page, or any `<py-script>` tag, would automatically run its own code or the file defined as `src` attribute, after bootstrapping the *pyodide* interpreter.
|
||||||
|
|
||||||
|
If no `<script type="py">` or `<py-script>` tag is present, it is still possible to use the module to bootstrap via JS a *Worker*, bypassing the need to bootstrap *pyodide* on the main thread, hence without ever blocking the page.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module">
|
||||||
|
import { PyWorker } from "https://cdn.jsdelivr.net/npm/@pyscript/core";
|
||||||
|
|
||||||
|
const worker = PyWorker("./code.py", { config: "./config.toml" /* optional */ });
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, it is possible to specify a `worker` attribute to either run embedded code or the provided `src` file.
|
||||||
|
|
||||||
|
#### CSS
|
||||||
|
|
||||||
|
If you are planning to use either `<py-config>` or `<py-script>` tags on the page, where latter case is usually better off with `<script type="py">` instead, you can also use CDNs to land our custom CSS:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Option 1: based on esm.sh which in turns is jsdlvr -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@pyscript/core/dist/core.css">
|
||||||
|
|
||||||
|
<!-- Option 2: based on unpkg.com -->
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/@pyscript/core/dist/core.css">
|
||||||
|
|
||||||
|
<!-- Option X: any CDN that uses npmjs registry should work -->
|
||||||
|
```
|
||||||
|
|
||||||
|
The CSS is needed to avoid seeing content on the page before *PyScript* gets a chance to initialize itself. This means both `py-config` and `py-script` tags will have a `display:none` property which is overwritten by *PyScript* once it initialize each `py-script` custom element.
|
||||||
|
|
||||||
|
Once again, if you use `<script type="py">` instead, you won't need CSS unless you also have a `py-config` on the page, instead of using an external `config` file, defined via the `config` attribute:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="py" config="./config.toml">
|
||||||
|
from pyscript import display
|
||||||
|
|
||||||
|
display("Hello PyScript Next")
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### HTML Example
|
||||||
|
|
||||||
|
This is a complete reference to bootstrap *PyScript* in a HTML document.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>PyScript Next</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@pyscript/core/dist/core.css">
|
||||||
|
<script type="module" src="https://cdn.jsdelivr.net/npm/@pyscript/core"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py">
|
||||||
|
from pyscript import document
|
||||||
|
|
||||||
|
document.body.textContent = "PyScript Next"
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Tag attributes API
|
||||||
|
|
||||||
|
Either `<script type="py">` or `<py-script>` can have zero, one or more attributes:
|
||||||
|
|
||||||
|
* **src** if defined, the content of the tag is ignored and the *Python* code in the file will be evaluated instead.
|
||||||
|
* **config** if defined, the code will be evaluated after the configuration has been parsed but this can also be directly *JSON* so that both `config='{"packages":["numpy"]}'` and `config="./config.json"`, or `config="./config.toml"`, would be valid options.
|
||||||
|
* **async** if present, it will run the *Python* code asynchronously.
|
||||||
|
* **worker** if present, it will not bootstrap *pyodide* on the main page, only on the worker file it points at, as in `<script type="py" worker="./worker.py"></script>`. Both `async` and `config` attributes are also available and used to bootstrap the worker as desired.
|
||||||
|
|
||||||
|
Please note that other [polyscript's attributes](https://pyscript.github.io/polyscript/#script-attributes) are available too but their usage is more advanced.
|
||||||
|
|
||||||
|
|
||||||
|
## JS Module API
|
||||||
|
|
||||||
|
The module itself is currently exporting the following utilities:
|
||||||
|
|
||||||
|
* **PyWorker**, which allows to bootstrap a *worker* with *pyodide* and the *pyscript* module available within the code. This callback accepts a file as argument, and an additional, and optional, `options` object literal, able to understand a `config`, which could also be directly a *JS* object literal instead of a JSON string or a file to point at, and `async` which if `true` will run the worker code with top level await enabled. Please note that the returned reference is exactly the same as [the polyscript's XWorker](https://pyscript.github.io/polyscript/#the-xworker-reference), exposing exact same utilities but granting on bootstrap all hooks are in place and the type is always *pyodide*.
|
||||||
|
* **hooks**, which allows plugins to define *ASAP* callbacks or strings that should be executed either in the main thread or the worker before, or after, the code has been executed.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { hooks } from "https://cdn.jsdelivr.net/npm/@pyscript/core";
|
||||||
|
|
||||||
|
// example
|
||||||
|
hooks.onInterpreterReady.add((utils, element) => {
|
||||||
|
console.log(element, 'found', 'pyscript is ready');
|
||||||
|
});
|
||||||
|
|
||||||
|
// the hooks namespace
|
||||||
|
({
|
||||||
|
// each function is invoked before or after python gets executed
|
||||||
|
// via: callback(pyScriptUtils, currentElement)
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onBeforeRun: new Set(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onBeforeRunAync: new Set(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onAfterRun: new Set(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onAfterRunAsync: new Set(),
|
||||||
|
|
||||||
|
// each function is invoked once when PyScript is ready
|
||||||
|
// and for each element via: callback(pyScriptUtils, currentElement)
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onInterpreterReady: new Set(),
|
||||||
|
|
||||||
|
// each string is prepended or appended to the worker code
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeBeforeRunWorker: new Set(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeBeforeRunWorkerAsync: new Set(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeAfterRunWorker: new Set(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeAfterRunWorkerAsync: new Set(),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that a *worker* is a completely different environment and it's not possible, by specifications, to pass a callback to it, which is why worker sets are strings and not functions.
|
||||||
|
|
||||||
|
However, each worker string can use `from pyscript import x, y, z` as that will be available out of the box.
|
||||||
|
|
||||||
|
## PyScript Python API
|
||||||
|
|
||||||
|
The `pyscript` python package offers various utilities in either the main thread or the worker.
|
||||||
|
|
||||||
|
The commonly shared utilities are:
|
||||||
|
|
||||||
|
* **window** in both main and worker, refers to the actual main thread global window context. In classic PyScript that'd be the equivalent of `import js` in the main, which is still available in *PyScript Next*. However, to make code easily portable between main and workers, we decided to offer this named export but please note that in workers, this is still the *main* window, not the worker global context, which would be reachable instead still via `import js`.
|
||||||
|
* **document** in both main and worker, refers to the actual main page `document`. In classic PyScript, this is the equivalent of `from js import document` on the main thread, but this won't ever work in a worker because there is no `document` in there. Fear not though, *PyScript Next* `document` will instead work out of the box, still accessing the main document behind the scene, so that `from pyscript import document` is granted to work in both main and workers seamlessly.
|
||||||
|
* **display** in both main and worker, refers to the good old `display` utility except:
|
||||||
|
* in the *main* it automatically uses the current script `target` to display content
|
||||||
|
* in the *worker* it still needs to know *where* to display content using the `target="dom-id"` named argument, as workers don't get a default target attached
|
||||||
|
* in both main and worker, the `append=True` is the *default* behavior, which is inherited from the classic PyScript.
|
||||||
|
|
||||||
|
#### Extra main-only features
|
||||||
|
|
||||||
|
* **PyWorker** which allows Python code to create a PyScript worker with the *pyscript* module pre-bundled. Please note that running PyScript on the main requires *pyodide* bootstrap, but also every worker requires *pyodide* bootstrap a part, as each worker is an environment / sandbox a part. This means that using *PyWorker* in the main will take, even if the main interpreter is already up and running, a bit of time to bootstrap the worker, also accordingly to the config files or packages in it.
|
||||||
|
|
||||||
|
|
||||||
|
#### Extra worker-only features
|
||||||
|
|
||||||
|
* **sync** which allows both main and the worker to seamlessly pass any serializable data around, without the need to convert Python dictionaries to JS object literals, as that's done automatically.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module">
|
||||||
|
import { PyWorker } from "https://cdn.jsdelivr.net/npm/@pyscript/core";
|
||||||
|
|
||||||
|
const worker = PyWorker("./worker.py");
|
||||||
|
|
||||||
|
worker.sync.alert_message = message => {
|
||||||
|
alert(message);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pyscript import sync
|
||||||
|
|
||||||
|
sync.alert_message("Hello Main!")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Worker requirements
|
||||||
|
|
||||||
|
To make it possible to use what looks like *synchronous* DOM APIs, or any other API available via the `window` within a *worker*, we are using latest Web features such as [Atomics](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics).
|
||||||
|
|
||||||
|
Without going into too many details, this means that the *SharedArrayBuffer* primitive must be available, and to do so, the server should enable the following headers:
|
||||||
|
|
||||||
|
```
|
||||||
|
Cross-Origin-Opener-Policy: same-origin
|
||||||
|
Cross-Origin-Embedder-Policy: require-corp
|
||||||
|
Cross-Origin-Resource-Policy: cross-origin
|
||||||
|
```
|
||||||
|
|
||||||
|
These headers allow local files to be secured and yet able to load resources from the Web (i.e. pyodide library or its packages).
|
||||||
|
|
||||||
|
> ℹ️ **Careful**: we are using and testing these headers on both Desktop and Mobile to be sure all major browsers work as expected (Safari, Firefox, Chromium based browsers). If you change the value of these headers please be sure you test your target devices and browsers properly.
|
||||||
|
|
||||||
|
Please note that if you don't have control over your server's headers, it is possible to simply put [mini-coi](https://github.com/WebReflection/mini-coi#readme) script at the root of your *PyScript with Workers* enabled folder (site root, or any subfolder).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd project-folder
|
||||||
|
|
||||||
|
# grab mini-coi content and save it locally as mini-coi.js
|
||||||
|
curl -Ls https://unpkg.com/mini-coi -o mini-coi.js
|
||||||
|
```
|
||||||
|
|
||||||
|
With either these two solutions, it should be now possible to bootstrap a *PyScript Worker* without any issue.
|
||||||
|
|
||||||
|
#### mini-coi example
|
||||||
|
```html
|
||||||
|
<!doctype html>
|
||||||
|
<script src="/mini-coi.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
import { PyWorker } from "https://unpkg.com/@pyscript/core";
|
||||||
|
PyWorker("./test.py");
|
||||||
|
</script>
|
||||||
|
<!-- ./test.py -->
|
||||||
|
<!--
|
||||||
|
from pyscript import document
|
||||||
|
document.body.textContent = "Hello PyScript Worker"
|
||||||
|
-->
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that a local or remote web server is still needed to allow the Service Worker and `python -m http.server` would do locally, *except* we need to reach `http://localhost:8000/`, not `http://0.0.0.0:8000/`, because the browser does not consider safe non localhost sites when the insecure `http://` protocol, instead of `https://`, is reached.
|
||||||
|
|
||||||
|
|
||||||
|
#### local server example
|
||||||
|
If you'd like to test locally these headers, without needing the *mini-coi* Service Worker, you can use various projects or, if you have *NodeJS* available, simply run the following command in the folder containing the site/project:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# bootstrap a local server with all headers needed
|
||||||
|
npx static-handler --cors --coep --coop --corp .
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### F.A.Q.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>why config attribute can also contain JSON but not TOML?</strong></summary>
|
||||||
|
<div markdown=1>
|
||||||
|
|
||||||
|
The *JSON* standard doesn't require new lines or indentation so it felt quick and desired to allow inline JSON as attribute content.
|
||||||
|
|
||||||
|
It's true that HTML attributes can be multi-line too, if properly embedded, but that looked too awkward and definitively harder to explain to me.
|
||||||
|
|
||||||
|
We might decide to allow TOML too in the future, but the direct config as attribute, instead of a proper file, or the usage of `<py-config>`, is meant for quick and simple packages or files dependencies and not much else.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>what are the worker's caveats?</strong></summary>
|
||||||
|
<div markdown=1>
|
||||||
|
|
||||||
|
When interacting with `window` or `document` it's important to understand that these use, behind the scene, an orchestrated [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) dance.
|
||||||
|
|
||||||
|
This means that some kind of data that cannot be passed around, specially not compatible with the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
|
||||||
|
|
||||||
|
In short, please try to stick with *JS* references when passing along, or dealing with, *DOM* or other *APIs*.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
1
pyscript.core/index.js
Normal file
1
pyscript.core/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./dist/core.js";
|
||||||
2
pyscript.core/jsdelivr.js
Normal file
2
pyscript.core/jsdelivr.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// @see https://github.com/jsdelivr/jsdelivr/issues/18528
|
||||||
|
export * from "./core/dist/core.js";
|
||||||
3657
pyscript.core/package-lock.json
generated
Normal file
3657
pyscript.core/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
81
pyscript.core/package.json
Normal file
81
pyscript.core/package.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"name": "@pyscript/core",
|
||||||
|
"version": "0.3.9",
|
||||||
|
"type": "module",
|
||||||
|
"description": "PyScript",
|
||||||
|
"module": "./index.js",
|
||||||
|
"unpkg": "./index.js",
|
||||||
|
"jsdelivr": "./jsdelivr.js",
|
||||||
|
"browser": "./index.js",
|
||||||
|
"main": "./index.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./types/core.d.ts",
|
||||||
|
"import": "./src/core.js"
|
||||||
|
},
|
||||||
|
"./css": {
|
||||||
|
"import": "./dist/core.css"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"server": "npx static-handler --coi .",
|
||||||
|
"build": "npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy",
|
||||||
|
"build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/",
|
||||||
|
"build:plugins": "node rollup/plugins.cjs",
|
||||||
|
"build:stdlib": "node rollup/stdlib.cjs",
|
||||||
|
"build:3rd-party": "node rollup/3rd-party.cjs",
|
||||||
|
"clean:3rd-party": "rm src/3rd-party/*.js && rm src/3rd-party/*.css",
|
||||||
|
"test:mpy": "static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; playwright test --fully-parallel test/ || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
|
||||||
|
"dev": "node dev.cjs",
|
||||||
|
"release": "npm run build && npm run zip",
|
||||||
|
"size": "echo -e \"\\033[1mdist/*.js file size\\033[0m\"; for js in $(ls dist/*.js); do cat $js | brotli > ._; echo -e \"\\033[2m$js:\\033[0m $(du -h --apparent-size ._ | sed -e 's/[[:space:]]*._//')\"; rm ._; done",
|
||||||
|
"ts": "rm -rf types && tsc -p .",
|
||||||
|
"zip": "zip -r dist.zip ./dist"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"pyscript",
|
||||||
|
"core"
|
||||||
|
],
|
||||||
|
"author": "Anaconda Inc.",
|
||||||
|
"license": "APACHE-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ungap/with-resolvers": "^0.1.0",
|
||||||
|
"basic-devtools": "^0.1.6",
|
||||||
|
"polyscript": "^0.6.2",
|
||||||
|
"sticky-module": "^0.1.1",
|
||||||
|
"to-json-callback": "^0.1.1",
|
||||||
|
"type-checked-collections": "^0.1.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@codemirror/commands": "^6.3.2",
|
||||||
|
"@codemirror/lang-python": "^6.1.3",
|
||||||
|
"@codemirror/language": "^6.9.3",
|
||||||
|
"@codemirror/state": "^6.3.3",
|
||||||
|
"@codemirror/view": "^6.22.1",
|
||||||
|
"@playwright/test": "^1.40.1",
|
||||||
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"@webreflection/toml-j0.4": "^1.1.3",
|
||||||
|
"@xterm/addon-fit": "^0.9.0-beta.1",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
|
"eslint": "^8.55.0",
|
||||||
|
"rollup": "^4.6.1",
|
||||||
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
|
"rollup-plugin-string": "^3.0.0",
|
||||||
|
"static-handler": "^0.4.3",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"xterm": "^5.3.0",
|
||||||
|
"xterm-readline": "^1.1.1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/pyscript/pyscript.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/pyscript/pyscript/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/pyscript/pyscript#readme"
|
||||||
|
}
|
||||||
74
pyscript.core/rollup/3rd-party.cjs
Normal file
74
pyscript.core/rollup/3rd-party.cjs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
const { copyFileSync, writeFileSync } = require("node:fs");
|
||||||
|
const { join } = require("node:path");
|
||||||
|
|
||||||
|
const CDN = "https://cdn.jsdelivr.net/npm";
|
||||||
|
|
||||||
|
const targets = join(__dirname, "..", "src", "3rd-party");
|
||||||
|
const node_modules = join(__dirname, "..", "node_modules");
|
||||||
|
|
||||||
|
const { devDependencies } = require(join(__dirname, "..", "package.json"));
|
||||||
|
|
||||||
|
const v = (name) => devDependencies[name].replace(/[^\d.]/g, "");
|
||||||
|
|
||||||
|
const dropSourceMap = (str) =>
|
||||||
|
str.replace(/^\/.+? sourceMappingURL=\/.+$/m, "");
|
||||||
|
|
||||||
|
// Fetch a module via jsdelivr CDN `/+esm` orchestration
|
||||||
|
// then sanitize the resulting outcome to avoid importing
|
||||||
|
// anything via `/npm/...` through Rollup
|
||||||
|
const resolve = (name) => {
|
||||||
|
const cdn = `${CDN}/${name}@${v(name)}/+esm`;
|
||||||
|
console.debug("fetching", cdn);
|
||||||
|
return fetch(cdn)
|
||||||
|
.then((b) => b.text())
|
||||||
|
.then((text) =>
|
||||||
|
text.replace(
|
||||||
|
/("|')\/npm\/(.+)?\+esm\1/g,
|
||||||
|
// normalize `/npm/module@version/+esm` as
|
||||||
|
// just `module` so that rollup can do the rest
|
||||||
|
(_, quote, module) => {
|
||||||
|
const i = module.lastIndexOf("@");
|
||||||
|
return `${quote}${module.slice(0, i)}${quote}`;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// create a file rollup can then process and understand
|
||||||
|
const reBundle = (name) => Promise.resolve(`export * from "${name}";\n`);
|
||||||
|
|
||||||
|
// key/value pairs as:
|
||||||
|
// "3rd-party/file-name.js"
|
||||||
|
// string as content or
|
||||||
|
// Promise<string> as resolved content
|
||||||
|
const modules = {
|
||||||
|
// toml
|
||||||
|
"toml.js": join(node_modules, "@webreflection", "toml-j0.4", "toml.js"),
|
||||||
|
|
||||||
|
// xterm
|
||||||
|
"xterm.js": resolve("xterm"),
|
||||||
|
"xterm-readline.js": resolve("xterm-readline"),
|
||||||
|
"xterm_addon-fit.js": fetch(`${CDN}/@xterm/addon-fit/+esm`).then((b) =>
|
||||||
|
b.text(),
|
||||||
|
),
|
||||||
|
"xterm.css": fetch(`${CDN}/xterm@${v("xterm")}/css/xterm.min.css`).then(
|
||||||
|
(b) => b.text(),
|
||||||
|
),
|
||||||
|
|
||||||
|
// codemirror
|
||||||
|
"codemirror.js": reBundle("codemirror"),
|
||||||
|
"codemirror_state.js": reBundle("@codemirror/state"),
|
||||||
|
"codemirror_lang-python.js": reBundle("@codemirror/lang-python"),
|
||||||
|
"codemirror_language.js": reBundle("@codemirror/language"),
|
||||||
|
"codemirror_view.js": reBundle("@codemirror/view"),
|
||||||
|
"codemirror_commands.js": reBundle("@codemirror/commands"),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [target, source] of Object.entries(modules)) {
|
||||||
|
if (typeof source === "string") copyFileSync(source, join(targets, target));
|
||||||
|
else {
|
||||||
|
source.then((text) =>
|
||||||
|
writeFileSync(join(targets, target), dropSourceMap(text)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
pyscript.core/rollup/core.config.js
Normal file
43
pyscript.core/rollup/core.config.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// This file generates /core.js minified version of the module, which is
|
||||||
|
// the default exported as npm entry.
|
||||||
|
|
||||||
|
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||||
|
import commonjs from "@rollup/plugin-commonjs";
|
||||||
|
import terser from "@rollup/plugin-terser";
|
||||||
|
import postcss from "rollup-plugin-postcss";
|
||||||
|
|
||||||
|
const plugins = [];
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
input: "./src/core.js",
|
||||||
|
plugins: plugins.concat(
|
||||||
|
process.env.NO_MIN
|
||||||
|
? [nodeResolve(), commonjs()]
|
||||||
|
: [nodeResolve(), commonjs(), terser()],
|
||||||
|
),
|
||||||
|
output: {
|
||||||
|
esModule: true,
|
||||||
|
dir: "./dist",
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "./src/core.css",
|
||||||
|
plugins: [
|
||||||
|
postcss({
|
||||||
|
extract: true,
|
||||||
|
sourceMap: false,
|
||||||
|
minimize: !process.env.NO_MIN,
|
||||||
|
plugins: [],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
file: "./dist/core.css",
|
||||||
|
},
|
||||||
|
onwarn(warning, warn) {
|
||||||
|
if (warning.code === "FILE_NAME_CONFLICT") return;
|
||||||
|
warn(warning);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
28
pyscript.core/rollup/plugins.cjs
Normal file
28
pyscript.core/rollup/plugins.cjs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const { readdirSync, writeFileSync } = require("node:fs");
|
||||||
|
const { join } = require("node:path");
|
||||||
|
|
||||||
|
const plugins = [""];
|
||||||
|
|
||||||
|
for (const file of readdirSync(join(__dirname, "..", "src", "plugins"))) {
|
||||||
|
if (/\.js$/.test(file)) {
|
||||||
|
const name = file.slice(0, -3);
|
||||||
|
const key = /^[a-zA-Z0-9$_]+$/.test(name)
|
||||||
|
? name
|
||||||
|
: `[${JSON.stringify(name)}]`;
|
||||||
|
const value = JSON.stringify(`./plugins/${file}`);
|
||||||
|
plugins.push(
|
||||||
|
// this comment is needed to avoid bundlers eagerly embedding lazy
|
||||||
|
// dependencies, causing all sort of issues once in production
|
||||||
|
` ${key}: () => import(/* webpackIgnore: true */ ${value}),`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins.push("");
|
||||||
|
|
||||||
|
writeFileSync(
|
||||||
|
join(__dirname, "..", "src", "plugins.js"),
|
||||||
|
`// ⚠️ This file is an artifact: DO NOT MODIFY\nexport default {${plugins.join(
|
||||||
|
"\n",
|
||||||
|
)}};\n`,
|
||||||
|
);
|
||||||
29
pyscript.core/rollup/stdlib.cjs
Normal file
29
pyscript.core/rollup/stdlib.cjs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const {
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
statSync,
|
||||||
|
writeFileSync,
|
||||||
|
} = require("node:fs");
|
||||||
|
const { join } = require("node:path");
|
||||||
|
|
||||||
|
const crawl = (path, json) => {
|
||||||
|
for (const file of readdirSync(path)) {
|
||||||
|
const full = join(path, file);
|
||||||
|
if (/\.py$/.test(file)) json[file] = readFileSync(full).toString();
|
||||||
|
else if (statSync(full).isDirectory() && !file.endsWith("_"))
|
||||||
|
crawl(full, (json[file] = {}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const json = {};
|
||||||
|
|
||||||
|
crawl(join(__dirname, "..", "src", "stdlib"), json);
|
||||||
|
|
||||||
|
writeFileSync(
|
||||||
|
join(__dirname, "..", "src", "stdlib", "pyscript.js"),
|
||||||
|
`// ⚠️ This file is an artifact: DO NOT MODIFY\nexport default ${JSON.stringify(
|
||||||
|
json,
|
||||||
|
null,
|
||||||
|
" ",
|
||||||
|
)};\n`,
|
||||||
|
);
|
||||||
7
pyscript.core/src/3rd-party/README.md
vendored
Normal file
7
pyscript.core/src/3rd-party/README.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# PyScript 3rd Party
|
||||||
|
|
||||||
|
This folder contains artifacts created via [3rd-party.cjs](../../rollup/3rd-party.cjs).
|
||||||
|
|
||||||
|
As we would like to offer a way to run PyScript offline, and we already offer a `dist` folder with all the necessary scripts, we have created a foreign dependencies resolver that allow to lazy-load CDN dependencies out of the box.
|
||||||
|
|
||||||
|
Please **note** these dependencies are **not interpreters**, because interpreters have their own mechanism, folders structure, WASM files, and whatnot, to work locally, but at least XTerm or the TOML parser, among other lazy dependencies, should be available within the dist folder.
|
||||||
17
pyscript.core/src/all-done.js
Normal file
17
pyscript.core/src/all-done.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import TYPES from "./types.js";
|
||||||
|
|
||||||
|
const waitForIt = [];
|
||||||
|
|
||||||
|
for (const [TYPE] of TYPES) {
|
||||||
|
const selectors = [`script[type="${TYPE}"]`, `${TYPE}-script`];
|
||||||
|
for (const element of document.querySelectorAll(selectors.join(","))) {
|
||||||
|
const { promise, resolve } = Promise.withResolvers();
|
||||||
|
waitForIt.push(promise);
|
||||||
|
element.addEventListener(`${TYPE}:done`, resolve, { once: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for all the things then cleanup
|
||||||
|
Promise.all(waitForIt).then(() => {
|
||||||
|
dispatchEvent(new Event("py:all-done"));
|
||||||
|
});
|
||||||
152
pyscript.core/src/config.js
Normal file
152
pyscript.core/src/config.js
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
/**
|
||||||
|
* This file parses a generic <py-config> or config attribute
|
||||||
|
* to use as base config for all py-script elements, importing
|
||||||
|
* also a queue of plugins *before* the interpreter (if any) resolves.
|
||||||
|
*/
|
||||||
|
import { $$ } from "basic-devtools";
|
||||||
|
|
||||||
|
import TYPES from "./types.js";
|
||||||
|
import allPlugins from "./plugins.js";
|
||||||
|
import { robustFetch as fetch, getText } from "./fetch.js";
|
||||||
|
import { ErrorCode } from "./exceptions.js";
|
||||||
|
|
||||||
|
const { BAD_CONFIG, CONFLICTING_CODE } = ErrorCode;
|
||||||
|
|
||||||
|
const badURL = (url, expected = "") => {
|
||||||
|
let message = `(${BAD_CONFIG}): Invalid URL: ${url}`;
|
||||||
|
if (expected) message += `\nexpected ${expected} content`;
|
||||||
|
throw new Error(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a string, returns its trimmed content as text,
|
||||||
|
* fetching it from a file if the content is a URL.
|
||||||
|
* @param {string} config either JSON, TOML, or a file to fetch
|
||||||
|
* @param {string?} type the optional type to enforce
|
||||||
|
* @returns {{json: boolean, toml: boolean, text: string}}
|
||||||
|
*/
|
||||||
|
const configDetails = async (config, type) => {
|
||||||
|
let text = config?.trim();
|
||||||
|
// we only support an object as root config
|
||||||
|
let url = "",
|
||||||
|
toml = false,
|
||||||
|
json = /^{/.test(text) && /}$/.test(text);
|
||||||
|
// handle files by extension (relaxing urls parts after)
|
||||||
|
if (!json && /\.(\w+)(?:\?\S*)?$/.test(text)) {
|
||||||
|
const ext = RegExp.$1;
|
||||||
|
if (ext === "json" && type !== "toml") json = true;
|
||||||
|
else if (ext === "toml" && type !== "json") toml = true;
|
||||||
|
else badURL(text, type);
|
||||||
|
url = text;
|
||||||
|
text = (await fetch(url).then(getText)).trim();
|
||||||
|
}
|
||||||
|
return { json, toml: toml || (!json && !!text), text, url };
|
||||||
|
};
|
||||||
|
|
||||||
|
const conflictError = (reason) => new Error(`(${CONFLICTING_CODE}): ${reason}`);
|
||||||
|
|
||||||
|
const syntaxError = (type, url, { message }) => {
|
||||||
|
let str = `(${BAD_CONFIG}): Invalid ${type}`;
|
||||||
|
if (url) str += ` @ ${url}`;
|
||||||
|
return new SyntaxError(`${str}\n${message}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const configs = new Map();
|
||||||
|
|
||||||
|
for (const [TYPE] of TYPES) {
|
||||||
|
/** @type {Promise<[...any]>} A Promise wrapping any plugins which should be loaded. */
|
||||||
|
let plugins;
|
||||||
|
|
||||||
|
/** @type {any} The PyScript configuration parsed from the JSON or TOML object*. May be any of the return types of JSON.parse() or toml-j0.4's parse() ( {number | string | boolean | null | object | Array} ) */
|
||||||
|
let parsed;
|
||||||
|
|
||||||
|
/** @type {Error | undefined} The error thrown when parsing the PyScript config, if any.*/
|
||||||
|
let error;
|
||||||
|
|
||||||
|
let config,
|
||||||
|
type,
|
||||||
|
pyElement,
|
||||||
|
pyConfigs = $$(`${TYPE}-config`),
|
||||||
|
attrConfigs = $$(
|
||||||
|
[
|
||||||
|
`script[type="${TYPE}"][config]:not([worker])`,
|
||||||
|
`${TYPE}-script[config]:not([worker])`,
|
||||||
|
].join(","),
|
||||||
|
);
|
||||||
|
|
||||||
|
// throw an error if there are multiple <py-config> or <mpy-config>
|
||||||
|
if (pyConfigs.length > 1) {
|
||||||
|
error = conflictError(`Too many ${TYPE}-config`);
|
||||||
|
} else {
|
||||||
|
// throw an error if there are <x-config> and config="x" attributes
|
||||||
|
if (pyConfigs.length && attrConfigs.length) {
|
||||||
|
error = conflictError(
|
||||||
|
`Ambiguous ${TYPE}-config VS config attribute`,
|
||||||
|
);
|
||||||
|
} else if (pyConfigs.length) {
|
||||||
|
[pyElement] = pyConfigs;
|
||||||
|
config = pyElement.getAttribute("src") || pyElement.textContent;
|
||||||
|
type = pyElement.getAttribute("type");
|
||||||
|
} else if (attrConfigs.length) {
|
||||||
|
[pyElement, ...attrConfigs] = attrConfigs;
|
||||||
|
config = pyElement.getAttribute("config");
|
||||||
|
// throw an error if dirrent scripts use different configs
|
||||||
|
if (
|
||||||
|
attrConfigs.some((el) => el.getAttribute("config") !== config)
|
||||||
|
) {
|
||||||
|
error = conflictError(
|
||||||
|
"Unable to use different configs on main",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// catch possible fetch errors
|
||||||
|
if (!error && config) {
|
||||||
|
try {
|
||||||
|
const { json, toml, text, url } = await configDetails(config, type);
|
||||||
|
config = text;
|
||||||
|
if (json || type === "json") {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
error = syntaxError("JSON", url, e);
|
||||||
|
}
|
||||||
|
} else if (toml || type === "toml") {
|
||||||
|
try {
|
||||||
|
const { parse } = await import(
|
||||||
|
/* webpackIgnore: true */ "./3rd-party/toml.js"
|
||||||
|
);
|
||||||
|
parsed = parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
error = syntaxError("TOML", url, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse all plugins and optionally ignore only
|
||||||
|
// those flagged as "undesired" via `!` prefix
|
||||||
|
const toBeAwaited = [];
|
||||||
|
for (const [key, value] of Object.entries(allPlugins)) {
|
||||||
|
if (error) {
|
||||||
|
if (key === "error") {
|
||||||
|
// show on page the config is broken, meaning that
|
||||||
|
// it was not possible to disable error plugin neither
|
||||||
|
// as that part wasn't correctly parsed anyway
|
||||||
|
value().then(({ notify }) => notify(error.message));
|
||||||
|
}
|
||||||
|
} else if (!parsed?.plugins?.includes(`!${key}`)) {
|
||||||
|
toBeAwaited.push(value().then(({ default: p }) => p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign plugins as Promise.all only if needed
|
||||||
|
plugins = Promise.all(toBeAwaited);
|
||||||
|
|
||||||
|
configs.set(TYPE, { config: parsed, plugins, error });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default configs;
|
||||||
44
pyscript.core/src/core.css
Normal file
44
pyscript.core/src/core.css
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
py-script,
|
||||||
|
py-config,
|
||||||
|
mpy-script,
|
||||||
|
mpy-config {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PyEditor */
|
||||||
|
.py-editor-box,
|
||||||
|
.mpy-editor-box {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
.py-editor-input,
|
||||||
|
.mpy-editor-input {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.py-editor-box::before,
|
||||||
|
.mpy-editor-box::before {
|
||||||
|
content: attr(data-env);
|
||||||
|
display: block;
|
||||||
|
font-size: x-small;
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
.py-editor-output,
|
||||||
|
.mpy-editor-output {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
.py-editor-run-button,
|
||||||
|
.mpy-editor-run-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5rem;
|
||||||
|
bottom: 0.5rem;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.py-editor-box:hover .py-editor-run-button,
|
||||||
|
.mpy-editor-box:hover .mpy-editor-run-button,
|
||||||
|
.py-editor-run-button:focus,
|
||||||
|
.py-editor-run-button:disabled,
|
||||||
|
.mpy-editor-run-button:focus,
|
||||||
|
.mpy-editor-run-button:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
333
pyscript.core/src/core.js
Normal file
333
pyscript.core/src/core.js
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
/*! (c) PyScript Development Team */
|
||||||
|
|
||||||
|
import stickyModule from "sticky-module";
|
||||||
|
import "@ungap/with-resolvers";
|
||||||
|
|
||||||
|
import {
|
||||||
|
INVALID_CONTENT,
|
||||||
|
Hook,
|
||||||
|
XWorker,
|
||||||
|
assign,
|
||||||
|
dedent,
|
||||||
|
define,
|
||||||
|
defineProperty,
|
||||||
|
dispatch,
|
||||||
|
queryTarget,
|
||||||
|
unescape,
|
||||||
|
whenDefined,
|
||||||
|
} from "polyscript/exports";
|
||||||
|
|
||||||
|
import "./all-done.js";
|
||||||
|
import TYPES from "./types.js";
|
||||||
|
import configs from "./config.js";
|
||||||
|
import sync from "./sync.js";
|
||||||
|
import bootstrapNodeAndPlugins from "./plugins-helper.js";
|
||||||
|
import { ErrorCode } from "./exceptions.js";
|
||||||
|
import { robustFetch as fetch, getText } from "./fetch.js";
|
||||||
|
import { hooks, main, worker, codeFor, createFunction } from "./hooks.js";
|
||||||
|
|
||||||
|
// allows lazy element features on code evaluation
|
||||||
|
let currentElement;
|
||||||
|
|
||||||
|
// generic helper to disambiguate between custom element and script
|
||||||
|
const isScript = ({ tagName }) => tagName === "SCRIPT";
|
||||||
|
|
||||||
|
let shouldRegister = true;
|
||||||
|
const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
||||||
|
// automatically use the pyscript stderr (when/if defined)
|
||||||
|
// this defaults to console.error
|
||||||
|
function PyWorker(...args) {
|
||||||
|
const worker = $XWorker(...args);
|
||||||
|
worker.onerror = ({ error }) => io.stderr(error);
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enrich the Python env with some JS utility for main
|
||||||
|
interpreter.registerJsModule("_pyscript", {
|
||||||
|
PyWorker,
|
||||||
|
get target() {
|
||||||
|
return isScript(currentElement)
|
||||||
|
? currentElement.target.id
|
||||||
|
: currentElement.id;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// avoid multiple initialization of the same library
|
||||||
|
const [
|
||||||
|
{
|
||||||
|
PyWorker: exportedPyWorker,
|
||||||
|
hooks: exportedHooks,
|
||||||
|
config: exportedConfig,
|
||||||
|
whenDefined: exportedWhenDefined,
|
||||||
|
},
|
||||||
|
alreadyLive,
|
||||||
|
] = stickyModule("@pyscript/core", {
|
||||||
|
PyWorker,
|
||||||
|
hooks,
|
||||||
|
config: {},
|
||||||
|
whenDefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
TYPES,
|
||||||
|
exportedPyWorker as PyWorker,
|
||||||
|
exportedHooks as hooks,
|
||||||
|
exportedConfig as config,
|
||||||
|
exportedWhenDefined as whenDefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const hooked = new Map();
|
||||||
|
|
||||||
|
for (const [TYPE, interpreter] of TYPES) {
|
||||||
|
// avoid any dance if the module already landed
|
||||||
|
if (alreadyLive) break;
|
||||||
|
|
||||||
|
const dispatchDone = (element, isAsync, result) => {
|
||||||
|
if (isAsync) result.then(() => dispatch(element, TYPE, "done"));
|
||||||
|
else dispatch(element, TYPE, "done");
|
||||||
|
};
|
||||||
|
|
||||||
|
const { config, plugins, error } = configs.get(TYPE);
|
||||||
|
|
||||||
|
// create a unique identifier when/if needed
|
||||||
|
let id = 0;
|
||||||
|
const getID = (prefix = TYPE) => `${prefix}-${id++}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a generic DOM Element, tries to fetch the 'src' attribute, if present.
|
||||||
|
* It either throws an error if the 'src' can't be fetched or it returns a fallback
|
||||||
|
* content as source.
|
||||||
|
*/
|
||||||
|
const fetchSource = async (tag, io, asText) => {
|
||||||
|
if (tag.hasAttribute("src")) {
|
||||||
|
try {
|
||||||
|
return await fetch(tag.getAttribute("src")).then(getText);
|
||||||
|
} catch (error) {
|
||||||
|
io.stderr(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asText) return dedent(tag.textContent);
|
||||||
|
|
||||||
|
const code = dedent(unescape(tag.innerHTML));
|
||||||
|
console.warn(
|
||||||
|
`Deprecated: use <script type="${TYPE}"> for an always safe content parsing:\n`,
|
||||||
|
code,
|
||||||
|
);
|
||||||
|
return code;
|
||||||
|
};
|
||||||
|
|
||||||
|
// define the module as both `<script type="py">` and `<py-script>`
|
||||||
|
// but only if the config didn't throw an error
|
||||||
|
if (!error) {
|
||||||
|
// ensure plugins are bootstrapped already before custom type definition
|
||||||
|
// NOTE: we cannot top-level await in here as plugins import other utilities
|
||||||
|
// from core.js itself so that custom definition should not be blocking.
|
||||||
|
plugins.then(() => {
|
||||||
|
// possible early errors sent by polyscript
|
||||||
|
const errors = new Map();
|
||||||
|
|
||||||
|
// specific main and worker hooks
|
||||||
|
const hooks = {
|
||||||
|
main: {
|
||||||
|
...codeFor(main),
|
||||||
|
async onReady(wrap, element) {
|
||||||
|
if (shouldRegister) {
|
||||||
|
shouldRegister = false;
|
||||||
|
registerModule(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allows plugins to do whatever they want with the element
|
||||||
|
// before regular stuff happens in here
|
||||||
|
for (const callback of main("onReady"))
|
||||||
|
await callback(wrap, element);
|
||||||
|
|
||||||
|
// now that all possible plugins are configured,
|
||||||
|
// bail out if polyscript encountered an error
|
||||||
|
if (errors.has(element)) {
|
||||||
|
let { message } = errors.get(element);
|
||||||
|
errors.delete(element);
|
||||||
|
const clone = message === INVALID_CONTENT;
|
||||||
|
message = `(${ErrorCode.CONFLICTING_CODE}) ${message} for `;
|
||||||
|
message += element.cloneNode(clone).outerHTML;
|
||||||
|
wrap.io.stderr(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isScript(element)) {
|
||||||
|
const {
|
||||||
|
attributes: { async: isAsync, target },
|
||||||
|
} = element;
|
||||||
|
const hasTarget = !!target?.value;
|
||||||
|
const show = hasTarget
|
||||||
|
? queryTarget(element, target.value)
|
||||||
|
: document.createElement("script-py");
|
||||||
|
|
||||||
|
if (!hasTarget) {
|
||||||
|
const { head, body } = document;
|
||||||
|
if (head.contains(element)) body.append(show);
|
||||||
|
else element.after(show);
|
||||||
|
}
|
||||||
|
if (!show.id) show.id = getID();
|
||||||
|
|
||||||
|
// allows the code to retrieve the target element via
|
||||||
|
// document.currentScript.target if needed
|
||||||
|
defineProperty(element, "target", { value: show });
|
||||||
|
|
||||||
|
// notify before the code runs
|
||||||
|
dispatch(element, TYPE, "ready");
|
||||||
|
dispatchDone(
|
||||||
|
element,
|
||||||
|
isAsync,
|
||||||
|
wrap[`run${isAsync ? "Async" : ""}`](
|
||||||
|
await fetchSource(element, wrap.io, true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// resolve PyScriptElement to allow connectedCallback
|
||||||
|
element._wrap.resolve(wrap);
|
||||||
|
}
|
||||||
|
console.debug("[pyscript/main] PyScript Ready");
|
||||||
|
},
|
||||||
|
onWorker(_, xworker) {
|
||||||
|
assign(xworker.sync, sync);
|
||||||
|
for (const callback of main("onWorker"))
|
||||||
|
callback(_, xworker);
|
||||||
|
},
|
||||||
|
onBeforeRun(wrap, element) {
|
||||||
|
currentElement = element;
|
||||||
|
bootstrapNodeAndPlugins(
|
||||||
|
main,
|
||||||
|
wrap,
|
||||||
|
element,
|
||||||
|
"onBeforeRun",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onBeforeRunAsync(wrap, element) {
|
||||||
|
currentElement = element;
|
||||||
|
return bootstrapNodeAndPlugins(
|
||||||
|
main,
|
||||||
|
wrap,
|
||||||
|
element,
|
||||||
|
"onBeforeRunAsync",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onAfterRun(wrap, element) {
|
||||||
|
bootstrapNodeAndPlugins(
|
||||||
|
main,
|
||||||
|
wrap,
|
||||||
|
element,
|
||||||
|
"onAfterRun",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onAfterRunAsync(wrap, element) {
|
||||||
|
return bootstrapNodeAndPlugins(
|
||||||
|
main,
|
||||||
|
wrap,
|
||||||
|
element,
|
||||||
|
"onAfterRunAsync",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
worker: {
|
||||||
|
...codeFor(worker),
|
||||||
|
// these are lazy getters that returns a composition
|
||||||
|
// of the current hooks or undefined, if no hook is present
|
||||||
|
get onReady() {
|
||||||
|
return createFunction(this, "onReady", true);
|
||||||
|
},
|
||||||
|
get onBeforeRun() {
|
||||||
|
return createFunction(this, "onBeforeRun", false);
|
||||||
|
},
|
||||||
|
get onBeforeRunAsync() {
|
||||||
|
return createFunction(this, "onBeforeRunAsync", true);
|
||||||
|
},
|
||||||
|
get onAfterRun() {
|
||||||
|
return createFunction(this, "onAfterRun", false);
|
||||||
|
},
|
||||||
|
get onAfterRunAsync() {
|
||||||
|
return createFunction(this, "onAfterRunAsync", true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
hooked.set(TYPE, hooks);
|
||||||
|
|
||||||
|
define(TYPE, {
|
||||||
|
config,
|
||||||
|
interpreter,
|
||||||
|
hooks,
|
||||||
|
env: `${TYPE}-script`,
|
||||||
|
version: config?.interpreter,
|
||||||
|
onerror(error, element) {
|
||||||
|
errors.set(element, error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
customElements.define(
|
||||||
|
`${TYPE}-script`,
|
||||||
|
class extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
assign(super(), {
|
||||||
|
_wrap: Promise.withResolvers(),
|
||||||
|
srcCode: "",
|
||||||
|
executed: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
get id() {
|
||||||
|
return super.id || (super.id = getID());
|
||||||
|
}
|
||||||
|
set id(value) {
|
||||||
|
super.id = value;
|
||||||
|
}
|
||||||
|
async connectedCallback() {
|
||||||
|
if (!this.executed) {
|
||||||
|
this.executed = true;
|
||||||
|
const isAsync = this.hasAttribute("async");
|
||||||
|
const { io, run, runAsync } = await this._wrap
|
||||||
|
.promise;
|
||||||
|
this.srcCode = await fetchSource(
|
||||||
|
this,
|
||||||
|
io,
|
||||||
|
!this.childElementCount,
|
||||||
|
);
|
||||||
|
this.replaceChildren();
|
||||||
|
this.style.display = "block";
|
||||||
|
dispatch(this, TYPE, "ready");
|
||||||
|
dispatchDone(
|
||||||
|
this,
|
||||||
|
isAsync,
|
||||||
|
(isAsync ? runAsync : run)(this.srcCode),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// export the used config without allowing leaks through it
|
||||||
|
exportedConfig[TYPE] = structuredClone(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
||||||
|
* @param {string} file the python file to run ina worker.
|
||||||
|
* @param {{config?: string | object, async?: boolean}} [options] optional configuration for the worker.
|
||||||
|
* @returns {Worker & {sync: ProxyHandler<object>}}
|
||||||
|
*/
|
||||||
|
function PyWorker(file, options) {
|
||||||
|
const hooks = hooked.get("py");
|
||||||
|
// this propagates pyscript worker hooks without needing a pyscript
|
||||||
|
// bootstrap + it passes arguments and enforces `pyodide`
|
||||||
|
// as the interpreter to use in the worker, as all hooks assume that
|
||||||
|
// and as `pyodide` is the only default interpreter that can deal with
|
||||||
|
// all the features we need to deliver pyscript out there.
|
||||||
|
const xworker = XWorker.call(new Hook(null, hooks), file, {
|
||||||
|
type: "pyodide",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
assign(xworker.sync, sync);
|
||||||
|
return xworker;
|
||||||
|
}
|
||||||
109
pyscript.core/src/exceptions.js
Normal file
109
pyscript.core/src/exceptions.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { assign } from "polyscript/exports";
|
||||||
|
|
||||||
|
const CLOSEBUTTON =
|
||||||
|
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='currentColor' width='12px'><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These error codes are used to identify the type of error that occurred.
|
||||||
|
* @see https://pyscript.github.io/docs/latest/reference/exceptions.html?highlight=errors
|
||||||
|
*/
|
||||||
|
export const ErrorCode = {
|
||||||
|
GENERIC: "PY0000", // Use this only for development then change to a more specific error code
|
||||||
|
CONFLICTING_CODE: "PY0409",
|
||||||
|
BAD_CONFIG: "PY1000",
|
||||||
|
MICROPIP_INSTALL_ERROR: "PY1001",
|
||||||
|
BAD_PLUGIN_FILE_EXTENSION: "PY2000",
|
||||||
|
NO_DEFAULT_EXPORT: "PY2001",
|
||||||
|
TOP_LEVEL_AWAIT: "PY9000",
|
||||||
|
// Currently these are created depending on error code received from fetching
|
||||||
|
FETCH_ERROR: "PY0001",
|
||||||
|
FETCH_NAME_ERROR: "PY0002",
|
||||||
|
FETCH_UNAUTHORIZED_ERROR: "PY0401",
|
||||||
|
FETCH_FORBIDDEN_ERROR: "PY0403",
|
||||||
|
FETCH_NOT_FOUND_ERROR: "PY0404",
|
||||||
|
FETCH_SERVER_ERROR: "PY0500",
|
||||||
|
FETCH_UNAVAILABLE_ERROR: "PY0503",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keys of the ErrorCode object
|
||||||
|
* @typedef {keyof ErrorCode} ErrorCodes
|
||||||
|
* */
|
||||||
|
|
||||||
|
export class UserError extends Error {
|
||||||
|
/**
|
||||||
|
* @param {ErrorCodes} errorCode
|
||||||
|
* @param {string} message
|
||||||
|
* @param {string} messageType
|
||||||
|
* */
|
||||||
|
constructor(errorCode, message = "", messageType = "text") {
|
||||||
|
super(`(${errorCode}): ${message}`);
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
this.messageType = messageType;
|
||||||
|
this.name = "UserError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FetchError extends UserError {
|
||||||
|
/**
|
||||||
|
* @param {ErrorCodes} errorCode
|
||||||
|
* @param {string} message
|
||||||
|
* */
|
||||||
|
constructor(errorCode, message) {
|
||||||
|
super(errorCode, message);
|
||||||
|
this.name = "FetchError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InstallError extends UserError {
|
||||||
|
/**
|
||||||
|
* @param {ErrorCodes} errorCode
|
||||||
|
* @param {string} message
|
||||||
|
* */
|
||||||
|
constructor(errorCode, message) {
|
||||||
|
super(errorCode, message);
|
||||||
|
this.name = "InstallError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function for creating alert banners on the page
|
||||||
|
* @param {string} message The message to be displayed to the user
|
||||||
|
* @param {string} level The alert level of the message. Can be any string; 'error' or 'warning' cause matching messages to be emitted to the console
|
||||||
|
* @param {string} [messageType="text"] If set to "html", the message content will be assigned to the banner's innerHTML directly, instead of its textContent
|
||||||
|
* @param {any} [logMessage=true] An additional flag for whether the message should be sent to the console log.
|
||||||
|
*/
|
||||||
|
export function _createAlertBanner(
|
||||||
|
message,
|
||||||
|
level,
|
||||||
|
messageType = "text",
|
||||||
|
logMessage = true,
|
||||||
|
) {
|
||||||
|
switch (`log-${level}-${logMessage}`) {
|
||||||
|
case "log-error-true":
|
||||||
|
console.error(message);
|
||||||
|
break;
|
||||||
|
case "log-warning-true":
|
||||||
|
console.warn(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = messageType === "html" ? "innerHTML" : "textContent";
|
||||||
|
const banner = assign(document.createElement("div"), {
|
||||||
|
className: `alert-banner py-${level}`,
|
||||||
|
[content]: message,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (level === "warning") {
|
||||||
|
const closeButton = assign(document.createElement("button"), {
|
||||||
|
id: "alert-close-button",
|
||||||
|
innerHTML: CLOSEBUTTON,
|
||||||
|
});
|
||||||
|
|
||||||
|
banner.appendChild(closeButton).addEventListener("click", () => {
|
||||||
|
banner.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.prepend(banner);
|
||||||
|
}
|
||||||
66
pyscript.core/src/fetch.js
Normal file
66
pyscript.core/src/fetch.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { FetchError, ErrorCode } from "./exceptions.js";
|
||||||
|
import { getText } from "polyscript/exports";
|
||||||
|
|
||||||
|
export { getText };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a fetch wrapper that handles any non 200 responses and throws a
|
||||||
|
* FetchError with the right ErrorCode. This is useful because our FetchError
|
||||||
|
* will automatically create an alert banner.
|
||||||
|
*
|
||||||
|
* @param {string} url - URL to fetch
|
||||||
|
* @param {Request} [options] - options to pass to fetch
|
||||||
|
* @returns {Promise<Response>}
|
||||||
|
*/
|
||||||
|
export async function robustFetch(url, options) {
|
||||||
|
let response;
|
||||||
|
|
||||||
|
// Note: We need to wrap fetch into a try/catch block because fetch
|
||||||
|
// throws a TypeError if the URL is invalid such as http://blah.blah
|
||||||
|
try {
|
||||||
|
response = await fetch(url, options);
|
||||||
|
} catch (err) {
|
||||||
|
const error = err;
|
||||||
|
let errMsg;
|
||||||
|
if (url.startsWith("http")) {
|
||||||
|
errMsg =
|
||||||
|
`Fetching from URL ${url} failed with error ` +
|
||||||
|
`'${error.message}'. Are your filename and path correct?`;
|
||||||
|
} else {
|
||||||
|
errMsg = `Polyscript: Access to local files
|
||||||
|
(using [[fetch]] configurations in <py-config>)
|
||||||
|
is not available when directly opening a HTML file;
|
||||||
|
you must use a webserver to serve the additional files.
|
||||||
|
See <a style="text-decoration: underline;" href="https://github.com/pyscript/pyscript/issues/257#issuecomment-1119595062">this reference</a>
|
||||||
|
on starting a simple webserver with Python.
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
throw new FetchError(ErrorCode.FETCH_ERROR, errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that response.ok is true for 200-299 responses
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorMsg = `Fetching from URL ${url} failed with error ${response.status} (${response.statusText}). Are your filename and path correct?`;
|
||||||
|
switch (response.status) {
|
||||||
|
case 404:
|
||||||
|
throw new FetchError(ErrorCode.FETCH_NOT_FOUND_ERROR, errorMsg);
|
||||||
|
case 401:
|
||||||
|
throw new FetchError(
|
||||||
|
ErrorCode.FETCH_UNAUTHORIZED_ERROR,
|
||||||
|
errorMsg,
|
||||||
|
);
|
||||||
|
case 403:
|
||||||
|
throw new FetchError(ErrorCode.FETCH_FORBIDDEN_ERROR, errorMsg);
|
||||||
|
case 500:
|
||||||
|
throw new FetchError(ErrorCode.FETCH_SERVER_ERROR, errorMsg);
|
||||||
|
case 503:
|
||||||
|
throw new FetchError(
|
||||||
|
ErrorCode.FETCH_UNAVAILABLE_ERROR,
|
||||||
|
errorMsg,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new FetchError(ErrorCode.FETCH_ERROR, errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
104
pyscript.core/src/hooks.js
Normal file
104
pyscript.core/src/hooks.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { typedSet } from "type-checked-collections";
|
||||||
|
import { dedent } from "polyscript/exports";
|
||||||
|
import toJSONCallback from "to-json-callback";
|
||||||
|
|
||||||
|
import stdlib from "./stdlib.js";
|
||||||
|
|
||||||
|
export const main = (name) => hooks.main[name];
|
||||||
|
export const worker = (name) => hooks.worker[name];
|
||||||
|
|
||||||
|
const code = (hooks, branch, key, lib) => {
|
||||||
|
hooks[key] = () => {
|
||||||
|
const arr = lib ? [lib] : [];
|
||||||
|
arr.push(...branch(key));
|
||||||
|
return arr.map(dedent).join("\n");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const codeFor = (branch) => {
|
||||||
|
const hooks = {};
|
||||||
|
code(hooks, branch, `codeBeforeRun`, stdlib);
|
||||||
|
code(hooks, branch, `codeBeforeRunAsync`, stdlib);
|
||||||
|
code(hooks, branch, `codeAfterRun`);
|
||||||
|
code(hooks, branch, `codeAfterRunAsync`);
|
||||||
|
return hooks;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFunction = (self, name) => {
|
||||||
|
const cbs = [...worker(name)];
|
||||||
|
if (cbs.length) {
|
||||||
|
const cb = toJSONCallback(
|
||||||
|
self[`_${name}`] ||
|
||||||
|
(name.endsWith("Async")
|
||||||
|
? async (wrap, xworker, ...cbs) => {
|
||||||
|
for (const cb of cbs) await cb(wrap, xworker);
|
||||||
|
}
|
||||||
|
: (wrap, xworker, ...cbs) => {
|
||||||
|
for (const cb of cbs) cb(wrap, xworker);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const a = cbs.map(toJSONCallback).join(", ");
|
||||||
|
return Function(`return(w,x)=>(${cb})(w,x,...[${a}])`)();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SetFunction = typedSet({ typeof: "function" });
|
||||||
|
const SetString = typedSet({ typeof: "string" });
|
||||||
|
|
||||||
|
const inputFailure = `
|
||||||
|
import builtins
|
||||||
|
def input(prompt=""):
|
||||||
|
raise Exception("\\n ".join([
|
||||||
|
"input() doesn't work when PyScript runs in the main thread.",
|
||||||
|
"Consider using the worker attribute: https://pyscript.github.io/docs/2023.11.2/user-guide/workers/"
|
||||||
|
]))
|
||||||
|
|
||||||
|
builtins.input = input
|
||||||
|
del builtins
|
||||||
|
del input
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const hooks = {
|
||||||
|
main: {
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onWorker: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onReady: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onBeforeRun: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onBeforeRunAsync: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onAfterRun: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onAfterRunAsync: new SetFunction(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeBeforeRun: new SetString([inputFailure]),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeBeforeRunAsync: new SetString(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeAfterRun: new SetString(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeAfterRunAsync: new SetString(),
|
||||||
|
},
|
||||||
|
worker: {
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onReady: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onBeforeRun: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onBeforeRunAsync: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onAfterRun: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onAfterRunAsync: new SetFunction(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeBeforeRun: new SetString(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeBeforeRunAsync: new SetString(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeAfterRun: new SetString(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeAfterRunAsync: new SetString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
26
pyscript.core/src/plugins-helper.js
Normal file
26
pyscript.core/src/plugins-helper.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { defineProperty } from "polyscript/exports";
|
||||||
|
|
||||||
|
// helper for all script[type="py"] out there
|
||||||
|
const before = (script) => {
|
||||||
|
defineProperty(document, "currentScript", {
|
||||||
|
configurable: true,
|
||||||
|
get: () => script,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const after = () => {
|
||||||
|
delete document.currentScript;
|
||||||
|
};
|
||||||
|
|
||||||
|
// common life-cycle handlers for any node
|
||||||
|
export default async (main, wrap, element, hook) => {
|
||||||
|
const isAsync = hook.endsWith("Async");
|
||||||
|
const isBefore = hook.startsWith("onBefore");
|
||||||
|
// make it possible to reach the current target node via Python
|
||||||
|
// or clean up for other scripts executing around this one
|
||||||
|
(isBefore ? before : after)(element);
|
||||||
|
for (const fn of main(hook)) {
|
||||||
|
if (isAsync) await fn(wrap, element);
|
||||||
|
else fn(wrap, element);
|
||||||
|
}
|
||||||
|
};
|
||||||
27
pyscript.core/src/plugins/deprecations-manager.js
Normal file
27
pyscript.core/src/plugins/deprecations-manager.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// PyScript Derepcations Plugin
|
||||||
|
import { hooks } from "../core.js";
|
||||||
|
import { notify } from "./error.js";
|
||||||
|
|
||||||
|
// react lazily on PyScript bootstrap
|
||||||
|
hooks.main.onReady.add(checkDeprecations);
|
||||||
|
hooks.main.onWorker.add(checkDeprecations);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that there are no scripts loading from pyscript.net/latest
|
||||||
|
*/
|
||||||
|
function checkDeprecations() {
|
||||||
|
const scripts = document.querySelectorAll("script");
|
||||||
|
for (const script of scripts) checkLoadingScriptsFromLatest(script.src);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if src being loaded from pyscript.net/latest and display a notification if true
|
||||||
|
* * @param {string} src
|
||||||
|
*/
|
||||||
|
function checkLoadingScriptsFromLatest(src) {
|
||||||
|
if (/\/pyscript\.net\/latest/.test(src)) {
|
||||||
|
notify(
|
||||||
|
"Loading scripts from latest is deprecated and will be removed soon. Please use a specific version instead.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
pyscript.core/src/plugins/error.js
Normal file
47
pyscript.core/src/plugins/error.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// PyScript Error Plugin
|
||||||
|
import { hooks } from "../core.js";
|
||||||
|
|
||||||
|
hooks.main.onReady.add(function override(pyScript) {
|
||||||
|
// be sure this override happens only once
|
||||||
|
hooks.main.onReady.delete(override);
|
||||||
|
|
||||||
|
// trap generic `stderr` to propagate to it regardless
|
||||||
|
const { stderr } = pyScript.io;
|
||||||
|
|
||||||
|
// override it with our own logic
|
||||||
|
pyScript.io.stderr = (error, ...rest) => {
|
||||||
|
notify(error.message || error);
|
||||||
|
// let other plugins or stderr hook, if any, do the rest
|
||||||
|
return stderr(error, ...rest);
|
||||||
|
};
|
||||||
|
|
||||||
|
// be sure uncaught Python errors are also visible
|
||||||
|
addEventListener("error", ({ message }) => {
|
||||||
|
if (message.startsWith("Uncaught PythonError")) notify(message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Error hook utilities
|
||||||
|
|
||||||
|
// Custom function to show notifications
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a banner to the top of the page, notifying the user of an error
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
export function notify(message) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.className = "py-error";
|
||||||
|
div.textContent = message;
|
||||||
|
div.style.cssText = `
|
||||||
|
border: 1px solid red;
|
||||||
|
background: #ffdddd;
|
||||||
|
color: black;
|
||||||
|
font-family: courier, monospace;
|
||||||
|
white-space: pre;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
`;
|
||||||
|
document.body.append(div);
|
||||||
|
}
|
||||||
229
pyscript.core/src/plugins/py-editor.js
Normal file
229
pyscript.core/src/plugins/py-editor.js
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
// PyScript py-editor plugin
|
||||||
|
import { Hook, XWorker, dedent } from "polyscript/exports";
|
||||||
|
import { TYPES } from "../core.js";
|
||||||
|
|
||||||
|
const RUN_BUTTON = `<svg style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>`;
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
const getID = (type) => `${type}-editor-${id++}`;
|
||||||
|
|
||||||
|
const envs = new Map();
|
||||||
|
|
||||||
|
const hooks = {
|
||||||
|
worker: {
|
||||||
|
// works on both Pyodide and MicroPython
|
||||||
|
onReady: ({ runAsync, io }, { sync }) => {
|
||||||
|
io.stdout = (line) => sync.write(line);
|
||||||
|
io.stderr = (line) => sync.writeErr(line);
|
||||||
|
sync.revoke();
|
||||||
|
sync.runAsync = runAsync;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function execute({ currentTarget }) {
|
||||||
|
const { env, pySrc, outDiv } = this;
|
||||||
|
|
||||||
|
currentTarget.disabled = true;
|
||||||
|
outDiv.innerHTML = "";
|
||||||
|
|
||||||
|
if (!envs.has(env)) {
|
||||||
|
const srcLink = URL.createObjectURL(new Blob([""]));
|
||||||
|
const xworker = XWorker.call(new Hook(null, hooks), srcLink, {
|
||||||
|
type: this.interpreter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { sync } = xworker;
|
||||||
|
const { promise, resolve } = Promise.withResolvers();
|
||||||
|
envs.set(env, promise);
|
||||||
|
sync.revoke = () => {
|
||||||
|
URL.revokeObjectURL(srcLink);
|
||||||
|
resolve(xworker);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for the env then set the target div
|
||||||
|
// before executing the current code
|
||||||
|
envs.get(env).then((xworker) => {
|
||||||
|
xworker.onerror = ({ error }) => {
|
||||||
|
outDiv.innerHTML += `<span style='color:red'>${
|
||||||
|
error.message || error
|
||||||
|
}</span>\n`;
|
||||||
|
console.error(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
const enable = () => {
|
||||||
|
currentTarget.disabled = false;
|
||||||
|
};
|
||||||
|
const { sync } = xworker;
|
||||||
|
sync.write = (str) => {
|
||||||
|
outDiv.innerText += `${str}\n`;
|
||||||
|
};
|
||||||
|
sync.writeErr = (str) => {
|
||||||
|
outDiv.innerHTML += `<span style='color:red'>${str}</span>\n`;
|
||||||
|
};
|
||||||
|
sync.runAsync(pySrc).then(enable, enable);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeRunButton = (listener, type) => {
|
||||||
|
const runButton = document.createElement("button");
|
||||||
|
runButton.className = `absolute ${type}-editor-run-button`;
|
||||||
|
runButton.innerHTML = RUN_BUTTON;
|
||||||
|
runButton.setAttribute("aria-label", "Python Script Run Button");
|
||||||
|
runButton.addEventListener("click", listener);
|
||||||
|
return runButton;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeEditorDiv = (listener, type) => {
|
||||||
|
const editorDiv = document.createElement("div");
|
||||||
|
editorDiv.className = `${type}-editor-input`;
|
||||||
|
editorDiv.setAttribute("aria-label", "Python Script Area");
|
||||||
|
|
||||||
|
const runButton = makeRunButton(listener, type);
|
||||||
|
const editorShadowContainer = document.createElement("div");
|
||||||
|
|
||||||
|
// avoid outer elements intercepting key events (reveal as example)
|
||||||
|
editorShadowContainer.addEventListener("keydown", (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
editorDiv.append(runButton, editorShadowContainer);
|
||||||
|
|
||||||
|
return editorDiv;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeOutDiv = (type) => {
|
||||||
|
const outDiv = document.createElement("div");
|
||||||
|
outDiv.className = `${type}-editor-output`;
|
||||||
|
outDiv.id = `${getID(type)}-output`;
|
||||||
|
return outDiv;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeBoxDiv = (listener, type) => {
|
||||||
|
const boxDiv = document.createElement("div");
|
||||||
|
boxDiv.className = `${type}-editor-box`;
|
||||||
|
|
||||||
|
const editorDiv = makeEditorDiv(listener, type);
|
||||||
|
const outDiv = makeOutDiv(type);
|
||||||
|
boxDiv.append(editorDiv, outDiv);
|
||||||
|
|
||||||
|
return [boxDiv, outDiv];
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = async (script, type, interpreter) => {
|
||||||
|
const [
|
||||||
|
{ basicSetup, EditorView },
|
||||||
|
{ Compartment },
|
||||||
|
{ python },
|
||||||
|
{ indentUnit },
|
||||||
|
{ keymap },
|
||||||
|
{ defaultKeymap },
|
||||||
|
] = await Promise.all([
|
||||||
|
// TODO: find a way to actually produce these bundles locally
|
||||||
|
import(/* webpackIgnore: true */ "../3rd-party/codemirror.js"),
|
||||||
|
import(/* webpackIgnore: true */ "../3rd-party/codemirror_state.js"),
|
||||||
|
import(
|
||||||
|
/* webpackIgnore: true */ "../3rd-party/codemirror_lang-python.js"
|
||||||
|
),
|
||||||
|
import(/* webpackIgnore: true */ "../3rd-party/codemirror_language.js"),
|
||||||
|
import(/* webpackIgnore: true */ "../3rd-party/codemirror_view.js"),
|
||||||
|
import(/* webpackIgnore: true */ "../3rd-party/codemirror_commands.js"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const selector = script.getAttribute("target");
|
||||||
|
|
||||||
|
let target;
|
||||||
|
if (selector) {
|
||||||
|
target =
|
||||||
|
document.getElementById(selector) ||
|
||||||
|
document.querySelector(selector);
|
||||||
|
if (!target) throw new Error(`Unknown target ${selector}`);
|
||||||
|
} else {
|
||||||
|
target = document.createElement(`${type}-editor`);
|
||||||
|
target.style.display = "block";
|
||||||
|
script.after(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target.id) target.id = getID(type);
|
||||||
|
if (!target.hasAttribute("exec-id")) target.setAttribute("exec-id", 0);
|
||||||
|
if (!target.hasAttribute("root")) target.setAttribute("root", target.id);
|
||||||
|
|
||||||
|
const env = `${interpreter}-${script.getAttribute("env") || getID(type)}`;
|
||||||
|
const context = {
|
||||||
|
interpreter,
|
||||||
|
env,
|
||||||
|
get pySrc() {
|
||||||
|
return editor.state.doc.toString();
|
||||||
|
},
|
||||||
|
get outDiv() {
|
||||||
|
return outDiv;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// @see https://github.com/JeffersGlass/mkdocs-pyscript/blob/main/mkdocs_pyscript/js/makeblocks.js
|
||||||
|
const listener = execute.bind(context);
|
||||||
|
const [boxDiv, outDiv] = makeBoxDiv(listener, type);
|
||||||
|
boxDiv.dataset.env = script.hasAttribute("env") ? env : interpreter;
|
||||||
|
|
||||||
|
const inputChild = boxDiv.querySelector(`.${type}-editor-input > div`);
|
||||||
|
const parent = inputChild.attachShadow({ mode: "open" });
|
||||||
|
// avoid inheriting styles from the outer component
|
||||||
|
parent.innerHTML = `<style> :host { all: initial; }</style>`;
|
||||||
|
|
||||||
|
target.appendChild(boxDiv);
|
||||||
|
|
||||||
|
const doc = dedent(script.textContent).trim();
|
||||||
|
|
||||||
|
// preserve user indentation, if any
|
||||||
|
const indentation = /^(\s+)/m.test(doc) ? RegExp.$1 : " ";
|
||||||
|
|
||||||
|
const editor = new EditorView({
|
||||||
|
extensions: [
|
||||||
|
indentUnit.of(indentation),
|
||||||
|
new Compartment().of(python()),
|
||||||
|
keymap.of([
|
||||||
|
...defaultKeymap,
|
||||||
|
{ key: "Ctrl-Enter", run: listener, preventDefault: true },
|
||||||
|
{ key: "Cmd-Enter", run: listener, preventDefault: true },
|
||||||
|
{ key: "Shift-Enter", run: listener, preventDefault: true },
|
||||||
|
]),
|
||||||
|
basicSetup,
|
||||||
|
],
|
||||||
|
parent,
|
||||||
|
doc,
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
// avoid too greedy MutationObserver operations at distance
|
||||||
|
let timeout = 0;
|
||||||
|
|
||||||
|
// reset interval value then check for new scripts
|
||||||
|
const resetTimeout = () => {
|
||||||
|
timeout = 0;
|
||||||
|
pyEditor();
|
||||||
|
};
|
||||||
|
|
||||||
|
// triggered both ASAP on the living DOM and via MutationObserver later
|
||||||
|
const pyEditor = async () => {
|
||||||
|
if (timeout) return;
|
||||||
|
timeout = setTimeout(resetTimeout, 250);
|
||||||
|
for (const [type, interpreter] of TYPES) {
|
||||||
|
const selector = `script[type="${type}-editor"]`;
|
||||||
|
for (const script of document.querySelectorAll(selector)) {
|
||||||
|
// avoid any further bootstrap
|
||||||
|
script.type += "-active";
|
||||||
|
await init(script, type, interpreter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new MutationObserver(pyEditor).observe(document, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// try to check the current document ASAP
|
||||||
|
export default pyEditor();
|
||||||
150
pyscript.core/src/plugins/py-terminal.js
Normal file
150
pyscript.core/src/plugins/py-terminal.js
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
// PyScript py-terminal plugin
|
||||||
|
import { TYPES, hooks } from "../core.js";
|
||||||
|
import { notify } from "./error.js";
|
||||||
|
|
||||||
|
const SELECTOR = [...TYPES.keys()]
|
||||||
|
.map((type) => `script[type="${type}"][terminal],${type}-script[terminal]`)
|
||||||
|
.join(",");
|
||||||
|
|
||||||
|
// show the error on main and
|
||||||
|
// stops the module from keep executing
|
||||||
|
const notifyAndThrow = (message) => {
|
||||||
|
notify(message);
|
||||||
|
throw new Error(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pyTerminal = async () => {
|
||||||
|
const terminals = document.querySelectorAll(SELECTOR);
|
||||||
|
|
||||||
|
// no results will look further for runtime nodes
|
||||||
|
if (!terminals.length) return;
|
||||||
|
|
||||||
|
// if we arrived this far, let's drop the MutationObserver
|
||||||
|
// as we only support one terminal per page (right now).
|
||||||
|
mo.disconnect();
|
||||||
|
|
||||||
|
// we currently support only one terminal as in "classic"
|
||||||
|
if (terminals.length > 1) notifyAndThrow("You can use at most 1 terminal.");
|
||||||
|
|
||||||
|
const [element] = terminals;
|
||||||
|
// hopefully to be removed in the near future!
|
||||||
|
if (element.matches('script[type="mpy"],mpy-script'))
|
||||||
|
notifyAndThrow("Unsupported terminal.");
|
||||||
|
|
||||||
|
// import styles lazily
|
||||||
|
document.head.append(
|
||||||
|
Object.assign(document.createElement("link"), {
|
||||||
|
rel: "stylesheet",
|
||||||
|
href: new URL("./xterm.css", import.meta.url),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// lazy load these only when a valid terminal is found
|
||||||
|
const [{ Terminal }, { Readline }, { FitAddon }] = await Promise.all([
|
||||||
|
import(/* webpackIgnore: true */ "../3rd-party/xterm.js"),
|
||||||
|
import(/* webpackIgnore: true */ "../3rd-party/xterm-readline.js"),
|
||||||
|
import(/* webpackIgnore: true */ "../3rd-party/xterm_addon-fit.js"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const readline = new Readline();
|
||||||
|
|
||||||
|
// common main thread initialization for both worker
|
||||||
|
// or main case, bootstrapping the terminal on its target
|
||||||
|
const init = (options) => {
|
||||||
|
let target = element;
|
||||||
|
const selector = element.getAttribute("target");
|
||||||
|
if (selector) {
|
||||||
|
target =
|
||||||
|
document.getElementById(selector) ||
|
||||||
|
document.querySelector(selector);
|
||||||
|
if (!target) throw new Error(`Unknown target ${selector}`);
|
||||||
|
} else {
|
||||||
|
target = document.createElement("py-terminal");
|
||||||
|
target.style.display = "block";
|
||||||
|
element.after(target);
|
||||||
|
}
|
||||||
|
const terminal = new Terminal({
|
||||||
|
theme: {
|
||||||
|
background: "#191A19",
|
||||||
|
foreground: "#F5F2E7",
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
const fitAddon = new FitAddon();
|
||||||
|
terminal.loadAddon(fitAddon);
|
||||||
|
terminal.loadAddon(readline);
|
||||||
|
terminal.open(target);
|
||||||
|
fitAddon.fit();
|
||||||
|
terminal.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
// branch logic for the worker
|
||||||
|
if (element.hasAttribute("worker")) {
|
||||||
|
// when the remote thread onReady triggers:
|
||||||
|
// setup the interpreter stdout and stderr
|
||||||
|
const workerReady = ({ interpreter }, { sync }) => {
|
||||||
|
sync.pyterminal_drop_hooks();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let data = "";
|
||||||
|
const generic = {
|
||||||
|
isatty: true,
|
||||||
|
write(buffer) {
|
||||||
|
data = decoder.decode(buffer);
|
||||||
|
sync.pyterminal_write(data);
|
||||||
|
return buffer.length;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
interpreter.setStdout(generic);
|
||||||
|
interpreter.setStderr(generic);
|
||||||
|
interpreter.setStdin({
|
||||||
|
isatty: true,
|
||||||
|
stdin: () => sync.pyterminal_read(data),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// add a hook on the main thread to setup all sync helpers
|
||||||
|
// also bootstrapping the XTerm target on main
|
||||||
|
hooks.main.onWorker.add(function worker(_, xworker) {
|
||||||
|
hooks.main.onWorker.delete(worker);
|
||||||
|
init({
|
||||||
|
disableStdin: false,
|
||||||
|
cursorBlink: true,
|
||||||
|
cursorStyle: "block",
|
||||||
|
});
|
||||||
|
xworker.sync.pyterminal_read = readline.read.bind(readline);
|
||||||
|
xworker.sync.pyterminal_write = readline.write.bind(readline);
|
||||||
|
// allow a worker to drop main thread hooks ASAP
|
||||||
|
xworker.sync.pyterminal_drop_hooks = () => {
|
||||||
|
hooks.worker.onReady.delete(workerReady);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// setup remote thread JS/Python code for whenever the
|
||||||
|
// worker is ready to become a terminal
|
||||||
|
hooks.worker.onReady.add(workerReady);
|
||||||
|
} else {
|
||||||
|
// in the main case, just bootstrap XTerm without
|
||||||
|
// allowing any input as that's not possible / awkward
|
||||||
|
hooks.main.onReady.add(function main({ io }) {
|
||||||
|
console.warn("py-terminal is read only on main thread");
|
||||||
|
hooks.main.onReady.delete(main);
|
||||||
|
init({
|
||||||
|
disableStdin: true,
|
||||||
|
cursorBlink: false,
|
||||||
|
cursorStyle: "underline",
|
||||||
|
});
|
||||||
|
io.stdout = (value) => {
|
||||||
|
readline.write(`${value}\n`);
|
||||||
|
};
|
||||||
|
io.stderr = (error) => {
|
||||||
|
readline.write(`${error.message || error}\n`);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mo = new MutationObserver(pyTerminal);
|
||||||
|
mo.observe(document, { childList: true, subtree: true });
|
||||||
|
|
||||||
|
// try to check the current document ASAP
|
||||||
|
export default pyTerminal();
|
||||||
45
pyscript.core/src/stdlib.js
Normal file
45
pyscript.core/src/stdlib.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Create through Python the pyscript module through
|
||||||
|
* the artifact generated at build time.
|
||||||
|
* This the returned value is a string that must be used
|
||||||
|
* either before a worker execute code or when the module
|
||||||
|
* is registered on the main thread.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import pyscript from "./stdlib/pyscript.js";
|
||||||
|
|
||||||
|
const { entries } = Object;
|
||||||
|
|
||||||
|
const python = [
|
||||||
|
"import os as _os",
|
||||||
|
"from pathlib import Path as _Path",
|
||||||
|
"_path = None",
|
||||||
|
];
|
||||||
|
|
||||||
|
const write = (base, literal) => {
|
||||||
|
for (const [key, value] of entries(literal)) {
|
||||||
|
python.push(`_path = _Path("${base}/${key}")`);
|
||||||
|
if (typeof value === "string") {
|
||||||
|
const code = JSON.stringify(value);
|
||||||
|
python.push(`_path.write_text(${code})`);
|
||||||
|
} else {
|
||||||
|
// @see https://github.com/pyscript/pyscript/pull/1813#issuecomment-1781502909
|
||||||
|
python.push(`if not _os.path.exists("${base}/${key}"):`);
|
||||||
|
python.push(" _path.mkdir(parents=True, exist_ok=True)");
|
||||||
|
write(`${base}/${key}`, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
write(".", pyscript);
|
||||||
|
|
||||||
|
// in order to fix js.document in the Worker case
|
||||||
|
// we need to bootstrap pyscript module ASAP
|
||||||
|
python.push("import pyscript as _pyscript");
|
||||||
|
|
||||||
|
python.push(
|
||||||
|
...["_Path", "_path", "_os", "_pyscript"].map((ref) => `del ${ref}`),
|
||||||
|
);
|
||||||
|
python.push("\n");
|
||||||
|
|
||||||
|
export default python.join("\n");
|
||||||
50
pyscript.core/src/stdlib/pyscript/__init__.py
Normal file
50
pyscript.core/src/stdlib/pyscript/__init__.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Some notes about the naming conventions and the relationship between various
|
||||||
|
# similar-but-different names.
|
||||||
|
#
|
||||||
|
# import pyscript
|
||||||
|
# this package contains the main user-facing API offered by pyscript. All
|
||||||
|
# the names which are supposed be used by end users should be made
|
||||||
|
# available in pyscript/__init__.py (i.e., this file)
|
||||||
|
#
|
||||||
|
# import _pyscript
|
||||||
|
# this is an internal module implemented in JS. It is used internally by
|
||||||
|
# the pyscript package, end users should not use it directly. For its
|
||||||
|
# implementation, grep for `interpreter.registerJsModule("_pyscript",
|
||||||
|
# ...)` in core.js
|
||||||
|
#
|
||||||
|
# import js
|
||||||
|
# this is the JS globalThis, as exported by pyodide and/or micropython's
|
||||||
|
# FFIs. As such, it contains different things in the main thread or in a
|
||||||
|
# worker.
|
||||||
|
#
|
||||||
|
# import pyscript.magic_js
|
||||||
|
# this submodule abstracts away some of the differences between the main
|
||||||
|
# thread and the worker. In particular, it defines `window` and `document`
|
||||||
|
# in such a way that these names work in both cases: in the main thread,
|
||||||
|
# they are the "real" objects, in the worker they are proxies which work
|
||||||
|
# thanks to coincident.
|
||||||
|
#
|
||||||
|
# from pyscript import window, document
|
||||||
|
# these are just the window and document objects as defined by
|
||||||
|
# pyscript.magic_js. This is the blessed way to access them from pyscript,
|
||||||
|
# as it works transparently in both the main thread and worker cases.
|
||||||
|
|
||||||
|
from pyscript.display import HTML, display
|
||||||
|
from pyscript.magic_js import (
|
||||||
|
RUNNING_IN_WORKER,
|
||||||
|
PyWorker,
|
||||||
|
current_target,
|
||||||
|
document,
|
||||||
|
js_modules,
|
||||||
|
sync,
|
||||||
|
window,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pyscript.event_handling import when
|
||||||
|
except:
|
||||||
|
from pyscript.util import NotSupported
|
||||||
|
|
||||||
|
when = NotSupported(
|
||||||
|
"pyscript.when", "pyscript.when currently not available with this interpreter"
|
||||||
|
)
|
||||||
177
pyscript.core/src/stdlib/pyscript/display.py
Normal file
177
pyscript.core/src/stdlib/pyscript/display.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import base64
|
||||||
|
import html
|
||||||
|
import io
|
||||||
|
import re
|
||||||
|
|
||||||
|
from pyscript.magic_js import current_target, document, window
|
||||||
|
|
||||||
|
_MIME_METHODS = {
|
||||||
|
"__repr__": "text/plain",
|
||||||
|
"_repr_html_": "text/html",
|
||||||
|
"_repr_markdown_": "text/markdown",
|
||||||
|
"_repr_svg_": "image/svg+xml",
|
||||||
|
"_repr_pdf_": "application/pdf",
|
||||||
|
"_repr_jpeg_": "image/jpeg",
|
||||||
|
"_repr_png_": "image/png",
|
||||||
|
"_repr_latex": "text/latex",
|
||||||
|
"_repr_json_": "application/json",
|
||||||
|
"_repr_javascript_": "application/javascript",
|
||||||
|
"savefig": "image/png",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _render_image(mime, value, meta):
|
||||||
|
# If the image value is using bytes we should convert it to base64
|
||||||
|
# otherwise it will return raw bytes and the browser will not be able to
|
||||||
|
# render it.
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
value = base64.b64encode(value).decode("utf-8")
|
||||||
|
|
||||||
|
# This is the pattern of base64 strings
|
||||||
|
base64_pattern = re.compile(
|
||||||
|
r"^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$"
|
||||||
|
)
|
||||||
|
# If value doesn't match the base64 pattern we should encode it to base64
|
||||||
|
if len(value) > 0 and not base64_pattern.match(value):
|
||||||
|
value = base64.b64encode(value.encode("utf-8")).decode("utf-8")
|
||||||
|
|
||||||
|
data = f"data:{mime};charset=utf-8;base64,{value}"
|
||||||
|
attrs = " ".join(['{k}="{v}"' for k, v in meta.items()])
|
||||||
|
return f'<img src="{data}" {attrs}></img>'
|
||||||
|
|
||||||
|
|
||||||
|
def _identity(value, meta):
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
_MIME_RENDERERS = {
|
||||||
|
"text/plain": html.escape,
|
||||||
|
"text/html": _identity,
|
||||||
|
"image/png": lambda value, meta: _render_image("image/png", value, meta),
|
||||||
|
"image/jpeg": lambda value, meta: _render_image("image/jpeg", value, meta),
|
||||||
|
"image/svg+xml": _identity,
|
||||||
|
"application/json": _identity,
|
||||||
|
"application/javascript": lambda value, meta: f"<script>{value}<\\/script>",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class HTML:
|
||||||
|
"""
|
||||||
|
Wrap a string so that display() can render it as plain HTML
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, html):
|
||||||
|
self._html = html
|
||||||
|
|
||||||
|
def _repr_html_(self):
|
||||||
|
return self._html
|
||||||
|
|
||||||
|
|
||||||
|
def _eval_formatter(obj, print_method):
|
||||||
|
"""
|
||||||
|
Evaluates a formatter method.
|
||||||
|
"""
|
||||||
|
if print_method == "__repr__":
|
||||||
|
return repr(obj)
|
||||||
|
elif hasattr(obj, print_method):
|
||||||
|
if print_method == "savefig":
|
||||||
|
buf = io.BytesIO()
|
||||||
|
obj.savefig(buf, format="png")
|
||||||
|
buf.seek(0)
|
||||||
|
return base64.b64encode(buf.read()).decode("utf-8")
|
||||||
|
return getattr(obj, print_method)()
|
||||||
|
elif print_method == "_repr_mimebundle_":
|
||||||
|
return {}, {}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _format_mime(obj):
|
||||||
|
"""
|
||||||
|
Formats object using _repr_x_ methods.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, str):
|
||||||
|
return html.escape(obj), "text/plain"
|
||||||
|
|
||||||
|
mimebundle = _eval_formatter(obj, "_repr_mimebundle_")
|
||||||
|
if isinstance(mimebundle, tuple):
|
||||||
|
format_dict, _ = mimebundle
|
||||||
|
else:
|
||||||
|
format_dict = mimebundle
|
||||||
|
|
||||||
|
output, not_available = None, []
|
||||||
|
for method, mime_type in reversed(_MIME_METHODS.items()):
|
||||||
|
if mime_type in format_dict:
|
||||||
|
output = format_dict[mime_type]
|
||||||
|
else:
|
||||||
|
output = _eval_formatter(obj, method)
|
||||||
|
|
||||||
|
if output is None:
|
||||||
|
continue
|
||||||
|
elif mime_type not in _MIME_RENDERERS:
|
||||||
|
not_available.append(mime_type)
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
if output is None:
|
||||||
|
if not_available:
|
||||||
|
window.console.warn(
|
||||||
|
f"Rendered object requested unavailable MIME renderers: {not_available}"
|
||||||
|
)
|
||||||
|
output = repr(output)
|
||||||
|
mime_type = "text/plain"
|
||||||
|
elif isinstance(output, tuple):
|
||||||
|
output, meta = output
|
||||||
|
else:
|
||||||
|
meta = {}
|
||||||
|
return _MIME_RENDERERS[mime_type](output, meta), mime_type
|
||||||
|
|
||||||
|
|
||||||
|
def _write(element, value, append=False):
|
||||||
|
html, mime_type = _format_mime(value)
|
||||||
|
if html == "\\n":
|
||||||
|
return
|
||||||
|
|
||||||
|
if append:
|
||||||
|
out_element = document.createElement("div")
|
||||||
|
element.append(out_element)
|
||||||
|
else:
|
||||||
|
out_element = element.lastElementChild
|
||||||
|
if out_element is None:
|
||||||
|
out_element = element
|
||||||
|
|
||||||
|
if mime_type in ("application/javascript", "text/html"):
|
||||||
|
script_element = document.createRange().createContextualFragment(html)
|
||||||
|
out_element.append(script_element)
|
||||||
|
else:
|
||||||
|
out_element.innerHTML = html
|
||||||
|
|
||||||
|
|
||||||
|
def display(*values, target=None, append=True):
|
||||||
|
if target is None:
|
||||||
|
target = current_target()
|
||||||
|
elif not isinstance(target, str):
|
||||||
|
raise TypeError(f"target must be str or None, not {target.__class__.__name__}")
|
||||||
|
elif target == "":
|
||||||
|
raise ValueError("Cannot have an empty target")
|
||||||
|
elif target.startswith("#"):
|
||||||
|
# note: here target is str and not None!
|
||||||
|
# align with @when behavior
|
||||||
|
target = target[1:]
|
||||||
|
|
||||||
|
element = document.getElementById(target)
|
||||||
|
|
||||||
|
# If target cannot be found on the page, a ValueError is raised
|
||||||
|
if element is None:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid selector with id={target}. Cannot be found in the page."
|
||||||
|
)
|
||||||
|
|
||||||
|
# if element is a <script type="py">, it has a 'target' attribute which
|
||||||
|
# points to the visual element holding the displayed values. In that case,
|
||||||
|
# use that.
|
||||||
|
if element.tagName == "SCRIPT" and hasattr(element, "target"):
|
||||||
|
element = element.target
|
||||||
|
|
||||||
|
for v in values:
|
||||||
|
if not append:
|
||||||
|
element.replaceChildren()
|
||||||
|
_write(element, v, append=append)
|
||||||
45
pyscript.core/src/stdlib/pyscript/event_handling.py
Normal file
45
pyscript.core/src/stdlib/pyscript/event_handling.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import inspect
|
||||||
|
|
||||||
|
from pyodide.ffi.wrappers import add_event_listener
|
||||||
|
from pyscript.magic_js import document
|
||||||
|
|
||||||
|
|
||||||
|
def when(event_type=None, selector=None):
|
||||||
|
"""
|
||||||
|
Decorates a function and passes py-* events to the decorated function
|
||||||
|
The events might or not be an argument of the decorated function
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
if isinstance(selector, str):
|
||||||
|
elements = document.querySelectorAll(selector)
|
||||||
|
else:
|
||||||
|
# TODO: This is a hack that will be removed when pyscript becomes a package
|
||||||
|
# and we can better manage the imports without circular dependencies
|
||||||
|
from pyweb import pydom
|
||||||
|
|
||||||
|
if isinstance(selector, pydom.Element):
|
||||||
|
elements = [selector._js]
|
||||||
|
elif isinstance(selector, pydom.ElementCollection):
|
||||||
|
elements = [el._js for el in selector]
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid selector: {selector}. Selector must"
|
||||||
|
" be a string, a pydom.Element or a pydom.ElementCollection."
|
||||||
|
)
|
||||||
|
|
||||||
|
sig = inspect.signature(func)
|
||||||
|
# Function doesn't receive events
|
||||||
|
if not sig.parameters:
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
func()
|
||||||
|
|
||||||
|
for el in elements:
|
||||||
|
add_event_listener(el, event_type, wrapper)
|
||||||
|
else:
|
||||||
|
for el in elements:
|
||||||
|
add_event_listener(el, event_type, func)
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
37
pyscript.core/src/stdlib/pyscript/magic_js.py
Normal file
37
pyscript.core/src/stdlib/pyscript/magic_js.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import js as globalThis
|
||||||
|
from polyscript import js_modules
|
||||||
|
from pyscript.util import NotSupported
|
||||||
|
|
||||||
|
RUNNING_IN_WORKER = not hasattr(globalThis, "document")
|
||||||
|
|
||||||
|
if RUNNING_IN_WORKER:
|
||||||
|
import js
|
||||||
|
import polyscript
|
||||||
|
|
||||||
|
PyWorker = NotSupported(
|
||||||
|
"pyscript.PyWorker",
|
||||||
|
"pyscript.PyWorker works only when running in the main thread",
|
||||||
|
)
|
||||||
|
window = polyscript.xworker.window
|
||||||
|
document = window.document
|
||||||
|
js.document = document
|
||||||
|
sync = polyscript.xworker.sync
|
||||||
|
|
||||||
|
# in workers the display does not have a default ID
|
||||||
|
# but there is a sync utility from xworker
|
||||||
|
def current_target():
|
||||||
|
return polyscript.target
|
||||||
|
|
||||||
|
else:
|
||||||
|
import _pyscript
|
||||||
|
from _pyscript import PyWorker
|
||||||
|
|
||||||
|
window = globalThis
|
||||||
|
document = globalThis.document
|
||||||
|
sync = NotSupported(
|
||||||
|
"pyscript.sync", "pyscript.sync works only when running in a worker"
|
||||||
|
)
|
||||||
|
|
||||||
|
# in MAIN the current element target exist, just use it
|
||||||
|
def current_target():
|
||||||
|
return _pyscript.target
|
||||||
21
pyscript.core/src/stdlib/pyscript/util.py
Normal file
21
pyscript.core/src/stdlib/pyscript/util.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
class NotSupported:
|
||||||
|
"""
|
||||||
|
Small helper that raises exceptions if you try to get/set any attribute on
|
||||||
|
it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, error):
|
||||||
|
object.__setattr__(self, "name", name)
|
||||||
|
object.__setattr__(self, "error", error)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<NotSupported {self.name} [{self.error}]>"
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
raise AttributeError(self.error)
|
||||||
|
|
||||||
|
def __setattr__(self, attr, value):
|
||||||
|
raise AttributeError(self.error)
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
raise TypeError(self.error)
|
||||||
433
pyscript.core/src/stdlib/pyweb/pydom.py
Normal file
433
pyscript.core/src/stdlib/pyweb/pydom.py
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
from functools import cached_property
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pyodide.ffi import JsProxy
|
||||||
|
from pyscript import display, document, window
|
||||||
|
|
||||||
|
alert = window.alert
|
||||||
|
|
||||||
|
|
||||||
|
class BaseElement:
|
||||||
|
def __init__(self, js_element):
|
||||||
|
self._js = js_element
|
||||||
|
self._parent = None
|
||||||
|
self.style = StyleProxy(self)
|
||||||
|
self._proxies = {}
|
||||||
|
|
||||||
|
def __eq__(self, obj):
|
||||||
|
"""Check if the element is the same as the other element by comparing
|
||||||
|
the underlying JS element"""
|
||||||
|
return isinstance(obj, BaseElement) and obj._js == self._js
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent(self):
|
||||||
|
if self._parent:
|
||||||
|
return self._parent
|
||||||
|
|
||||||
|
if self._js.parentElement:
|
||||||
|
self._parent = self.__class__(self._js.parentElement)
|
||||||
|
|
||||||
|
return self._parent
|
||||||
|
|
||||||
|
@property
|
||||||
|
def __class(self):
|
||||||
|
return self.__class__ if self.__class__ != PyDom else Element
|
||||||
|
|
||||||
|
def create(self, type_, is_child=True, classes=None, html=None, label=None):
|
||||||
|
js_el = document.createElement(type_)
|
||||||
|
element = self.__class(js_el)
|
||||||
|
|
||||||
|
if classes:
|
||||||
|
for class_ in classes:
|
||||||
|
element.add_class(class_)
|
||||||
|
|
||||||
|
if html is not None:
|
||||||
|
element.html = html
|
||||||
|
|
||||||
|
if label is not None:
|
||||||
|
element.label = label
|
||||||
|
|
||||||
|
if is_child:
|
||||||
|
self.append(element)
|
||||||
|
|
||||||
|
return element
|
||||||
|
|
||||||
|
def find(self, selector):
|
||||||
|
"""Return an ElementCollection representing all the child elements that
|
||||||
|
match the specified selector.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
selector (str): A string containing a selector expression
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ElementCollection: A collection of elements matching the selector
|
||||||
|
"""
|
||||||
|
elements = self._js.querySelectorAll(selector)
|
||||||
|
if not elements:
|
||||||
|
return None
|
||||||
|
return ElementCollection([Element(el) for el in elements])
|
||||||
|
|
||||||
|
|
||||||
|
class Element(BaseElement):
|
||||||
|
@property
|
||||||
|
def children(self):
|
||||||
|
return [self.__class__(el) for el in self._js.children]
|
||||||
|
|
||||||
|
def append(self, child):
|
||||||
|
# TODO: this is Pyodide specific for now!!!!!!
|
||||||
|
# if we get passed a JSProxy Element directly we just map it to the
|
||||||
|
# higher level Python element
|
||||||
|
if isinstance(child, JsProxy):
|
||||||
|
return self.append(Element(child))
|
||||||
|
|
||||||
|
elif isinstance(child, Element):
|
||||||
|
self._js.appendChild(child._js)
|
||||||
|
|
||||||
|
return child
|
||||||
|
|
||||||
|
elif isinstance(child, ElementCollection):
|
||||||
|
for el in child:
|
||||||
|
self.append(el)
|
||||||
|
|
||||||
|
# -------- Pythonic Interface to Element -------- #
|
||||||
|
@property
|
||||||
|
def html(self):
|
||||||
|
return self._js.innerHTML
|
||||||
|
|
||||||
|
@html.setter
|
||||||
|
def html(self, value):
|
||||||
|
self._js.innerHTML = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content(self):
|
||||||
|
# TODO: This breaks with with standard template elements. Define how to best
|
||||||
|
# handle this specifica use case. Just not support for now?
|
||||||
|
if self._js.tagName == "TEMPLATE":
|
||||||
|
warnings.warn(
|
||||||
|
"Content attribute not supported for template elements.", stacklevel=2
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return self._js.innerHTML
|
||||||
|
|
||||||
|
@content.setter
|
||||||
|
def content(self, value):
|
||||||
|
# TODO: (same comment as above)
|
||||||
|
if self._js.tagName == "TEMPLATE":
|
||||||
|
warnings.warn(
|
||||||
|
"Content attribute not supported for template elements.", stacklevel=2
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
display(value, target=self.id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
return self._js.id
|
||||||
|
|
||||||
|
@id.setter
|
||||||
|
def id(self, value):
|
||||||
|
self._js.id = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self):
|
||||||
|
if "options" in self._proxies:
|
||||||
|
return self._proxies["options"]
|
||||||
|
|
||||||
|
if not self._js.tagName.lower() in {"select", "datalist", "optgroup"}:
|
||||||
|
raise AttributeError(
|
||||||
|
f"Element {self._js.tagName} has no options attribute."
|
||||||
|
)
|
||||||
|
self._proxies["options"] = OptionsProxy(self)
|
||||||
|
return self._proxies["options"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._js.value
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
# in order to avoid confusion to the user, we don't allow setting the
|
||||||
|
# value of elements that don't have a value attribute
|
||||||
|
if not hasattr(self._js, "value"):
|
||||||
|
raise AttributeError(
|
||||||
|
f"Element {self._js.tagName} has no value attribute. If you want to "
|
||||||
|
"force a value attribute, set it directly using the `_js.value = <value>` "
|
||||||
|
"javascript API attribute instead."
|
||||||
|
)
|
||||||
|
self._js.value = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selected(self):
|
||||||
|
return self._js.selected
|
||||||
|
|
||||||
|
@selected.setter
|
||||||
|
def selected(self, value):
|
||||||
|
# in order to avoid confusion to the user, we don't allow setting the
|
||||||
|
# value of elements that don't have a value attribute
|
||||||
|
if not hasattr(self._js, "selected"):
|
||||||
|
raise AttributeError(
|
||||||
|
f"Element {self._js.tagName} has no value attribute. If you want to "
|
||||||
|
"force a value attribute, set it directly using the `_js.value = <value>` "
|
||||||
|
"javascript API attribute instead."
|
||||||
|
)
|
||||||
|
self._js.selected = value
|
||||||
|
|
||||||
|
def clone(self, new_id=None):
|
||||||
|
clone = Element(self._js.cloneNode(True))
|
||||||
|
clone.id = new_id
|
||||||
|
|
||||||
|
return clone
|
||||||
|
|
||||||
|
def remove_class(self, classname):
|
||||||
|
classList = self._js.classList
|
||||||
|
if isinstance(classname, list):
|
||||||
|
classList.remove(*classname)
|
||||||
|
else:
|
||||||
|
classList.remove(classname)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def add_class(self, classname):
|
||||||
|
classList = self._js.classList
|
||||||
|
if isinstance(classname, list):
|
||||||
|
classList.add(*classname)
|
||||||
|
else:
|
||||||
|
self._js.classList.add(classname)
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def classes(self):
|
||||||
|
classes = self._js.classList.values()
|
||||||
|
return [x for x in classes]
|
||||||
|
|
||||||
|
def show_me(self):
|
||||||
|
self._js.scrollIntoView()
|
||||||
|
|
||||||
|
|
||||||
|
class OptionsProxy:
|
||||||
|
"""This class represents the options of a select element. It
|
||||||
|
allows to access to add and remove options by using the `add` and `remove` methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, element: Element) -> None:
|
||||||
|
self._element = element
|
||||||
|
if self._element._js.tagName.lower() != "select":
|
||||||
|
raise AttributeError(
|
||||||
|
f"Element {self._element._js.tagName} has no options attribute."
|
||||||
|
)
|
||||||
|
|
||||||
|
def add(
|
||||||
|
self,
|
||||||
|
value: Any = None,
|
||||||
|
html: str = None,
|
||||||
|
text: str = None,
|
||||||
|
before: Element | int = None,
|
||||||
|
**kws,
|
||||||
|
) -> None:
|
||||||
|
"""Add a new option to the select element"""
|
||||||
|
# create the option element and set the attributes
|
||||||
|
option = document.createElement("option")
|
||||||
|
if value is not None:
|
||||||
|
kws["value"] = value
|
||||||
|
if html is not None:
|
||||||
|
option.innerHTML = html
|
||||||
|
if text is not None:
|
||||||
|
kws["text"] = text
|
||||||
|
|
||||||
|
for key, value in kws.items():
|
||||||
|
option.setAttribute(key, value)
|
||||||
|
|
||||||
|
if before:
|
||||||
|
if isinstance(before, Element):
|
||||||
|
before = before._js
|
||||||
|
|
||||||
|
self._element._js.add(option, before)
|
||||||
|
|
||||||
|
def remove(self, item: int) -> None:
|
||||||
|
"""Remove the option at the specified index"""
|
||||||
|
self._element._js.remove(item)
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Remove all the options"""
|
||||||
|
for i in range(len(self)):
|
||||||
|
self.remove(0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self):
|
||||||
|
"""Return the list of options"""
|
||||||
|
return [Element(opt) for opt in self._element._js.options]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selected(self):
|
||||||
|
"""Return the selected option"""
|
||||||
|
return self.options[self._element._js.selectedIndex]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield from self.options
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.options)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__} (length: {len(self)}) {self.options}"
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.options[key]
|
||||||
|
|
||||||
|
|
||||||
|
class StyleProxy(dict):
|
||||||
|
def __init__(self, element: Element) -> None:
|
||||||
|
self._element = element
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def _style(self):
|
||||||
|
return self._element._js.style
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._style.getPropertyValue(key)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._style.setProperty(key, value)
|
||||||
|
|
||||||
|
def remove(self, key):
|
||||||
|
self._style.removeProperty(key)
|
||||||
|
|
||||||
|
def set(self, **kws):
|
||||||
|
for k, v in kws.items():
|
||||||
|
self._element._js.style.setProperty(k, v)
|
||||||
|
|
||||||
|
# CSS Properties
|
||||||
|
# Reference: https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L3799C1-L5005C2
|
||||||
|
# Following prperties automatically generated from the above reference using
|
||||||
|
# tools/codegen_css_proxy.py
|
||||||
|
@property
|
||||||
|
def visible(self):
|
||||||
|
return self._element._js.style.visibility
|
||||||
|
|
||||||
|
@visible.setter
|
||||||
|
def visible(self, value):
|
||||||
|
self._element._js.style.visibility = value
|
||||||
|
|
||||||
|
|
||||||
|
class StyleCollection:
|
||||||
|
def __init__(self, collection: "ElementCollection") -> None:
|
||||||
|
self._collection = collection
|
||||||
|
|
||||||
|
def __get__(self, obj, objtype=None):
|
||||||
|
return obj._get_attribute("style")
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._collection._get_attribute("style")[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
for element in self._collection._elements:
|
||||||
|
element.style[key] = value
|
||||||
|
|
||||||
|
def remove(self, key):
|
||||||
|
for element in self._collection._elements:
|
||||||
|
element.style.remove(key)
|
||||||
|
|
||||||
|
|
||||||
|
class ElementCollection:
|
||||||
|
def __init__(self, elements: [Element]) -> None:
|
||||||
|
self._elements = elements
|
||||||
|
self.style = StyleCollection(self)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
# If it's an integer we use it to access the elements in the collection
|
||||||
|
if isinstance(key, int):
|
||||||
|
return self._elements[key]
|
||||||
|
# If it's a slice we use it to support slice operations over the elements
|
||||||
|
# in the collection
|
||||||
|
elif isinstance(key, slice):
|
||||||
|
return ElementCollection(self._elements[key])
|
||||||
|
|
||||||
|
# If it's anything else (basically a string) we use it as a selector
|
||||||
|
# TODO: Write tests!
|
||||||
|
elements = self._element.querySelectorAll(key)
|
||||||
|
return ElementCollection([Element(el) for el in elements])
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._elements)
|
||||||
|
|
||||||
|
def __eq__(self, obj):
|
||||||
|
"""Check if the element is the same as the other element by comparing
|
||||||
|
the underlying JS element"""
|
||||||
|
return isinstance(obj, ElementCollection) and obj._elements == self._elements
|
||||||
|
|
||||||
|
def _get_attribute(self, attr, index=None):
|
||||||
|
if index is None:
|
||||||
|
return [getattr(el, attr) for el in self._elements]
|
||||||
|
|
||||||
|
# As JQuery, when getting an attr, only return it for the first element
|
||||||
|
return getattr(self._elements[index], attr)
|
||||||
|
|
||||||
|
def _set_attribute(self, attr, value):
|
||||||
|
for el in self._elements:
|
||||||
|
setattr(el, attr, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def html(self):
|
||||||
|
return self._get_attribute("html")
|
||||||
|
|
||||||
|
@html.setter
|
||||||
|
def html(self, value):
|
||||||
|
self._set_attribute("html", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._get_attribute("value")
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
self._set_attribute("value", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def children(self):
|
||||||
|
return self._elements
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield from self._elements
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__} (length: {len(self._elements)}) {self._elements}"
|
||||||
|
|
||||||
|
|
||||||
|
class DomScope:
|
||||||
|
def __getattr__(self, __name: str) -> Any:
|
||||||
|
element = document[f"#{__name}"]
|
||||||
|
if element:
|
||||||
|
return element[0]
|
||||||
|
|
||||||
|
|
||||||
|
class PyDom(BaseElement):
|
||||||
|
# Add objects we want to expose to the DOM namespace since this class instance is being
|
||||||
|
# remapped as "the module" itself
|
||||||
|
BaseElement = BaseElement
|
||||||
|
Element = Element
|
||||||
|
ElementCollection = ElementCollection
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(document)
|
||||||
|
self.ids = DomScope()
|
||||||
|
self.body = Element(document.body)
|
||||||
|
self.head = Element(document.head)
|
||||||
|
|
||||||
|
def create(self, type_, classes=None, html=None):
|
||||||
|
return super().create(type_, is_child=False, classes=classes, html=html)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if isinstance(key, int):
|
||||||
|
indices = range(*key.indices(len(self.list)))
|
||||||
|
return [self.list[i] for i in indices]
|
||||||
|
|
||||||
|
elements = self._js.querySelectorAll(key)
|
||||||
|
if not elements:
|
||||||
|
return None
|
||||||
|
return ElementCollection([Element(el) for el in elements])
|
||||||
|
|
||||||
|
|
||||||
|
dom = PyDom()
|
||||||
|
|
||||||
|
sys.modules[__name__] = dom
|
||||||
9
pyscript.core/src/sync.js
Normal file
9
pyscript.core/src/sync.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 'Sleep' for the given number of seconds. Used to implement Python's time.sleep in Worker threads.
|
||||||
|
* @param {number} seconds The number of seconds to sleep.
|
||||||
|
*/
|
||||||
|
sleep(seconds) {
|
||||||
|
return new Promise(($) => setTimeout($, seconds * 1000));
|
||||||
|
},
|
||||||
|
};
|
||||||
4
pyscript.core/src/types.js
Normal file
4
pyscript.core/src/types.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default new Map([
|
||||||
|
["py", "pyodide"],
|
||||||
|
["mpy", "micropython"],
|
||||||
|
]);
|
||||||
1
pyscript.core/test/a.py
Normal file
1
pyscript.core/test/a.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
print("a")
|
||||||
39
pyscript.core/test/all-done.html
Normal file
39
pyscript.core/test/all-done.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module">
|
||||||
|
import '../dist/core.js';
|
||||||
|
|
||||||
|
document.body.append('loading ...', document.createElement('br'));
|
||||||
|
|
||||||
|
addEventListener(
|
||||||
|
'py:all-done',
|
||||||
|
() => {
|
||||||
|
document.body.append('all executed');
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py">
|
||||||
|
print(1)
|
||||||
|
</script>
|
||||||
|
<py-script>
|
||||||
|
print(2)
|
||||||
|
</py-script>
|
||||||
|
<py-script async>
|
||||||
|
print(3)
|
||||||
|
</py-script>
|
||||||
|
<script type="py" worker>
|
||||||
|
print(4)
|
||||||
|
</script>
|
||||||
|
<py-script async worker>
|
||||||
|
print(5)
|
||||||
|
</py-script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
pyscript.core/test/async.html
Normal file
15
pyscript.core/test/async.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<py-script async>
|
||||||
|
import asyncio
|
||||||
|
print('foo')
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
print('bar')
|
||||||
|
</py-script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
pyscript.core/test/bad.toml
Normal file
1
pyscript.core/test/bad.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
files = [
|
||||||
29
pyscript.core/test/click.html
Normal file
29
pyscript.core/test/click.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PyScript Next Plugin Bug?</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py">
|
||||||
|
from pyscript import display, document
|
||||||
|
from datetime import datetime as dt
|
||||||
|
from pyodide.ffi.wrappers import add_event_listener
|
||||||
|
|
||||||
|
element = document.querySelector("#just-a-button")
|
||||||
|
|
||||||
|
def on_click(event):
|
||||||
|
print(f"Hello from Python! {dt.now()}")
|
||||||
|
display(f"Hello from Python! {dt.now()}", append=False, target='result')
|
||||||
|
|
||||||
|
add_event_listener(element, "click", on_click)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button id="just-a-button">click and check the console</button>
|
||||||
|
|
||||||
|
<div id="result"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
pyscript.core/test/combo.html
Normal file
17
pyscript.core/test/combo.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyScript Error</title>
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<py-config>
|
||||||
|
[[fetch]]
|
||||||
|
files = ["a.py"]
|
||||||
|
</py-config>
|
||||||
|
<script type="py" worker>
|
||||||
|
import a
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
11
pyscript.core/test/config-url.html
Normal file
11
pyscript.core/test/config-url.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PyScript Next Plugin</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<py-config src="bad.toml" type="toml"></py-config>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
13
pyscript.core/test/config.html
Normal file
13
pyscript.core/test/config.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PyScript Next Plugin</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<py-config>
|
||||||
|
files = [
|
||||||
|
</py-config>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
5
pyscript.core/test/config.json
Normal file
5
pyscript.core/test/config.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"fetch": [{
|
||||||
|
"files": ["./a.py"]
|
||||||
|
}]
|
||||||
|
}
|
||||||
19
pyscript.core/test/config/ambiguous-config.html
Normal file
19
pyscript.core/test/config/ambiguous-config.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyScript Next</title>
|
||||||
|
<script>
|
||||||
|
addEventListener("error", ({ message }) => {
|
||||||
|
document.body.innerHTML += `<p>${message}</p>`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="../../dist/core.css">
|
||||||
|
<script type="module" src="../../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<py-config>name = "first"</py-config>
|
||||||
|
<script type="py" config="second.toml"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
pyscript.core/test/config/index.html
Normal file
16
pyscript.core/test/config/index.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<ul>
|
||||||
|
<li><a href="./ambiguous-config.html">ambiguous py-config VS config attribute</a></li>
|
||||||
|
<li><a href="./too-many-config.html">too many config attributes</a></li>
|
||||||
|
<li><a href="./too-many-py-config.html">too many <py-config></a></li>
|
||||||
|
<li><a href="./same-config.html">same config attributes</a></li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
pyscript.core/test/config/same-config.html
Normal file
20
pyscript.core/test/config/same-config.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyScript Next</title>
|
||||||
|
<script>
|
||||||
|
addEventListener("error", ({ message }) => {
|
||||||
|
document.body.innerHTML += `<p>${message}</p>`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="../../dist/core.css">
|
||||||
|
<script type="module" src="../../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
OK
|
||||||
|
<script type="py" config='{"name":"OK"}'></script>
|
||||||
|
<script type="py" config='{"name":"OK"}'></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
pyscript.core/test/config/too-many-config.html
Normal file
19
pyscript.core/test/config/too-many-config.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyScript Next</title>
|
||||||
|
<script>
|
||||||
|
addEventListener("error", ({ message }) => {
|
||||||
|
document.body.innerHTML += `<p>${message}</p>`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="../../dist/core.css">
|
||||||
|
<script type="module" src="../../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py" config="first.toml"></script>
|
||||||
|
<script type="py" config="second.toml"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
pyscript.core/test/config/too-many-py-config.html
Normal file
19
pyscript.core/test/config/too-many-py-config.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyScript Next</title>
|
||||||
|
<script>
|
||||||
|
addEventListener("error", ({ message }) => {
|
||||||
|
document.body.innerHTML += `<p>${message}</p>`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="../../dist/core.css">
|
||||||
|
<script type="module" src="../../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<py-config>name = "first"</py-config>
|
||||||
|
<py-config>name = "second"</py-config>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
pyscript.core/test/create-element.html
Normal file
36
pyscript.core/test/create-element.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
customElements.whenDefined('py-script').then(PyScript => {
|
||||||
|
const textContent = `
|
||||||
|
from pyscript import display
|
||||||
|
|
||||||
|
display("Hello World")
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.append(
|
||||||
|
// test <script type="py">
|
||||||
|
Object.assign(
|
||||||
|
document.createElement('script'),
|
||||||
|
{ type: "py", textContent }
|
||||||
|
),
|
||||||
|
|
||||||
|
// test <py-script>
|
||||||
|
Object.assign(
|
||||||
|
document.createElement('py-script'),
|
||||||
|
{ textContent }
|
||||||
|
),
|
||||||
|
|
||||||
|
// test PyScript class
|
||||||
|
Object.assign(
|
||||||
|
new PyScript(),
|
||||||
|
{ textContent }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
31
pyscript.core/test/dialog.html
Normal file
31
pyscript.core/test/dialog.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyScript Next</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
const loader = document.querySelector('#loader');
|
||||||
|
loader.showModal();
|
||||||
|
addEventListener(
|
||||||
|
'py:all-done',
|
||||||
|
() => {
|
||||||
|
loader.close();
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<script type="py">
|
||||||
|
from pyscript import document
|
||||||
|
|
||||||
|
document.body.textContent = "PyScript Ready";
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<dialog id="loader">
|
||||||
|
Loading PyScript ...
|
||||||
|
</dialog>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
pyscript.core/test/display.html
Normal file
30
pyscript.core/test/display.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyScript Next</title>
|
||||||
|
<script>
|
||||||
|
addEventListener("py:all-done", ({ type }) => console.log(type));
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py" worker async>
|
||||||
|
from pyscript import display
|
||||||
|
display('hello 1')
|
||||||
|
|
||||||
|
import js
|
||||||
|
import time
|
||||||
|
js.console.log('sleeping...')
|
||||||
|
time.sleep(2)
|
||||||
|
js.console.log('...done')
|
||||||
|
</script>
|
||||||
|
<p>hello 2</p>
|
||||||
|
<script type="py" worker async>
|
||||||
|
from pyscript import display
|
||||||
|
display('hello 3')
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
pyscript.core/test/error.html
Normal file
23
pyscript.core/test/error.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PyScript Next Plugin</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<script type="py">
|
||||||
|
print(1, 2, 3)
|
||||||
|
first()
|
||||||
|
</script>
|
||||||
|
<py-script>
|
||||||
|
print(4, 5, 6)
|
||||||
|
second()
|
||||||
|
</py-script>
|
||||||
|
<py-script src="whatever.py">
|
||||||
|
print(4, 5, 6)
|
||||||
|
second()
|
||||||
|
</py-script>
|
||||||
|
<py-script src="main.py" worker="worker.py"></py-script>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
39
pyscript.core/test/error.js
Normal file
39
pyscript.core/test/error.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// PyScript Error Plugin
|
||||||
|
import { hooks } from '@pyscript/core';
|
||||||
|
|
||||||
|
hooks.onBeforeRun.add(function override(pyScript) {
|
||||||
|
// be sure this override happens only once
|
||||||
|
hooks.onBeforeRun.delete(override);
|
||||||
|
|
||||||
|
// trap generic `stderr` to propagate to it regardless
|
||||||
|
const { stderr } = pyScript.io;
|
||||||
|
|
||||||
|
// override it with our own logic
|
||||||
|
pyScript.io.stderr = (...args) => {
|
||||||
|
// grab the message of the first argument (Error)
|
||||||
|
const [ { message } ] = args;
|
||||||
|
// show it
|
||||||
|
notify(message);
|
||||||
|
// still let other plugins or PyScript itself do the rest
|
||||||
|
return stderr(...args);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Error hook utilities
|
||||||
|
|
||||||
|
// Custom function to show notifications
|
||||||
|
function notify(message) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = message;
|
||||||
|
div.style.cssText = `
|
||||||
|
border: 1px solid red;
|
||||||
|
background: #ffdddd;
|
||||||
|
color: black;
|
||||||
|
font-family: courier, monospace;
|
||||||
|
white-space: pre;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
`;
|
||||||
|
document.body.append(div);
|
||||||
|
}
|
||||||
60
pyscript.core/test/hooks.html
Normal file
60
pyscript.core/test/hooks.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PyScript Next Plugin Bug?</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module">
|
||||||
|
addEventListener('mpy:done', () => {
|
||||||
|
document.documentElement.classList.add('done');
|
||||||
|
});
|
||||||
|
|
||||||
|
import { hooks } from "../dist/core.js";
|
||||||
|
|
||||||
|
// Main
|
||||||
|
hooks.main.onReady.add((wrap, element) => {
|
||||||
|
console.log("main", "onReady");
|
||||||
|
if (location.search === '?debug') {
|
||||||
|
console.debug("main", "wrap", wrap);
|
||||||
|
console.debug("main", "element", element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
hooks.main.onBeforeRun.add(() => {
|
||||||
|
console.log("main", "onBeforeRun");
|
||||||
|
});
|
||||||
|
hooks.main.codeBeforeRun.add('print("main", "codeBeforeRun")');
|
||||||
|
hooks.main.codeAfterRun.add('print("main", "codeAfterRun")');
|
||||||
|
hooks.main.onAfterRun.add(() => {
|
||||||
|
console.log("main", "onAfterRun");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Worker
|
||||||
|
hooks.worker.onReady.add((wrap, xworker) => {
|
||||||
|
console.log("worker", "onReady");
|
||||||
|
if (location.search === '?debug') {
|
||||||
|
console.debug("worker", "wrap", wrap);
|
||||||
|
console.debug("worker", "xworker", xworker);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
hooks.worker.onBeforeRun.add(() => {
|
||||||
|
console.log("worker", "onBeforeRun");
|
||||||
|
});
|
||||||
|
hooks.worker.codeBeforeRun.add('print("worker", "codeBeforeRun")');
|
||||||
|
hooks.worker.codeAfterRun.add('print("worker", "codeAfterRun")');
|
||||||
|
hooks.worker.onAfterRun.add(() => {
|
||||||
|
console.log("worker", "onAfterRun");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="mpy" worker>
|
||||||
|
from pyscript import document
|
||||||
|
print("actual code in worker")
|
||||||
|
document.documentElement.classList.add('worker')
|
||||||
|
</script>
|
||||||
|
<script type="mpy">
|
||||||
|
print("actual code in main")
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
pyscript.core/test/html-decode.html
Normal file
35
pyscript.core/test/html-decode.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<body>
|
||||||
|
<py-script>import js; js.console.log(1<2, 1>2)</py-script>
|
||||||
|
<py-script>import js; js.console.log("<div></div>")</py-script>
|
||||||
|
<script type="py">
|
||||||
|
import js
|
||||||
|
js.console.log("A", 1<2, 1>2)
|
||||||
|
js.console.log("B <div></div>")
|
||||||
|
</script>
|
||||||
|
<py-script>
|
||||||
|
import js
|
||||||
|
js.console.log("C", 1<2, 1>2)
|
||||||
|
js.console.log("D <div></div>")
|
||||||
|
</py-script>
|
||||||
|
<py-script worker>import js; js.console.log(1<2, 1>2)</py-script>
|
||||||
|
<py-script worker>import js; js.console.log("<div></div>")</py-script>
|
||||||
|
<script type="py" worker>
|
||||||
|
import js
|
||||||
|
js.console.log("A", 1<2, 1>2)
|
||||||
|
js.console.log("B <div></div>")
|
||||||
|
</script>
|
||||||
|
<py-script worker>
|
||||||
|
import js
|
||||||
|
js.console.log("C", 1<2, 1>2)
|
||||||
|
js.console.log("D <div></div>")
|
||||||
|
</py-script>
|
||||||
|
</body>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
pyscript.core/test/index.html
Normal file
19
pyscript.core/test/index.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyScript Next</title>
|
||||||
|
<script>
|
||||||
|
addEventListener("py:ready", console.log);
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py">
|
||||||
|
from pyscript import display
|
||||||
|
display("Hello", "PyScript Next", append=False)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
21
pyscript.core/test/input.html
Normal file
21
pyscript.core/test/input.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyScript Next</title>
|
||||||
|
<script>
|
||||||
|
addEventListener("py:ready", console.log);
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<py-script>
|
||||||
|
input("what's your name?")
|
||||||
|
</py-script>
|
||||||
|
<mpy-script>
|
||||||
|
input("what's your name?")
|
||||||
|
</mpy-script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
pyscript.core/test/mpy.html
Normal file
30
pyscript.core/test/mpy.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyScript Next</title>
|
||||||
|
<script>
|
||||||
|
addEventListener('mpy:done', () => {
|
||||||
|
document.documentElement.classList.add('done');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="mpy">
|
||||||
|
from pyscript import display
|
||||||
|
display("Hello", "M-PyScript Main 1", append=False)
|
||||||
|
</script>
|
||||||
|
<mpy-script>
|
||||||
|
from pyscript import display
|
||||||
|
display("Hello", "M-PyScript Main 2", append=False)
|
||||||
|
</mpy-script>
|
||||||
|
<mpy-script worker>
|
||||||
|
from pyscript import display, document
|
||||||
|
display("Hello", "M-PyScript Worker", append=False)
|
||||||
|
document.documentElement.classList.add('worker')
|
||||||
|
</mpy-script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
37
pyscript.core/test/mpy.spec.js
Normal file
37
pyscript.core/test/mpy.spec.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('MicroPython display', async ({ page }) => {
|
||||||
|
await page.goto('http://localhost:8080/test/mpy.html');
|
||||||
|
await page.waitForSelector('html.done.worker');
|
||||||
|
const body = await page.evaluate(() => document.body.innerText);
|
||||||
|
await expect(body.trim()).toBe([
|
||||||
|
'M-PyScript Main 1',
|
||||||
|
'M-PyScript Main 2',
|
||||||
|
'M-PyScript Worker',
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('MicroPython hooks', async ({ page }) => {
|
||||||
|
const logs = [];
|
||||||
|
page.on('console', msg => {
|
||||||
|
const text = msg.text();
|
||||||
|
if (!text.startsWith('['))
|
||||||
|
logs.push(text);
|
||||||
|
});
|
||||||
|
await page.goto('http://localhost:8080/test/hooks.html');
|
||||||
|
await page.waitForSelector('html.done.worker');
|
||||||
|
await expect(logs.join('\n')).toBe([
|
||||||
|
'main onReady',
|
||||||
|
'main onBeforeRun',
|
||||||
|
'main codeBeforeRun',
|
||||||
|
'actual code in main',
|
||||||
|
'main codeAfterRun',
|
||||||
|
'main onAfterRun',
|
||||||
|
'worker onReady',
|
||||||
|
'worker onBeforeRun',
|
||||||
|
'worker codeBeforeRun',
|
||||||
|
'actual code in worker',
|
||||||
|
'worker codeAfterRun',
|
||||||
|
'worker onAfterRun',
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
23
pyscript.core/test/no-error.html
Normal file
23
pyscript.core/test/no-error.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PyScript Next No Plugin</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<py-config>plugins = ['!error']</py-config>
|
||||||
|
<script type="py">
|
||||||
|
print(1, 2, 3)
|
||||||
|
first()
|
||||||
|
</script>
|
||||||
|
<py-script>
|
||||||
|
print(4, 5, 6)
|
||||||
|
second()
|
||||||
|
</py-script>
|
||||||
|
<py-script src="whatever.py">
|
||||||
|
print(4, 5, 6)
|
||||||
|
second()
|
||||||
|
</py-script>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
18
pyscript.core/test/piratical.html
Normal file
18
pyscript.core/test/piratical.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Arrr - Piratical PyScript</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css" />
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Arrr</h1>
|
||||||
|
<p>Translate English into Pirate speak...</p>
|
||||||
|
<input type="text" id="english" placeholder="Type English here..." />
|
||||||
|
<button py-click="translate_english">Translate</button>
|
||||||
|
<div id="output"></div>
|
||||||
|
<script type="py" src="./piratical.py" config="./piratical.toml"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9
pyscript.core/test/piratical.py
Normal file
9
pyscript.core/test/piratical.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import arrr
|
||||||
|
from js import document
|
||||||
|
|
||||||
|
|
||||||
|
def translate_english(event):
|
||||||
|
input_text = document.querySelector("#english")
|
||||||
|
english = input_text.value
|
||||||
|
output_div = document.querySelector("#output")
|
||||||
|
output_div.innerText = arrr.translate(english)
|
||||||
1
pyscript.core/test/piratical.toml
Normal file
1
pyscript.core/test/piratical.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
packages = ["arrr"]
|
||||||
33
pyscript.core/test/py-editor.html
Normal file
33
pyscript.core/test/py-editor.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyTerminal</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py-editor">
|
||||||
|
import sys
|
||||||
|
print(sys.version)
|
||||||
|
</script>
|
||||||
|
<script type="mpy-editor">
|
||||||
|
import sys
|
||||||
|
print(sys.version)
|
||||||
|
a = 42
|
||||||
|
print(a)
|
||||||
|
</script>
|
||||||
|
<script type="mpy-editor" env="shared">
|
||||||
|
if not 'a' in globals():
|
||||||
|
a = 1
|
||||||
|
else:
|
||||||
|
a += 1
|
||||||
|
print(a)
|
||||||
|
</script>
|
||||||
|
<script type="mpy-editor" env="shared">
|
||||||
|
# doubled a
|
||||||
|
print(a * 2)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
29
pyscript.core/test/py-terminal.html
Normal file
29
pyscript.core/test/py-terminal.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyTerminal</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<style>.xterm { padding: .5rem; }</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py">
|
||||||
|
def greetings(event):
|
||||||
|
print('hello world')
|
||||||
|
</script>
|
||||||
|
<py-script worker terminal>
|
||||||
|
import sys
|
||||||
|
from pyscript import display, document
|
||||||
|
display("Hello", "PyScript Next - PyTerminal", append=False)
|
||||||
|
print("this should go to the terminal")
|
||||||
|
print("another line")
|
||||||
|
|
||||||
|
# this works as expected
|
||||||
|
print("this goes to stderr", file=sys.stderr)
|
||||||
|
document.addEventListener('click', lambda event: print(event.type));
|
||||||
|
</py-script>
|
||||||
|
<button id="my-button" py-click="greetings">Click me</button>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
pyscript.core/test/pydom.html
Normal file
19
pyscript.core/test/pydom.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PyScript Next Plugin</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py" src="pydom.py"></script>
|
||||||
|
|
||||||
|
<button id="just-a-button">Click For Time</button>
|
||||||
|
<button id="color-button">Click For Color</button>
|
||||||
|
<button id="color-reset-button">Reset Color</button>
|
||||||
|
|
||||||
|
<div id="result"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
pyscript.core/test/pydom.py
Normal file
27
pyscript.core/test/pydom.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import random
|
||||||
|
from datetime import datetime as dt
|
||||||
|
|
||||||
|
from pyscript import display
|
||||||
|
from pyweb import pydom
|
||||||
|
from pyweb.base import when
|
||||||
|
|
||||||
|
|
||||||
|
@when("click", "#just-a-button")
|
||||||
|
def on_click(event):
|
||||||
|
print(f"Hello from Python! {dt.now()}")
|
||||||
|
display(f"Hello from Python! {dt.now()}", append=False, target="result")
|
||||||
|
|
||||||
|
|
||||||
|
@when("click", "#color-button")
|
||||||
|
def on_color_click(event):
|
||||||
|
print("1")
|
||||||
|
btn = pydom["#result"]
|
||||||
|
print("2")
|
||||||
|
btn.style["background-color"] = f"#{random.randrange(0x1000000):06x}"
|
||||||
|
|
||||||
|
|
||||||
|
def reset_color():
|
||||||
|
pydom["#result"].style["background-color"] = "white"
|
||||||
|
|
||||||
|
|
||||||
|
# btn_reset = pydom["#color-reset-button"][0].when('click', reset_color)
|
||||||
128
pyscript.core/test/pyscript_dom/index.html
Normal file
128
pyscript.core/test/pyscript_dom/index.html
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>PyperCard PyTest Suite</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="../../dist/core.css">
|
||||||
|
<script type="module" src="../../dist/core.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import url("https://fonts.googleapis.com/css?family=Roboto:100,400");
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
*:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Courier New", Courier, "Lucida Sans Typewriter", "Lucida Typewriter", monospace;
|
||||||
|
font-size: 14px; font-style: normal; font-variant: normal; font-weight: 400; line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { font-size: 24px; font-weight: 700; line-height: 26.4px; }
|
||||||
|
h2 { font-size: 14px; font-weight: 700; line-height: 15.4px; }
|
||||||
|
|
||||||
|
#tests-terminal{
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py" src="run_tests.py" config="tests.toml"></script>
|
||||||
|
|
||||||
|
<h1>pyscript.dom Tests</h1>
|
||||||
|
<p>You can pass test parameters to this test suite by passing them as query params on the url.
|
||||||
|
For instance, to pass "-v -s --pdb" to pytest, you would use the following url:
|
||||||
|
<label style="color: blue">?-v&-s&--pdb</label>
|
||||||
|
</p>
|
||||||
|
<div id="tests-terminal"></div>
|
||||||
|
|
||||||
|
<template id="test_card_with_element_template">
|
||||||
|
<p>This is a test. {foo}</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div id="test_id_selector" style="visibility: hidden;">You found test_id_selector</div>
|
||||||
|
<div id="test_class_selector" class="a-test-class" style="visibility: hidden;">You found test_class_selector</div>
|
||||||
|
<div id="test_selector_w_children" class="a-test-class" style="visibility: hidden;">
|
||||||
|
<div id="test_selector_w_children_child_1" class="a-test-class" style="visibility: hidden;">Child 1</div>
|
||||||
|
<div id="test_selector_w_children_child_2" style="visibility: hidden;">Child 2</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-no-classes"></div>
|
||||||
|
|
||||||
|
<div style="visibility: hidden;">
|
||||||
|
<h2>Test Read and Write</h2>
|
||||||
|
<div id="test_rr_div">Content test_rr_div</div>
|
||||||
|
<h3 id="test_rr_h3">Content test_rr_h3</h3>
|
||||||
|
|
||||||
|
<div id="multi-elem-div" class="multi-elems">Content multi-elem-div</div>
|
||||||
|
<p id="multi-elem-p" class="multi-elems">Content multi-elem-p</p>
|
||||||
|
<h2 id="multi-elem-h2" class="multi-elems">Content multi-elem-h2</h2>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<input id="test_rr_input_text" type="text" value="Content test_rr_input_text">
|
||||||
|
<input id="test_rr_input_button" type="button" value="Content test_rr_input_button">
|
||||||
|
<input id="test_rr_input_email" type="email" value="Content test_rr_input_email">
|
||||||
|
<input id="test_rr_input_password" type="password" value="Content test_rr_input_password">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<select id="test_select_element"></select>
|
||||||
|
<select id="test_select_element_w_options">
|
||||||
|
<option value="1">Option 1</option>
|
||||||
|
<option value="2" selected="selected">Option 2</option>
|
||||||
|
</select>
|
||||||
|
<select id="test_select_element_to_clear">
|
||||||
|
<option value="1">Option 1</option>
|
||||||
|
<option value="2">Option 2</option>
|
||||||
|
<option value="4">Option 4</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="test_select_element_to_remove">
|
||||||
|
<option value="1">Option 1</option>
|
||||||
|
<option value="2">Option 2</option>
|
||||||
|
<option value="3">Option 3</option>
|
||||||
|
<option value="4">Option 4</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div id="element-creation-test"></div>
|
||||||
|
|
||||||
|
<button id="a-test-button">I'm a button to be clicked</button>
|
||||||
|
<button>I'm another button you can click</button>
|
||||||
|
<button id="a-third-button">2 is better than 3 :)</button>
|
||||||
|
|
||||||
|
<div id="element-append-tests"></div>
|
||||||
|
<p class="collection"></p>
|
||||||
|
<div class="collection"></div>
|
||||||
|
<h3 class="collection"></h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script defer>
|
||||||
|
console.log("remapping console.log")
|
||||||
|
const terminalDiv = document.getElementById("tests-terminal");
|
||||||
|
const log = console.log.bind(console)
|
||||||
|
let testsStarted = false;
|
||||||
|
console.log = (...args) => {
|
||||||
|
let txt = args.join(" ");
|
||||||
|
let token = "<br>";
|
||||||
|
if (txt.endsWith("FAILED"))
|
||||||
|
token = " ❌<br>";
|
||||||
|
else if (txt.endsWith("PASSED"))
|
||||||
|
token = " ✅<br>";
|
||||||
|
if (testsStarted)
|
||||||
|
terminalDiv.innerHTML += args.join(" ") + token;
|
||||||
|
|
||||||
|
log(...args)
|
||||||
|
|
||||||
|
// if we got the flag that tests are starting, then we can start logging
|
||||||
|
if (args.join(" ") == "tests starting")
|
||||||
|
testsStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user