snail-lang 0.3.4__cp310-abi3-win_amd64.whl → 0.3.7__cp310-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/_native.pyd CHANGED
Binary file
snail/cli.py CHANGED
@@ -21,6 +21,7 @@ def main(argv: list[str] | None = None) -> int:
21
21
  parser.add_argument("-f", dest="file", metavar="file")
22
22
  parser.add_argument("-a", "--awk", action="store_true")
23
23
  parser.add_argument("-P", "--no-print", action="store_true")
24
+ parser.add_argument("-I", "--no-auto-import", action="store_true")
24
25
  parser.add_argument("--parse-only", action="store_true")
25
26
  parser.add_argument("-v", "--version", action="store_true")
26
27
  parser.add_argument("args", nargs=argparse.REMAINDER)
@@ -59,6 +60,7 @@ def main(argv: list[str] | None = None) -> int:
59
60
  argv=args,
60
61
  mode=mode,
61
62
  auto_print=not namespace.no_print,
63
+ auto_import=not namespace.no_auto_import,
62
64
  filename=filename,
63
65
  )
64
66
 
snail/runtime/__init__.py CHANGED
@@ -1,16 +1,51 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import functools
4
+ import importlib
5
+ from typing import Any
6
+
3
7
  from .compact_try import compact_try
4
8
  from .regex import regex_compile, regex_search
5
9
  from .structured_accessor import (
6
- JsonObject,
7
- JsonPipelineWrapper,
8
- StructuredAccessor,
9
- json,
10
+ __snail_jmespath_query,
11
+ js,
10
12
  )
11
13
  from .subprocess import SubprocessCapture, SubprocessStatus
12
14
 
13
- __all__ = ["install_helpers"]
15
+ __all__ = ["install_helpers", "AutoImportDict", "AUTO_IMPORT_NAMES"]
16
+
17
+ # Names that can be auto-imported when first referenced.
18
+ # Maps name -> (module, attribute) where attribute is None for whole-module imports.
19
+ AUTO_IMPORT_NAMES: dict[str, tuple[str, str | None]] = {
20
+ # Whole module imports: import X
21
+ "sys": ("sys", None),
22
+ "os": ("os", None),
23
+ # Attribute imports: from X import Y
24
+ "Path": ("pathlib", "Path"),
25
+ }
26
+
27
+
28
+ class AutoImportDict(dict):
29
+ """A dict subclass that lazily imports allowed names on first access.
30
+
31
+ When a key lookup fails, if the key is in AUTO_IMPORT_NAMES,
32
+ the corresponding module/attribute is imported and stored in the dict.
33
+ Supports both whole-module imports (import sys) and attribute imports
34
+ (from pathlib import Path).
35
+ """
36
+
37
+ def __missing__(self, key: str) -> Any:
38
+ if key in AUTO_IMPORT_NAMES:
39
+ module_name, attr_name = AUTO_IMPORT_NAMES[key]
40
+ module = importlib.import_module(module_name)
41
+ value = getattr(module, attr_name) if attr_name else module
42
+ self[key] = value
43
+ return value
44
+ raise KeyError(key)
45
+
46
+
47
+ def __snail_partial(func, /, *args, **kwargs):
48
+ return functools.partial(func, *args, **kwargs)
14
49
 
15
50
 
16
51
  def install_helpers(globals_dict: dict) -> None:
@@ -19,7 +54,6 @@ def install_helpers(globals_dict: dict) -> None:
19
54
  globals_dict["__snail_regex_compile"] = regex_compile
20
55
  globals_dict["__SnailSubprocessCapture"] = SubprocessCapture
21
56
  globals_dict["__SnailSubprocessStatus"] = SubprocessStatus
