rapydscript-ns 0.9.1 → 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/CHANGELOG.md +22 -1
- package/PYTHON_GAPS.md +420 -0
- package/README.md +154 -30
- package/TODO.md +22 -7
- package/language-service/index.js +241 -12
- package/language-service/language-service.d.ts +1 -1
- package/memory/project_string_impl.md +43 -0
- package/package.json +6 -2
- package/release/baselib-plain-pretty.js +248 -38
- package/release/baselib-plain-ugly.js +8 -8
- package/release/compiler.js +821 -305
- package/release/signatures.json +15 -15
- 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/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/csv.pyj +494 -0
- 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/logging.pyj +672 -0
- package/src/lib/pythonize.pyj +1 -1
- package/src/lib/string.pyj +357 -0
- package/src/lib/textwrap.pyj +329 -0
- 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/monaco-language-service/analyzer.js +5 -2
- package/src/monaco-language-service/completions.js +26 -0
- package/src/monaco-language-service/diagnostics.js +204 -5
- package/src/monaco-language-service/index.js +2 -2
- package/src/monaco-language-service/scope.js +1 -0
- package/src/output/functions.pyj +152 -6
- package/src/output/loops.pyj +26 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +15 -0
- package/src/output/stream.pyj +0 -1
- package/src/parse.pyj +80 -17
- package/src/tokenizer.pyj +1 -1
- 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/float_special.pyj +64 -0
- package/test/heapq.pyj +174 -0
- package/test/html.pyj +212 -0
- package/test/http.pyj +259 -0
- package/test/imports.pyj +7 -0
- package/test/logging.pyj +356 -0
- package/test/long.pyj +130 -0
- 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/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 +80 -6
- package/test/unit/language-service-completions.js +119 -0
- package/test/unit/language-service-scope.js +32 -0
- package/test/unit/language-service.js +128 -4
- 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 -0
- package/tools/embedded_compiler.js +7 -7
- package/web-repl/main.js +1 -1
- package/web-repl/rapydscript.js +3 -3
- package/test/omit_function_metadata.pyj +0 -20
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# python_modulo.pyj
|
|
5
|
+
# Tests for Python-style modulo: result takes the sign of the divisor.
|
|
6
|
+
# python_modulo defaults to True; no __python__ import needed.
|
|
7
|
+
|
|
8
|
+
ae = assrt.equal
|
|
9
|
+
ok = assrt.ok
|
|
10
|
+
|
|
11
|
+
# ── Positive operands ────────────────────────────────────────────────────────
|
|
12
|
+
ae(7 % 3, 1)
|
|
13
|
+
ae(0 % 5, 0)
|
|
14
|
+
ae(6 % 3, 0)
|
|
15
|
+
|
|
16
|
+
# ── Negative dividend ────────────────────────────────────────────────────────
|
|
17
|
+
ae(-7 % 3, 2)
|
|
18
|
+
ae(-1 % 3, 2)
|
|
19
|
+
ae(-6 % 3, 0)
|
|
20
|
+
|
|
21
|
+
# ── Negative divisor ─────────────────────────────────────────────────────────
|
|
22
|
+
ae(7 % -3, -2)
|
|
23
|
+
ae(1 % -3, -2)
|
|
24
|
+
ae(6 % -3, 0)
|
|
25
|
+
|
|
26
|
+
# ── Both negative ────────────────────────────────────────────────────────────
|
|
27
|
+
ae(-7 % -3, -1)
|
|
28
|
+
ae(-1 % -3, -1)
|
|
29
|
+
ae(-6 % -3, 0)
|
|
30
|
+
|
|
31
|
+
# ── Floats ───────────────────────────────────────────────────────────────────
|
|
32
|
+
ae(-7.5 % 2, 0.5)
|
|
33
|
+
ae(7.5 % -2, -0.5)
|
|
34
|
+
ae(2.5 % 1, 0.5)
|
|
35
|
+
|
|
36
|
+
# ── Booleans (treated as numbers) ────────────────────────────────────────────
|
|
37
|
+
ae(5 % True, 0)
|
|
38
|
+
ae(False % 3, 0)
|
|
39
|
+
|
|
40
|
+
# ── Augmented `%=` ───────────────────────────────────────────────────────────
|
|
41
|
+
x = -7
|
|
42
|
+
x %= 3
|
|
43
|
+
ae(x, 2)
|
|
44
|
+
|
|
45
|
+
y = 7
|
|
46
|
+
y %= -3
|
|
47
|
+
ae(y, -2)
|
|
48
|
+
|
|
49
|
+
z = -7
|
|
50
|
+
z %= -3
|
|
51
|
+
ae(z, -1)
|
|
52
|
+
|
|
53
|
+
# ── Inside expressions ───────────────────────────────────────────────────────
|
|
54
|
+
ae((-7 % 3) + 10, 12)
|
|
55
|
+
ae(((-10) % 3) * 2, 4)
|
|
56
|
+
|
|
57
|
+
# ── Variables (not just literals) ────────────────────────────────────────────
|
|
58
|
+
a = -7
|
|
59
|
+
b = 3
|
|
60
|
+
ae(a % b, 2)
|
|
61
|
+
|
|
62
|
+
# ── BigInt (Python semantics already worked; regression guard) ───────────────
|
|
63
|
+
ae(BigInt(-7) % BigInt(3), BigInt(2))
|
|
64
|
+
ae(BigInt(7) % BigInt(-3), BigInt(-2))
|
|
65
|
+
|
|
66
|
+
# ── Custom __mod__ wins over numeric path ────────────────────────────────────
|
|
67
|
+
# python_modulo routes through ρσ_op_mod_ns which dispatches __mod__/__rmod__
|
|
68
|
+
# even without overload_operators.
|
|
69
|
+
class Mod:
|
|
70
|
+
def __init__(self, v):
|
|
71
|
+
self.v = v
|
|
72
|
+
def __mod__(self, other):
|
|
73
|
+
return 'mod-' + self.v + '-' + other
|
|
74
|
+
|
|
75
|
+
m = Mod('a')
|
|
76
|
+
ae(m % 'b', 'mod-a-b')
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# python_modulo_off.pyj
|
|
5
|
+
# Tests for opting out of Python-style modulo via no_python_modulo.
|
|
6
|
+
# With the flag off, `%` emits raw JS modulo (sign of dividend).
|
|
7
|
+
|
|
8
|
+
from __python__ import no_python_modulo
|
|
9
|
+
|
|
10
|
+
ae = assrt.equal
|
|
11
|
+
|
|
12
|
+
# JS modulo: result takes the sign of the dividend.
|
|
13
|
+
ae(-7 % 3, -1) # Python would give 2
|
|
14
|
+
ae(7 % -3, 1) # Python would give -2
|
|
15
|
+
ae(-7 % -3, -1) # same as Python here
|
|
16
|
+
ae(7 % 3, 1) # positive case identical
|
|
17
|
+
|
|
18
|
+
# Augmented `%=` follows the same flag.
|
|
19
|
+
x = -7
|
|
20
|
+
x %= 3
|
|
21
|
+
ae(x, -1) # Python would give 2
|
package/test/str.pyj
CHANGED
|
@@ -221,3 +221,17 @@ ae('{{}}'.format(), '{}')
|
|
|
221
221
|
ae('{x}}}'.format(x=1), '1}')
|
|
222
222
|
a = 1
|
|
223
223
|
ae(f'{{ {a} }}', '{ 1 }')
|
|
224
|
+
|
|
225
|
+
# str.replace — Python semantics (replace all by default)
|
|
226
|
+
ae('bbb', 'aaa'.replace('a', 'b'))
|
|
227
|
+
ae('hello world', 'hello earth'.replace('earth', 'world'))
|
|
228
|
+
ae('baa', 'aaa'.replace('a', 'b', 1))
|
|
229
|
+
ae('bba', 'aaa'.replace('a', 'b', 2))
|
|
230
|
+
ae('aaa', 'aaa'.replace('a', 'b', 0))
|
|
231
|
+
ae('aaa', 'aaa'.replace('x', 'y'))
|
|
232
|
+
ae('', 'aaa'.replace('a', ''))
|
|
233
|
+
ae('xbxbxb', 'ababab'.replace('a', 'x'))
|
|
234
|
+
ae('a$b$c', 'a-b-c'.replace('-', '$'))
|
|
235
|
+
# negative count replaces all (same as omitting count)
|
|
236
|
+
ae('bbb', 'aaa'.replace('a', 'b', -1))
|
|
237
|
+
ae('bbb', 'aaa'.replace('a', 'b', -99))
|
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'))
|