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.
Files changed (151) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +19 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/PYTHON_GAPS.md +420 -0
  8. package/README.md +153 -29
  9. package/TODO.md +16 -118
  10. package/add-toc-to-readme +2 -2
  11. package/bin/export +75 -75
  12. package/bin/rapydscript +70 -70
  13. package/bin/web-repl-export +102 -102
  14. package/build +2 -2
  15. package/language-service/index.js +237 -8
  16. package/memory/project_string_impl.md +43 -0
  17. package/package.json +1 -1
  18. package/publish.py +37 -37
  19. package/release/baselib-plain-pretty.js +248 -38
  20. package/release/baselib-plain-ugly.js +8 -8
  21. package/release/compiler.js +778 -277
  22. package/release/signatures.json +30 -30
  23. package/session.vim +4 -4
  24. package/setup.cfg +2 -2
  25. package/src/ast.pyj +4 -1
  26. package/src/baselib-builtins.pyj +56 -2
  27. package/src/baselib-containers.pyj +2 -0
  28. package/src/baselib-errors.pyj +7 -3
  29. package/src/baselib-internal.pyj +51 -6
  30. package/src/baselib-str.pyj +5 -3
  31. package/src/compiler.pyj +36 -36
  32. package/src/errors.pyj +30 -30
  33. package/src/lib/aes.pyj +646 -646
  34. package/src/lib/asyncio.pyj +534 -0
  35. package/src/lib/base64.pyj +399 -0
  36. package/src/lib/bisect.pyj +73 -0
  37. package/src/lib/collections.pyj +1 -1
  38. package/src/lib/copy.pyj +120 -120
  39. package/src/lib/csv.pyj +494 -0
  40. package/src/lib/elementmaker.pyj +83 -83
  41. package/src/lib/encodings.pyj +126 -126
  42. package/src/lib/gettext.pyj +569 -569
  43. package/src/lib/heapq.pyj +98 -0
  44. package/src/lib/html.pyj +382 -0
  45. package/src/lib/http/__init__.pyj +98 -0
  46. package/src/lib/http/client.pyj +304 -0
  47. package/src/lib/http/cookies.pyj +236 -0
  48. package/src/lib/itertools.pyj +580 -580
  49. package/src/lib/logging.pyj +672 -0
  50. package/src/lib/math.pyj +193 -193
  51. package/src/lib/operator.pyj +11 -11
  52. package/src/lib/pythonize.pyj +20 -20
  53. package/src/lib/random.pyj +118 -118
  54. package/src/lib/react.pyj +74 -74
  55. package/src/lib/string.pyj +357 -0
  56. package/src/lib/textwrap.pyj +329 -0
  57. package/src/lib/traceback.pyj +63 -63
  58. package/src/lib/urllib/__init__.pyj +14 -0
  59. package/src/lib/urllib/error.pyj +66 -0
  60. package/src/lib/urllib/parse.pyj +475 -0
  61. package/src/lib/urllib/request.pyj +86 -0
  62. package/src/lib/uuid.pyj +77 -77
  63. package/src/monaco-language-service/analyzer.js +5 -2
  64. package/src/monaco-language-service/completions.js +26 -0
  65. package/src/monaco-language-service/diagnostics.js +202 -3
  66. package/src/monaco-language-service/dts.js +550 -550
  67. package/src/monaco-language-service/scope.js +1 -0
  68. package/src/output/comments.pyj +45 -45
  69. package/src/output/exceptions.pyj +201 -201
  70. package/src/output/functions.pyj +152 -6
  71. package/src/output/jsx.pyj +164 -164
  72. package/src/output/loops.pyj +17 -2
  73. package/src/output/modules.pyj +1 -1
  74. package/src/output/operators.pyj +15 -0
  75. package/src/output/stream.pyj +0 -1
  76. package/src/output/treeshake.pyj +182 -182
  77. package/src/output/utils.pyj +72 -72
  78. package/src/parse.pyj +80 -17
  79. package/src/string_interpolation.pyj +72 -72
  80. package/src/tokenizer.pyj +1 -1
  81. package/src/unicode_aliases.pyj +576 -576
  82. package/src/utils.pyj +192 -192
  83. package/test/_import_one.pyj +37 -37
  84. package/test/_import_two/__init__.pyj +11 -11
  85. package/test/_import_two/level2/deep.pyj +4 -4
  86. package/test/_import_two/other.pyj +6 -6
  87. package/test/_import_two/sub.pyj +13 -13
  88. package/test/aes_vectors.pyj +421 -421
  89. package/test/annotations.pyj +80 -80
  90. package/test/async_generators.pyj +144 -0
  91. package/test/asyncio.pyj +307 -0
  92. package/test/base64.pyj +202 -0
  93. package/test/bisect.pyj +178 -0
  94. package/test/csv.pyj +405 -0
  95. package/test/decorators.pyj +77 -77
  96. package/test/docstrings.pyj +39 -39
  97. package/test/elementmaker_test.pyj +45 -45
  98. package/test/float_special.pyj +64 -0
  99. package/test/functions.pyj +151 -151
  100. package/test/generators.pyj +41 -41
  101. package/test/generic.pyj +370 -370
  102. package/test/heapq.pyj +174 -0
  103. package/test/html.pyj +212 -0
  104. package/test/http.pyj +259 -0
  105. package/test/imports.pyj +79 -72
  106. package/test/internationalization.pyj +73 -73
  107. package/test/lint.pyj +164 -164
  108. package/test/logging.pyj +356 -0
  109. package/test/long.pyj +130 -0
  110. package/test/loops.pyj +85 -85
  111. package/test/numpy.pyj +734 -734
  112. package/test/parenthesized_with.pyj +141 -0
  113. package/test/python_compat.pyj +3 -5
  114. package/test/python_modulo.pyj +76 -0
  115. package/test/python_modulo_off.pyj +21 -0
  116. package/test/repl.pyj +121 -121
  117. package/test/scoped_flags.pyj +76 -76
  118. package/test/str.pyj +14 -0
  119. package/test/string.pyj +245 -0
  120. package/test/textwrap.pyj +172 -0
  121. package/test/type_display.pyj +48 -0
  122. package/test/type_enforcement.pyj +164 -0
  123. package/test/unit/index.js +14 -6
  124. package/test/unit/language-service-completions.js +119 -0
  125. package/test/unit/language-service-dts.js +543 -543
  126. package/test/unit/language-service-hover.js +455 -455
  127. package/test/unit/language-service-scope.js +32 -0
  128. package/test/unit/language-service.js +127 -3
  129. package/test/unit/run-language-service.js +17 -3
  130. package/test/unit/web-repl.js +2094 -29
  131. package/test/urllib.pyj +193 -0
  132. package/tools/compile.js +1 -1
  133. package/tools/compiler.d.ts +367 -367
  134. package/tools/completer.js +131 -131
  135. package/tools/embedded_compiler.js +7 -7
  136. package/tools/gettext.js +185 -185
  137. package/tools/ini.js +65 -65
  138. package/tools/msgfmt.js +187 -187
  139. package/tools/repl.js +223 -223
  140. package/tools/test.js +118 -118
  141. package/tools/utils.js +128 -128
  142. package/tools/web_repl.js +95 -95
  143. package/try +41 -41
  144. package/web-repl/env.js +196 -196
  145. package/web-repl/index.html +163 -163
  146. package/web-repl/main.js +1 -1
  147. package/web-repl/prism.css +139 -139
  148. package/web-repl/prism.js +113 -113
  149. package/web-repl/rapydscript.js +224 -224
  150. package/web-repl/sha1.js +25 -25
  151. package/test/omit_function_metadata.pyj +0 -20
@@ -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.)
@@ -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 directly to console.log(x)",
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(failures.length + " test(s) failed.", "red"));
5382
- } else {
5383
- console.log(colored("All " + tests.length + " unit tests passed!", "green"));
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