22
- globals_dict["__SnailStructuredAccessor"] = StructuredAccessor
23
- globals_dict["__SnailJsonObject"] = JsonObject
24
- globals_dict["__SnailJsonPipelineWrapper"] = JsonPipelineWrapper
25
- globals_dict["json"] = json
57
+ globals_dict["__snail_jmespath_query"] = __snail_jmespath_query
58
+ globals_dict["__snail_partial"] = __snail_partial
59
+ globals_dict["js"] = js
@@ -1,71 +1,75 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json as _json
4
+ import os as _os
4
5
  import sys as _sys
5
6
 
6
7
  from ..vendor import jmespath
7
8
 
8
9
 
9
- class StructuredAccessor:
10
- def __init__(self, query: str) -> None:
11
- self.query = query
10
+ def __snail_jmespath_query(query: str):
11
+ """Create a callable that applies JMESPath query.
12
12
 
13
- def __pipeline__(self, obj):
14
- if not hasattr(obj, "__structured__"):
15
- raise TypeError(
16
- "Pipeline target must implement __structured__, "
17
- f"got {type(obj).__name__}"
18
- )
19
- return obj.__structured__(self.query)
13
+ Used by the $[query] syntax which lowers to __snail_jmespath_query(query).
14
+ """
20
15
 
16
+ def apply(data):
17
+ return jmespath.search(query, data)
21
18
 
22
- class JsonObject:
23
- def __init__(self, data) -> None:
24
- self.data = data
19
+ return apply
25
20
 
26
- def __structured__(self, query: str):
27
- return jmespath.search(query, self.data)
28
21
 
29
- def __repr__(self) -> str:
30
- return _json.dumps(self.data, indent=2)
22
+ def _parse_jsonl(content: str):
23
+ lines = [line for line in content.splitlines() if line.strip()]
24
+ if not lines:
25
+ return []
31
26
 
32
-
33
- class JsonPipelineWrapper:
34
- """Wrapper for json() to support pipeline operator without blocking stdin."""
35
-
36
- def __pipeline__(self, input_data):
37
- return json(input_data)
38
-
39
- def __structured__(self, query: str):
40
- data = json(_sys.stdin)
41
- return data.__structured__(query)
42
-
43
- def __repr__(self) -> str:
44
- data = json(_sys.stdin)
45
- return repr(data)
46
-
47
-
48
- def json(input_data=None):
49
- """Parse JSON from various input sources."""
27
+ items = []
28
+ for line in lines:
29
+ try:
30
+ items.append(_json.loads(line))
31
+ except _json.JSONDecodeError as exc:
32
+ raise _json.JSONDecodeError(
33
+ f"Invalid JSONL line: {exc.msg}",
34
+ line,
35
+ exc.pos,
36
+ ) from exc
37
+ return items
38
+
39
+
40
+ def js(input_data=None):
41
+ """Parse JSON from various input sources.
42
+
43
+ Returns the parsed Python object (dict, list, etc.) directly.
44
+ If called with no arguments, reads from stdin.
45
+ """
50
46
  if input_data is None:
51
- return JsonPipelineWrapper()
47
+ input_data = _sys.stdin
52
48
 
53
49
  if isinstance(input_data, str):
54
50
  try:
55
- data = _json.loads(input_data)
51
+ return _json.loads(input_data)
56
52
  except _json.JSONDecodeError:
57
- with open(input_data, "r", encoding="utf-8") as handle:
58
- data = _json.load(handle)
53
+ if _os.path.exists(input_data):
54
+ with open(input_data, "r", encoding="utf-8") as handle:
55
+ content = handle.read()
56
+ try:
57
+ return _json.loads(content)
58
+ except _json.JSONDecodeError:
59
+ return _parse_jsonl(content)
60
+ else:
61
+ return _parse_jsonl(input_data)
59
62
  elif hasattr(input_data, "read"):
60
63
  content = input_data.read()
61
64
  if isinstance(content, bytes):
62
65
  content = content.decode("utf-8")
