snail-lang 0.7.2__cp38-abi3-win_amd64.whl
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.
- snail/__init__.py +35 -0
- snail/_native.pyd +0 -0
- snail/cli.py +423 -0
- snail/runtime/__init__.py +261 -0
- snail/runtime/augmented.py +49 -0
- snail/runtime/compact_try.py +13 -0
- snail/runtime/env.py +26 -0
- snail/runtime/lazy_file.py +41 -0
- snail/runtime/lazy_text.py +50 -0
- snail/runtime/regex.py +37 -0
- snail/runtime/structured_accessor.py +74 -0
- snail/runtime/subprocess.py +66 -0
- snail_lang-0.7.2.dist-info/METADATA +329 -0
- snail_lang-0.7.2.dist-info/RECORD +17 -0
- snail_lang-0.7.2.dist-info/WHEEL +4 -0
- snail_lang-0.7.2.dist-info/entry_points.txt +2 -0
- snail_lang-0.7.2.dist-info/licenses/LICENSE +20 -0
snail/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._native import __build_info__, compile, compile_ast, exec, parse, parse_ast
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _resolve_version() -> str:
|
|
7
|
+
try:
|
|
8
|
+
from importlib.metadata import version
|
|
9
|
+
|
|
10
|
+
return version("snail-lang")
|
|
11
|
+
except Exception: # pragma: no cover - during development
|
|
12
|
+
return "0.0.0"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def __getattr__(name: str):
|
|
16
|
+
if name == "__version__":
|
|
17
|
+
value = _resolve_version()
|
|
18
|
+
globals()["__version__"] = value
|
|
19
|
+
return value
|
|
20
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def __dir__() -> list[str]:
|
|
24
|
+
return sorted(list(globals().keys()) + ["__version__"])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"compile",
|
|
29
|
+
"compile_ast",
|
|
30
|
+
"exec",
|
|
31
|
+
"parse",
|
|
32
|
+
"parse_ast",
|
|
33
|
+
"__version__",
|
|
34
|
+
"__build_info__",
|
|
35
|
+
]
|
snail/_native.pyd
ADDED
|
Binary file
|
snail/cli.py
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from . import __build_info__, compile_ast, exec
|
|
8
|
+
|
|
9
|
+
_USAGE = (
|
|
10
|
+
"snail [options] -f <file> [args]...\n"
|
|
11
|
+
" snail [options] <code> [args]..."
|
|
12
|
+
)
|
|
13
|
+
_DESCRIPTION = "Snail programming language interpreter"
|
|
14
|
+
_BOOLEAN_FLAGS = frozenset("amPIvh")
|
|
15
|
+
_VALUE_FLAGS = frozenset("fbe")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _display_filename(filename: str) -> str:
|
|
19
|
+
if filename.startswith("snail:"):
|
|
20
|
+
return filename
|
|
21
|
+
return f"snail:{filename}"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _trim_internal_prefix(stack, internal_files: set[str]) -> None:
|
|
25
|
+
if not stack:
|
|
26
|
+
return
|
|
27
|
+
trim_count = 0
|
|
28
|
+
for frame in stack:
|
|
29
|
+
filename = frame.filename
|
|
30
|
+
if filename.startswith("snail:"):
|
|
31
|
+
break
|
|
32
|
+
if filename in internal_files:
|
|
33
|
+
trim_count += 1
|
|
34
|
+
continue
|
|
35
|
+
if os.path.isabs(filename) and os.path.abspath(filename) in internal_files:
|
|
36
|
+
trim_count += 1
|
|
37
|
+
continue
|
|
38
|
+
break
|
|
39
|
+
if 0 < trim_count < len(stack):
|
|
40
|
+
del stack[:trim_count]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _trim_traceback_exception(tb_exc, internal_files: set[str]) -> None:
|
|
44
|
+
_trim_internal_prefix(tb_exc.stack, internal_files)
|
|
45
|
+
cause = getattr(tb_exc, "__cause__", None)
|
|
46
|
+
if cause is not None:
|
|
47
|
+
_trim_traceback_exception(cause, internal_files)
|
|
48
|
+
context = getattr(tb_exc, "__context__", None)
|
|
49
|
+
if context is not None:
|
|
50
|
+
_trim_traceback_exception(context, internal_files)
|
|
51
|
+
for group_exc in getattr(tb_exc, "exceptions", ()) or ():
|
|
52
|
+
_trim_traceback_exception(group_exc, internal_files)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _install_trimmed_excepthook() -> None:
|
|
56
|
+
entrypoint = os.path.abspath(sys.argv[0])
|
|
57
|
+
cli_path = os.path.abspath(__file__)
|
|
58
|
+
internal_files = {entrypoint, cli_path}
|
|
59
|
+
original_excepthook = sys.excepthook
|
|
60
|
+
|
|
61
|
+
def _snail_excepthook(
|
|
62
|
+
exc_type: type[BaseException],
|
|
63
|
+
exc: BaseException,
|
|
64
|
+
tb: object,
|
|
65
|
+
) -> None:
|
|
66
|
+
if exc_type is KeyboardInterrupt:
|
|
67
|
+
return original_excepthook(exc_type, exc, tb)
|
|
68
|
+
import traceback
|
|
69
|
+
|
|
70
|
+
tb_exc = traceback.TracebackException(
|
|
71
|
+
exc_type,
|
|
72
|
+
exc,
|
|
73
|
+
tb,
|
|
74
|
+
capture_locals=False,
|
|
75
|
+
)
|
|
76
|
+
_trim_traceback_exception(tb_exc, internal_files)
|
|
77
|
+
try:
|
|
78
|
+
import _colorize
|
|
79
|
+
|
|
80
|
+
colorize = _colorize.can_colorize(file=sys.stderr)
|
|
81
|
+
except Exception:
|
|
82
|
+
colorize = hasattr(sys.stderr, "isatty") and sys.stderr.isatty()
|
|
83
|
+
try:
|
|
84
|
+
formatted = tb_exc.format(colorize=colorize)
|
|
85
|
+
except TypeError:
|
|
86
|
+
formatted = tb_exc.format()
|
|
87
|
+
for line in formatted:
|
|
88
|
+
sys.stderr.write(line)
|
|
89
|
+
|
|
90
|
+
sys.excepthook = _snail_excepthook
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class _Args:
|
|
94
|
+
def __init__(self) -> None:
|
|
95
|
+
self.file: Optional[str] = None
|
|
96
|
+
self.awk = False
|
|
97
|
+
self.map = False
|
|
98
|
+
self.no_print = False
|
|
99
|
+
self.no_auto_import = False
|
|
100
|
+
self.debug = False
|
|
101
|
+
self.debug_snail_ast = False
|
|
102
|
+
self.debug_python_ast = False
|
|
103
|
+
self.version = False
|
|
104
|
+
self.help = False
|
|
105
|
+
self.begin_code: list[str] = []
|
|
106
|
+
self.end_code: list[str] = []
|
|
107
|
+
self.args: list[str] = []
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _print_help(file=None) -> None:
|
|
111
|
+
if file is None:
|
|
112
|
+
file = sys.stdout
|
|
113
|
+
print(f"usage: {_USAGE}", file=file)
|
|
114
|
+
print("", file=file)
|
|
115
|
+
print(_DESCRIPTION, file=file)
|
|
116
|
+
print("", file=file)
|
|
117
|
+
print("options:", file=file)
|
|
118
|
+
print(" -f <file> read Snail source from file", file=file)
|
|
119
|
+
print(" -a, --awk awk mode", file=file)
|
|
120
|
+
print(" -m, --map map mode (process files one at a time)", file=file)
|
|
121
|
+
print(
|
|
122
|
+
" -b, --begin <code> begin block code (awk/map mode, repeatable)",
|
|
123
|
+
file=file,
|
|
124
|
+
)
|
|
125
|
+
print(
|
|
126
|
+
" -e, --end <code> end block code (awk/map mode, repeatable)",
|
|
127
|
+
file=file,
|
|
128
|
+
)
|
|
129
|
+
print(
|
|
130
|
+
" -P, --no-print disable auto-print of implicit return value",
|
|
131
|
+
file=file,
|
|
132
|
+
)
|
|
133
|
+
print(" -I, --no-auto-import disable auto-imports", file=file)
|
|
134
|
+
print(" --debug parse and compile, then print, do not run", file=file)
|
|
135
|
+
print(" --debug-snail-ast parse and print Snail AST, do not run", file=file)
|
|
136
|
+
print(" --debug-python-ast parse and print Python AST, do not run", file=file)
|
|
137
|
+
print(" -v, --version show version and exit", file=file)
|
|
138
|
+
print(" -h, --help show this help message and exit", file=file)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _expand_short_options(argv: list[str]) -> list[str]:
|
|
142
|
+
expanded: list[str] = []
|
|
143
|
+
idx = 0
|
|
144
|
+
while idx < len(argv):
|
|
145
|
+
token = argv[idx]
|
|
146
|
+
if token == "--":
|
|
147
|
+
expanded.append(token)
|
|
148
|
+
expanded.extend(argv[idx + 1 :])
|
|
149
|
+
return expanded
|
|
150
|
+
if token == "-" or not token.startswith("-") or token.startswith("--"):
|
|
151
|
+
expanded.append(token)
|
|
152
|
+
idx += 1
|
|
153
|
+
continue
|
|
154
|
+
if len(token) == 2:
|
|
155
|
+
expanded.append(token)
|
|
156
|
+
idx += 1
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
flags = token[1:]
|
|
160
|
+
pos = 0
|
|
161
|
+
while pos < len(flags):
|
|
162
|
+
flag = flags[pos]
|
|
163
|
+
if flag in _BOOLEAN_FLAGS:
|
|
164
|
+
expanded.append(f"-{flag}")
|
|
165
|
+
pos += 1
|
|
166
|
+
continue
|
|
167
|
+
if flag in _VALUE_FLAGS:
|
|
168
|
+
remainder = flags[pos + 1 :]
|
|
169
|
+
if not remainder:
|
|
170
|
+
expanded.append(f"-{flag}")
|
|
171
|
+
pos += 1
|
|
172
|
+
continue
|
|
173
|
+
if all(
|
|
174
|
+
ch in _BOOLEAN_FLAGS or ch in _VALUE_FLAGS for ch in remainder
|
|
175
|
+
):
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"option -{flag} requires an argument and must be last in a "
|
|
178
|
+
"combined flag group"
|
|
179
|
+
)
|
|
180
|
+
expanded.append(f"-{flag}")
|
|
181
|
+
expanded.append(remainder)
|
|
182
|
+
pos = len(flags)
|
|
183
|
+
break
|
|
184
|
+
raise ValueError(f"unknown option: -{flag}")
|
|
185
|
+
idx += 1
|
|
186
|
+
return expanded
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _parse_args(argv: list[str]) -> _Args:
|
|
190
|
+
argv = _expand_short_options(argv)
|
|
191
|
+
args = _Args()
|
|
192
|
+
idx = 0
|
|
193
|
+
code_found = False
|
|
194
|
+
while idx < len(argv):
|
|
195
|
+
token = argv[idx]
|
|
196
|
+
if token == "--":
|
|
197
|
+
args.args = argv[idx + 1 :]
|
|
198
|
+
return args
|
|
199
|
+
if token == "-" or not token.startswith("-"):
|
|
200
|
+
if not code_found:
|
|
201
|
+
# This is the code (or the first arg when -f is used)
|
|
202
|
+
args.args = [token]
|
|
203
|
+
code_found = True
|
|
204
|
+
else:
|
|
205
|
+
args.args.append(token)
|
|
206
|
+
idx += 1
|
|
207
|
+
continue
|
|
208
|
+
if token in ("-h", "--help"):
|
|
209
|
+
args.help = True
|
|
210
|
+
return args
|
|
211
|
+
if token in ("-v", "--version"):
|
|
212
|
+
args.version = True
|
|
213
|
+
idx += 1
|
|
214
|
+
continue
|
|
215
|
+
if token in ("-a", "--awk"):
|
|
216
|
+
args.awk = True
|
|
217
|
+
idx += 1
|
|
218
|
+
continue
|
|
219
|
+
if token in ("-m", "--map"):
|
|
220
|
+
args.map = True
|
|
221
|
+
idx += 1
|
|
222
|
+
continue
|
|
223
|
+
if token in ("-P", "--no-print"):
|
|
224
|
+
args.no_print = True
|
|
225
|
+
idx += 1
|
|
226
|
+
continue
|
|
227
|
+
if token in ("-I", "--no-auto-import"):
|
|
228
|
+
args.no_auto_import = True
|
|
229
|
+
idx += 1
|
|
230
|
+
continue
|
|
231
|
+
if token == "--debug":
|
|
232
|
+
args.debug = True
|
|
233
|
+
idx += 1
|
|
234
|
+
continue
|
|
235
|
+
if token == "--debug-snail-ast":
|
|
236
|
+
args.debug_snail_ast = True
|
|
237
|
+
idx += 1
|
|
238
|
+
continue
|
|
239
|
+
if token == "--debug-python-ast":
|
|
240
|
+
args.debug_python_ast = True
|
|
241
|
+
idx += 1
|
|
242
|
+
continue
|
|
243
|
+
if token == "-f":
|
|
244
|
+
if idx + 1 >= len(argv):
|
|
245
|
+
raise ValueError("option -f requires an argument")
|
|
246
|
+
args.file = argv[idx + 1]
|
|
247
|
+
code_found = True
|
|
248
|
+
idx += 2
|
|
249
|
+
continue
|
|
250
|
+
if token in ("-b", "--begin"):
|
|
251
|
+
if idx + 1 >= len(argv):
|
|
252
|
+
raise ValueError(f"option {token} requires an argument")
|
|
253
|
+
args.begin_code.append(argv[idx + 1])
|
|
254
|
+
idx += 2
|
|
255
|
+
continue
|
|
256
|
+
if token in ("-e", "--end"):
|
|
257
|
+
if idx + 1 >= len(argv):
|
|
258
|
+
raise ValueError(f"option {token} requires an argument")
|
|
259
|
+
args.end_code.append(argv[idx + 1])
|
|
260
|
+
idx += 2
|
|
261
|
+
continue
|
|
262
|
+
raise ValueError(f"unknown option: {token}")
|
|
263
|
+
return args
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _format_version(version: str, build_info: Optional[dict[str, object]]) -> str:
|
|
267
|
+
display_version = version if version.startswith("v") else f"v{version}"
|
|
268
|
+
if not build_info:
|
|
269
|
+
return display_version
|
|
270
|
+
git_rev = build_info.get("git_rev")
|
|
271
|
+
if not git_rev:
|
|
272
|
+
return display_version
|
|
273
|
+
|
|
274
|
+
suffixes: list[str] = []
|
|
275
|
+
if build_info.get("dirty"):
|
|
276
|
+
suffixes.append("!dirty")
|
|
277
|
+
if build_info.get("untagged"):
|
|
278
|
+
suffixes.append("!untagged")
|
|
279
|
+
|
|
280
|
+
if suffixes:
|
|
281
|
+
return f"{display_version} ({git_rev}) {' '.join(suffixes)}"
|
|
282
|
+
return f"{display_version} ({git_rev})"
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _get_version() -> str:
|
|
286
|
+
from . import __version__ as version
|
|
287
|
+
|
|
288
|
+
return version
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _format_python_runtime() -> str:
|
|
292
|
+
version = (
|
|
293
|
+
f"{sys.version_info.major}."
|
|
294
|
+
f"{sys.version_info.minor}."
|
|
295
|
+
f"{sys.version_info.micro}"
|
|
296
|
+
)
|
|
297
|
+
executable = sys.executable or "<unknown>"
|
|
298
|
+
if executable != "<unknown>":
|
|
299
|
+
executable = os.path.abspath(executable)
|
|
300
|
+
return f"Python {version} ({executable})"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
304
|
+
if argv is None:
|
|
305
|
+
_install_trimmed_excepthook()
|
|
306
|
+
argv = sys.argv[1:]
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
namespace = _parse_args(argv)
|
|
310
|
+
except ValueError as exc:
|
|
311
|
+
_print_help(file=sys.stderr)
|
|
312
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
313
|
+
return 2
|
|
314
|
+
|
|
315
|
+
if namespace.help:
|
|
316
|
+
_print_help()
|
|
317
|
+
return 0
|
|
318
|
+
if namespace.version:
|
|
319
|
+
print(_format_version(_get_version(), __build_info__))
|
|
320
|
+
print(_format_python_runtime())
|
|
321
|
+
return 0
|
|
322
|
+
|
|
323
|
+
# Validate --awk and --map are mutually exclusive
|
|
324
|
+
if namespace.awk and namespace.map:
|
|
325
|
+
print("error: --awk and --map cannot be used together", file=sys.stderr)
|
|
326
|
+
return 2
|
|
327
|
+
|
|
328
|
+
# Validate -b/--begin and -e/--end only with --awk or --map mode
|
|
329
|
+
if (namespace.begin_code or namespace.end_code) and not (namespace.awk or namespace.map):
|
|
330
|
+
print(
|
|
331
|
+
"error: -b/--begin and -e/--end options require --awk or --map mode",
|
|
332
|
+
file=sys.stderr,
|
|
333
|
+
)
|
|
334
|
+
return 2
|
|
335
|
+
|
|
336
|
+
mode = "map" if namespace.map else ("awk" if namespace.awk else "snail")
|
|
337
|
+
|
|
338
|
+
if namespace.file:
|
|
339
|
+
from pathlib import Path
|
|
340
|
+
|
|
341
|
+
path = Path(namespace.file)
|
|
342
|
+
try:
|
|
343
|
+
source = path.read_text()
|
|
344
|
+
except OSError as exc:
|
|
345
|
+
print(f"failed to read {path}: {exc}", file=sys.stderr)
|
|
346
|
+
return 1
|
|
347
|
+
filename = str(path)
|
|
348
|
+
args = [filename, *namespace.args]
|
|
349
|
+
else:
|
|
350
|
+
if not namespace.args:
|
|
351
|
+
print("no input provided", file=sys.stderr)
|
|
352
|
+
return 1
|
|
353
|
+
source = namespace.args[0]
|
|
354
|
+
filename = "<cmd>"
|
|
355
|
+
args = ["--", *namespace.args[1:]]
|
|
356
|
+
|
|
357
|
+
if namespace.debug_snail_ast:
|
|
358
|
+
from . import parse_ast
|
|
359
|
+
|
|
360
|
+
snail_ast = parse_ast(
|
|
361
|
+
source,
|
|
362
|
+
mode=mode,
|
|
363
|
+
filename=filename,
|
|
364
|
+
begin_code=namespace.begin_code,
|
|
365
|
+
end_code=namespace.end_code,
|
|
366
|
+
)
|
|
367
|
+
print(snail_ast)
|
|
368
|
+
return 0
|
|
369
|
+
|
|
370
|
+
if namespace.debug_python_ast:
|
|
371
|
+
import ast
|
|
372
|
+
|
|
373
|
+
python_ast = compile_ast(
|
|
374
|
+
source,
|
|
375
|
+
mode=mode,
|
|
376
|
+
auto_print=not namespace.no_print,
|
|
377
|
+
filename=filename,
|
|
378
|
+
begin_code=namespace.begin_code,
|
|
379
|
+
end_code=namespace.end_code,
|
|
380
|
+
)
|
|
381
|
+
try:
|
|
382
|
+
output = ast.dump(python_ast, indent=2)
|
|
383
|
+
except TypeError:
|
|
384
|
+
output = ast.dump(python_ast)
|
|
385
|
+
print(output)
|
|
386
|
+
return 0
|
|
387
|
+
|
|
388
|
+
if namespace.debug:
|
|
389
|
+
import ast
|
|
390
|
+
import builtins
|
|
391
|
+
|
|
392
|
+
python_ast = compile_ast(
|
|
393
|
+
source,
|
|
394
|
+
mode=mode,
|
|
395
|
+
auto_print=not namespace.no_print,
|
|
396
|
+
filename=filename,
|
|
397
|
+
begin_code=namespace.begin_code,
|
|
398
|
+
end_code=namespace.end_code,
|
|
399
|
+
)
|
|
400
|
+
builtins.compile(python_ast, _display_filename(filename), "exec")
|
|
401
|
+
try:
|
|
402
|
+
output = ast.unparse(python_ast)
|
|
403
|
+
except AttributeError:
|
|
404
|
+
import astunparse
|
|
405
|
+
|
|
406
|
+
output = astunparse.unparse(python_ast).rstrip("\n")
|
|
407
|
+
print(output)
|
|
408
|
+
return 0
|
|
409
|
+
|
|
410
|
+
return exec(
|
|
411
|
+
source,
|
|
412
|
+
argv=args,
|
|
413
|
+
mode=mode,
|
|
414
|
+
auto_print=not namespace.no_print,
|
|
415
|
+
auto_import=not namespace.no_auto_import,
|
|
416
|
+
filename=filename,
|
|
417
|
+
begin_code=namespace.begin_code,
|
|
418
|
+
end_code=namespace.end_code,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
if __name__ == "__main__":
|
|
423
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
__all__ = ["install_helpers", "AutoImportDict", "AUTO_IMPORT_NAMES"]
|
|
7
|
+
|
|
8
|
+
# Names that can be auto-imported when first referenced.
|
|
9
|
+
# Maps name -> (module, attribute) where attribute is None for whole-module imports.
|
|
10
|
+
AUTO_IMPORT_NAMES: dict[str, tuple[str, Optional[str]]] = {
|
|
11
|
+
# Whole module imports: import X
|
|
12
|
+
"sys": ("sys", None),
|
|
13
|
+
"os": ("os", None),
|
|
14
|
+
# Attribute imports: from X import Y
|
|
15
|
+
"Path": ("pathlib", "Path"),
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AutoImportDict(dict):
|
|
20
|
+
"""A dict subclass that lazily imports allowed names on first access.
|
|
21
|
+
|
|
22
|
+
When a key lookup fails, if the key is in AUTO_IMPORT_NAMES,
|
|
23
|
+
the corresponding module/attribute is imported and stored in the dict.
|
|
24
|
+
Supports both whole-module imports (import sys) and attribute imports
|
|
25
|
+
(from pathlib import Path).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __missing__(self, key: str) -> object:
|
|
29
|
+
if key in AUTO_IMPORT_NAMES:
|
|
30
|
+
module_name, attr_name = AUTO_IMPORT_NAMES[key]
|
|
31
|
+
module = importlib.import_module(module_name)
|
|
32
|
+
value = getattr(module, attr_name) if attr_name else module
|
|
33
|
+
self[key] = value
|
|
34
|
+
return value
|
|
35
|
+
raise KeyError(key)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
_compact_try = None
|
|
39
|
+
_regex_search = None
|
|
40
|
+
_regex_compile = None
|
|
41
|
+
_subprocess_capture = None
|
|
42
|
+
_subprocess_status = None
|
|
43
|
+
_jmespath_query = None
|
|
44
|
+
_js = None
|
|
45
|
+
_lazy_text_class = None
|
|
46
|
+
_lazy_file_class = None
|
|
47
|
+
_incr_attr = None
|
|
48
|
+
_incr_index = None
|
|
49
|
+
_aug_attr = None
|
|
50
|
+
_aug_index = None
|
|
51
|
+
_env_map = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _get_compact_try():
|
|
55
|
+
global _compact_try
|
|
56
|
+
if _compact_try is None:
|
|
57
|
+
from .compact_try import compact_try
|
|
58
|
+
|
|
59
|
+
_compact_try = compact_try
|
|
60
|
+
return _compact_try
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _get_regex_search():
|
|
64
|
+
global _regex_search
|
|
65
|
+
if _regex_search is None:
|
|
66
|
+
from .regex import regex_search
|
|
67
|
+
|
|
68
|
+
_regex_search = regex_search
|
|
69
|
+
return _regex_search
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _get_regex_compile():
|
|
73
|
+
global _regex_compile
|
|
74
|
+
if _regex_compile is None:
|
|
75
|
+
from .regex import regex_compile
|
|
76
|
+
|
|
77
|
+
_regex_compile = regex_compile
|
|
78
|
+
return _regex_compile
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _get_subprocess_capture():
|
|
82
|
+
global _subprocess_capture
|
|
83
|
+
if _subprocess_capture is None:
|
|
84
|
+
from .subprocess import SubprocessCapture
|
|
85
|
+
|
|
86
|
+
_subprocess_capture = SubprocessCapture
|
|
87
|
+
return _subprocess_capture
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _get_subprocess_status():
|
|
91
|
+
global _subprocess_status
|
|
92
|
+
if _subprocess_status is None:
|
|
93
|
+
from .subprocess import SubprocessStatus
|
|
94
|
+
|
|
95
|
+
_subprocess_status = SubprocessStatus
|
|
96
|
+
return _subprocess_status
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _get_jmespath_query():
|
|
100
|
+
global _jmespath_query
|
|
101
|
+
if _jmespath_query is None:
|
|
102
|
+
from .structured_accessor import __snail_jmespath_query
|
|
103
|
+
|
|
104
|
+
_jmespath_query = __snail_jmespath_query
|
|
105
|
+
return _jmespath_query
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _get_js():
|
|
109
|
+
global _js
|
|
110
|
+
if _js is None:
|
|
111
|
+
from .structured_accessor import js
|
|
112
|
+
|
|
113
|
+
_js = js
|
|
114
|
+
return _js
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _get_lazy_text_class():
|
|
118
|
+
global _lazy_text_class
|
|
119
|
+
if _lazy_text_class is None:
|
|
120
|
+
from .lazy_text import LazyText
|
|
121
|
+
|
|
122
|
+
_lazy_text_class = LazyText
|
|
123
|
+
return _lazy_text_class
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _get_lazy_file_class():
|
|
127
|
+
global _lazy_file_class
|
|
128
|
+
if _lazy_file_class is None:
|
|
129
|
+
from .lazy_file import LazyFile
|
|
130
|
+
|
|
131
|
+
_lazy_file_class = LazyFile
|
|
132
|
+
return _lazy_file_class
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _get_env_map():
|
|
136
|
+
global _env_map
|
|
137
|
+
if _env_map is None:
|
|
138
|
+
from .env import EnvMap
|
|
139
|
+
|
|
140
|
+
_env_map = EnvMap()
|
|
141
|
+
return _env_map
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _get_incr_attr():
|
|
145
|
+
global _incr_attr
|
|
146
|
+
if _incr_attr is None:
|
|
147
|
+
from .augmented import __snail_incr_attr
|
|
148
|
+
|
|
149
|
+
_incr_attr = __snail_incr_attr
|
|
150
|
+
return _incr_attr
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _get_incr_index():
|
|
154
|
+
global _incr_index
|
|
155
|
+
if _incr_index is None:
|
|
156
|
+
from .augmented import __snail_incr_index
|
|
157
|
+
|
|
158
|
+
_incr_index = __snail_incr_index
|
|
159
|
+
return _incr_index
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _get_aug_attr():
|
|
163
|
+
global _aug_attr
|
|
164
|
+
if _aug_attr is None:
|
|
165
|
+
from .augmented import __snail_aug_attr
|
|
166
|
+
|
|
167
|
+
_aug_attr = __snail_aug_attr
|
|
168
|
+
return _aug_attr
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _get_aug_index():
|
|
172
|
+
global _aug_index
|
|
173
|
+
if _aug_index is None:
|
|
174
|
+
from .augmented import __snail_aug_index
|
|
175
|
+
|
|
176
|
+
_aug_index = __snail_aug_index
|
|
177
|
+
return _aug_index
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _lazy_compact_try(expr_fn, fallback_fn=None):
|
|
181
|
+
return _get_compact_try()(expr_fn, fallback_fn)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _lazy_regex_search(value, pattern):
|
|
185
|
+
return _get_regex_search()(value, pattern)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _lazy_regex_compile(pattern):
|
|
189
|
+
return _get_regex_compile()(pattern)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _lazy_subprocess_capture(cmd: str):
|
|
193
|
+
return _get_subprocess_capture()(cmd)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _lazy_subprocess_status(cmd: str):
|
|
197
|
+
return _get_subprocess_status()(cmd)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _lazy_jmespath_query(query: str):
|
|
201
|
+
return _get_jmespath_query()(query)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _lazy_js(input_data=None):
|
|
205
|
+
return _get_js()(input_data)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _lazy_incr_attr(obj, attr: str, delta: int, pre: bool):
|
|
209
|
+
return _get_incr_attr()(obj, attr, delta, pre)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _lazy_incr_index(obj, index, delta: int, pre: bool):
|
|
213
|
+
return _get_incr_index()(obj, index, delta, pre)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _lazy_aug_attr(obj, attr: str, value, op: str):
|
|
217
|
+
return _get_aug_attr()(obj, attr, value, op)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _lazy_aug_index(obj, index, value, op: str):
|
|
221
|
+
return _get_aug_index()(obj, index, value, op)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def __snail_partial(func, /, *args, **kwargs):
|
|
225
|
+
import functools
|
|
226
|
+
|
|
227
|
+
return functools.partial(func, *args, **kwargs)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def __snail_contains__(left, right):
|
|
231
|
+
method = getattr(right, "__snail_contains__", None)
|
|
232
|
+
if method is not None:
|
|
233
|
+
return method(left)
|
|
234
|
+
return left in right
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def __snail_contains_not__(left, right):
|
|
238
|
+
method = getattr(right, "__snail_contains__", None)
|
|
239
|
+
if method is not None:
|
|
240
|
+
return not bool(method(left))
|
|
241
|
+
return left not in right
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def install_helpers(globals_dict: dict) -> None:
|
|
245
|
+
globals_dict["__snail_compact_try"] = _lazy_compact_try
|
|
246
|
+
globals_dict["__snail_regex_search"] = _lazy_regex_search
|
|
247
|
+
globals_dict["__snail_regex_compile"] = _lazy_regex_compile
|
|
248
|
+
globals_dict["__SnailSubprocessCapture"] = _lazy_subprocess_capture
|
|
249
|
+
globals_dict["__SnailSubprocessStatus"] = _lazy_subprocess_status
|
|
250
|
+
globals_dict["__snail_jmespath_query"] = _lazy_jmespath_query
|
|
251
|
+
globals_dict["__snail_partial"] = __snail_partial
|
|
252
|
+
globals_dict["__snail_contains__"] = __snail_contains__
|
|
253
|
+
globals_dict["__snail_contains_not__"] = __snail_contains_not__
|
|
254
|
+
globals_dict["__snail_incr_attr"] = _lazy_incr_attr
|
|
255
|
+
globals_dict["__snail_incr_index"] = _lazy_incr_index
|
|
256
|
+
globals_dict["__snail_aug_attr"] = _lazy_aug_attr
|
|
257
|
+
globals_dict["__snail_aug_index"] = _lazy_aug_index
|
|
258
|
+
globals_dict["__snail_env"] = _get_env_map()
|
|
259
|
+
globals_dict["js"] = _lazy_js
|
|
260
|
+
globals_dict["__SnailLazyText"] = _get_lazy_text_class()
|
|
261
|
+
globals_dict["__SnailLazyFile"] = _get_lazy_file_class()
|