rapydscript-ns 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agignore +1 -1
- package/.github/workflows/ci.yml +38 -38
- package/=template.pyj +5 -5
- package/CHANGELOG.md +19 -0
- package/HACKING.md +103 -103
- package/LICENSE +24 -24
- package/PYTHON_GAPS.md +420 -0
- package/README.md +153 -29
- package/TODO.md +16 -118
- package/add-toc-to-readme +2 -2
- package/bin/export +75 -75
- package/bin/rapydscript +70 -70
- package/bin/web-repl-export +102 -102
- package/build +2 -2
- package/language-service/index.js +237 -8
- package/memory/project_string_impl.md +43 -0
- package/package.json +1 -1
- package/publish.py +37 -37
- package/release/baselib-plain-pretty.js +248 -38
- package/release/baselib-plain-ugly.js +8 -8
- package/release/compiler.js +778 -277
- package/release/signatures.json +30 -30
- package/session.vim +4 -4
- package/setup.cfg +2 -2
- package/src/ast.pyj +4 -1
- package/src/baselib-builtins.pyj +56 -2
- package/src/baselib-containers.pyj +2 -0
- package/src/baselib-errors.pyj +7 -3
- package/src/baselib-internal.pyj +51 -6
- package/src/baselib-str.pyj +5 -3
- package/src/compiler.pyj +36 -36
- package/src/errors.pyj +30 -30
- package/src/lib/aes.pyj +646 -646
- package/src/lib/asyncio.pyj +534 -0
- package/src/lib/base64.pyj +399 -0
- package/src/lib/bisect.pyj +73 -0
- package/src/lib/collections.pyj +1 -1
- package/src/lib/copy.pyj +120 -120
- package/src/lib/csv.pyj +494 -0
- package/src/lib/elementmaker.pyj +83 -83
- package/src/lib/encodings.pyj +126 -126
- package/src/lib/gettext.pyj +569 -569
- package/src/lib/heapq.pyj +98 -0
- package/src/lib/html.pyj +382 -0
- package/src/lib/http/__init__.pyj +98 -0
- package/src/lib/http/client.pyj +304 -0
- package/src/lib/http/cookies.pyj +236 -0
- package/src/lib/itertools.pyj +580 -580
- package/src/lib/logging.pyj +672 -0
- package/src/lib/math.pyj +193 -193
- package/src/lib/operator.pyj +11 -11
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/random.pyj +118 -118
- package/src/lib/react.pyj +74 -74
- package/src/lib/string.pyj +357 -0
- package/src/lib/textwrap.pyj +329 -0
- package/src/lib/traceback.pyj +63 -63
- package/src/lib/urllib/__init__.pyj +14 -0
- package/src/lib/urllib/error.pyj +66 -0
- package/src/lib/urllib/parse.pyj +475 -0
- package/src/lib/urllib/request.pyj +86 -0
- package/src/lib/uuid.pyj +77 -77
- package/src/monaco-language-service/analyzer.js +5 -2
- package/src/monaco-language-service/completions.js +26 -0
- package/src/monaco-language-service/diagnostics.js +202 -3
- package/src/monaco-language-service/dts.js +550 -550
- package/src/monaco-language-service/scope.js +1 -0
- package/src/output/comments.pyj +45 -45
- package/src/output/exceptions.pyj +201 -201
- package/src/output/functions.pyj +152 -6
- package/src/output/jsx.pyj +164 -164
- package/src/output/loops.pyj +17 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +15 -0
- package/src/output/stream.pyj +0 -1
- package/src/output/treeshake.pyj +182 -182
- package/src/output/utils.pyj +72 -72
- package/src/parse.pyj +80 -17
- package/src/string_interpolation.pyj +72 -72
- package/src/tokenizer.pyj +1 -1
- package/src/unicode_aliases.pyj +576 -576
- package/src/utils.pyj +192 -192
- package/test/_import_one.pyj +37 -37
- package/test/_import_two/__init__.pyj +11 -11
- package/test/_import_two/level2/deep.pyj +4 -4
- package/test/_import_two/other.pyj +6 -6
- package/test/_import_two/sub.pyj +13 -13
- package/test/aes_vectors.pyj +421 -421
- package/test/annotations.pyj +80 -80
- package/test/async_generators.pyj +144 -0
- package/test/asyncio.pyj +307 -0
- package/test/base64.pyj +202 -0
- package/test/bisect.pyj +178 -0
- package/test/csv.pyj +405 -0
- package/test/decorators.pyj +77 -77
- package/test/docstrings.pyj +39 -39
- package/test/elementmaker_test.pyj +45 -45
- package/test/float_special.pyj +64 -0
- package/test/functions.pyj +151 -151
- package/test/generators.pyj +41 -41
- package/test/generic.pyj +370 -370
- package/test/heapq.pyj +174 -0
- package/test/html.pyj +212 -0
- package/test/http.pyj +259 -0
- package/test/imports.pyj +79 -72
- package/test/internationalization.pyj +73 -73
- package/test/lint.pyj +164 -164
- package/test/logging.pyj +356 -0
- package/test/long.pyj +130 -0
- package/test/loops.pyj +85 -85
- package/test/numpy.pyj +734 -734
- package/test/parenthesized_with.pyj +141 -0
- package/test/python_compat.pyj +3 -5
- package/test/python_modulo.pyj +76 -0
- package/test/python_modulo_off.pyj +21 -0
- package/test/repl.pyj +121 -121
- package/test/scoped_flags.pyj +76 -76
- package/test/str.pyj +14 -0
- package/test/string.pyj +245 -0
- package/test/textwrap.pyj +172 -0
- package/test/type_display.pyj +48 -0
- package/test/type_enforcement.pyj +164 -0
- package/test/unit/index.js +14 -6
- package/test/unit/language-service-completions.js +119 -0
- package/test/unit/language-service-dts.js +543 -543
- package/test/unit/language-service-hover.js +455 -455
- package/test/unit/language-service-scope.js +32 -0
- package/test/unit/language-service.js +127 -3
- package/test/unit/run-language-service.js +17 -3
- package/test/unit/web-repl.js +2094 -29
- package/test/urllib.pyj +193 -0
- package/tools/compile.js +1 -1
- package/tools/compiler.d.ts +367 -367
- package/tools/completer.js +131 -131
- package/tools/embedded_compiler.js +7 -7
- package/tools/gettext.js +185 -185
- package/tools/ini.js +65 -65
- package/tools/msgfmt.js +187 -187
- package/tools/repl.js +223 -223
- package/tools/test.js +118 -118
- package/tools/utils.js +128 -128
- package/tools/web_repl.js +95 -95
- package/try +41 -41
- package/web-repl/env.js +196 -196
- package/web-repl/index.html +163 -163
- package/web-repl/main.js +1 -1
- package/web-repl/prism.css +139 -139
- package/web-repl/prism.js +113 -113
- package/web-repl/rapydscript.js +224 -224
- package/web-repl/sha1.js +25 -25
- package/test/omit_function_metadata.pyj +0 -20
package/test/heapq.pyj
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# heapq.pyj
|
|
5
|
+
# Tests for the heapq standard library module.
|
|
6
|
+
|
|
7
|
+
from heapq import heappush, heappop, heapify, heapreplace, heappushpop, nlargest, nsmallest
|
|
8
|
+
|
|
9
|
+
ae = assrt.equal
|
|
10
|
+
ade = assrt.deepEqual
|
|
11
|
+
ok = assrt.ok
|
|
12
|
+
|
|
13
|
+
# ── 1. heappush / heappop — basic sort order ──────────────────────────────────
|
|
14
|
+
|
|
15
|
+
h = []
|
|
16
|
+
heappush(h, 3)
|
|
17
|
+
heappush(h, 1)
|
|
18
|
+
heappush(h, 4)
|
|
19
|
+
heappush(h, 1)
|
|
20
|
+
heappush(h, 5)
|
|
21
|
+
ae(heappop(h), 1)
|
|
22
|
+
ae(heappop(h), 1)
|
|
23
|
+
ae(heappop(h), 3)
|
|
24
|
+
ae(heappop(h), 4)
|
|
25
|
+
ae(heappop(h), 5)
|
|
26
|
+
ae(h.length, 0)
|
|
27
|
+
|
|
28
|
+
# ── 2. heapify — min at root ──────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
x = [5, 3, 8, 1, 2, 4]
|
|
31
|
+
heapify(x)
|
|
32
|
+
ae(x[0], 1)
|
|
33
|
+
|
|
34
|
+
# verify heap invariant: every parent <= its children
|
|
35
|
+
_hi = 0
|
|
36
|
+
while _hi < Math.floor(x.length / 2):
|
|
37
|
+
_left = 2 * _hi + 1
|
|
38
|
+
_right = 2 * _hi + 2
|
|
39
|
+
ok(x[_hi] <= x[_left], 'parent <= left child at ' + str(_hi))
|
|
40
|
+
if _right < x.length:
|
|
41
|
+
ok(x[_hi] <= x[_right], 'parent <= right child at ' + str(_hi))
|
|
42
|
+
_hi += 1
|
|
43
|
+
|
|
44
|
+
# ── 3. heapify + heappop produces sorted output ───────────────────────────────
|
|
45
|
+
|
|
46
|
+
_sort_data = [5, 3, 8, 1, 2, 4]
|
|
47
|
+
heapify(_sort_data)
|
|
48
|
+
_sort_result = []
|
|
49
|
+
while _sort_data.length > 0:
|
|
50
|
+
_sort_result.push(heappop(_sort_data))
|
|
51
|
+
ade(_sort_result, [1, 2, 3, 4, 5, 8])
|
|
52
|
+
|
|
53
|
+
# ── 4. heapreplace — returns old min, inserts new item ────────────────────────
|
|
54
|
+
|
|
55
|
+
_r = [1, 3, 5, 7, 9]
|
|
56
|
+
heapify(_r)
|
|
57
|
+
_old = heapreplace(_r, 4)
|
|
58
|
+
ae(_old, 1)
|
|
59
|
+
ae(_r[0], 3)
|
|
60
|
+
|
|
61
|
+
_r_sorted = []
|
|
62
|
+
while _r.length > 0:
|
|
63
|
+
_r_sorted.push(heappop(_r))
|
|
64
|
+
ade(_r_sorted, [3, 4, 5, 7, 9])
|
|
65
|
+
|
|
66
|
+
# ── 5. heappushpop ────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
# item > heap[0]: swap root out, sift new item in
|
|
69
|
+
_pp = [1, 3, 5]
|
|
70
|
+
heapify(_pp)
|
|
71
|
+
_res_pp = heappushpop(_pp, 2)
|
|
72
|
+
ae(_res_pp, 1)
|
|
73
|
+
ae(_pp[0], 2)
|
|
74
|
+
|
|
75
|
+
# item <= heap[0]: return item unchanged, heap unmodified
|
|
76
|
+
_pp2 = [5, 7, 9]
|
|
77
|
+
heapify(_pp2)
|
|
78
|
+
_res_pp2 = heappushpop(_pp2, 4)
|
|
79
|
+
ae(_res_pp2, 4)
|
|
80
|
+
ae(_pp2[0], 5)
|
|
81
|
+
|
|
82
|
+
# ── 6. heappop raises IndexError on empty heap ────────────────────────────────
|
|
83
|
+
|
|
84
|
+
_pop_err = False
|
|
85
|
+
try:
|
|
86
|
+
heappop([])
|
|
87
|
+
except IndexError:
|
|
88
|
+
_pop_err = True
|
|
89
|
+
ok(_pop_err, 'heappop on empty heap raises IndexError')
|
|
90
|
+
|
|
91
|
+
# ── 7. heapreplace raises IndexError on empty heap ───────────────────────────
|
|
92
|
+
|
|
93
|
+
_rep_err = False
|
|
94
|
+
try:
|
|
95
|
+
heapreplace([], 1)
|
|
96
|
+
except IndexError:
|
|
97
|
+
_rep_err = True
|
|
98
|
+
ok(_rep_err, 'heapreplace on empty heap raises IndexError')
|
|
99
|
+
|
|
100
|
+
# ── 8. nsmallest — basic ─────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
_ns_data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
|
|
103
|
+
ade(nsmallest(3, _ns_data), [1, 1, 2])
|
|
104
|
+
ade(nsmallest(1, _ns_data), [1])
|
|
105
|
+
ade(nsmallest(0, _ns_data), [])
|
|
106
|
+
ade(nsmallest(100, _ns_data), [1, 1, 2, 3, 3, 4, 5, 5, 6, 9])
|
|
107
|
+
|
|
108
|
+
# ── 9. nlargest — basic ──────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
ade(nlargest(3, _ns_data), [9, 6, 5])
|
|
111
|
+
ade(nlargest(1, _ns_data), [9])
|
|
112
|
+
ade(nlargest(0, _ns_data), [])
|
|
113
|
+
ade(nlargest(100, _ns_data), [9, 6, 5, 5, 4, 3, 3, 2, 1, 1])
|
|
114
|
+
|
|
115
|
+
# ── 10. nsmallest / nlargest with key ────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
_pairs = [[3, 'c'], [1, 'a'], [4, 'd'], [1, 'b'], [5, 'e']]
|
|
118
|
+
_kfn = def(p): return p[0];
|
|
119
|
+
|
|
120
|
+
_sm2 = nsmallest(2, _pairs, key=_kfn)
|
|
121
|
+
ae(_sm2.length, 2)
|
|
122
|
+
ae(_sm2[0][0], 1)
|
|
123
|
+
ae(_sm2[1][0], 1)
|
|
124
|
+
|
|
125
|
+
_lg2 = nlargest(2, _pairs, key=_kfn)
|
|
126
|
+
ae(_lg2.length, 2)
|
|
127
|
+
ae(_lg2[0][0], 5)
|
|
128
|
+
ae(_lg2[1][0], 4)
|
|
129
|
+
|
|
130
|
+
# ── 11. Single element ────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
_single = [42]
|
|
133
|
+
heapify(_single)
|
|
134
|
+
ae(_single[0], 42)
|
|
135
|
+
ae(heappop(_single), 42)
|
|
136
|
+
ae(_single.length, 0)
|
|
137
|
+
|
|
138
|
+
# ── 12. Negative numbers ─────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
_neg = [-3, -1, -4, -1, -5]
|
|
141
|
+
heapify(_neg)
|
|
142
|
+
_neg_sorted = []
|
|
143
|
+
while _neg.length > 0:
|
|
144
|
+
_neg_sorted.push(heappop(_neg))
|
|
145
|
+
ade(_neg_sorted, [-5, -4, -3, -1, -1])
|
|
146
|
+
|
|
147
|
+
# ── 13. Duplicates ───────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
_dups = [2, 2, 2, 1, 1, 1, 3, 3, 3]
|
|
150
|
+
heapify(_dups)
|
|
151
|
+
_dups_sorted = []
|
|
152
|
+
while _dups.length > 0:
|
|
153
|
+
_dups_sorted.push(heappop(_dups))
|
|
154
|
+
ade(_dups_sorted, [1, 1, 1, 2, 2, 2, 3, 3, 3])
|
|
155
|
+
|
|
156
|
+
# ── 14. heappush onto pre-existing heap ──────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
_pre = [2, 5, 8]
|
|
159
|
+
heapify(_pre)
|
|
160
|
+
heappush(_pre, 1)
|
|
161
|
+
ae(_pre[0], 1)
|
|
162
|
+
heappush(_pre, 3)
|
|
163
|
+
_pre_sorted = []
|
|
164
|
+
while _pre.length > 0:
|
|
165
|
+
_pre_sorted.push(heappop(_pre))
|
|
166
|
+
ade(_pre_sorted, [1, 2, 3, 5, 8])
|
|
167
|
+
|
|
168
|
+
# ── 15. nsmallest / nlargest do not mutate original ──────────────────────────
|
|
169
|
+
|
|
170
|
+
_orig = [5, 2, 8, 1, 9]
|
|
171
|
+
_orig_copy = _orig.slice()
|
|
172
|
+
nsmallest(3, _orig)
|
|
173
|
+
nlargest(3, _orig)
|
|
174
|
+
ade(_orig, _orig_copy)
|
package/test/html.pyj
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# html.pyj
|
|
5
|
+
# Tests for the html standard library module.
|
|
6
|
+
|
|
7
|
+
from html import escape, unescape, HTMLParser
|
|
8
|
+
|
|
9
|
+
ae = assrt.equal
|
|
10
|
+
ade = assrt.deepEqual
|
|
11
|
+
ok = assrt.ok
|
|
12
|
+
|
|
13
|
+
# ── 1. escape – individual special characters ─────────────────────────────
|
|
14
|
+
ae(escape('<'), '<')
|
|
15
|
+
ae(escape('>'), '>')
|
|
16
|
+
ae(escape('&'), '&')
|
|
17
|
+
ae(escape('"'), '"')
|
|
18
|
+
ae(escape("'"), ''')
|
|
19
|
+
|
|
20
|
+
# ── 2. escape – combined string ───────────────────────────────────────────
|
|
21
|
+
ae(escape('<script>alert("xss")</script>'),
|
|
22
|
+
'<script>alert("xss")</script>')
|
|
23
|
+
|
|
24
|
+
# ── 3. escape – quote=False leaves quotes unchanged ───────────────────────
|
|
25
|
+
ae(escape('A & B', quote=False), 'A & B')
|
|
26
|
+
ae(escape('"quoted"', quote=False), '"quoted"')
|
|
27
|
+
ae(escape("it's", quote=False), "it's")
|
|
28
|
+
|
|
29
|
+
# ── 4. escape – plain text unchanged ──────────────────────────────────────
|
|
30
|
+
ae(escape('hello world'), 'hello world')
|
|
31
|
+
ae(escape(''), '')
|
|
32
|
+
|
|
33
|
+
# ── 5. unescape – named entities ──────────────────────────────────────────
|
|
34
|
+
ae(unescape('&'), '&')
|
|
35
|
+
ae(unescape('<'), '<')
|
|
36
|
+
ae(unescape('>'), '>')
|
|
37
|
+
ae(unescape('"'), '"')
|
|
38
|
+
ae(unescape('''), "'")
|
|
39
|
+
ae(unescape('©'), '©')
|
|
40
|
+
ae(unescape('€'), '€')
|
|
41
|
+
ae(unescape('–'), '–')
|
|
42
|
+
ae(unescape('—'), '—')
|
|
43
|
+
ae(unescape('™'), '™')
|
|
44
|
+
|
|
45
|
+
# ── 6. unescape – numeric references ──────────────────────────────────────
|
|
46
|
+
ae(unescape('A'), 'A') # decimal
|
|
47
|
+
ae(unescape('A'), 'A') # hex lowercase
|
|
48
|
+
ae(unescape('A'), 'A') # hex uppercase
|
|
49
|
+
ae(unescape('©'), '©') # © by decimal
|
|
50
|
+
ae(unescape('€'), '€') # € by hex
|
|
51
|
+
|
|
52
|
+
# ── 7. unescape – high code point (emoji via numeric ref) ─────────────────
|
|
53
|
+
ae(unescape('😀'), chr(128512)) # grinning face emoji
|
|
54
|
+
|
|
55
|
+
# ── 8. unescape – unknown entity left intact ──────────────────────────────
|
|
56
|
+
ae(unescape('&nosuchentity;'), '&nosuchentity;')
|
|
57
|
+
|
|
58
|
+
# ── 9. unescape – mixed string ────────────────────────────────────────────
|
|
59
|
+
ae(unescape('Hello & World <3'), 'Hello & World <3')
|
|
60
|
+
|
|
61
|
+
# ── 10. round-trip ───────────────────────────────────────────────────────
|
|
62
|
+
s = '<a href="http://example.com">Hello & World</a>'
|
|
63
|
+
ae(unescape(escape(s)), s)
|
|
64
|
+
|
|
65
|
+
# ── 11. HTMLParser – start/end/data ──────────────────────────────────────
|
|
66
|
+
class _P1(HTMLParser):
|
|
67
|
+
def __init__(self):
|
|
68
|
+
HTMLParser.__init__(self)
|
|
69
|
+
self.events = []
|
|
70
|
+
def handle_starttag(self, tag, attrs):
|
|
71
|
+
self.events.push(['start', tag, attrs])
|
|
72
|
+
def handle_endtag(self, tag):
|
|
73
|
+
self.events.push(['end', tag])
|
|
74
|
+
def handle_data(self, data):
|
|
75
|
+
self.events.push(['data', data])
|
|
76
|
+
|
|
77
|
+
p1 = _P1()
|
|
78
|
+
p1.feed('<p class="greeting">Hello, World!</p>')
|
|
79
|
+
ae(p1.events[0][0], 'start')
|
|
80
|
+
ae(p1.events[0][1], 'p')
|
|
81
|
+
ae(p1.events[0][2][0][0], 'class')
|
|
82
|
+
ae(p1.events[0][2][0][1], 'greeting')
|
|
83
|
+
ae(p1.events[1][0], 'data')
|
|
84
|
+
ae(p1.events[1][1], 'Hello, World!')
|
|
85
|
+
ae(p1.events[2][0], 'end')
|
|
86
|
+
ae(p1.events[2][1], 'p')
|
|
87
|
+
|
|
88
|
+
# ── 12. HTMLParser – entity auto-conversion in data ───────────────────────
|
|
89
|
+
class _P2(HTMLParser):
|
|
90
|
+
def __init__(self):
|
|
91
|
+
HTMLParser.__init__(self)
|
|
92
|
+
self.chunks = []
|
|
93
|
+
def handle_data(self, data):
|
|
94
|
+
self.chunks.push(data)
|
|
95
|
+
|
|
96
|
+
p2 = _P2()
|
|
97
|
+
p2.feed('<p>Hello & World <3</p>')
|
|
98
|
+
ae(p2.chunks[0], 'Hello & World <3')
|
|
99
|
+
|
|
100
|
+
# ── 13. HTMLParser – comment ──────────────────────────────────────────────
|
|
101
|
+
class _P3(HTMLParser):
|
|
102
|
+
def __init__(self):
|
|
103
|
+
HTMLParser.__init__(self)
|
|
104
|
+
self.comments = []
|
|
105
|
+
def handle_comment(self, data):
|
|
106
|
+
self.comments.push(data)
|
|
107
|
+
|
|
108
|
+
p3 = _P3()
|
|
109
|
+
p3.feed('<!-- this is a comment -->')
|
|
110
|
+
ae(p3.comments.length, 1)
|
|
111
|
+
ae(p3.comments[0], ' this is a comment ')
|
|
112
|
+
|
|
113
|
+
# ── 14. HTMLParser – multiple attributes ─────────────────────────────────
|
|
114
|
+
class _P4(HTMLParser):
|
|
115
|
+
def __init__(self):
|
|
116
|
+
HTMLParser.__init__(self)
|
|
117
|
+
self.attrs = None
|
|
118
|
+
def handle_starttag(self, tag, attrs):
|
|
119
|
+
self.attrs = attrs
|
|
120
|
+
|
|
121
|
+
p4 = _P4()
|
|
122
|
+
p4.feed('<a href="http://example.com" target="_blank" rel="noopener">')
|
|
123
|
+
ae(p4.attrs.length, 3)
|
|
124
|
+
ae(p4.attrs[0][0], 'href')
|
|
125
|
+
ae(p4.attrs[0][1], 'http://example.com')
|
|
126
|
+
ae(p4.attrs[1][0], 'target')
|
|
127
|
+
ae(p4.attrs[1][1], '_blank')
|
|
128
|
+
ae(p4.attrs[2][0], 'rel')
|
|
129
|
+
ae(p4.attrs[2][1], 'noopener')
|
|
130
|
+
|
|
131
|
+
# ── 15. HTMLParser – self-closing tag ─────────────────────────────────────
|
|
132
|
+
class _P5(HTMLParser):
|
|
133
|
+
def __init__(self):
|
|
134
|
+
HTMLParser.__init__(self)
|
|
135
|
+
self.events = []
|
|
136
|
+
def handle_starttag(self, tag, attrs):
|
|
137
|
+
self.events.push('start:' + tag)
|
|
138
|
+
def handle_endtag(self, tag):
|
|
139
|
+
self.events.push('end:' + tag)
|
|
140
|
+
|
|
141
|
+
p5 = _P5()
|
|
142
|
+
p5.feed('<br/>')
|
|
143
|
+
ae(p5.events.length, 2)
|
|
144
|
+
ae(p5.events[0], 'start:br')
|
|
145
|
+
ae(p5.events[1], 'end:br')
|
|
146
|
+
|
|
147
|
+
# ── 16. HTMLParser – get_starttag_text ───────────────────────────────────
|
|
148
|
+
class _P6(HTMLParser):
|
|
149
|
+
def __init__(self):
|
|
150
|
+
HTMLParser.__init__(self)
|
|
151
|
+
self.last_raw = None
|
|
152
|
+
def handle_starttag(self, tag, attrs):
|
|
153
|
+
self.last_raw = self.get_starttag_text()
|
|
154
|
+
|
|
155
|
+
p6 = _P6()
|
|
156
|
+
p6.feed('<img src="pic.png" alt="photo">')
|
|
157
|
+
ae(p6.last_raw, '<img src="pic.png" alt="photo">')
|
|
158
|
+
|
|
159
|
+
# ── 17. HTMLParser – DOCTYPE declaration ─────────────────────────────────
|
|
160
|
+
class _P7(HTMLParser):
|
|
161
|
+
def __init__(self):
|
|
162
|
+
HTMLParser.__init__(self)
|
|
163
|
+
self.decls = []
|
|
164
|
+
def handle_decl(self, decl):
|
|
165
|
+
self.decls.push(decl)
|
|
166
|
+
|
|
167
|
+
p7 = _P7()
|
|
168
|
+
p7.feed('<!DOCTYPE html>')
|
|
169
|
+
ae(p7.decls.length, 1)
|
|
170
|
+
ae(p7.decls[0], 'DOCTYPE html')
|
|
171
|
+
|
|
172
|
+
# ── 18. HTMLParser – tag names lowercased ────────────────────────────────
|
|
173
|
+
class _P8(HTMLParser):
|
|
174
|
+
def __init__(self):
|
|
175
|
+
HTMLParser.__init__(self)
|
|
176
|
+
self.tags = []
|
|
177
|
+
def handle_starttag(self, tag, attrs):
|
|
178
|
+
self.tags.push(tag)
|
|
179
|
+
|
|
180
|
+
p8 = _P8()
|
|
181
|
+
p8.feed('<DIV><SPAN></SPAN></DIV>')
|
|
182
|
+
ae(p8.tags[0], 'div')
|
|
183
|
+
ae(p8.tags[1], 'span')
|
|
184
|
+
|
|
185
|
+
# ── 19. HTMLParser – valueless attribute yields None ──────────────────────
|
|
186
|
+
class _P9(HTMLParser):
|
|
187
|
+
def __init__(self):
|
|
188
|
+
HTMLParser.__init__(self)
|
|
189
|
+
self.attrs = None
|
|
190
|
+
def handle_starttag(self, tag, attrs):
|
|
191
|
+
self.attrs = attrs
|
|
192
|
+
|
|
193
|
+
p9 = _P9()
|
|
194
|
+
p9.feed('<input disabled required>')
|
|
195
|
+
ae(p9.attrs[0][0], 'disabled')
|
|
196
|
+
ae(p9.attrs[0][1], None)
|
|
197
|
+
ae(p9.attrs[1][0], 'required')
|
|
198
|
+
ae(p9.attrs[1][1], None)
|
|
199
|
+
|
|
200
|
+
# ── 20. HTMLParser – incremental feed ────────────────────────────────────
|
|
201
|
+
class _P10(HTMLParser):
|
|
202
|
+
def __init__(self):
|
|
203
|
+
HTMLParser.__init__(self)
|
|
204
|
+
self.data = []
|
|
205
|
+
def handle_data(self, d):
|
|
206
|
+
self.data.push(d)
|
|
207
|
+
|
|
208
|
+
p10 = _P10()
|
|
209
|
+
p10.feed('<p>Hello')
|
|
210
|
+
p10.feed(' World</p>')
|
|
211
|
+
ok(p10.data.length >= 1)
|
|
212
|
+
ae(p10.data.join(''), 'Hello World')
|
package/test/http.pyj
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# http.pyj
|
|
5
|
+
# Tests for the http standard library module.
|
|
6
|
+
#
|
|
7
|
+
# Covers http (HTTPStatus), http.client (pure-JS synchronous parts), and
|
|
8
|
+
# http.cookies. Network-dependent tests (getresponse) are exercised only in
|
|
9
|
+
# the web-repl bundle tests via a mocked fetch environment.
|
|
10
|
+
|
|
11
|
+
from __python__ import overload_getitem
|
|
12
|
+
|
|
13
|
+
from http import HTTPStatus
|
|
14
|
+
from http.client import (HTTPConnection, HTTPSConnection,
|
|
15
|
+
HTTPException, NotConnected, InvalidURL,
|
|
16
|
+
RemoteDisconnected, HTTP_PORT, HTTPS_PORT,
|
|
17
|
+
HTTPResponse)
|
|
18
|
+
from http.cookies import SimpleCookie, Morsel, CookieError
|
|
19
|
+
|
|
20
|
+
ae = assrt.equal
|
|
21
|
+
ade = assrt.deepEqual
|
|
22
|
+
ok = assrt.ok
|
|
23
|
+
|
|
24
|
+
# ── 1. HTTPStatus constants ───────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
ae(HTTPStatus.OK, 200)
|
|
27
|
+
ae(HTTPStatus.CREATED, 201)
|
|
28
|
+
ae(HTTPStatus.NO_CONTENT, 204)
|
|
29
|
+
ae(HTTPStatus.MOVED_PERMANENTLY, 301)
|
|
30
|
+
ae(HTTPStatus.NOT_MODIFIED, 304)
|
|
31
|
+
ae(HTTPStatus.BAD_REQUEST, 400)
|
|
32
|
+
ae(HTTPStatus.UNAUTHORIZED, 401)
|
|
33
|
+
ae(HTTPStatus.FORBIDDEN, 403)
|
|
34
|
+
ae(HTTPStatus.NOT_FOUND, 404)
|
|
35
|
+
ae(HTTPStatus.METHOD_NOT_ALLOWED, 405)
|
|
36
|
+
ae(HTTPStatus.INTERNAL_SERVER_ERROR, 500)
|
|
37
|
+
ae(HTTPStatus.SERVICE_UNAVAILABLE, 503)
|
|
38
|
+
ae(HTTPStatus.IM_A_TEAPOT, 418)
|
|
39
|
+
|
|
40
|
+
# ── 2. Port constants ─────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
ae(HTTP_PORT, 80)
|
|
43
|
+
ae(HTTPS_PORT, 443)
|
|
44
|
+
|
|
45
|
+
# ── 3. HTTPException hierarchy ────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
raise HTTPException('base error')
|
|
49
|
+
ok(False)
|
|
50
|
+
except HTTPException as e:
|
|
51
|
+
ok('base error' in str(e))
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
raise NotConnected('not connected')
|
|
55
|
+
ok(False)
|
|
56
|
+
except HTTPException as e:
|
|
57
|
+
ok(isinstance(e, NotConnected))
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
raise InvalidURL('bad url')
|
|
61
|
+
ok(False)
|
|
62
|
+
except HTTPException as e:
|
|
63
|
+
ok(isinstance(e, InvalidURL))
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
raise RemoteDisconnected('gone')
|
|
67
|
+
ok(False)
|
|
68
|
+
except HTTPException as e:
|
|
69
|
+
ok(isinstance(e, RemoteDisconnected))
|
|
70
|
+
|
|
71
|
+
# ── 4. HTTPConnection — _build_url ────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
conn = HTTPConnection('example.com')
|
|
74
|
+
ae(conn._build_url('/path'), 'http://example.com/path')
|
|
75
|
+
ae(conn._build_url('/'), 'http://example.com/')
|
|
76
|
+
|
|
77
|
+
conn2 = HTTPConnection('example.com', 8080)
|
|
78
|
+
ae(conn2._build_url('/api'), 'http://example.com:8080/api')
|
|
79
|
+
|
|
80
|
+
# Default port 80 should be omitted
|
|
81
|
+
conn3 = HTTPConnection('example.com', 80)
|
|
82
|
+
ae(conn3._build_url('/'), 'http://example.com/')
|
|
83
|
+
|
|
84
|
+
# Full URL passes through unchanged
|
|
85
|
+
ae(conn._build_url('https://other.com/x'), 'https://other.com/x')
|
|
86
|
+
ae(conn._build_url('http://other.com/x'), 'http://other.com/x')
|
|
87
|
+
|
|
88
|
+
# ── 5. HTTPSConnection — _build_url ──────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
sconn = HTTPSConnection('secure.example.com')
|
|
91
|
+
ae(sconn._build_url('/data'), 'https://secure.example.com/data')
|
|
92
|
+
|
|
93
|
+
sconn2 = HTTPSConnection('secure.example.com', 8443)
|
|
94
|
+
ae(sconn2._build_url('/data'), 'https://secure.example.com:8443/data')
|
|
95
|
+
|
|
96
|
+
# Default port 443 should be omitted
|
|
97
|
+
sconn3 = HTTPSConnection('secure.example.com', 443)
|
|
98
|
+
ae(sconn3._build_url('/data'), 'https://secure.example.com/data')
|
|
99
|
+
|
|
100
|
+
# ── 6. HTTPConnection.request stores state ───────────────────────────────────
|
|
101
|
+
|
|
102
|
+
conn4 = HTTPConnection('api.example.com')
|
|
103
|
+
conn4.request('POST', '/items', body='a=1', headers={'Content-Type': 'text/plain'})
|
|
104
|
+
ae(conn4._method, 'POST')
|
|
105
|
+
ae(conn4._path, '/items')
|
|
106
|
+
ae(conn4._body, 'a=1')
|
|
107
|
+
ae(conn4._headers['content-type'], 'text/plain')
|
|
108
|
+
|
|
109
|
+
# ── 7. putrequest / putheader / endheaders ───────────────────────────────────
|
|
110
|
+
|
|
111
|
+
conn5 = HTTPConnection('example.com')
|
|
112
|
+
conn5.putrequest('GET', '/search')
|
|
113
|
+
conn5.putheader('Accept', 'application/json')
|
|
114
|
+
conn5.putheader('X-Custom', 'hello')
|
|
115
|
+
conn5.endheaders()
|
|
116
|
+
ae(conn5._method, 'GET')
|
|
117
|
+
ae(conn5._path, '/search')
|
|
118
|
+
ae(conn5._headers['accept'], 'application/json')
|
|
119
|
+
ae(conn5._headers['x-custom'], 'hello')
|
|
120
|
+
ae(conn5._body, None)
|
|
121
|
+
|
|
122
|
+
conn5b = HTTPConnection('example.com')
|
|
123
|
+
conn5b.putrequest('POST', '/upload')
|
|
124
|
+
conn5b.endheaders('payload')
|
|
125
|
+
ae(conn5b._body, 'payload')
|
|
126
|
+
|
|
127
|
+
# ── 8. HTTPConnection.close resets state ─────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
conn6 = HTTPConnection('example.com')
|
|
130
|
+
conn6.request('DELETE', '/items/1')
|
|
131
|
+
conn6.close()
|
|
132
|
+
ae(conn6._method, None)
|
|
133
|
+
ae(conn6._path, None)
|
|
134
|
+
ae(conn6._body, None)
|
|
135
|
+
|
|
136
|
+
# ── 9. HTTPResponse — sync accessors ─────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
hdrs = {'content-type': 'application/json', 'x-rate-limit': '100'}
|
|
139
|
+
resp = HTTPResponse(200, 'OK', hdrs, '{"ok":true}', 'https://example.com/api')
|
|
140
|
+
ae(resp.status, 200)
|
|
141
|
+
ae(resp.reason, 'OK')
|
|
142
|
+
ae(resp.url, 'https://example.com/api')
|
|
143
|
+
ae(resp.getheader('content-type'), 'application/json')
|
|
144
|
+
ae(resp.getheader('Content-Type'), 'application/json')
|
|
145
|
+
ae(resp.getheader('missing'), None)
|
|
146
|
+
ae(resp.getheader('missing', 'default'), 'default')
|
|
147
|
+
ok(not resp.closed)
|
|
148
|
+
resp.close()
|
|
149
|
+
ok(resp.closed)
|
|
150
|
+
|
|
151
|
+
# ── 10. HTTPResponse.getheaders ───────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
pairs = resp.getheaders()
|
|
154
|
+
ae(len(pairs), 2)
|
|
155
|
+
found = False
|
|
156
|
+
for pair in pairs:
|
|
157
|
+
if pair[0] is 'content-type' and pair[1] is 'application/json':
|
|
158
|
+
found = True
|
|
159
|
+
ok(found)
|
|
160
|
+
|
|
161
|
+
# ── 11. HTTPResponse.read / .json return Promises ────────────────────────────
|
|
162
|
+
|
|
163
|
+
resp2 = HTTPResponse(200, 'OK', {}, '{"n":42}', 'https://x.com/')
|
|
164
|
+
read_p = resp2.read()
|
|
165
|
+
json_p = resp2.json()
|
|
166
|
+
ok(read_p is not None)
|
|
167
|
+
ok(json_p is not None)
|
|
168
|
+
ok(jstype(read_p.then) is 'function')
|
|
169
|
+
ok(jstype(json_p.then) is 'function')
|
|
170
|
+
|
|
171
|
+
# ── 12. SimpleCookie — basic parsing ─────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
c = SimpleCookie()
|
|
174
|
+
c.load('session=abc123; user=alice')
|
|
175
|
+
ok('session' in c.keys())
|
|
176
|
+
ok('user' in c.keys())
|
|
177
|
+
ae(c['session'].value, 'abc123')
|
|
178
|
+
ae(c['user'].value, 'alice')
|
|
179
|
+
|
|
180
|
+
# ── 13. SimpleCookie — set a cookie ──────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
c2 = SimpleCookie()
|
|
183
|
+
c2['token'] = 'xyz789'
|
|
184
|
+
ok('token' in c2.keys())
|
|
185
|
+
ae(c2['token'].value, 'xyz789')
|
|
186
|
+
ae(c2['token'].coded_value, 'xyz789')
|
|
187
|
+
|
|
188
|
+
# ── 14. SimpleCookie — cookie attributes ─────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
c3 = SimpleCookie()
|
|
191
|
+
c3['id'] = '1'
|
|
192
|
+
c3['id']['path'] = '/'
|
|
193
|
+
c3['id']['max-age'] = 3600
|
|
194
|
+
c3['id']['secure'] = True
|
|
195
|
+
ae(c3['id']['path'], '/')
|
|
196
|
+
ae(c3['id']['max-age'], 3600)
|
|
197
|
+
ok(c3['id']['secure'])
|
|
198
|
+
|
|
199
|
+
# ── 15. Morsel.OutputString ───────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
m = Morsel()
|
|
202
|
+
m.set('token', 'abc', 'abc')
|
|
203
|
+
m._attrs['path'] = '/'
|
|
204
|
+
m._attrs['max-age'] = '3600'
|
|
205
|
+
s = m.OutputString()
|
|
206
|
+
ok('token=abc' in s)
|
|
207
|
+
ok('Path=/' in s)
|
|
208
|
+
ok('Max-Age=3600' in s)
|
|
209
|
+
|
|
210
|
+
# ── 16. Morsel.output includes header ────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
out = m.output()
|
|
213
|
+
ok('Set-Cookie: token=abc' in out)
|
|
214
|
+
|
|
215
|
+
out2 = m.output('Set-Cookie')
|
|
216
|
+
ok('Set-Cookie: token=abc' in out2)
|
|
217
|
+
|
|
218
|
+
# ── 17. SimpleCookie.output ───────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
c4 = SimpleCookie()
|
|
221
|
+
c4['a'] = '1'
|
|
222
|
+
c4['b'] = '2'
|
|
223
|
+
full = c4.output()
|
|
224
|
+
ok('Set-Cookie: a=1' in full)
|
|
225
|
+
ok('Set-Cookie: b=2' in full)
|
|
226
|
+
|
|
227
|
+
# ── 18. SimpleCookie constructor with initial data ────────────────────────────
|
|
228
|
+
|
|
229
|
+
c5 = SimpleCookie('x=10; y=20')
|
|
230
|
+
ae(c5['x'].value, '10')
|
|
231
|
+
ae(c5['y'].value, '20')
|
|
232
|
+
|
|
233
|
+
# ── 19. SimpleCookie.keys / values / items ───────────────────────────────────
|
|
234
|
+
|
|
235
|
+
c6 = SimpleCookie()
|
|
236
|
+
c6['p'] = 'hello'
|
|
237
|
+
c6['q'] = 'world'
|
|
238
|
+
ks = c6.keys()
|
|
239
|
+
ok('p' in ks) # ks is a list — 'in' checks array membership
|
|
240
|
+
ok('q' in ks)
|
|
241
|
+
vs = c6.values()
|
|
242
|
+
ae(len(vs), 2)
|
|
243
|
+
itms = c6.items()
|
|
244
|
+
ae(len(itms), 2)
|
|
245
|
+
|
|
246
|
+
# ── 20. CookieError is an Exception ──────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
raise CookieError('bad cookie')
|
|
250
|
+
ok(False)
|
|
251
|
+
except CookieError as e:
|
|
252
|
+
ok('bad cookie' in str(e))
|
|
253
|
+
|
|
254
|
+
# ── 21. set_debuglevel / connect no-op ───────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
conn7 = HTTPConnection('example.com')
|
|
257
|
+
conn7.set_debuglevel(1)
|
|
258
|
+
conn7.connect()
|
|
259
|
+
ok(True)
|