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/string.pyj
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# string.pyj
|
|
5
|
+
# Tests for the string standard library module.
|
|
6
|
+
|
|
7
|
+
from string import (
|
|
8
|
+
ascii_lowercase, ascii_uppercase, ascii_letters,
|
|
9
|
+
digits, hexdigits, octdigits,
|
|
10
|
+
punctuation, whitespace, printable,
|
|
11
|
+
Template, Formatter,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
ae = assrt.equal
|
|
15
|
+
ade = assrt.deepEqual
|
|
16
|
+
ok = assrt.ok
|
|
17
|
+
throws = assrt.throws
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ── 1. Character constants ────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
ae(len(ascii_lowercase), 26)
|
|
23
|
+
ae(ascii_lowercase[0], 'a')
|
|
24
|
+
ae(ascii_lowercase[25], 'z')
|
|
25
|
+
|
|
26
|
+
ae(len(ascii_uppercase), 26)
|
|
27
|
+
ae(ascii_uppercase[0], 'A')
|
|
28
|
+
ae(ascii_uppercase[25], 'Z')
|
|
29
|
+
|
|
30
|
+
ae(ascii_letters, ascii_lowercase + ascii_uppercase)
|
|
31
|
+
ae(len(ascii_letters), 52)
|
|
32
|
+
|
|
33
|
+
ae(digits, '0123456789')
|
|
34
|
+
ae(len(digits), 10)
|
|
35
|
+
|
|
36
|
+
ae(hexdigits, '0123456789abcdefABCDEF')
|
|
37
|
+
ae(len(hexdigits), 22)
|
|
38
|
+
|
|
39
|
+
ae(octdigits, '01234567')
|
|
40
|
+
ae(len(octdigits), 8)
|
|
41
|
+
|
|
42
|
+
# punctuation: 32 chars, contains known members
|
|
43
|
+
ae(len(punctuation), 32)
|
|
44
|
+
ok(punctuation.indexOf('!') >= 0)
|
|
45
|
+
ok(punctuation.indexOf('~') >= 0)
|
|
46
|
+
ok(punctuation.indexOf('\\') >= 0) # backslash present
|
|
47
|
+
ok(punctuation.indexOf('`') >= 0) # backtick present
|
|
48
|
+
ok(punctuation.indexOf("'") >= 0) # single-quote present
|
|
49
|
+
|
|
50
|
+
# whitespace: space, tab, newline, carriage-return, vertical-tab, form-feed
|
|
51
|
+
ae(len(whitespace), 6)
|
|
52
|
+
ok(whitespace.indexOf(' ') >= 0)
|
|
53
|
+
ok(whitespace.indexOf('\t') >= 0)
|
|
54
|
+
ok(whitespace.indexOf('\n') >= 0)
|
|
55
|
+
|
|
56
|
+
# printable is a concatenation of the four sets
|
|
57
|
+
ae(len(printable), len(digits) + len(ascii_letters) + len(punctuation) + len(whitespace))
|
|
58
|
+
ok(printable.indexOf('A') >= 0)
|
|
59
|
+
ok(printable.indexOf('0') >= 0)
|
|
60
|
+
ok(printable.indexOf('!') >= 0)
|
|
61
|
+
ok(printable.indexOf(' ') >= 0)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ── 2. Template — basic substitution ─────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
t1 = Template('Hello $name!')
|
|
67
|
+
ae(t1.substitute({'name': 'World'}), 'Hello World!')
|
|
68
|
+
|
|
69
|
+
# $$ → literal $
|
|
70
|
+
t2 = Template('Price: $$5.00')
|
|
71
|
+
ae(t2.substitute({}), 'Price: $5.00')
|
|
72
|
+
|
|
73
|
+
# ${identifier} brace form
|
|
74
|
+
t3 = Template('${foo}bar')
|
|
75
|
+
ae(t3.substitute({'foo': 'baz'}), 'bazbar')
|
|
76
|
+
|
|
77
|
+
# template attribute
|
|
78
|
+
ae(t1.template, 'Hello $name!')
|
|
79
|
+
|
|
80
|
+
# multiple substitutions in one string
|
|
81
|
+
t4 = Template('$a + $b = $c')
|
|
82
|
+
ae(t4.substitute({'a': '1', 'b': '2', 'c': '3'}), '1 + 2 = 3')
|
|
83
|
+
|
|
84
|
+
# numeric values coerced to string
|
|
85
|
+
t5 = Template('$x items')
|
|
86
|
+
ae(t5.substitute({'x': 42}), '42 items')
|
|
87
|
+
|
|
88
|
+
# None → 'None'
|
|
89
|
+
t6 = Template('val=$v')
|
|
90
|
+
ae(t6.substitute({'v': None}), 'val=None')
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ── 3. Template — missing key errors ─────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
err_missing = False
|
|
96
|
+
try:
|
|
97
|
+
Template('Hello $missing').substitute({})
|
|
98
|
+
except KeyError:
|
|
99
|
+
err_missing = True
|
|
100
|
+
ok(err_missing)
|
|
101
|
+
|
|
102
|
+
# safe_substitute leaves unknown placeholder intact
|
|
103
|
+
ae(Template('Hello $missing').safe_substitute({}), 'Hello $missing')
|
|
104
|
+
ae(Template('${missing} world').safe_substitute({}), '${missing} world')
|
|
105
|
+
|
|
106
|
+
# safe_substitute replaces known, leaves unknown
|
|
107
|
+
t7 = Template('$a and $b')
|
|
108
|
+
ae(t7.safe_substitute({'a': 'yes'}), 'yes and $b')
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ── 4. Template — edge cases ─────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
# leading/trailing dollar
|
|
114
|
+
ae(Template('$$').substitute({}), '$')
|
|
115
|
+
ae(Template('$$$$').substitute({}), '$$')
|
|
116
|
+
|
|
117
|
+
# empty template
|
|
118
|
+
ae(Template('').substitute({}), '')
|
|
119
|
+
ae(Template('').safe_substitute({}), '')
|
|
120
|
+
|
|
121
|
+
# no placeholders
|
|
122
|
+
ae(Template('plain text').substitute({}), 'plain text')
|
|
123
|
+
|
|
124
|
+
# trailing $ — safe_substitute leaves it, substitute raises ValueError
|
|
125
|
+
ae(Template('end$').safe_substitute({}), 'end$')
|
|
126
|
+
|
|
127
|
+
err_dollar = False
|
|
128
|
+
try:
|
|
129
|
+
Template('end$').substitute({})
|
|
130
|
+
except ValueError:
|
|
131
|
+
err_dollar = True
|
|
132
|
+
ok(err_dollar)
|
|
133
|
+
|
|
134
|
+
# class attributes
|
|
135
|
+
ae(Template.delimiter, '$')
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ── 5. Formatter — positional args ───────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
f = Formatter()
|
|
141
|
+
|
|
142
|
+
ae(f.format('Hello {}!', 'World'), 'Hello World!')
|
|
143
|
+
ae(f.format('{0} {1}', 'a', 'b'), 'a b')
|
|
144
|
+
ae(f.format('{1} {0}', 'a', 'b'), 'b a')
|
|
145
|
+
ae(f.format('no fields'), 'no fields')
|
|
146
|
+
ae(f.format('{0}{0}{0}', 'x'), 'xxx')
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ── 6. Formatter — format specs ──────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
ae(f.format('{:.2f}', 3.14159), '3.14')
|
|
152
|
+
ae(f.format('{:d}', 42), '42')
|
|
153
|
+
ae(f.format('{:05d}', 7), '00007')
|
|
154
|
+
ae(f.format('{:>10}', 'hi'), ' hi')
|
|
155
|
+
ae(f.format('{:<10}', 'hi'), 'hi ')
|
|
156
|
+
ae(f.format('{:^5}', 'x'), ' x ')
|
|
157
|
+
ae(f.format('{:x}', 255), 'ff')
|
|
158
|
+
ae(f.format('{:X}', 255), 'FF')
|
|
159
|
+
ae(f.format('{:b}', 10), '1010')
|
|
160
|
+
ae(f.format('{:o}', 8), '10')
|
|
161
|
+
ae(f.format('{:e}', 100000.0), '1.000000e+5')
|
|
162
|
+
|
|
163
|
+
# {{ }} escaping
|
|
164
|
+
ae(f.format('{{ }}'), '{ }')
|
|
165
|
+
ae(f.format('{{{0}}}', 'inner'), '{inner}')
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ── 7. Formatter — conversion flags ──────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
ae(f.format('{0!s}', 42), '42')
|
|
171
|
+
|
|
172
|
+
# repr adds quotes around strings
|
|
173
|
+
_repr_result = f.format('{0!r}', 'hi')
|
|
174
|
+
ok(_repr_result.indexOf('hi') >= 0)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# ── 8. Formatter — vformat with named kwargs ─────────────────────────────────
|
|
178
|
+
|
|
179
|
+
ae(f.vformat('{name}', [], {'name': 'Alice'}), 'Alice')
|
|
180
|
+
ae(f.vformat('{x} + {y}', [], {'x': '1', 'y': '2'}), '1 + 2')
|
|
181
|
+
ae(f.vformat('{0} {name}', ['hi'], {'name': 'Bob'}), 'hi Bob')
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ── 9. Formatter — parse() ───────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
_parsed = f.parse('literal {0} more {name!r:.5}')
|
|
187
|
+
ae(len(_parsed), 3)
|
|
188
|
+
# first item: literal before first field
|
|
189
|
+
ae(_parsed[0][0], 'literal ')
|
|
190
|
+
ae(_parsed[0][1], '0')
|
|
191
|
+
ae(_parsed[0][2], '')
|
|
192
|
+
ae(_parsed[0][3], None)
|
|
193
|
+
# second item
|
|
194
|
+
ae(_parsed[1][0], ' more ')
|
|
195
|
+
ae(_parsed[1][1], 'name')
|
|
196
|
+
ae(_parsed[1][2], '.5')
|
|
197
|
+
ae(_parsed[1][3], 'r')
|
|
198
|
+
# trailing literal
|
|
199
|
+
ae(_parsed[2][0], '')
|
|
200
|
+
ae(_parsed[2][1], None)
|
|
201
|
+
|
|
202
|
+
# parse with only a trailing literal
|
|
203
|
+
_p2 = f.parse('no fields here')
|
|
204
|
+
ae(len(_p2), 1)
|
|
205
|
+
ae(_p2[0][0], 'no fields here')
|
|
206
|
+
ae(_p2[0][1], None)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ── 10. Formatter — subclassing ──────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
class UpperFormatter(Formatter):
|
|
212
|
+
def format_field(self, value, format_spec):
|
|
213
|
+
result = Formatter.format_field(self, value, format_spec)
|
|
214
|
+
return v'result.toUpperCase()'
|
|
215
|
+
|
|
216
|
+
uf = UpperFormatter()
|
|
217
|
+
ae(uf.format('Hello {0}!', 'world'), 'Hello WORLD!')
|
|
218
|
+
ae(uf.format('{0}', 'lower'), 'LOWER')
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ── 11. Formatter — get_value / get_field ────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
ae(f.get_value(0, ['a', 'b'], {}), 'a')
|
|
224
|
+
ae(f.get_value(1, ['a', 'b'], {}), 'b')
|
|
225
|
+
ae(f.get_value('x', [], {'x': 99}), 99)
|
|
226
|
+
|
|
227
|
+
# get_field with dotted access
|
|
228
|
+
_obj = {'name': 'Alice'}
|
|
229
|
+
_gf = f.get_field('0', [_obj], {})
|
|
230
|
+
ae(_gf[1], '0') # first key used
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ── 12. Formatter — convert_field ────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
ae(f.convert_field(42, 's'), '42')
|
|
236
|
+
ok(f.convert_field(42, 'r').indexOf('42') >= 0)
|
|
237
|
+
ae(f.convert_field('x', None), 'x')
|
|
238
|
+
ae(f.convert_field('x', undefined), 'x')
|
|
239
|
+
|
|
240
|
+
_bad_conv = False
|
|
241
|
+
try:
|
|
242
|
+
f.convert_field(1, 'z')
|
|
243
|
+
except ValueError:
|
|
244
|
+
_bad_conv = True
|
|
245
|
+
ok(_bad_conv)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# textwrap.pyj
|
|
5
|
+
# Tests for the textwrap standard library module.
|
|
6
|
+
|
|
7
|
+
from textwrap import wrap, fill, dedent, indent, shorten, TextWrapper
|
|
8
|
+
|
|
9
|
+
ae = assrt.equal
|
|
10
|
+
ade = assrt.deepEqual
|
|
11
|
+
ok = assrt.ok
|
|
12
|
+
|
|
13
|
+
# ── 1. wrap — basic ──────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
# Short text fits on one line
|
|
16
|
+
ade(wrap('hello world', 20), ['hello world'])
|
|
17
|
+
|
|
18
|
+
# Empty string
|
|
19
|
+
ade(wrap('', 10), [])
|
|
20
|
+
|
|
21
|
+
# Single word
|
|
22
|
+
ade(wrap('hello', 10), ['hello'])
|
|
23
|
+
|
|
24
|
+
# Width-limited: "one two" (7), "three four" (10), "five" (4)
|
|
25
|
+
_r1 = wrap('one two three four five', 10)
|
|
26
|
+
ae(_r1.length, 3)
|
|
27
|
+
ae(_r1[0], 'one two')
|
|
28
|
+
ae(_r1[1], 'three four')
|
|
29
|
+
ae(_r1[2], 'five')
|
|
30
|
+
|
|
31
|
+
# Single word exactly equal to width
|
|
32
|
+
ade(wrap('hello', 5), ['hello'])
|
|
33
|
+
|
|
34
|
+
# Word longer than width with break_long_words=True (default)
|
|
35
|
+
_r2 = wrap('superlongword', 5)
|
|
36
|
+
ae(_r2[0], 'super')
|
|
37
|
+
ae(_r2[1], 'longw')
|
|
38
|
+
ae(_r2[2], 'ord')
|
|
39
|
+
|
|
40
|
+
# ── 2. wrap — break_long_words=False ─────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
_r3 = wrap('superlongword short', 5, break_long_words=False)
|
|
43
|
+
ae(_r3[0], 'superlongword')
|
|
44
|
+
ae(_r3[1], 'short')
|
|
45
|
+
|
|
46
|
+
# ── 3. wrap — initial_indent / subsequent_indent ─────────────────────────────
|
|
47
|
+
|
|
48
|
+
# Line 1 avail=12-2=10: "one two" (7); line 2 avail=10: "three four" (10)
|
|
49
|
+
_r4 = wrap('one two three four', 12, initial_indent='> ', subsequent_indent=' ')
|
|
50
|
+
ae(_r4.length, 2)
|
|
51
|
+
ae(_r4[0], '> one two')
|
|
52
|
+
ae(_r4[1], ' three four')
|
|
53
|
+
|
|
54
|
+
# ── 4. fill ───────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
ae(fill('one two three', 8), 'one two\nthree')
|
|
57
|
+
ae(fill('hello', 20), 'hello')
|
|
58
|
+
ae(fill('', 10), '')
|
|
59
|
+
|
|
60
|
+
# ── 5. fill — initial/subsequent indent ──────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
ae(fill('one two three four', 12, initial_indent='> ', subsequent_indent=' '),
|
|
63
|
+
'> one two\n three four')
|
|
64
|
+
|
|
65
|
+
# ── 6. TextWrapper — basic ───────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
_tw1 = TextWrapper(width=10)
|
|
68
|
+
ade(_tw1.wrap('one two three'), ['one two', 'three'])
|
|
69
|
+
ae(_tw1.fill('one two three'), 'one two\nthree')
|
|
70
|
+
|
|
71
|
+
# ── 7. TextWrapper — max_lines with placeholder ──────────────────────────────
|
|
72
|
+
|
|
73
|
+
# width=15, max_lines=2, placeholder=' ...' (4 chars)
|
|
74
|
+
# Line 1: "alpha beta" (10), fits; line 2 needs truncation with placeholder.
|
|
75
|
+
_tw2 = TextWrapper(width=15, max_lines=2, placeholder=' ...')
|
|
76
|
+
_r5 = _tw2.wrap('alpha beta gamma delta epsilon')
|
|
77
|
+
ae(_r5.length, 2)
|
|
78
|
+
ae(_r5[0], 'alpha beta')
|
|
79
|
+
ae(_r5[1], 'gamma delta ...')
|
|
80
|
+
|
|
81
|
+
# ── 8. TextWrapper — fix_sentence_endings ────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
_tw3 = TextWrapper(width=70, fix_sentence_endings=True)
|
|
84
|
+
_r6 = _tw3.wrap('end of sentence. New sentence.')
|
|
85
|
+
ae(_r6.length, 1)
|
|
86
|
+
ae(_r6[0], 'end of sentence. New sentence.')
|
|
87
|
+
|
|
88
|
+
# Without fix_sentence_endings: single space stays
|
|
89
|
+
_tw4 = TextWrapper(width=70, fix_sentence_endings=False)
|
|
90
|
+
_r7 = _tw4.wrap('end of sentence. New sentence.')
|
|
91
|
+
ae(_r7[0], 'end of sentence. New sentence.')
|
|
92
|
+
|
|
93
|
+
# ── 9. dedent — common indent removed ────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
ae(dedent(' hello\n world'), 'hello\nworld')
|
|
96
|
+
|
|
97
|
+
# Tab-based common indent
|
|
98
|
+
ae(dedent('\thello\n\tworld'), 'hello\nworld')
|
|
99
|
+
|
|
100
|
+
# No common indent → unchanged
|
|
101
|
+
ae(dedent('hello\n world'), 'hello\n world')
|
|
102
|
+
|
|
103
|
+
# Empty lines ignored when computing margin
|
|
104
|
+
ae(dedent(' hello\n\n world'), 'hello\n\nworld')
|
|
105
|
+
|
|
106
|
+
# Partial common indent: " " vs " " → margin is " "
|
|
107
|
+
ae(dedent(' foo\n bar'), ' foo\nbar')
|
|
108
|
+
|
|
109
|
+
# Already no indent
|
|
110
|
+
ae(dedent('hello\nworld'), 'hello\nworld')
|
|
111
|
+
|
|
112
|
+
# Leading blank line, then indented
|
|
113
|
+
ae(dedent('\n foo\n bar'), '\nfoo\nbar')
|
|
114
|
+
|
|
115
|
+
# ── 10. indent — basic ───────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
ae(indent('hello\nworld', ' '), ' hello\n world')
|
|
118
|
+
|
|
119
|
+
# Empty lines not indented by default (strip() == '')
|
|
120
|
+
ae(indent('hello\n\nworld', ' '), ' hello\n\n world')
|
|
121
|
+
|
|
122
|
+
# Whitespace-only line not indented by default
|
|
123
|
+
ae(indent('hello\n \nworld', '> '), '> hello\n \n> world')
|
|
124
|
+
|
|
125
|
+
# ── 11. indent — custom predicate ────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
# Indent ALL lines including empty
|
|
128
|
+
_pred_all = def(line): return True;
|
|
129
|
+
ae(indent('hello\n\nworld', '> ', _pred_all), '> hello\n> \n> world')
|
|
130
|
+
|
|
131
|
+
# Indent only lines starting with '#'
|
|
132
|
+
_pred_hash = def(line): return line.startsWith('#');
|
|
133
|
+
ae(indent('# comment\ncode\n# another', '!! ', _pred_hash),
|
|
134
|
+
'!! # comment\ncode\n!! # another')
|
|
135
|
+
|
|
136
|
+
# ── 12. shorten — no truncation needed ───────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
ae(shorten('hello world', 20), 'hello world')
|
|
139
|
+
|
|
140
|
+
# ── 13. shorten — truncation ─────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
# "one two three [...]": "one two three" (13) + " [...]" (6) = 19 ≤ 20
|
|
143
|
+
ae(shorten('one two three four five', 20), 'one two three [...]')
|
|
144
|
+
|
|
145
|
+
# Custom placeholder
|
|
146
|
+
ae(shorten('hello world foo bar', 14, placeholder='...'), 'hello world...')
|
|
147
|
+
|
|
148
|
+
# ── 14. shorten — normalises internal whitespace ─────────────────────────────
|
|
149
|
+
|
|
150
|
+
# Multiple spaces collapsed
|
|
151
|
+
ae(shorten('hello world', 20), 'hello world')
|
|
152
|
+
|
|
153
|
+
# ── 15. wrap — drop_whitespace=False ─────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
# With drop_whitespace=False trailing space is kept on the line.
|
|
156
|
+
_r8 = wrap(' hello world', 10, drop_whitespace=False)
|
|
157
|
+
ok(_r8.length >= 1)
|
|
158
|
+
ae(_r8[0], ' hello ')
|
|
159
|
+
|
|
160
|
+
# ── 16. wrap — replace_whitespace=False ──────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
# Newlines in source are treated as whitespace separators normally;
|
|
163
|
+
# with replace_whitespace=False they appear in chunks.
|
|
164
|
+
_r9 = wrap('hello\nworld extra long line here', 15, replace_whitespace=False)
|
|
165
|
+
ok(_r9.length >= 1)
|
|
166
|
+
|
|
167
|
+
# ── 17. wrap — max_lines=1 on module-level wrap ──────────────────────────────
|
|
168
|
+
|
|
169
|
+
# "one two" (7) + " ..." (4) = 11 ≤ 15; "one two three" (13) + " ..." (4) = 17 > 15
|
|
170
|
+
_r10 = wrap('one two three four five', 15, max_lines=1, placeholder=' ...')
|
|
171
|
+
ae(_r10.length, 1)
|
|
172
|
+
ae(_r10[0], 'one two ...')
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# vim:fileencoding=utf-8
|
|
2
|
+
# globals: assrt
|
|
3
|
+
|
|
4
|
+
ae = assrt.equal
|
|
5
|
+
ok = assrt.ok
|
|
6
|
+
|
|
7
|
+
# ── type() display: <class 'name'> formatting ─────────────────────────────────
|
|
8
|
+
|
|
9
|
+
# Built-in Python-mapped types
|
|
10
|
+
ae(str(type([])), "<class 'list'>")
|
|
11
|
+
ae(str(type('hello')), "<class 'str'>")
|
|
12
|
+
ae(str(type(42)), "<class 'float'>")
|
|
13
|
+
ae(str(type(True)), "<class 'bool'>")
|
|
14
|
+
ae(str(type(None)), "<class 'NoneType'>")
|
|
15
|
+
|
|
16
|
+
# Explicit dict/set
|
|
17
|
+
ae(str(type(dict())), "<class 'dict'>")
|
|
18
|
+
ae(str(type(set())), "<class 'set'>")
|
|
19
|
+
|
|
20
|
+
# User-defined classes
|
|
21
|
+
class Dog:
|
|
22
|
+
def __init__(self, name):
|
|
23
|
+
self.name = name
|
|
24
|
+
|
|
25
|
+
d = Dog('Rex')
|
|
26
|
+
ae(str(type(d)), "<class 'Dog'>")
|
|
27
|
+
|
|
28
|
+
class Cat(Dog):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
c = Cat('Mittens')
|
|
32
|
+
ae(str(type(c)), "<class 'Cat'>")
|
|
33
|
+
|
|
34
|
+
# type() identity is preserved (type(x) is Cls still works)
|
|
35
|
+
ok(type(d) is Dog)
|
|
36
|
+
ok(type(c) is Cat)
|
|
37
|
+
ok(type(c) is not Dog)
|
|
38
|
+
ok(type([]) is list)
|
|
39
|
+
ok(type(None) is type(None))
|
|
40
|
+
|
|
41
|
+
# __name__ attribute is accessible on the returned type
|
|
42
|
+
ae(type([]) .__name__, 'list')
|
|
43
|
+
ae(type(d) .__name__, 'Dog')
|
|
44
|
+
ae(type(None).__name__, 'NoneType')
|
|
45
|
+
|
|
46
|
+
# Same type called twice returns same constructor (no double-patching issue)
|
|
47
|
+
ok(type([]) is type([]))
|
|
48
|
+
ok(type('a') is type('b'))
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# vim:fileencoding=utf-8
|
|
2
|
+
# Tests for from __python__ import type_enforcement
|
|
3
|
+
# globals: assrt
|
|
4
|
+
|
|
5
|
+
from __python__ import type_enforcement
|
|
6
|
+
|
|
7
|
+
eq = assrt.equal
|
|
8
|
+
de = assrt.deepEqual
|
|
9
|
+
throws = assrt.throws
|
|
10
|
+
ok = assrt.ok
|
|
11
|
+
|
|
12
|
+
def _err(fn):
|
|
13
|
+
"""Call fn and return the TypeError message, or raise if no error."""
|
|
14
|
+
try:
|
|
15
|
+
fn()
|
|
16
|
+
raise AssertionError("Expected TypeError but none was raised")
|
|
17
|
+
except TypeError as e:
|
|
18
|
+
return e.message or str(e)
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# 1. Max positional-arg count
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
def _fixed(a, b):
|
|
25
|
+
return a + b
|
|
26
|
+
|
|
27
|
+
throws(def(): _fixed(1, 2, 3);, TypeError)
|
|
28
|
+
eq(_fixed(1, 2), 3)
|
|
29
|
+
|
|
30
|
+
# With defaults
|
|
31
|
+
def _with_def(a, b=10):
|
|
32
|
+
return a + b
|
|
33
|
+
|
|
34
|
+
throws(def(): _with_def(1, 2, 3);, TypeError)
|
|
35
|
+
eq(_with_def(5), 15)
|
|
36
|
+
eq(_with_def(5, 20), 25)
|
|
37
|
+
|
|
38
|
+
# With *args: no max limit
|
|
39
|
+
def _varargs(a, *rest):
|
|
40
|
+
return [a] + list(rest)
|
|
41
|
+
|
|
42
|
+
de(_varargs(1, 2, 3), [1, 2, 3])
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# 2. Missing required positional arg
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
msg = _err(def(): _fixed(1);)
|
|
49
|
+
ok(msg.indexOf('missing required positional argument') >= 0)
|
|
50
|
+
ok(msg.indexOf('b') >= 0)
|
|
51
|
+
|
|
52
|
+
# Simple function (no defaults)
|
|
53
|
+
def _simple(x):
|
|
54
|
+
return x * 2
|
|
55
|
+
|
|
56
|
+
throws(def(): _simple();, TypeError)
|
|
57
|
+
eq(_simple(7), 14)
|
|
58
|
+
|
|
59
|
+
# Required arg can be provided as kwarg (valid for non-posonly)
|
|
60
|
+
eq(_fixed(a=3, b=4), 7)
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
# 3. Positional-only enforcement
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
def _po(a, b, /):
|
|
67
|
+
return a - b
|
|
68
|
+
|
|
69
|
+
eq(_po(10, 3), 7)
|
|
70
|
+
throws(def(): _po(a=10, b=3);, TypeError)
|
|
71
|
+
throws(def(): _po(b=3);, TypeError)
|
|
72
|
+
|
|
73
|
+
# Mixed: posonly + normal
|
|
74
|
+
def _mixed(x, /, y=5):
|
|
75
|
+
return x + y
|
|
76
|
+
|
|
77
|
+
eq(_mixed(1), 6)
|
|
78
|
+
eq(_mixed(1, y=10), 11)
|
|
79
|
+
throws(def(): _mixed(x=1);, TypeError)
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# 4. Keyword-only enforcement (bare *)
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
def _ko(a, *, b):
|
|
86
|
+
return a + b
|
|
87
|
+
|
|
88
|
+
# b is required kwonly; must be passed as keyword
|
|
89
|
+
eq(_ko(1, b=2), 3)
|
|
90
|
+
throws(def(): _ko(1);, TypeError) # b missing
|
|
91
|
+
|
|
92
|
+
def _ko_opt(a, *, b=100):
|
|
93
|
+
return a + b
|
|
94
|
+
|
|
95
|
+
eq(_ko_opt(1), 101)
|
|
96
|
+
eq(_ko_opt(1, b=200), 201)
|
|
97
|
+
|
|
98
|
+
# Cannot pass kwonly positionally (it goes to *args slot which doesn't exist here)
|
|
99
|
+
# Passing positionally puts value into wrong position; enforce checks that b is set
|
|
100
|
+
# In RapydScript, _ko(1, 2) passes 2 positionally but b is kwonly → b === undefined
|
|
101
|
+
throws(def(): _ko(1, 2);, TypeError)
|
|
102
|
+
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
# 5. Positional-only + keyword-only combined
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
def _combo(a, b, /, c=1, *, d):
|
|
108
|
+
return a + b + c + d
|
|
109
|
+
|
|
110
|
+
eq(_combo(1, 2, d=10), 14)
|
|
111
|
+
eq(_combo(1, 2, c=3, d=10), 16)
|
|
112
|
+
throws(def(): _combo(a=1, b=2, d=10);, TypeError) # a is posonly
|
|
113
|
+
throws(def(): _combo(1, 2);, TypeError) # d is required kwonly
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# 6. Type annotation enforcement
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
def _typed(x: int, y: str):
|
|
120
|
+
return str(x) + y
|
|
121
|
+
|
|
122
|
+
eq(_typed(1, 'a'), '1a')
|
|
123
|
+
throws(def(): _typed('bad', 'a');, TypeError)
|
|
124
|
+
throws(def(): _typed(1, 42);, TypeError)
|
|
125
|
+
|
|
126
|
+
# Type error message mentions the arg name
|
|
127
|
+
msg = _err(def(): _typed('bad', 'ok');)
|
|
128
|
+
ok(msg.indexOf("argument 'x' must be") >= 0)
|
|
129
|
+
|
|
130
|
+
# Annotated with default: type checked when provided
|
|
131
|
+
def _ann_def(n: int = 0):
|
|
132
|
+
return n + 1
|
|
133
|
+
|
|
134
|
+
eq(_ann_def(), 1)
|
|
135
|
+
eq(_ann_def(5), 6)
|
|
136
|
+
throws(def(): _ann_def('five');, TypeError)
|
|
137
|
+
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
# 7. Class method enforcement
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
class Calc:
|
|
143
|
+
def add(self, a: int, b: int):
|
|
144
|
+
return a + b
|
|
145
|
+
|
|
146
|
+
def greet(self, name, /, *, loud=False):
|
|
147
|
+
return name.upper() if loud else name
|
|
148
|
+
|
|
149
|
+
c = Calc()
|
|
150
|
+
eq(c.add(3, 4), 7)
|
|
151
|
+
throws(def(): c.add(3, 'x');, TypeError) # b must be int
|
|
152
|
+
throws(def(): c.add(3, 4, 5);, TypeError) # too many args
|
|
153
|
+
throws(def(): c.greet(name='Alice');, TypeError) # name is posonly
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# 8. Return annotation stored but not enforced (Python-compatible)
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
def _ret(x: int) -> int:
|
|
160
|
+
return x * 2
|
|
161
|
+
|
|
162
|
+
eq(_ret(3), 6)
|
|
163
|
+
# Return type is metadata only — no TypeError from returning wrong type
|
|
164
|
+
# (this matches Python's own behaviour without beartype etc.)
|
package/test/unit/index.js
CHANGED
|
@@ -1831,13 +1831,13 @@ assrt.equal(fib(15), 610)
|
|
|
1831
1831
|
|
|
1832
1832
|
{
|
|
1833
1833
|
name: "print_compiles_to_console_log",
|
|
1834
|
-
description: "print(x) compiles
|
|
1834
|
+
description: "print(x) compiles to console.log(ρσ_str(x)) for Python-style string conversion",
|
|
1835
1835
|
src: [
|
|
1836
1836
|
"# globals: assrt",
|
|
1837
1837
|
"print('hello')",
|
|
1838
1838
|
"print(1, 2, 3)",
|
|
1839
1839
|
].join("\n"),
|
|
1840
|
-
js_checks: ["console.log(\"hello\")", "console.log(1, 2, 3)"],
|
|
1840
|
+
js_checks: ["console.log(ρσ_str(\"hello\"))", "console.log(ρσ_str(1), ρσ_str(2), ρσ_str(3))"],
|
|
1841
1841
|
},
|
|
1842
1842
|
|
|
1843
1843
|
{
|
|
@@ -1883,7 +1883,7 @@ assrt.equal(fib(15), 610)
|
|
|
1883
1883
|
"print('test')",
|
|
1884
1884
|
].join("\n"),
|
|
1885
1885
|
// The compiled JS must NOT contain 'var print = ' (which would overwrite window.print)
|
|
1886
|
-
js_checks: [/console\.log\("test"\)/],
|
|
1886
|
+
js_checks: [/console\.log\(ρσ_str\("test"\)\)/],
|
|
1887
1887
|
},
|
|
1888
1888
|
|
|
1889
1889
|
{
|
|
@@ -5376,12 +5376,20 @@ function run_tests(filter) {
|
|
|
5376
5376
|
" – " + test.description);
|
|
5377
5377
|
});
|
|
5378
5378
|
|
|
5379
|
+
var passed = tests.length - failures.length;
|
|
5379
5380
|
console.log("");
|
|
5380
5381
|
if (failures.length) {
|
|
5381
|
-
console.log(colored(
|
|
5382
|
-
|
|
5383
|
-
|
|
5382
|
+
console.log(colored("Failed tests:", "red"));
|
|
5383
|
+
failures.forEach(function (name) {
|
|
5384
|
+
console.log(colored(" ✗ " + name, "red"));
|
|
5385
|
+
});
|
|
5386
|
+
console.log("");
|
|
5384
5387
|
}
|
|
5388
|
+
var summary = "unit tests — " +
|
|
5389
|
+
colored("passed: " + passed, "green") + " " +
|
|
5390
|
+
(failures.length ? colored("failed: " + failures.length, "red") : colored("failed: 0", "green")) +
|
|
5391
|
+
" total: " + tests.length;
|
|
5392
|
+
console.log(summary);
|
|
5385
5393
|
process.exit(failures.length ? 1 : 0);
|
|
5386
5394
|
}
|
|
5387
5395
|
|