snail-lang 0.2.0__cp310-abi3-macosx_11_0_arm64.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.
@@ -0,0 +1,329 @@
1
+ import operator
2
+
3
+ from . import functions
4
+ from .compat import string_type
5
+ from numbers import Number
6
+
7
+
8
+ def _equals(x, y):
9
+ if _is_special_number_case(x, y):
10
+ return False
11
+ else:
12
+ return x == y
13
+
14
+
15
+ def _is_special_number_case(x, y):
16
+ # We need to special case comparing 0 or 1 to
17
+ # True/False. While normally comparing any
18
+ # integer other than 0/1 to True/False will always
19
+ # return False. However 0/1 have this:
20
+ # >>> 0 == True
21
+ # False
22
+ # >>> 0 == False
23
+ # True
24
+ # >>> 1 == True
25
+ # True
26
+ # >>> 1 == False
27
+ # False
28
+ #
29
+ # Also need to consider that:
30
+ # >>> 0 in [True, False]
31
+ # True
32
+ if _is_actual_number(x) and x in (0, 1):
33
+ return isinstance(y, bool)
34
+ elif _is_actual_number(y) and y in (0, 1):
35
+ return isinstance(x, bool)
36
+
37
+
38
+ def _is_comparable(x):
39
+ # The spec doesn't officially support string types yet,
40
+ # but enough people are relying on this behavior that
41
+ # it's been added back. This should eventually become
42
+ # part of the official spec.
43
+ return _is_actual_number(x) or isinstance(x, string_type)
44
+
45
+
46
+ def _is_actual_number(x):
47
+ # We need to handle python's quirkiness with booleans,
48
+ # specifically:
49
+ #
50
+ # >>> isinstance(False, int)
51
+ # True
52
+ # >>> isinstance(True, int)
53
+ # True
54
+ if isinstance(x, bool):
55
+ return False
56
+ return isinstance(x, Number)
57
+
58
+
59
+ class Options(object):
60
+ """Options to control how a JMESPath function is evaluated."""
61
+
62
+ def __init__(self, dict_cls=None, custom_functions=None):
63
+ #: The class to use when creating a dict. The interpreter
64
+ # may create dictionaries during the evaluation of a JMESPath
65
+ # expression. For example, a multi-select hash will
66
+ # create a dictionary. By default we use a dict() type.
67
+ # You can set this value to change what dict type is used.
68
+ # The most common reason you would change this is if you
69
+ # want to set a collections.OrderedDict so that you can
70
+ # have predictable key ordering.
71
+ self.dict_cls = dict_cls
72
+ self.custom_functions = custom_functions
73
+
74
+
75
+ class _Expression(object):
76
+ def __init__(self, expression, interpreter):
77
+ self.expression = expression
78
+ self.interpreter = interpreter
79
+
80
+ def visit(self, node, *args, **kwargs):
81
+ return self.interpreter.visit(node, *args, **kwargs)
82
+
83
+
84
+ class Visitor(object):
85
+ def __init__(self):
86
+ self._method_cache = {}
87
+
88
+ def visit(self, node, *args, **kwargs):
89
+ node_type = node["type"]
90
+ method = self._method_cache.get(node_type)
91
+ if method is None:
92
+ method = getattr(self, "visit_%s" % node["type"], self.default_visit)
93
+ self._method_cache[node_type] = method
94
+ return method(node, *args, **kwargs)
95
+
96
+ def default_visit(self, node, *args, **kwargs):
97
+ raise NotImplementedError("default_visit")
98
+
99
+
100
+ class TreeInterpreter(Visitor):
101
+ COMPARATOR_FUNC = {
102
+ "eq": _equals,
103
+ "ne": lambda x, y: not _equals(x, y),
104
+ "lt": operator.lt,
105
+ "gt": operator.gt,
106
+ "lte": operator.le,
107
+ "gte": operator.ge,
108
+ }
109
+ _EQUALITY_OPS = ["eq", "ne"]
110
+ MAP_TYPE = dict
111
+
112
+ def __init__(self, options=None):
113
+ super(TreeInterpreter, self).__init__()
114
+ self._dict_cls = self.MAP_TYPE
115
+ if options is None:
116
+ options = Options()
117
+ self._options = options
118
+ if options.dict_cls is not None:
119
+ self._dict_cls = self._options.dict_cls
120
+ if options.custom_functions is not None:
121
+ self._functions = self._options.custom_functions
122
+ else:
123
+ self._functions = functions.Functions()
124
+
125
+ def default_visit(self, node, *args, **kwargs):
126
+ raise NotImplementedError(node["type"])
127
+
128
+ def visit_subexpression(self, node, value):
129
+ result = value
130
+ for node in node["children"]:
131
+ result = self.visit(node, result)
132
+ return result
133
+
134
+ def visit_field(self, node, value):
135
+ try:
136
+ return value.get(node["value"])
137
+ except AttributeError:
138
+ return None
139
+
140
+ def visit_comparator(self, node, value):
141
+ # Common case: comparator is == or !=
142
+ comparator_func = self.COMPARATOR_FUNC[node["value"]]
143
+ if node["value"] in self._EQUALITY_OPS:
144
+ return comparator_func(
145
+ self.visit(node["children"][0], value),
146
+ self.visit(node["children"][1], value),
147
+ )
148
+ else:
149
+ # Ordering operators are only valid for numbers.
150
+ # Evaluating any other type with a comparison operator
151
+ # will yield a None value.
152
+ left = self.visit(node["children"][0], value)
153
+ right = self.visit(node["children"][1], value)
154
+ num_types = (int, float)
155
+ if not (_is_comparable(left) and _is_comparable(right)):
156
+ return None
157
+ return comparator_func(left, right)
158
+
159
+ def visit_current(self, node, value):
160
+ return value
161
+
162
+ def visit_expref(self, node, value):
163
+ return _Expression(node["children"][0], self)
164
+
165
+ def visit_function_expression(self, node, value):
166
+ resolved_args = []
167
+ for child in node["children"]:
168
+ current = self.visit(child, value)
169
+ resolved_args.append(current)
170
+ return self._functions.call_function(node["value"], resolved_args)
171
+
172
+ def visit_filter_projection(self, node, value):
173
+ base = self.visit(node["children"][0], value)
174
+ if not isinstance(base, list):
175
+ return None
176
+ comparator_node = node["children"][2]
177
+ collected = []
178
+ for element in base:
179
+ if self._is_true(self.visit(comparator_node, element)):
180
+ current = self.visit(node["children"][1], element)
181
+ if current is not None:
182
+ collected.append(current)
183
+ return collected
184
+
185
+ def visit_flatten(self, node, value):
186
+ base = self.visit(node["children"][0], value)
187
+ if not isinstance(base, list):
188
+ # Can't flatten the object if it's not a list.
189
+ return None
190
+ merged_list = []
191
+ for element in base:
192
+ if isinstance(element, list):
193
+ merged_list.extend(element)
194
+ else:
195
+ merged_list.append(element)
196
+ return merged_list
197
+
198
+ def visit_identity(self, node, value):
199
+ return value
200
+
201
+ def visit_index(self, node, value):
202
+ # Even though we can index strings, we don't
203
+ # want to support that.
204
+ if not isinstance(value, list):
205
+ return None
206
+ try:
207
+ return value[node["value"]]
208
+ except IndexError:
209
+ return None
210
+
211
+ def visit_index_expression(self, node, value):
212
+ result = value
213
+ for node in node["children"]:
214
+ result = self.visit(node, result)
215
+ return result
216
+
217
+ def visit_slice(self, node, value):
218
+ if not isinstance(value, list):
219
+ return None
220
+ s = slice(*node["children"])
221
+ return value[s]
222
+
223
+ def visit_key_val_pair(self, node, value):
224
+ return self.visit(node["children"][0], value)
225
+
226
+ def visit_literal(self, node, value):
227
+ return node["value"]
228
+
229
+ def visit_multi_select_dict(self, node, value):
230
+ if value is None:
231
+ return None
232
+ collected = self._dict_cls()
233
+ for child in node["children"]:
234
+ collected[child["value"]] = self.visit(child, value)
235
+ return collected
236
+
237
+ def visit_multi_select_list(self, node, value):
238
+ if value is None:
239
+ return None
240
+ collected = []
241
+ for child in node["children"]:
242
+ collected.append(self.visit(child, value))
243
+ return collected
244
+
245
+ def visit_or_expression(self, node, value):
246
+ matched = self.visit(node["children"][0], value)
247
+ if self._is_false(matched):
248
+ matched = self.visit(node["children"][1], value)
249
+ return matched
250
+
251
+ def visit_and_expression(self, node, value):
252
+ matched = self.visit(node["children"][0], value)
253
+ if self._is_false(matched):
254
+ return matched
255
+ return self.visit(node["children"][1], value)
256
+
257
+ def visit_not_expression(self, node, value):
258
+ original_result = self.visit(node["children"][0], value)
259
+ if _is_actual_number(original_result) and original_result == 0:
260
+ # Special case for 0, !0 should be false, not true.
261
+ # 0 is not a special cased integer in jmespath.
262
+ return False
263
+ return not original_result
264
+
265
+ def visit_pipe(self, node, value):
266
+ result = value
267
+ for node in node["children"]:
268
+ result = self.visit(node, result)
269
+ return result
270
+
271
+ def visit_projection(self, node, value):
272
+ base = self.visit(node["children"][0], value)
273
+ if not isinstance(base, list):
274
+ return None
275
+ collected = []
276
+ for element in base:
277
+ current = self.visit(node["children"][1], element)
278
+ if current is not None:
279
+ collected.append(current)
280
+ return collected
281
+
282
+ def visit_value_projection(self, node, value):
283
+ base = self.visit(node["children"][0], value)
284
+ try:
285
+ base = base.values()
286
+ except AttributeError:
287
+ return None
288
+ collected = []
289
+ for element in base:
290
+ current = self.visit(node["children"][1], element)
291
+ if current is not None:
292
+ collected.append(current)
293
+ return collected
294
+
295
+ def _is_false(self, value):
296
+ # This looks weird, but we're explicitly using equality checks
297
+ # because the truth/false values are different between
298
+ # python and jmespath.
299
+ return (
300
+ value == "" or value == [] or value == {} or value is None or value is False
301
+ )
302
+
303
+ def _is_true(self, value):
304
+ return not self._is_false(value)
305
+
306
+
307
+ class GraphvizVisitor(Visitor):
308
+ def __init__(self):
309
+ super(GraphvizVisitor, self).__init__()
310
+ self._lines = []
311
+ self._count = 1
312
+
313
+ def visit(self, node, *args, **kwargs):
314
+ self._lines.append("digraph AST {")
315
+ current = "%s%s" % (node["type"], self._count)
316
+ self._count += 1
317
+ self._visit(node, current)
318
+ self._lines.append("}")
319
+ return "\n".join(self._lines)
320
+
321
+ def _visit(self, node, current):
322
+ self._lines.append(
323
+ '%s [label="%s(%s)"]' % (current, node["type"], node.get("value", ""))
324
+ )
325
+ for child in node.get("children", []):
326
+ child_name = "%s%s" % (child["type"], self._count)
327
+ self._count += 1
328
+ self._lines.append(" %s -> %s" % (current, child_name))
329
+ self._visit(child, child_name)
@@ -0,0 +1,343 @@
1
+ Metadata-Version: 2.4
2
+ Name: snail-lang
3
+ Version: 0.2.0
4
+ Requires-Dist: maturin>=1.5 ; extra == 'dev'
5
+ Requires-Dist: pytest ; extra == 'dev'
6
+ Provides-Extra: dev
7
+ License-File: LICENSE
8
+ Summary: Snail programming language interpreter
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
11
+
12
+ <p align="center">
13
+ <img src="logo.png" alt="Snail logo" width="200">
14
+ </p>
15
+
16
+ <h1 align="center">Snail</h1>
17
+ <p align="center"><em>What do you get when you shove a snake in a shell?</em></p>
18
+
19
+ <h1>Snail, while I hope it is useful to myself and others, is my attempt at
20
+ improving my knowledge of AI code developement. Things are probably broken
21
+ in interesting and horrible ways.</h1>
22
+
23
+ ---
24
+
25
+ **Snail** is a programming language that compiles to Python, combining Python's power with Perl/awk-inspired syntax for quick scripts and one-liners. No more whitespace sensitivity—just curly braces and concise expressions.
26
+
27
+ ## ✨ What Makes Snail Unique
28
+
29
+ ### Curly Braces, Not Indentation
30
+
31
+ Write Python logic without worrying about tabs vs spaces:
32
+
33
+ ```snail
34
+ def process(items) {
35
+ for item in items {
36
+ if item > 0 { print(item) }
37
+ else { continue }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### Built-in Subprocess Pipelines
43
+
44
+ Shell commands are first-class citizens with `$()` capture and `|` piping:
45
+
46
+ ```snail
47
+ # Capture command output with interpolation
48
+ name = "world"
49
+ greeting = $(echo hello {name})
50
+
51
+ # Pipe data through commands
52
+ result = "foo\nbar\nbaz" | $(grep bar) | $(cat -n)
53
+
54
+ # Check command status
55
+ @(make build)? # returns exit code on failure instead of raising
56
+ ```
57
+
58
+ ### Compact Error Handling
59
+
60
+ The `?` operator makes error handling terse yet expressive:
61
+
62
+ ```snail
63
+ # Swallow exception, get the error object
64
+ err = risky_operation()?
65
+
66
+ # Provide a fallback value (exception available as $e)
67
+ value = parse_json(data):{}?
68
+ details = fetch_url(url):"Error: {$e}"?
69
+
70
+ # Access attributes directly
71
+ name = risky()?.__class__.__name__
72
+ args = risky()?.args[0]
73
+ ```
74
+
75
+ ### Regex Literals
76
+
77
+ Pattern matching without `import re`:
78
+
79
+ ```snail
80
+ if email in /^[\w.]+@[\w.]+$/ {
81
+ print("Valid email")
82
+ }
83
+
84
+ # Compiled regex for reuse
85
+ pattern = /\d{3}-\d{4}/
86
+ match = pattern.search(phone)
87
+ ```
88
+
89
+ ### Awk Mode
90
+
91
+ Process files line-by-line with familiar awk semantics:
92
+
93
+ ```snail
94
+ #!/usr/bin/env -S snail --awk -f
95
+ BEGIN { total = 0 }
96
+ /^[0-9]+/ { total = total + int($f[0]) }
97
+ END { print("Sum:", total) }
98
+ ```
99
+
100
+ Built-in variables: `$l` (line), `$f` (fields), `$n` (line number), `$fn` (per-file line number), `$p` (file path), `$m` (last match).
101
+
102
+ ### Pipeline Operator
103
+
104
+ The `|` operator enables data pipelining through objects that implement `__pipeline__`:
105
+
106
+ ```snail
107
+ # Pipe data to subprocess stdin
108
+ result = "hello\nworld" | $(grep hello)
109
+
110
+ # Chain multiple transformations
111
+ output = "foo\nbar" | $(grep foo) | $(wc -l)
112
+
113
+ # Custom pipeline handlers
114
+ class Doubler {
115
+ def __pipeline__(self, x) { return x * 2 }
116
+ }
117
+ doubled = 21 | Doubler() # yields 42
118
+ ```
119
+
120
+ ### JSON Queries with JMESPath
121
+
122
+ Parse and query JSON data with the `json()` function and structured pipeline accessor:
123
+
124
+ ```snail
125
+ # Parse JSON and query with $[jmespath]
126
+ data = json($(curl -s api.example.com/users))
127
+ names = data | $[users[*].name]
128
+ first_email = data | $[users[0].email]
129
+
130
+ # Inline parsing and querying
131
+ result = json('{"foo": 12}') | $[foo]
132
+ ```
133
+
134
+ ### Full Python Interoperability
135
+
136
+ Snail compiles to Python AST—import any Python module, use any library:
137
+
138
+ ```snail
139
+ import pandas as pd
140
+ from pathlib import Path
141
+
142
+ df = pd.read_csv(Path("data.csv"))
143
+ filtered = df[df["value"] > 100]
144
+ ```
145
+
146
+ ## 🚀 Quick Start
147
+
148
+ ```bash
149
+ # Install from PyPI
150
+ pip install snail
151
+
152
+ # Run a one-liner
153
+ snail "print('Hello, Snail!')"
154
+
155
+ # Execute a script
156
+ snail -f script.snail
157
+
158
+ # Awk mode for text processing
159
+ cat data.txt | snail --awk '/error/ { print($l) }'
160
+ ```
161
+
162
+ ## 🏗️ Architecture
163
+
164
+ Snail compiles to Python through a multi-stage pipeline:
165
+
166
+ ```mermaid
167
+ flowchart TB
168
+ subgraph Input
169
+ A[Snail Source Code]
170
+ end
171
+
172
+ subgraph Parsing["Parsing (Pest PEG Parser)"]
173
+ B1[crates/snail-parser/src/snail.pest<br/>Grammar Definition]
174
+ B2[crates/snail-parser/<br/>Parser Implementation]
175
+ end
176
+
177
+ subgraph AST["Abstract Syntax Tree"]
178
+ C1[crates/snail-ast/src/ast.rs<br/>Program AST]
179
+ C2[crates/snail-ast/src/awk.rs<br/>AwkProgram AST]
180
+ end
181
+
182
+ subgraph Lowering["Lowering & Code Generation"]
183
+ D1[crates/snail-lower/<br/>AST → Python AST Transform]
184
+ D2[python/snail/runtime/<br/>Runtime Helpers]
185
+ D3[crates/snail-codegen/<br/>Python AST → Source Code]
186
+ end
187
+
188
+ subgraph Execution
189
+ E1[python/snail/cli.py<br/>CLI Interface]
190
+ E2[pyo3 extension<br/>in-process exec]
191
+ end
192
+
193
+ A -->|Regular Mode| B1
194
+ A -->|Awk Mode| B1
195
+ B1 --> B2
196
+ B2 -->|Regular| C1
197
+ B2 -->|Awk| C2
198
+ C1 --> D1
199
+ C2 --> D1
200
+ D1 --> D2
201
+ D1 --> D3
202
+ D2 --> D3
203
+ D3 --> E1
204
+ E1 --> E2
205
+ E2 --> F[Python Execution]
206
+
207
+ style A fill:#e1f5ff
208
+ style F fill:#e1ffe1
209
+ style D2 fill:#fff4e1
210
+ ```
211
+
212
+ **Key Components:**
213
+
214
+ - **Parser**: Uses [Pest](https://pest.rs/) parser generator with PEG grammar defined in `src/snail.pest`
215
+ - **AST**: Separate representations for regular Snail (`Program`) and awk mode (`AwkProgram`) with source spans for error reporting
216
+ - **Lowering**: Transforms Snail AST into Python AST, emitting helper calls backed by `snail.runtime`
217
+ - `?` operator → `__snail_compact_try`
218
+ - `$(cmd)` subprocess capture → `__SnailSubprocessCapture`
219
+ - `@(cmd)` subprocess status → `__SnailSubprocessStatus`
220
+ - Regex literals → `__snail_regex_search` and `__snail_regex_compile`
221
+ - **Code Generation**: Converts Python AST to Python source for in-process execution
222
+ - **CLI**: Python wrapper (`python/snail/cli.py`) that executes via the extension module
223
+
224
+ ## 📚 Documentation
225
+
226
+ - **[Language Reference](docs/REFERENCE.md)** — Complete syntax and semantics
227
+ - **[examples/all_syntax.snail](examples/all_syntax.snail)** — Every feature in one file
228
+ - **[examples/awk.snail](examples/awk.snail)** — Awk mode examples
229
+
230
+ ## 🔌 Editor Support
231
+
232
+ Vim/Neovim plugin with syntax highlighting, formatting, and run commands:
233
+
234
+ ```vim
235
+ Plug 'sudonym1/snail', { 'rtp': 'extras/vim' }
236
+ ```
237
+
238
+ See [extras/vim/README.md](extras/vim/README.md) for details. Tree-sitter grammar available in `extras/tree-sitter-snail/`.
239
+
240
+ ## 🛠️ Building from Source
241
+
242
+ ### Prerequisites
243
+
244
+ **Python 3.10+** (required at runtime)
245
+
246
+ Snail runs in-process via a Pyo3 extension module, so it uses the active Python environment.
247
+
248
+ Installation per platform:
249
+ - **Ubuntu/Debian**: `sudo apt install python3 python3-dev`
250
+ - **Fedora/RHEL**: `sudo dnf install python3 python3-devel`
251
+ - **macOS**: `brew install python@3.12` (or use the system Python 3)
252
+ - **Windows**: Download from [python.org](https://www.python.org/downloads/)
253
+
254
+ **No Python packages required**: Snail vendors jmespath under `snail.vendor`.
255
+
256
+ **Rust toolchain** (cargo and rustc)
257
+
258
+ Install Rust using [rustup](https://rustup.rs):
259
+
260
+ ```bash
261
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
262
+ ```
263
+
264
+ This installs `cargo` (Rust's package manager) and `rustc` (the Rust compiler). After installation, restart your shell or run:
265
+
266
+ ```bash
267
+ source $HOME/.cargo/env
268
+ ```
269
+
270
+ Verify installation:
271
+
272
+ ```bash
273
+ cargo --version # Should show cargo 1.70+
274
+ rustc --version # Should show rustc 1.70+
275
+ python3 --version # Should show Python 3.10+
276
+ ```
277
+
278
+ **maturin** (build tool)
279
+
280
+ ```bash
281
+ pip install maturin
282
+ ```
283
+
284
+ ### Build and Install
285
+
286
+ ```bash
287
+ # Clone the repository
288
+ git clone https://github.com/sudonym1/snail.git
289
+ cd snail
290
+
291
+ # Create and activate a venv (recommended)
292
+ python3 -m venv .venv
293
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
294
+
295
+ # Build and install into the venv
296
+ maturin develop
297
+
298
+ # Or build wheels for distribution
299
+ maturin build --release
300
+ ```
301
+
302
+ ### Running Tests
303
+
304
+ ```bash
305
+ # Run all Rust tests (parser, lowering, awk mode; excludes proptests by default)
306
+ cargo test
307
+
308
+ # Run tests including property-based tests (proptests)
309
+ cargo test --features run-proptests
310
+
311
+ # Check code formatting and linting
312
+ cargo fmt --check
313
+ cargo clippy -- -D warnings
314
+
315
+ # Build with all features enabled (required before committing)
316
+ cargo build --features run-proptests
317
+
318
+ # Run Python CLI tests
319
+ python -m pytest python/tests
320
+ ```
321
+
322
+ **Note on Proptests**: The `snail-proptest` crate contains property-based tests that are skipped by default to keep development iteration fast. Use `--features run-proptests` to run them. Before committing, verify that `cargo build --features run-proptests` compiles successfully.
323
+
324
+ ### Troubleshooting
325
+
326
+ **Using with virtual environments:**
327
+
328
+ Activate the environment before running snail so it uses the same interpreter:
329
+
330
+ ```bash
331
+ # Create and activate a venv
332
+ python3 -m venv myenv
333
+ source myenv/bin/activate # On Windows: myenv\Scripts\activate
334
+
335
+ # Install and run
336
+ pip install snail
337
+ snail "import sys; print(sys.prefix)"
338
+ ```
339
+
340
+ ## 📋 Project Status
341
+
342
+ See [docs/PLANNING.md](docs/PLANNING.md) for the development roadmap.
343
+
@@ -0,0 +1,23 @@
1
+ snail/__init__.py,sha256=x6m1eb3fFd1tq6RV9roZgCCEsf94BJUpA2g5bZpMLWI,267
2
+ snail/_native.abi3.so,sha256=gwaydlKG9KKVT3uqd00WvssSJHimvbqyntfmYzwYig4,1368112
3
+ snail/cli.py,sha256=7fow0B1F3oJGNCI0Q5qRPN90WjtUQm8YnS2mGj4rIoE,1858
4
+ snail/runtime/__init__.py,sha256=LE3OTZVB3-G2mvr9E1SDTbjk6sxMpwsMiPPRnzanvLo,889
5
+ snail/runtime/compact_try.py,sha256=gwsZONWMf35Qcoi44cph4SqR8iThC3ujG3StTCHEMAY,379
6
+ snail/runtime/regex.py,sha256=sfh-3zoYlO182mU25ATp1tg5jgILF2AKbLzeqsHe8mw,180
7
+ snail/runtime/structured_accessor.py,sha256=C9LTJj0vbGZyuKEhNB58vs7ZdkGQH5jYouqwmICHTkg,1982
8
+ snail/runtime/subprocess.py,sha256=e8y928wqTMUVyFgPNtdFDKA7HomHxzFSfMkHmfpKvc4,1887
9
+ snail/vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ snail/vendor/jmespath/LICENSE,sha256=bu-s-k1xuC0IQIx1FHCsjZhUU42iFCyye-Aof7E9Crk,1113
11
+ snail/vendor/jmespath/__init__.py,sha256=JACw3X0msw0dG4GRxgvPqv0H7lk7TouYoS3mOrZtrsA,266
12
+ snail/vendor/jmespath/ast.py,sha256=SiHRM1mdQPRt12R2qkQu_Ezbd-ghk5HLy1Hl1jHAyv8,2130
13
+ snail/vendor/jmespath/compat.py,sha256=rMHpvmG3JAxX_lQ0yPK1dhLz-yOuEybMqdrEq6RRAr4,381
14
+ snail/vendor/jmespath/exceptions.py,sha256=XIh7bvEnLdSyJvengYInkRvjL4-cA2N-QsUAx-ezUZQ,4134
15
+ snail/vendor/jmespath/functions.py,sha256=VIpxdqieb8-dbA4ofqRkUj3A7rf6tL2F24nIqbtxvMQ,12546
16
+ snail/vendor/jmespath/lexer.py,sha256=jyrrtScIUUxpVwJ2mI_CCZA9nBFOoRp1_bd8Soq4PYg,9410
17
+ snail/vendor/jmespath/parser.py,sha256=bWD6_J391IXtmrS5TM9pg6llxp8gFrpdd8pgSJlQRcU,18897
18
+ snail/vendor/jmespath/visitor.py,sha256=dlwE8OeKdXuon9r-1Y1ZjnsGg7gkf_CXsAMZ27ThLfQ,10800
19
+ snail_lang-0.2.0.dist-info/METADATA,sha256=Kk0VtB7NWwYfoz0SWZD19Xup6ZEps16ZXZdF_wwytSw,8945
20
+ snail_lang-0.2.0.dist-info/WHEEL,sha256=vZ12AMAE5CVtd8oYbYGrz3omfHuIZCNO_3P50V00s00,104
21
+ snail_lang-0.2.0.dist-info/entry_points.txt,sha256=8u6pjHZtKGdaZQlr6yKbftplObC07caDZ44j5PIPU6M,39
22
+ snail_lang-0.2.0.dist-info/licenses/LICENSE,sha256=erKVJCZPZ4jVaEjq4ZDw2OCXmmby5r1Z6xCpn2Akktc,1062
23
+ snail_lang-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.11.5)
3
+ Root-Is-Purelib: false
4
+ Tag: cp310-abi3-macosx_11_0_arm64
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ snail=snail.cli:main