rapydscript-ns 0.8.4 → 0.9.1
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 +26 -0
- package/HACKING.md +103 -103
- package/LICENSE +24 -24
- package/README.md +716 -169
- package/TODO.md +7 -2
- 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 +36 -27
- package/package.json +1 -1
- package/publish.py +37 -37
- package/release/baselib-plain-pretty.js +2358 -168
- package/release/baselib-plain-ugly.js +73 -3
- package/release/compiler.js +6283 -3093
- package/release/signatures.json +31 -30
- package/session.vim +4 -4
- package/setup.cfg +2 -2
- package/src/ast.pyj +1 -0
- package/src/baselib-builtins.pyj +340 -2
- package/src/baselib-bytes.pyj +664 -0
- package/src/baselib-errors.pyj +1 -1
- package/src/baselib-internal.pyj +267 -60
- package/src/baselib-itertools.pyj +110 -97
- package/src/baselib-str.pyj +22 -4
- package/src/compiler.pyj +36 -36
- package/src/errors.pyj +30 -30
- package/src/lib/abc.pyj +317 -0
- package/src/lib/aes.pyj +646 -646
- package/src/lib/contextlib.pyj +379 -0
- package/src/lib/copy.pyj +120 -120
- package/src/lib/dataclasses.pyj +532 -0
- package/src/lib/datetime.pyj +712 -0
- package/src/lib/elementmaker.pyj +83 -83
- package/src/lib/encodings.pyj +126 -126
- package/src/lib/enum.pyj +125 -0
- package/src/lib/gettext.pyj +569 -569
- package/src/lib/io.pyj +500 -0
- package/src/lib/itertools.pyj +580 -580
- package/src/lib/json.pyj +227 -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/re.pyj +504 -470
- package/src/lib/react.pyj +74 -74
- package/src/lib/traceback.pyj +63 -63
- package/src/lib/typing.pyj +577 -0
- package/src/lib/uuid.pyj +77 -77
- package/src/monaco-language-service/builtins.js +14 -4
- package/src/monaco-language-service/diagnostics.js +19 -20
- package/src/monaco-language-service/dts.js +550 -550
- package/src/output/classes.pyj +62 -26
- package/src/output/comments.pyj +45 -45
- package/src/output/exceptions.pyj +201 -201
- package/src/output/functions.pyj +78 -5
- package/src/output/jsx.pyj +164 -164
- package/src/output/loops.pyj +5 -2
- package/src/output/operators.pyj +100 -34
- package/src/output/treeshake.pyj +182 -182
- package/src/output/utils.pyj +72 -72
- package/src/parse.pyj +80 -16
- package/src/string_interpolation.pyj +72 -72
- package/src/tokenizer.pyj +10 -5
- 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/abc.pyj +291 -0
- package/test/aes_vectors.pyj +421 -421
- package/test/annotations.pyj +80 -80
- package/test/arithmetic_nostrict.pyj +88 -0
- package/test/arithmetic_types.pyj +169 -0
- package/test/baselib.pyj +91 -0
- package/test/bytes.pyj +467 -0
- package/test/classes.pyj +1 -0
- package/test/comparison_ops.pyj +173 -0
- package/test/contextlib.pyj +362 -0
- package/test/dataclasses.pyj +253 -0
- package/test/datetime.pyj +500 -0
- package/test/debugger_stmt.pyj +41 -0
- package/test/decorators.pyj +77 -77
- package/test/docstrings.pyj +39 -39
- package/test/elementmaker_test.pyj +45 -45
- package/test/enum.pyj +134 -0
- package/test/eval_exec.pyj +56 -0
- package/test/format.pyj +148 -0
- package/test/functions.pyj +151 -151
- package/test/generators.pyj +41 -41
- package/test/generic.pyj +370 -370
- package/test/imports.pyj +72 -72
- package/test/internationalization.pyj +73 -73
- package/test/io.pyj +316 -0
- package/test/json.pyj +196 -0
- package/test/lint.pyj +164 -164
- package/test/loops.pyj +85 -85
- package/test/numpy.pyj +734 -734
- package/test/object.pyj +64 -0
- package/test/omit_function_metadata.pyj +20 -20
- package/test/python_compat.pyj +17 -15
- package/test/python_features.pyj +70 -15
- package/test/regexp.pyj +83 -55
- package/test/repl.pyj +121 -121
- package/test/scoped_flags.pyj +76 -76
- package/test/tuples.pyj +96 -0
- package/test/typing.pyj +469 -0
- package/test/unit/index.js +116 -7
- package/test/unit/language-service-dts.js +543 -543
- package/test/unit/language-service-hover.js +455 -455
- package/test/unit/language-service.js +84 -0
- package/test/unit/web-repl.js +1337 -1
- package/test/vars_locals_globals.pyj +94 -0
- package/tools/cli.js +558 -547
- package/tools/compile.js +224 -219
- package/tools/completer.js +131 -131
- package/tools/embedded_compiler.js +262 -251
- package/tools/gettext.js +185 -185
- package/tools/ini.js +65 -65
- package/tools/lint.js +16 -19
- 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 +252 -252
- 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/PYTHON_DIFFERENCES_REPORT.md +0 -291
- package/PYTHON_FEATURE_COVERAGE.md +0 -200
package/test/docstrings.pyj
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
# vim:fileencoding=utf-8
|
|
2
|
-
# License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
3
|
-
# globals: ρσ_module_doc__
|
|
4
|
-
|
|
5
|
-
import _import_one
|
|
6
|
-
|
|
7
|
-
def f():
|
|
8
|
-
" A basic docstring "
|
|
9
|
-
pass
|
|
10
|
-
|
|
11
|
-
assrt.equal(f.__doc__, 'A basic docstring')
|
|
12
|
-
assrt.equal(_import_one.__doc__, 'Module level ds1\n\nModule level ds2\nline2\n\nModule level ds 3')
|
|
13
|
-
|
|
14
|
-
def g():
|
|
15
|
-
'''
|
|
16
|
-
A more complex docstring:
|
|
17
|
-
xxx
|
|
18
|
-
yyyy
|
|
19
|
-
|
|
20
|
-
the end
|
|
21
|
-
'''
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
assrt.equal(g.__doc__, 'A more complex docstring:\n xxx\n yyyy\n\nthe end')
|
|
25
|
-
|
|
26
|
-
class D:
|
|
27
|
-
' Docstring for a class '
|
|
28
|
-
|
|
29
|
-
def method(self):
|
|
30
|
-
'ds for a method'
|
|
31
|
-
pass
|
|
32
|
-
|
|
33
|
-
assrt.equal(D().__doc__, 'Docstring for a class')
|
|
34
|
-
assrt.equal(D().method.__doc__, 'ds for a method')
|
|
35
|
-
|
|
36
|
-
x = def():
|
|
37
|
-
'xxx'
|
|
38
|
-
|
|
39
|
-
assrt.equal(x.__doc__, 'xxx')
|
|
1
|
+
# vim:fileencoding=utf-8
|
|
2
|
+
# License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
3
|
+
# globals: ρσ_module_doc__
|
|
4
|
+
|
|
5
|
+
import _import_one
|
|
6
|
+
|
|
7
|
+
def f():
|
|
8
|
+
" A basic docstring "
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
assrt.equal(f.__doc__, 'A basic docstring')
|
|
12
|
+
assrt.equal(_import_one.__doc__, 'Module level ds1\n\nModule level ds2\nline2\n\nModule level ds 3')
|
|
13
|
+
|
|
14
|
+
def g():
|
|
15
|
+
'''
|
|
16
|
+
A more complex docstring:
|
|
17
|
+
xxx
|
|
18
|
+
yyyy
|
|
19
|
+
|
|
20
|
+
the end
|
|
21
|
+
'''
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
assrt.equal(g.__doc__, 'A more complex docstring:\n xxx\n yyyy\n\nthe end')
|
|
25
|
+
|
|
26
|
+
class D:
|
|
27
|
+
' Docstring for a class '
|
|
28
|
+
|
|
29
|
+
def method(self):
|
|
30
|
+
'ds for a method'
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
assrt.equal(D().__doc__, 'Docstring for a class')
|
|
34
|
+
assrt.equal(D().method.__doc__, 'ds for a method')
|
|
35
|
+
|
|
36
|
+
x = def():
|
|
37
|
+
'xxx'
|
|
38
|
+
|
|
39
|
+
assrt.equal(x.__doc__, 'xxx')
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
# vim:fileencoding=utf-8
|
|
2
|
-
# License: BSD Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
|
3
|
-
# globals: assrt
|
|
4
|
-
|
|
5
|
-
from elementmaker import E
|
|
6
|
-
|
|
7
|
-
eq = assrt.equal
|
|
8
|
-
|
|
9
|
-
def dummy_elem_eq(a, b):
|
|
10
|
-
eq(jstype(a), jstype(b))
|
|
11
|
-
if (jstype(a) == 'string'):
|
|
12
|
-
eq(a, b)
|
|
13
|
-
return
|
|
14
|
-
eq(a.attributes.length, b.attributes.length)
|
|
15
|
-
eq(a.children.length, b.children.length)
|
|
16
|
-
eq(a.name, b.name)
|
|
17
|
-
for attr in a.attributes:
|
|
18
|
-
eq(a[attr], b[attr])
|
|
19
|
-
for i, child in enumerate(a.children):
|
|
20
|
-
dummy_elem_eq(child, b.children[i])
|
|
21
|
-
|
|
22
|
-
q = E.div('text', id='1', class_='c', data_x='x')
|
|
23
|
-
dummy_elem_eq(q, {'name':'div', 'children':['text'], 'attributes':{'id':'1', 'class':'c', 'data-x': 'x'}})
|
|
24
|
-
|
|
25
|
-
q = E.div(
|
|
26
|
-
E.span('a'),
|
|
27
|
-
E.span('b'),
|
|
28
|
-
E.a(),
|
|
29
|
-
id='1',
|
|
30
|
-
boolean_attr=True,
|
|
31
|
-
)
|
|
32
|
-
dummy_elem_eq(q, {'name':'div', 'children':[
|
|
33
|
-
{
|
|
34
|
-
'name':'span',
|
|
35
|
-
'children':['a'], 'attributes': {}
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
'name':'span',
|
|
39
|
-
'children':['b'], 'attributes': {}
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
'name':'a',
|
|
43
|
-
'children':[], 'attributes': {}
|
|
44
|
-
},
|
|
45
|
-
], 'attributes':{'id':'1', 'boolean-attr':'boolean-attr'}})
|
|
1
|
+
# vim:fileencoding=utf-8
|
|
2
|
+
# License: BSD Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
|
3
|
+
# globals: assrt
|
|
4
|
+
|
|
5
|
+
from elementmaker import E
|
|
6
|
+
|
|
7
|
+
eq = assrt.equal
|
|
8
|
+
|
|
9
|
+
def dummy_elem_eq(a, b):
|
|
10
|
+
eq(jstype(a), jstype(b))
|
|
11
|
+
if (jstype(a) == 'string'):
|
|
12
|
+
eq(a, b)
|
|
13
|
+
return
|
|
14
|
+
eq(a.attributes.length, b.attributes.length)
|
|
15
|
+
eq(a.children.length, b.children.length)
|
|
16
|
+
eq(a.name, b.name)
|
|
17
|
+
for attr in a.attributes:
|
|
18
|
+
eq(a[attr], b[attr])
|
|
19
|
+
for i, child in enumerate(a.children):
|
|
20
|
+
dummy_elem_eq(child, b.children[i])
|
|
21
|
+
|
|
22
|
+
q = E.div('text', id='1', class_='c', data_x='x')
|
|
23
|
+
dummy_elem_eq(q, {'name':'div', 'children':['text'], 'attributes':{'id':'1', 'class':'c', 'data-x': 'x'}})
|
|
24
|
+
|
|
25
|
+
q = E.div(
|
|
26
|
+
E.span('a'),
|
|
27
|
+
E.span('b'),
|
|
28
|
+
E.a(),
|
|
29
|
+
id='1',
|
|
30
|
+
boolean_attr=True,
|
|
31
|
+
)
|
|
32
|
+
dummy_elem_eq(q, {'name':'div', 'children':[
|
|
33
|
+
{
|
|
34
|
+
'name':'span',
|
|
35
|
+
'children':['a'], 'attributes': {}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
'name':'span',
|
|
39
|
+
'children':['b'], 'attributes': {}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
'name':'a',
|
|
43
|
+
'children':[], 'attributes': {}
|
|
44
|
+
},
|
|
45
|
+
], 'attributes':{'id':'1', 'boolean-attr':'boolean-attr'}})
|
package/test/enum.pyj
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# enum.pyj
|
|
5
|
+
# Tests for the enum standard library (from enum import Enum).
|
|
6
|
+
|
|
7
|
+
ae = assrt.equal
|
|
8
|
+
ade = assrt.deepEqual
|
|
9
|
+
ok = assrt.ok
|
|
10
|
+
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
# ── 1. Basic member access: .name and .value ─────────────────────────────────
|
|
14
|
+
# STATUS: ✓ WORKS
|
|
15
|
+
|
|
16
|
+
class _Color(Enum):
|
|
17
|
+
RED = 1
|
|
18
|
+
GREEN = 2
|
|
19
|
+
BLUE = 3
|
|
20
|
+
|
|
21
|
+
ae(_Color.RED.name, 'RED')
|
|
22
|
+
ae(_Color.RED.value, 1)
|
|
23
|
+
ae(_Color.GREEN.name, 'GREEN')
|
|
24
|
+
ae(_Color.GREEN.value, 2)
|
|
25
|
+
ae(_Color.BLUE.name, 'BLUE')
|
|
26
|
+
ae(_Color.BLUE.value, 3)
|
|
27
|
+
|
|
28
|
+
# ── 2. Member singletons (identity) ──────────────────────────────────────────
|
|
29
|
+
# STATUS: ✓ WORKS
|
|
30
|
+
|
|
31
|
+
ok(_Color.RED is _Color.RED)
|
|
32
|
+
ok(_Color.RED is not _Color.GREEN)
|
|
33
|
+
ok(_Color.GREEN is not _Color.BLUE)
|
|
34
|
+
|
|
35
|
+
# ── 3. repr and str ──────────────────────────────────────────────────────────
|
|
36
|
+
# STATUS: ✓ WORKS
|
|
37
|
+
# Note: repr() for integer values uses the numeric representation.
|
|
38
|
+
# The class name in repr/str reflects the actual JS constructor name.
|
|
39
|
+
|
|
40
|
+
ae(repr(_Color.RED), '<_Color.RED: 1>')
|
|
41
|
+
ae(repr(_Color.GREEN), '<_Color.GREEN: 2>')
|
|
42
|
+
ae(str(_Color.RED), '_Color.RED')
|
|
43
|
+
ae(str(_Color.BLUE), '_Color.BLUE')
|
|
44
|
+
|
|
45
|
+
# ── 4. isinstance checks ─────────────────────────────────────────────────────
|
|
46
|
+
# STATUS: ✓ WORKS
|
|
47
|
+
|
|
48
|
+
ok(isinstance(_Color.RED, _Color))
|
|
49
|
+
ok(isinstance(_Color.RED, Enum))
|
|
50
|
+
ok(isinstance(_Color.GREEN, _Color))
|
|
51
|
+
ok(not isinstance(1, _Color))
|
|
52
|
+
ok(not isinstance(1, Enum))
|
|
53
|
+
|
|
54
|
+
# ── 5. Iteration via list() ───────────────────────────────────────────────────
|
|
55
|
+
# STATUS: ✓ WORKS
|
|
56
|
+
|
|
57
|
+
members = list(_Color)
|
|
58
|
+
ae(len(members), 3)
|
|
59
|
+
ae(members[0].name, 'RED')
|
|
60
|
+
ae(members[1].name, 'GREEN')
|
|
61
|
+
ae(members[2].name, 'BLUE')
|
|
62
|
+
ae(members[0].value, 1)
|
|
63
|
+
ae(members[1].value, 2)
|
|
64
|
+
ae(members[2].value, 3)
|
|
65
|
+
|
|
66
|
+
# ── 6. for loop iteration ─────────────────────────────────────────────────────
|
|
67
|
+
# STATUS: ✓ WORKS
|
|
68
|
+
|
|
69
|
+
_found_names = []
|
|
70
|
+
for _m in _Color:
|
|
71
|
+
_found_names.push(_m.name)
|
|
72
|
+
ade(_found_names, ['RED', 'GREEN', 'BLUE'])
|
|
73
|
+
|
|
74
|
+
_found_values = []
|
|
75
|
+
for _m in _Color:
|
|
76
|
+
_found_values.push(_m.value)
|
|
77
|
+
ade(_found_values, [1, 2, 3])
|
|
78
|
+
|
|
79
|
+
# ── 7. _member_names_ registry ───────────────────────────────────────────────
|
|
80
|
+
# STATUS: ✓ WORKS
|
|
81
|
+
|
|
82
|
+
ade(_Color._member_names_, ['RED', 'GREEN', 'BLUE'])
|
|
83
|
+
|
|
84
|
+
# ── 8. String-valued enum ─────────────────────────────────────────────────────
|
|
85
|
+
# STATUS: ✓ WORKS
|
|
86
|
+
|
|
87
|
+
class _Direction(Enum):
|
|
88
|
+
NORTH = 'north'
|
|
89
|
+
SOUTH = 'south'
|
|
90
|
+
EAST = 'east'
|
|
91
|
+
WEST = 'west'
|
|
92
|
+
|
|
93
|
+
ae(_Direction.NORTH.name, 'NORTH')
|
|
94
|
+
ae(_Direction.NORTH.value, 'north')
|
|
95
|
+
ae(_Direction.SOUTH.value, 'south')
|
|
96
|
+
# RapydScript repr() wraps strings in double quotes (JS convention)
|
|
97
|
+
ae(repr(_Direction.NORTH), '<_Direction.NORTH: "north">')
|
|
98
|
+
ae(str(_Direction.EAST), '_Direction.EAST')
|
|
99
|
+
ae(len(list(_Direction)), 4)
|
|
100
|
+
|
|
101
|
+
# ── 9. Enum subclass with extra methods ───────────────────────────────────────
|
|
102
|
+
# STATUS: ✓ WORKS
|
|
103
|
+
|
|
104
|
+
class _Status(Enum):
|
|
105
|
+
ACTIVE = 1
|
|
106
|
+
INACTIVE = 0
|
|
107
|
+
|
|
108
|
+
def is_active(self):
|
|
109
|
+
return this is _Status.ACTIVE
|
|
110
|
+
|
|
111
|
+
ok(_Status.ACTIVE.is_active())
|
|
112
|
+
ok(not _Status.INACTIVE.is_active())
|
|
113
|
+
|
|
114
|
+
# ── 10. Multiple Enum subclasses are independent ──────────────────────────────
|
|
115
|
+
# STATUS: ✓ WORKS
|
|
116
|
+
|
|
117
|
+
ae(len(list(_Color)), 3)
|
|
118
|
+
ae(len(list(_Direction)), 4)
|
|
119
|
+
ae(len(list(_Status)), 2)
|
|
120
|
+
ok(_Color._member_names_ is not _Direction._member_names_)
|
|
121
|
+
ok(_Color.RED is not _Status.ACTIVE) # different classes, different members
|
|
122
|
+
|
|
123
|
+
# ── 11. Members are not confused with plain class instances ───────────────────
|
|
124
|
+
# STATUS: ✓ WORKS
|
|
125
|
+
|
|
126
|
+
ok(isinstance(_Color.RED, _Color))
|
|
127
|
+
ok(_Color.RED is _Color.RED) # same object every time
|
|
128
|
+
|
|
129
|
+
# ── 12. Values from _members_by_value_ lookup ────────────────────────────────
|
|
130
|
+
# STATUS: ✓ WORKS
|
|
131
|
+
|
|
132
|
+
ae(_Color._members_by_value_[1].name, 'RED')
|
|
133
|
+
ae(_Color._members_by_value_[2].name, 'GREEN')
|
|
134
|
+
ae(_Color._members_by_value_[3].name, 'BLUE')
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# vim:fileencoding=utf-8
|
|
2
|
+
# globals: assrt
|
|
3
|
+
|
|
4
|
+
ae = assrt.equal
|
|
5
|
+
ade = assrt.deepEqual
|
|
6
|
+
ok = assrt.ok
|
|
7
|
+
|
|
8
|
+
# ── eval — basic expression evaluation ────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
ae(eval("1 + 2"), 3)
|
|
11
|
+
ae(eval("10 * 5"), 50)
|
|
12
|
+
ae(eval("100 - 37"), 63)
|
|
13
|
+
ae(eval('"hello" + " world"'), "hello world")
|
|
14
|
+
ae(eval("Math.pow(2, 8)"), 256)
|
|
15
|
+
ae(eval("True"), True)
|
|
16
|
+
ae(eval("None"), None)
|
|
17
|
+
|
|
18
|
+
# ── eval — with explicit globals dict ─────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
ae(eval("x + y", {"x": 10, "y": 5}), 15)
|
|
21
|
+
ae(eval("a * b", {"a": 3, "b": 4}), 12)
|
|
22
|
+
ae(eval("n * n", {"n": 7}), 49)
|
|
23
|
+
|
|
24
|
+
# ── eval — locals override globals ────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
ae(eval("x", {"x": 1}, {"x": 99}), 99)
|
|
27
|
+
ae(eval("x + y", {"x": 10, "y": 20}, {"y": 1}), 11)
|
|
28
|
+
|
|
29
|
+
# ── exec — always returns None ────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
ae(exec("1 + 2"), None)
|
|
32
|
+
ae(exec("_ignored = 42"), None)
|
|
33
|
+
|
|
34
|
+
# ── exec — side effects via mutable objects in globals ────────────────────────
|
|
35
|
+
|
|
36
|
+
log = []
|
|
37
|
+
exec("log.push('hello')", {"log": log})
|
|
38
|
+
ae(log[0], "hello")
|
|
39
|
+
|
|
40
|
+
exec("log.push(1 + 2)", {"log": log})
|
|
41
|
+
ae(log[1], 3)
|
|
42
|
+
|
|
43
|
+
# exec with a function in globals
|
|
44
|
+
out = []
|
|
45
|
+
def _adder(a, b): out.push(a + b);
|
|
46
|
+
exec("fn(10, 7)", {"fn": _adder, "out": out})
|
|
47
|
+
ae(out[0], 17)
|
|
48
|
+
|
|
49
|
+
# exec returns None even when globals/locals provided
|
|
50
|
+
ae(exec("1 + 1", {"x": 5}), None)
|
|
51
|
+
|
|
52
|
+
# ── exec — JS loop, mutation visible in caller ────────────────────────────────
|
|
53
|
+
|
|
54
|
+
nums = []
|
|
55
|
+
exec("for _i in range(4):\n nums.append(_i * _i)", {"nums": nums})
|
|
56
|
+
ade(nums, [0, 1, 4, 9])
|
package/test/format.pyj
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# vim:fileencoding=utf-8
|
|
2
|
+
# License: BSD
|
|
3
|
+
# Tests for __format__ dunder dispatch in format(), str.format(), and f-strings
|
|
4
|
+
# globals: assrt
|
|
5
|
+
|
|
6
|
+
ae = assrt.equal
|
|
7
|
+
|
|
8
|
+
# ── Basic custom __format__ ───────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
class Currency:
|
|
11
|
+
def __init__(self, amount):
|
|
12
|
+
self.amount = amount
|
|
13
|
+
def __str__(self):
|
|
14
|
+
return str(self.amount)
|
|
15
|
+
def __format__(self, spec):
|
|
16
|
+
if spec == 'usd':
|
|
17
|
+
return '$' + str(self.amount)
|
|
18
|
+
if spec == 'eur':
|
|
19
|
+
return '€' + str(self.amount)
|
|
20
|
+
return format(self.amount, spec)
|
|
21
|
+
|
|
22
|
+
c = Currency(42)
|
|
23
|
+
|
|
24
|
+
# format() builtin dispatches to __format__
|
|
25
|
+
ae(format(c, 'usd'), '$42')
|
|
26
|
+
ae(format(c, 'eur'), '€42')
|
|
27
|
+
ae(format(c, '.2f'), '42.00')
|
|
28
|
+
|
|
29
|
+
# str.format() dispatches to __format__
|
|
30
|
+
ae(str.format('{:usd}', c), '$42')
|
|
31
|
+
ae(str.format('{:eur}', c), '€42')
|
|
32
|
+
ae(str.format('{:.2f}', c), '42.00')
|
|
33
|
+
|
|
34
|
+
# f-strings dispatch to __format__
|
|
35
|
+
ae(f'{c:usd}', '$42')
|
|
36
|
+
ae(f'{c:eur}', '€42')
|
|
37
|
+
ae(f'{c:.2f}', '42.00')
|
|
38
|
+
|
|
39
|
+
# ── format() with empty spec calls __format__('') ────────────────────────────
|
|
40
|
+
|
|
41
|
+
class Tagged:
|
|
42
|
+
def __init__(self, v):
|
|
43
|
+
self.v = v
|
|
44
|
+
def __str__(self):
|
|
45
|
+
return 'str:' + str(self.v)
|
|
46
|
+
def __format__(self, spec):
|
|
47
|
+
if not spec:
|
|
48
|
+
return 'fmt:' + str(self.v)
|
|
49
|
+
return 'fmt:' + format(self.v, spec)
|
|
50
|
+
|
|
51
|
+
t = Tagged(7)
|
|
52
|
+
ae(format(t), 'fmt:7')
|
|
53
|
+
ae(format(t, ''), 'fmt:7')
|
|
54
|
+
ae(str.format('{}', t), 'fmt:7')
|
|
55
|
+
ae(f'{t}', 'fmt:7')
|
|
56
|
+
|
|
57
|
+
# ── Default __format__ delegates to __str__ when spec is empty ────────────────
|
|
58
|
+
|
|
59
|
+
class Simple:
|
|
60
|
+
def __str__(self):
|
|
61
|
+
return 'simple!'
|
|
62
|
+
|
|
63
|
+
s = Simple()
|
|
64
|
+
ae(format(s), 'simple!')
|
|
65
|
+
ae(format(s, ''), 'simple!')
|
|
66
|
+
ae(str.format('{}', s), 'simple!')
|
|
67
|
+
ae(f'{s}', 'simple!')
|
|
68
|
+
|
|
69
|
+
# ── Default __format__ raises TypeError for non-empty spec ───────────────────
|
|
70
|
+
|
|
71
|
+
class Plain:
|
|
72
|
+
def __str__(self):
|
|
73
|
+
return 'plain'
|
|
74
|
+
|
|
75
|
+
p = Plain()
|
|
76
|
+
raised = False
|
|
77
|
+
try:
|
|
78
|
+
format(p, 'd')
|
|
79
|
+
except TypeError:
|
|
80
|
+
raised = True
|
|
81
|
+
ae(raised, True)
|
|
82
|
+
|
|
83
|
+
raised = False
|
|
84
|
+
try:
|
|
85
|
+
str.format('{:d}', p)
|
|
86
|
+
except TypeError:
|
|
87
|
+
raised = True
|
|
88
|
+
ae(raised, True)
|
|
89
|
+
|
|
90
|
+
# ── Inheritance: subclass inherits parent __format__ ─────────────────────────
|
|
91
|
+
|
|
92
|
+
class Base:
|
|
93
|
+
def __format__(self, spec):
|
|
94
|
+
return 'base:' + spec
|
|
95
|
+
|
|
96
|
+
class Child(Base):
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
child = Child()
|
|
100
|
+
ae(format(child, 'x'), 'base:x')
|
|
101
|
+
ae(str.format('{:x}', child), 'base:x')
|
|
102
|
+
ae(f'{child:x}', 'base:x')
|
|
103
|
+
|
|
104
|
+
# ── Subclass can override __format__ ─────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
class Override(Base):
|
|
107
|
+
def __format__(self, spec):
|
|
108
|
+
return 'override:' + spec
|
|
109
|
+
|
|
110
|
+
o = Override()
|
|
111
|
+
ae(format(o, 'y'), 'override:y')
|
|
112
|
+
ae(str.format('{:y}', o), 'override:y')
|
|
113
|
+
|
|
114
|
+
# ── !r and !s transformers bypass __format__ ─────────────────────────────────
|
|
115
|
+
|
|
116
|
+
class WithRepr:
|
|
117
|
+
def __repr__(self):
|
|
118
|
+
return 'MyRepr'
|
|
119
|
+
def __str__(self):
|
|
120
|
+
return 'MyStr'
|
|
121
|
+
def __format__(self, spec):
|
|
122
|
+
return 'MyFmt'
|
|
123
|
+
|
|
124
|
+
wr = WithRepr()
|
|
125
|
+
# With no transformer, __format__ is called
|
|
126
|
+
ae(str.format('{}', wr), 'MyFmt')
|
|
127
|
+
# !s applies str(), result is a string (no __format__)
|
|
128
|
+
ae(str.format('{!s}', wr), 'MyStr')
|
|
129
|
+
# !r applies repr()
|
|
130
|
+
ae(str.format('{!r}', wr), 'MyRepr')
|
|
131
|
+
|
|
132
|
+
# ── Multiple fields with __format__ ──────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
class Qty:
|
|
135
|
+
def __init__(self, n):
|
|
136
|
+
self.n = n
|
|
137
|
+
def __str__(self):
|
|
138
|
+
return str(self.n)
|
|
139
|
+
def __format__(self, spec):
|
|
140
|
+
if spec == 'w':
|
|
141
|
+
words = ['zero', 'one', 'two', 'three', 'four', 'five']
|
|
142
|
+
return words[self.n] if self.n < len(words) else str(self.n)
|
|
143
|
+
return format(self.n, spec)
|
|
144
|
+
|
|
145
|
+
a, b = Qty(1), Qty(2)
|
|
146
|
+
ae(str.format('{:w} and {:w}', a, b), 'one and two')
|
|
147
|
+
ae(f'{a:w} + {b:w}', 'one + two')
|
|
148
|
+
ae(str.format('{0:d} + {1:d}', a, b), '1 + 2')
|