63
- data = _json.loads(content)
66
+ try:
67
+ return _json.loads(content)
68
+ except _json.JSONDecodeError:
69
+ return _parse_jsonl(content)
64
70
  elif isinstance(input_data, (dict, list, int, float, bool)) or input_data is None:
65
- data = input_data
71
+ return input_data
66
72
  else:
67
73
  raise TypeError(
68
- f"json() input must be JSON-compatible, got {type(input_data).__name__}"
74
+ f"js() input must be JSON-compatible, got {type(input_data).__name__}"
69
75
  )
70
-
71
- return JsonObject(data)
@@ -7,7 +7,7 @@ class SubprocessCapture:
7
7
  def __init__(self, cmd: str) -> None:
8
8
  self.cmd = cmd
9
9
 
10
- def __pipeline__(self, input_data):
10
+ def __call__(self, input_data=None):
11
11
  try:
12
12
  if input_data is None:
13
13
  completed = subprocess.run(
@@ -42,7 +42,7 @@ class SubprocessStatus:
42
42
  def __init__(self, cmd: str) -> None:
43
43
  self.cmd = cmd
44
44
 
45
- def __pipeline__(self, input_data):
45
+ def __call__(self, input_data=None):
46
46
  try:
47
47
  if input_data is None:
48
48
  subprocess.run(self.cmd, shell=True, check=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snail-lang
3
- Version: 0.3.4
3
+ Version: 0.3.7
4
4
  Requires-Dist: maturin>=1.5 ; extra == 'dev'
5
5
  Requires-Dist: pytest ; extra == 'dev'
6
6
  Provides-Extra: dev
@@ -64,7 +64,7 @@ The `?` operator makes error handling terse yet expressive:
64
64
  err = risky_operation()?
65
65
 
66
66
  # Provide a fallback value (exception available as $e)
67
- value = parse_json(data):{}?
67
+ value = js(data):{}?
68
68
  details = fetch_url(url):"Error: {$e}"?
69
69
 
70
70
  # Access attributes directly
@@ -101,7 +101,7 @@ Built-in variables: `$l` (line), `$f` (fields), `$n` (line number), `$fn` (per-f
101
101
 
102
102
  ### Pipeline Operator
103
103
 
104
- The `|` operator enables data pipelining through objects that implement `__pipeline__`:
104
+ The `|` operator enables data pipelining through pipeline-aware callables:
105
105
 
106
106
  ```snail
107
107
  # Pipe data to subprocess stdin
@@ -112,23 +112,37 @@ output = "foo\nbar" | $(grep foo) | $(wc -l)
112
112
 
113
113
  # Custom pipeline handlers
114
114
  class Doubler {
115
- def __pipeline__(self, x) { return x * 2 }
115
+ def __call__(self, x) { return x * 2 }
116
116
  }
117
117
  doubled = 21 | Doubler() # yields 42
118
+
119
+ # Use placeholders to control where piped values land in calls
120
+ greeting = "World" | greet("Hello ", _) # greet("Hello ", "World")
121
+ excited = "World" | greet(_, "!") # greet("World", "!")
122
+ formal = "World" | greet("Hello ", suffix=_) # greet("Hello ", "World")
118
123
  ```
119
124
 
125
+ When a pipeline targets a call expression, the left-hand value is passed to the
126
+ resulting callable. If the call includes a single `_` placeholder, Snail substitutes
127
+ the piped value at that position (including keyword arguments). Only one
128
+ placeholder is allowed in a piped call. Outside of pipeline calls, `_` remains a
129
+ normal identifier.
130
+
120
131
  ### JSON Queries with JMESPath
121
132
 
122
- Parse and query JSON data with the `json()` function and structured pipeline accessor:
133
+ Parse and query JSON data with the `js()` function and structured pipeline accessor:
123
134
 
124
135
  ```snail
125
136
  # Parse JSON and query with $[jmespath]
126
- data = json($(curl -s api.example.com/users))
137
+ data = js($(curl -s api.example.com/users))
127
138
  names = data | $[users[*].name]
128
139
  first_email = data | $[users[0].email]
129
140
 
130
141
  # Inline parsing and querying
131
- result = json('{"foo": 12}') | $[foo]
142
+ result = js('{"foo": 12}') | $[foo]
143
+
144
+ # JSONL parsing returns a list
145
+ names = js('{"name": "Ada"}\n{"name": "Lin"}') | $[[*].name]
132
146
  ```
133
147
 
134
148
  ### Full Python Interoperability
@@ -147,7 +161,7 @@ filtered = df[df["value"] > 100]
147
161
 
148
162
  ```bash
149
163
  # Install from PyPI
150
- pip install snail
164
+ pip install snail-lang
151
165
 
152
166
  # Run a one-liner
153
167
  snail "print('Hello, Snail!')"
@@ -179,10 +193,9 @@ flowchart TB
179
193
  C2[crates/snail-ast/src/awk.rs<br/>AwkProgram AST]
180
194
  end
181
195
 
182
- subgraph Lowering["Lowering & Code Generation"]
196
+ subgraph Lowering["Lowering"]
183
197
  D1[crates/snail-lower/<br/>AST → Python AST Transform]
184
198
  D2[python/snail/runtime/<br/>Runtime Helpers]
185
- D3[crates/snail-codegen/<br/>Python AST → Source Code]
186
199
  end
187
200
 
188
201
  subgraph Execution
@@ -198,9 +211,8 @@ flowchart TB
198
211
  C1 --> D1
199
212
  C2 --> D1
200
213
  D1 --> D2
201
- D1 --> D3
202
- D2 --> D3
203
- D3 --> E1
214
+ D1 --> E1
215
+ D2 --> E1
204
216
  E1 --> E2
205
217
  E2 --> F[Python Execution]
206
218
 
@@ -218,7 +230,7 @@ flowchart TB
218
230
  - `$(cmd)` subprocess capture → `__SnailSubprocessCapture`
219
231
  - `@(cmd)` subprocess status → `__SnailSubprocessStatus`
220
232
  - Regex literals → `__snail_regex_search` and `__snail_regex_compile`
221
- - **Code Generation**: Converts Python AST to Python source for in-process execution
233
+ - **Execution**: Compiles Python AST directly for in-process execution
222
234
  - **CLI**: Python wrapper (`python/snail/cli.py`) that executes via the extension module
223
235
 
224
236
  ## 📚 Documentation
@@ -333,7 +345,7 @@ python3 -m venv myenv
333
345
  source myenv/bin/activate # On Windows: myenv\Scripts\activate
334
346
 
335
347
  # Install and run
336
- pip install snail
348
+ pip install snail-lang
337
349
  snail "import sys; print(sys.prefix)"
338
350
  ```
339
351
 
@@ -1,11 +1,11 @@
1
1
  snail\__init__.py,sha256=QQdNHRMalbxBvCW3RVDkMh-F4ScPOtikNbmkpC4NznE,282
2
- snail\_native.pyd,sha256=PZBCt77uUK4laSy-I0I0wFgrAUO9HaiD-JSXMTH-Www,1538048
3
- snail\cli.py,sha256=RimI2F9PzQkJ5jAePDlzwZ_Vkqtvdw2Fn65S8YvtrKs,1925
4
- snail\runtime\__init__.py,sha256=LgoUPIGA2saLTeiywITPOzjx1LG67nIJctkWxOJA4Sc,914
2
+ snail\_native.pyd,sha256=bkJymYDAuavT8dk1cd-jII59pC74GdZrj2R8ERCpcA0,1511936
3
+ snail\cli.py,sha256=m1b8qS7SgyiEnPeC6l5gqq_VTm3AO01s4yt5R-ywP44,2048
4
+ snail\runtime\__init__.py,sha256=CZAaF3SHKUCuaEog6XnjcorchR19HKVWH4D92TYqOgU,2115
5
5
  snail\runtime\compact_try.py,sha256=GsGN8zJ_6kmv26_Wn1s9dfylcJjE0fZGQxplCR2Mx8A,392
6
6
  snail\runtime\regex.py,sha256=MH30z5yLiQrfk4uRWwVxneOvDbI8GCUP33Qhl8xvsHg,191
7
- snail\runtime\structured_accessor.py,sha256=fweg27DS2xqQ10vzGbdZtJtBMKrSbwujpkhNmZ5jcKc,2053
8
- snail\runtime\subprocess.py,sha256=gihvY_5Pi5uZAwYDgz94nqmVXAqgX1a4aASKd4o5wes,1953
7
+ snail\runtime\structured_accessor.py,sha256=mX5kANTV-Y04TDOjhJdcFrl9zPcu4NgvLGWfzuyuNWM,2235
8
+ snail\runtime\subprocess.py,sha256=ILDdfpqRXMFjWKYdpxQvEpie_31kItCuDO2WlsNpnks,1955
9
9
  snail\vendor\__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  snail\vendor\jmespath\LICENSE,sha256=2KWiDylgnlq10P3XDxl8OYugQB6n-HRR_M9FUsOlplI,1134
11
11
  snail\vendor\jmespath\__init__.py,sha256=uGroTH0i_1gETCVguY0L1EJiALD4b2Gr01KPySp2LJU,278
@@ -16,8 +16,8 @@ snail\vendor\jmespath\functions.py,sha256=YZ9H_JOmpn2-hHGX6Dif-9I2xb7w30fm-al8gA
16
16
  snail\vendor\jmespath\lexer.py,sha256=xM9tayCW9o9_wc7a8aAu563QCVJ2SlotHj5BDc78-80,9668
17
17
  snail\vendor\jmespath\parser.py,sha256=8FEJChdCFtdC0cq8TQdu2YxN5Y2ahZtFUkBkkUuTuzU,19423
18
18
  snail\vendor\jmespath\visitor.py,sha256=VN1KjK_Shmv4qTAVb8WaKOGC2LgCjo2xNg9k5CSFBv0,11129
19
- snail_lang-0.3.4.dist-info\METADATA,sha256=uHn9PY2IE9nqeaEchP-Eqa_77Bp_atcYACWufeQdbWE,9276
20
- snail_lang-0.3.4.dist-info\WHEEL,sha256=ZMDDxh9OPoaLQ4P2dJmgI1XsENYSzjzq8fErKKVw5iE,96
21
- snail_lang-0.3.4.dist-info\entry_points.txt,sha256=8u6pjHZtKGdaZQlr6yKbftplObC07caDZ44j5PIPU6M,39
22
- snail_lang-0.3.4.dist-info\licenses\LICENSE,sha256=iT_e39WExFFehp3Zh4faVe3dEv3BrhhX_v3obKdBM9M,1082
23
- snail_lang-0.3.4.dist-info\RECORD,,
19
+ snail_lang-0.3.7.dist-info\METADATA,sha256=LLOmdEiFolJ9OqebnsL7D9EIF7YA5o-iUk4WkBszVpU,9856
20
+ snail_lang-0.3.7.dist-info\WHEEL,sha256=ZMDDxh9OPoaLQ4P2dJmgI1XsENYSzjzq8fErKKVw5iE,96
21
+ snail_lang-0.3.7.dist-info\entry_points.txt,sha256=8u6pjHZtKGdaZQlr6yKbftplObC07caDZ44j5PIPU6M,39
22
+ snail_lang-0.3.7.dist-info\licenses\LICENSE,sha256=iT_e39WExFFehp3Zh4faVe3dEv3BrhhX_v3obKdBM9M,1082
23
+ snail_lang-0.3.7.dist-info\RECORD,,