just-bash 0.1.5__py3-none-any.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.
- just_bash/__init__.py +55 -0
- just_bash/ast/__init__.py +213 -0
- just_bash/ast/factory.py +320 -0
- just_bash/ast/types.py +953 -0
- just_bash/bash.py +220 -0
- just_bash/commands/__init__.py +23 -0
- just_bash/commands/argv/__init__.py +5 -0
- just_bash/commands/argv/argv.py +21 -0
- just_bash/commands/awk/__init__.py +5 -0
- just_bash/commands/awk/awk.py +1168 -0
- just_bash/commands/base64/__init__.py +5 -0
- just_bash/commands/base64/base64.py +138 -0
- just_bash/commands/basename/__init__.py +5 -0
- just_bash/commands/basename/basename.py +72 -0
- just_bash/commands/bash/__init__.py +5 -0
- just_bash/commands/bash/bash.py +188 -0
- just_bash/commands/cat/__init__.py +5 -0
- just_bash/commands/cat/cat.py +173 -0
- just_bash/commands/checksum/__init__.py +5 -0
- just_bash/commands/checksum/checksum.py +179 -0
- just_bash/commands/chmod/__init__.py +5 -0
- just_bash/commands/chmod/chmod.py +216 -0
- just_bash/commands/column/__init__.py +5 -0
- just_bash/commands/column/column.py +180 -0
- just_bash/commands/comm/__init__.py +5 -0
- just_bash/commands/comm/comm.py +150 -0
- just_bash/commands/compression/__init__.py +5 -0
- just_bash/commands/compression/compression.py +298 -0
- just_bash/commands/cp/__init__.py +5 -0
- just_bash/commands/cp/cp.py +149 -0
- just_bash/commands/curl/__init__.py +5 -0
- just_bash/commands/curl/curl.py +801 -0
- just_bash/commands/cut/__init__.py +5 -0
- just_bash/commands/cut/cut.py +327 -0
- just_bash/commands/date/__init__.py +5 -0
- just_bash/commands/date/date.py +258 -0
- just_bash/commands/diff/__init__.py +5 -0
- just_bash/commands/diff/diff.py +118 -0
- just_bash/commands/dirname/__init__.py +5 -0
- just_bash/commands/dirname/dirname.py +56 -0
- just_bash/commands/du/__init__.py +5 -0
- just_bash/commands/du/du.py +150 -0
- just_bash/commands/echo/__init__.py +5 -0
- just_bash/commands/echo/echo.py +125 -0
- just_bash/commands/env/__init__.py +5 -0
- just_bash/commands/env/env.py +163 -0
- just_bash/commands/expand/__init__.py +5 -0
- just_bash/commands/expand/expand.py +299 -0
- just_bash/commands/expr/__init__.py +5 -0
- just_bash/commands/expr/expr.py +273 -0
- just_bash/commands/file/__init__.py +5 -0
- just_bash/commands/file/file.py +274 -0
- just_bash/commands/find/__init__.py +5 -0
- just_bash/commands/find/find.py +623 -0
- just_bash/commands/fold/__init__.py +5 -0
- just_bash/commands/fold/fold.py +160 -0
- just_bash/commands/grep/__init__.py +5 -0
- just_bash/commands/grep/grep.py +418 -0
- just_bash/commands/head/__init__.py +5 -0
- just_bash/commands/head/head.py +167 -0
- just_bash/commands/help/__init__.py +5 -0
- just_bash/commands/help/help.py +67 -0
- just_bash/commands/hostname/__init__.py +5 -0
- just_bash/commands/hostname/hostname.py +21 -0
- just_bash/commands/html_to_markdown/__init__.py +5 -0
- just_bash/commands/html_to_markdown/html_to_markdown.py +191 -0
- just_bash/commands/join/__init__.py +5 -0
- just_bash/commands/join/join.py +252 -0
- just_bash/commands/jq/__init__.py +5 -0
- just_bash/commands/jq/jq.py +280 -0
- just_bash/commands/ln/__init__.py +5 -0
- just_bash/commands/ln/ln.py +127 -0
- just_bash/commands/ls/__init__.py +5 -0
- just_bash/commands/ls/ls.py +280 -0
- just_bash/commands/mkdir/__init__.py +5 -0
- just_bash/commands/mkdir/mkdir.py +92 -0
- just_bash/commands/mv/__init__.py +5 -0
- just_bash/commands/mv/mv.py +142 -0
- just_bash/commands/nl/__init__.py +5 -0
- just_bash/commands/nl/nl.py +180 -0
- just_bash/commands/od/__init__.py +5 -0
- just_bash/commands/od/od.py +157 -0
- just_bash/commands/paste/__init__.py +5 -0
- just_bash/commands/paste/paste.py +100 -0
- just_bash/commands/printf/__init__.py +5 -0
- just_bash/commands/printf/printf.py +157 -0
- just_bash/commands/pwd/__init__.py +5 -0
- just_bash/commands/pwd/pwd.py +23 -0
- just_bash/commands/read/__init__.py +5 -0
- just_bash/commands/read/read.py +185 -0
- just_bash/commands/readlink/__init__.py +5 -0
- just_bash/commands/readlink/readlink.py +86 -0
- just_bash/commands/registry.py +844 -0
- just_bash/commands/rev/__init__.py +5 -0
- just_bash/commands/rev/rev.py +74 -0
- just_bash/commands/rg/__init__.py +5 -0
- just_bash/commands/rg/rg.py +1048 -0
- just_bash/commands/rm/__init__.py +5 -0
- just_bash/commands/rm/rm.py +106 -0
- just_bash/commands/search_engine/__init__.py +13 -0
- just_bash/commands/search_engine/matcher.py +170 -0
- just_bash/commands/search_engine/regex.py +159 -0
- just_bash/commands/sed/__init__.py +5 -0
- just_bash/commands/sed/sed.py +863 -0
- just_bash/commands/seq/__init__.py +5 -0
- just_bash/commands/seq/seq.py +190 -0
- just_bash/commands/shell/__init__.py +5 -0
- just_bash/commands/shell/shell.py +206 -0
- just_bash/commands/sleep/__init__.py +5 -0
- just_bash/commands/sleep/sleep.py +62 -0
- just_bash/commands/sort/__init__.py +5 -0
- just_bash/commands/sort/sort.py +411 -0
- just_bash/commands/split/__init__.py +5 -0
- just_bash/commands/split/split.py +237 -0
- just_bash/commands/sqlite3/__init__.py +5 -0
- just_bash/commands/sqlite3/sqlite3_cmd.py +505 -0
- just_bash/commands/stat/__init__.py +5 -0
- just_bash/commands/stat/stat.py +150 -0
- just_bash/commands/strings/__init__.py +5 -0
- just_bash/commands/strings/strings.py +150 -0
- just_bash/commands/tac/__init__.py +5 -0
- just_bash/commands/tac/tac.py +158 -0
- just_bash/commands/tail/__init__.py +5 -0
- just_bash/commands/tail/tail.py +180 -0
- just_bash/commands/tar/__init__.py +5 -0
- just_bash/commands/tar/tar.py +1067 -0
- just_bash/commands/tee/__init__.py +5 -0
- just_bash/commands/tee/tee.py +63 -0
- just_bash/commands/timeout/__init__.py +5 -0
- just_bash/commands/timeout/timeout.py +188 -0
- just_bash/commands/touch/__init__.py +5 -0
- just_bash/commands/touch/touch.py +91 -0
- just_bash/commands/tr/__init__.py +5 -0
- just_bash/commands/tr/tr.py +297 -0
- just_bash/commands/tree/__init__.py +5 -0
- just_bash/commands/tree/tree.py +139 -0
- just_bash/commands/true/__init__.py +5 -0
- just_bash/commands/true/true.py +32 -0
- just_bash/commands/uniq/__init__.py +5 -0
- just_bash/commands/uniq/uniq.py +323 -0
- just_bash/commands/wc/__init__.py +5 -0
- just_bash/commands/wc/wc.py +169 -0
- just_bash/commands/which/__init__.py +5 -0
- just_bash/commands/which/which.py +52 -0
- just_bash/commands/xan/__init__.py +5 -0
- just_bash/commands/xan/xan.py +1663 -0
- just_bash/commands/xargs/__init__.py +5 -0
- just_bash/commands/xargs/xargs.py +136 -0
- just_bash/commands/yq/__init__.py +5 -0
- just_bash/commands/yq/yq.py +848 -0
- just_bash/fs/__init__.py +29 -0
- just_bash/fs/in_memory_fs.py +621 -0
- just_bash/fs/mountable_fs.py +504 -0
- just_bash/fs/overlay_fs.py +894 -0
- just_bash/fs/read_write_fs.py +455 -0
- just_bash/interpreter/__init__.py +37 -0
- just_bash/interpreter/builtins/__init__.py +92 -0
- just_bash/interpreter/builtins/alias.py +154 -0
- just_bash/interpreter/builtins/cd.py +76 -0
- just_bash/interpreter/builtins/control.py +127 -0
- just_bash/interpreter/builtins/declare.py +336 -0
- just_bash/interpreter/builtins/export.py +56 -0
- just_bash/interpreter/builtins/let.py +44 -0
- just_bash/interpreter/builtins/local.py +57 -0
- just_bash/interpreter/builtins/mapfile.py +152 -0
- just_bash/interpreter/builtins/misc.py +378 -0
- just_bash/interpreter/builtins/readonly.py +80 -0
- just_bash/interpreter/builtins/set.py +234 -0
- just_bash/interpreter/builtins/shopt.py +201 -0
- just_bash/interpreter/builtins/source.py +136 -0
- just_bash/interpreter/builtins/test.py +290 -0
- just_bash/interpreter/builtins/unset.py +53 -0
- just_bash/interpreter/conditionals.py +387 -0
- just_bash/interpreter/control_flow.py +381 -0
- just_bash/interpreter/errors.py +116 -0
- just_bash/interpreter/expansion.py +1156 -0
- just_bash/interpreter/interpreter.py +813 -0
- just_bash/interpreter/types.py +134 -0
- just_bash/network/__init__.py +1 -0
- just_bash/parser/__init__.py +39 -0
- just_bash/parser/lexer.py +948 -0
- just_bash/parser/parser.py +2162 -0
- just_bash/py.typed +0 -0
- just_bash/query_engine/__init__.py +83 -0
- just_bash/query_engine/builtins/__init__.py +1283 -0
- just_bash/query_engine/evaluator.py +578 -0
- just_bash/query_engine/parser.py +525 -0
- just_bash/query_engine/tokenizer.py +329 -0
- just_bash/query_engine/types.py +373 -0
- just_bash/types.py +180 -0
- just_bash-0.1.5.dist-info/METADATA +410 -0
- just_bash-0.1.5.dist-info/RECORD +193 -0
- just_bash-0.1.5.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,1283 @@
|
|
|
1
|
+
"""Builtin functions for the query engine.
|
|
2
|
+
|
|
3
|
+
This module provides all the builtin functions available in jq expressions.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
import re
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import Any
|
|
12
|
+
from urllib.parse import quote as uri_quote
|
|
13
|
+
|
|
14
|
+
from ..types import AstNode, EvalContext
|
|
15
|
+
|
|
16
|
+
# Type for the evaluate function passed from evaluator
|
|
17
|
+
EvalFunc = Callable[[Any, AstNode, EvalContext], list[Any]]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def call_builtin(
|
|
21
|
+
value: Any,
|
|
22
|
+
name: str,
|
|
23
|
+
args: list[AstNode],
|
|
24
|
+
ctx: EvalContext,
|
|
25
|
+
eval_fn: EvalFunc,
|
|
26
|
+
) -> list[Any]:
|
|
27
|
+
"""Call a builtin function.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
value: The current input value
|
|
31
|
+
name: The function name
|
|
32
|
+
args: The function arguments (as AST nodes)
|
|
33
|
+
ctx: The evaluation context
|
|
34
|
+
eval_fn: Function to evaluate AST nodes
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
A list of result values
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If the function is unknown
|
|
41
|
+
"""
|
|
42
|
+
# Core functions
|
|
43
|
+
if name == "keys":
|
|
44
|
+
if isinstance(value, list):
|
|
45
|
+
return [list(range(len(value)))]
|
|
46
|
+
if isinstance(value, dict):
|
|
47
|
+
return [sorted(value.keys())]
|
|
48
|
+
return [None]
|
|
49
|
+
|
|
50
|
+
if name == "keys_unsorted":
|
|
51
|
+
if isinstance(value, list):
|
|
52
|
+
return [list(range(len(value)))]
|
|
53
|
+
if isinstance(value, dict):
|
|
54
|
+
return [list(value.keys())]
|
|
55
|
+
return [None]
|
|
56
|
+
|
|
57
|
+
if name == "values":
|
|
58
|
+
if isinstance(value, list):
|
|
59
|
+
return [value]
|
|
60
|
+
if isinstance(value, dict):
|
|
61
|
+
return [list(value.values())]
|
|
62
|
+
return [None]
|
|
63
|
+
|
|
64
|
+
if name == "length":
|
|
65
|
+
if isinstance(value, str):
|
|
66
|
+
return [len(value)]
|
|
67
|
+
if isinstance(value, (list, dict)):
|
|
68
|
+
return [len(value)]
|
|
69
|
+
if value is None:
|
|
70
|
+
return [0]
|
|
71
|
+
return [None]
|
|
72
|
+
|
|
73
|
+
if name == "utf8bytelength":
|
|
74
|
+
if isinstance(value, str):
|
|
75
|
+
return [len(value.encode("utf-8"))]
|
|
76
|
+
return [None]
|
|
77
|
+
|
|
78
|
+
if name == "type":
|
|
79
|
+
if value is None:
|
|
80
|
+
return ["null"]
|
|
81
|
+
if isinstance(value, bool):
|
|
82
|
+
return ["boolean"]
|
|
83
|
+
if isinstance(value, (int, float)):
|
|
84
|
+
return ["number"]
|
|
85
|
+
if isinstance(value, str):
|
|
86
|
+
return ["string"]
|
|
87
|
+
if isinstance(value, list):
|
|
88
|
+
return ["array"]
|
|
89
|
+
if isinstance(value, dict):
|
|
90
|
+
return ["object"]
|
|
91
|
+
return ["null"]
|
|
92
|
+
|
|
93
|
+
if name == "empty":
|
|
94
|
+
return []
|
|
95
|
+
|
|
96
|
+
if name == "error":
|
|
97
|
+
msg = eval_fn(value, args[0], ctx)[0] if args else value
|
|
98
|
+
raise ValueError(str(msg))
|
|
99
|
+
|
|
100
|
+
if name == "not":
|
|
101
|
+
return [not _is_truthy(value)]
|
|
102
|
+
|
|
103
|
+
if name == "null":
|
|
104
|
+
return [None]
|
|
105
|
+
|
|
106
|
+
if name == "true":
|
|
107
|
+
return [True]
|
|
108
|
+
|
|
109
|
+
if name == "false":
|
|
110
|
+
return [False]
|
|
111
|
+
|
|
112
|
+
if name == "first":
|
|
113
|
+
if args:
|
|
114
|
+
results = eval_fn(value, args[0], ctx)
|
|
115
|
+
return [results[0]] if results else []
|
|
116
|
+
if isinstance(value, list) and value:
|
|
117
|
+
return [value[0]]
|
|
118
|
+
return [None]
|
|
119
|
+
|
|
120
|
+
if name == "last":
|
|
121
|
+
if args:
|
|
122
|
+
results = eval_fn(value, args[0], ctx)
|
|
123
|
+
return [results[-1]] if results else []
|
|
124
|
+
if isinstance(value, list) and value:
|
|
125
|
+
return [value[-1]]
|
|
126
|
+
return [None]
|
|
127
|
+
|
|
128
|
+
if name == "nth":
|
|
129
|
+
if not args:
|
|
130
|
+
return [None]
|
|
131
|
+
ns = eval_fn(value, args[0], ctx)
|
|
132
|
+
n = ns[0] if ns else 0
|
|
133
|
+
if len(args) > 1:
|
|
134
|
+
results = eval_fn(value, args[1], ctx)
|
|
135
|
+
return [results[n]] if isinstance(n, int) and 0 <= n < len(results) else []
|
|
136
|
+
if isinstance(value, list):
|
|
137
|
+
return [value[n]] if isinstance(n, int) and 0 <= n < len(value) else [None]
|
|
138
|
+
return [None]
|
|
139
|
+
|
|
140
|
+
if name == "range":
|
|
141
|
+
if not args:
|
|
142
|
+
return []
|
|
143
|
+
starts = eval_fn(value, args[0], ctx)
|
|
144
|
+
if len(args) == 1:
|
|
145
|
+
n = starts[0] if starts else 0
|
|
146
|
+
return list(range(int(n)))
|
|
147
|
+
ends = eval_fn(value, args[1], ctx)
|
|
148
|
+
start = int(starts[0]) if starts else 0
|
|
149
|
+
end = int(ends[0]) if ends else 0
|
|
150
|
+
return list(range(start, end))
|
|
151
|
+
|
|
152
|
+
if name == "reverse":
|
|
153
|
+
if isinstance(value, list):
|
|
154
|
+
return [list(reversed(value))]
|
|
155
|
+
if isinstance(value, str):
|
|
156
|
+
return [value[::-1]]
|
|
157
|
+
return [None]
|
|
158
|
+
|
|
159
|
+
if name == "sort":
|
|
160
|
+
if isinstance(value, list):
|
|
161
|
+
return [sorted(value, key=_jq_sort_key)]
|
|
162
|
+
return [None]
|
|
163
|
+
|
|
164
|
+
if name == "sort_by":
|
|
165
|
+
if not isinstance(value, list) or not args:
|
|
166
|
+
return [None]
|
|
167
|
+
items = [(eval_fn(item, args[0], ctx), item) for item in value]
|
|
168
|
+
sorted_items = sorted(items, key=lambda x: _jq_sort_key(x[0][0] if x[0] else None))
|
|
169
|
+
return [[item for _, item in sorted_items]]
|
|
170
|
+
|
|
171
|
+
if name == "unique":
|
|
172
|
+
if isinstance(value, list):
|
|
173
|
+
seen = set()
|
|
174
|
+
result = []
|
|
175
|
+
for item in value:
|
|
176
|
+
key = json.dumps(item, sort_keys=True)
|
|
177
|
+
if key not in seen:
|
|
178
|
+
seen.add(key)
|
|
179
|
+
result.append(item)
|
|
180
|
+
return [result]
|
|
181
|
+
return [None]
|
|
182
|
+
|
|
183
|
+
if name == "unique_by":
|
|
184
|
+
if not isinstance(value, list) or not args:
|
|
185
|
+
return [None]
|
|
186
|
+
seen = set()
|
|
187
|
+
result = []
|
|
188
|
+
for item in value:
|
|
189
|
+
key_vals = eval_fn(item, args[0], ctx)
|
|
190
|
+
key = json.dumps(key_vals[0] if key_vals else None, sort_keys=True)
|
|
191
|
+
if key not in seen:
|
|
192
|
+
seen.add(key)
|
|
193
|
+
result.append(item)
|
|
194
|
+
return [result]
|
|
195
|
+
|
|
196
|
+
if name == "group_by":
|
|
197
|
+
if not isinstance(value, list) or not args:
|
|
198
|
+
return [None]
|
|
199
|
+
groups: dict[str, list[Any]] = {}
|
|
200
|
+
for item in value:
|
|
201
|
+
key_vals = eval_fn(item, args[0], ctx)
|
|
202
|
+
key = json.dumps(key_vals[0] if key_vals else None, sort_keys=True)
|
|
203
|
+
if key not in groups:
|
|
204
|
+
groups[key] = []
|
|
205
|
+
groups[key].append(item)
|
|
206
|
+
return [list(groups.values())]
|
|
207
|
+
|
|
208
|
+
if name == "max":
|
|
209
|
+
if isinstance(value, list) and value:
|
|
210
|
+
return [max(value, key=_jq_sort_key)]
|
|
211
|
+
return [None]
|
|
212
|
+
|
|
213
|
+
if name == "max_by":
|
|
214
|
+
if not isinstance(value, list) or not value or not args:
|
|
215
|
+
return [None]
|
|
216
|
+
items = [(eval_fn(item, args[0], ctx), item) for item in value]
|
|
217
|
+
max_item = max(items, key=lambda x: _jq_sort_key(x[0][0] if x[0] else None))
|
|
218
|
+
return [max_item[1]]
|
|
219
|
+
|
|
220
|
+
if name == "min":
|
|
221
|
+
if isinstance(value, list) and value:
|
|
222
|
+
return [min(value, key=_jq_sort_key)]
|
|
223
|
+
return [None]
|
|
224
|
+
|
|
225
|
+
if name == "min_by":
|
|
226
|
+
if not isinstance(value, list) or not value or not args:
|
|
227
|
+
return [None]
|
|
228
|
+
items = [(eval_fn(item, args[0], ctx), item) for item in value]
|
|
229
|
+
min_item = min(items, key=lambda x: _jq_sort_key(x[0][0] if x[0] else None))
|
|
230
|
+
return [min_item[1]]
|
|
231
|
+
|
|
232
|
+
if name == "flatten":
|
|
233
|
+
if not isinstance(value, list):
|
|
234
|
+
return [None]
|
|
235
|
+
depth = float("inf")
|
|
236
|
+
if args:
|
|
237
|
+
depth_vals = eval_fn(value, args[0], ctx)
|
|
238
|
+
depth = depth_vals[0] if depth_vals else float("inf")
|
|
239
|
+
result = _flatten(value, int(depth) if depth != float("inf") else None)
|
|
240
|
+
return [result]
|
|
241
|
+
|
|
242
|
+
if name == "add":
|
|
243
|
+
if isinstance(value, list):
|
|
244
|
+
if not value:
|
|
245
|
+
return [None]
|
|
246
|
+
if all(isinstance(x, (int, float)) for x in value):
|
|
247
|
+
return [sum(value)]
|
|
248
|
+
if all(isinstance(x, str) for x in value):
|
|
249
|
+
return ["".join(value)]
|
|
250
|
+
if all(isinstance(x, list) for x in value):
|
|
251
|
+
result = []
|
|
252
|
+
for x in value:
|
|
253
|
+
result.extend(x)
|
|
254
|
+
return [result]
|
|
255
|
+
if all(isinstance(x, dict) for x in value):
|
|
256
|
+
result = {}
|
|
257
|
+
for x in value:
|
|
258
|
+
result.update(x)
|
|
259
|
+
return [result]
|
|
260
|
+
return [None]
|
|
261
|
+
|
|
262
|
+
if name == "any":
|
|
263
|
+
if args:
|
|
264
|
+
if isinstance(value, list):
|
|
265
|
+
return [
|
|
266
|
+
any(
|
|
267
|
+
_is_truthy(eval_fn(item, args[0], ctx)[0])
|
|
268
|
+
for item in value
|
|
269
|
+
if eval_fn(item, args[0], ctx)
|
|
270
|
+
)
|
|
271
|
+
]
|
|
272
|
+
return [False]
|
|
273
|
+
if isinstance(value, list):
|
|
274
|
+
return [any(_is_truthy(x) for x in value)]
|
|
275
|
+
return [False]
|
|
276
|
+
|
|
277
|
+
if name == "all":
|
|
278
|
+
if args:
|
|
279
|
+
if isinstance(value, list):
|
|
280
|
+
return [
|
|
281
|
+
all(
|
|
282
|
+
_is_truthy(eval_fn(item, args[0], ctx)[0])
|
|
283
|
+
for item in value
|
|
284
|
+
if eval_fn(item, args[0], ctx)
|
|
285
|
+
)
|
|
286
|
+
]
|
|
287
|
+
return [True]
|
|
288
|
+
if isinstance(value, list):
|
|
289
|
+
return [all(_is_truthy(x) for x in value)]
|
|
290
|
+
return [True]
|
|
291
|
+
|
|
292
|
+
if name == "select":
|
|
293
|
+
if not args:
|
|
294
|
+
return [value]
|
|
295
|
+
conds = eval_fn(value, args[0], ctx)
|
|
296
|
+
return [value] if any(_is_truthy(c) for c in conds) else []
|
|
297
|
+
|
|
298
|
+
if name == "map":
|
|
299
|
+
if not args or not isinstance(value, list):
|
|
300
|
+
return [None]
|
|
301
|
+
results = []
|
|
302
|
+
for item in value:
|
|
303
|
+
results.extend(eval_fn(item, args[0], ctx))
|
|
304
|
+
return [results]
|
|
305
|
+
|
|
306
|
+
if name == "map_values":
|
|
307
|
+
if not args:
|
|
308
|
+
return [None]
|
|
309
|
+
if isinstance(value, list):
|
|
310
|
+
results = []
|
|
311
|
+
for item in value:
|
|
312
|
+
item_results = eval_fn(item, args[0], ctx)
|
|
313
|
+
results.extend(item_results)
|
|
314
|
+
return [results]
|
|
315
|
+
if isinstance(value, dict):
|
|
316
|
+
result = {}
|
|
317
|
+
for k, v in value.items():
|
|
318
|
+
mapped = eval_fn(v, args[0], ctx)
|
|
319
|
+
if mapped:
|
|
320
|
+
result[k] = mapped[0]
|
|
321
|
+
return [result]
|
|
322
|
+
return [None]
|
|
323
|
+
|
|
324
|
+
if name == "has":
|
|
325
|
+
if not args:
|
|
326
|
+
return [False]
|
|
327
|
+
keys = eval_fn(value, args[0], ctx)
|
|
328
|
+
key = keys[0] if keys else None
|
|
329
|
+
if isinstance(value, list) and isinstance(key, int):
|
|
330
|
+
return [0 <= key < len(value)]
|
|
331
|
+
if isinstance(value, dict) and isinstance(key, str):
|
|
332
|
+
return [key in value]
|
|
333
|
+
return [False]
|
|
334
|
+
|
|
335
|
+
if name == "in":
|
|
336
|
+
if not args:
|
|
337
|
+
return [False]
|
|
338
|
+
objs = eval_fn(value, args[0], ctx)
|
|
339
|
+
obj = objs[0] if objs else None
|
|
340
|
+
if isinstance(obj, list) and isinstance(value, int):
|
|
341
|
+
return [0 <= value < len(obj)]
|
|
342
|
+
if isinstance(obj, dict) and isinstance(value, str):
|
|
343
|
+
return [value in obj]
|
|
344
|
+
return [False]
|
|
345
|
+
|
|
346
|
+
if name == "contains":
|
|
347
|
+
if not args:
|
|
348
|
+
return [False]
|
|
349
|
+
others = eval_fn(value, args[0], ctx)
|
|
350
|
+
other = others[0] if others else None
|
|
351
|
+
return [_contains_deep(value, other)]
|
|
352
|
+
|
|
353
|
+
if name == "inside":
|
|
354
|
+
if not args:
|
|
355
|
+
return [False]
|
|
356
|
+
others = eval_fn(value, args[0], ctx)
|
|
357
|
+
other = others[0] if others else None
|
|
358
|
+
return [_contains_deep(other, value)]
|
|
359
|
+
|
|
360
|
+
if name == "getpath":
|
|
361
|
+
if not args:
|
|
362
|
+
return [None]
|
|
363
|
+
paths = eval_fn(value, args[0], ctx)
|
|
364
|
+
path = paths[0] if paths else []
|
|
365
|
+
current = value
|
|
366
|
+
for key in path:
|
|
367
|
+
if current is None:
|
|
368
|
+
return [None]
|
|
369
|
+
if isinstance(current, list) and isinstance(key, int):
|
|
370
|
+
current = current[key] if 0 <= key < len(current) else None
|
|
371
|
+
elif isinstance(current, dict) and isinstance(key, str):
|
|
372
|
+
current = current.get(key)
|
|
373
|
+
else:
|
|
374
|
+
return [None]
|
|
375
|
+
return [current]
|
|
376
|
+
|
|
377
|
+
if name == "setpath":
|
|
378
|
+
if len(args) < 2:
|
|
379
|
+
return [None]
|
|
380
|
+
paths = eval_fn(value, args[0], ctx)
|
|
381
|
+
path = paths[0] if paths else []
|
|
382
|
+
vals = eval_fn(value, args[1], ctx)
|
|
383
|
+
new_val = vals[0] if vals else None
|
|
384
|
+
return [_set_path(value, path, new_val)]
|
|
385
|
+
|
|
386
|
+
if name == "delpaths":
|
|
387
|
+
if not args:
|
|
388
|
+
return [value]
|
|
389
|
+
path_lists = eval_fn(value, args[0], ctx)
|
|
390
|
+
paths = path_lists[0] if path_lists else []
|
|
391
|
+
result = value
|
|
392
|
+
# Delete longest paths first to avoid index shifting issues
|
|
393
|
+
for path in sorted(paths, key=len, reverse=True):
|
|
394
|
+
result = _delete_path(result, path)
|
|
395
|
+
return [result]
|
|
396
|
+
|
|
397
|
+
if name == "path":
|
|
398
|
+
if not args:
|
|
399
|
+
return [[]]
|
|
400
|
+
# Collect all paths that match the expression
|
|
401
|
+
paths = []
|
|
402
|
+
_collect_paths(value, args[0], ctx, eval_fn, [], paths)
|
|
403
|
+
return paths
|
|
404
|
+
|
|
405
|
+
if name == "del":
|
|
406
|
+
if not args:
|
|
407
|
+
return [value]
|
|
408
|
+
return [_apply_del(value, args[0], ctx, eval_fn)]
|
|
409
|
+
|
|
410
|
+
if name == "paths":
|
|
411
|
+
paths = _get_all_paths(value, [])
|
|
412
|
+
if args:
|
|
413
|
+
# Filter paths by predicate
|
|
414
|
+
filtered = []
|
|
415
|
+
for p in paths:
|
|
416
|
+
v = _get_value_at_path(value, p)
|
|
417
|
+
results = eval_fn(v, args[0], ctx)
|
|
418
|
+
if any(_is_truthy(r) for r in results):
|
|
419
|
+
filtered.append(p)
|
|
420
|
+
return filtered
|
|
421
|
+
return paths
|
|
422
|
+
|
|
423
|
+
if name == "leaf_paths":
|
|
424
|
+
return [_get_leaf_paths(value, [])]
|
|
425
|
+
|
|
426
|
+
if name == "to_entries":
|
|
427
|
+
if isinstance(value, dict):
|
|
428
|
+
return [[{"key": k, "value": v} for k, v in value.items()]]
|
|
429
|
+
return [None]
|
|
430
|
+
|
|
431
|
+
if name == "from_entries":
|
|
432
|
+
if isinstance(value, list):
|
|
433
|
+
result = {}
|
|
434
|
+
for item in value:
|
|
435
|
+
if isinstance(item, dict):
|
|
436
|
+
key = item.get("key") or item.get("name") or item.get("k")
|
|
437
|
+
val = item.get("value") if "value" in item else item.get("v")
|
|
438
|
+
if key is not None:
|
|
439
|
+
result[str(key)] = val
|
|
440
|
+
return [result]
|
|
441
|
+
return [None]
|
|
442
|
+
|
|
443
|
+
if name == "with_entries":
|
|
444
|
+
if not args:
|
|
445
|
+
return [value]
|
|
446
|
+
if isinstance(value, dict):
|
|
447
|
+
entries = [{"key": k, "value": v} for k, v in value.items()]
|
|
448
|
+
new_entries = []
|
|
449
|
+
for entry in entries:
|
|
450
|
+
results = eval_fn(entry, args[0], ctx)
|
|
451
|
+
new_entries.extend(results)
|
|
452
|
+
result = {}
|
|
453
|
+
for item in new_entries:
|
|
454
|
+
if isinstance(item, dict):
|
|
455
|
+
key = item.get("key") or item.get("name") or item.get("k")
|
|
456
|
+
val = item.get("value") if "value" in item else item.get("v")
|
|
457
|
+
if key is not None:
|
|
458
|
+
result[str(key)] = val
|
|
459
|
+
return [result]
|
|
460
|
+
return [None]
|
|
461
|
+
|
|
462
|
+
# String functions
|
|
463
|
+
if name == "join":
|
|
464
|
+
if not isinstance(value, list):
|
|
465
|
+
return [None]
|
|
466
|
+
seps = eval_fn(value, args[0], ctx) if args else [""]
|
|
467
|
+
sep = str(seps[0]) if seps else ""
|
|
468
|
+
return [sep.join(v if isinstance(v, str) else json.dumps(v) for v in value)]
|
|
469
|
+
|
|
470
|
+
if name == "split":
|
|
471
|
+
if not isinstance(value, str) or not args:
|
|
472
|
+
return [None]
|
|
473
|
+
seps = eval_fn(value, args[0], ctx)
|
|
474
|
+
sep = str(seps[0]) if seps else ""
|
|
475
|
+
return [value.split(sep)]
|
|
476
|
+
|
|
477
|
+
if name == "test":
|
|
478
|
+
if not isinstance(value, str) or not args:
|
|
479
|
+
return [False]
|
|
480
|
+
patterns = eval_fn(value, args[0], ctx)
|
|
481
|
+
pattern = str(patterns[0]) if patterns else ""
|
|
482
|
+
try:
|
|
483
|
+
flags = eval_fn(value, args[1], ctx)[0] if len(args) > 1 else ""
|
|
484
|
+
re_flags = _get_re_flags(flags)
|
|
485
|
+
return [bool(re.search(pattern, value, re_flags))]
|
|
486
|
+
except re.error:
|
|
487
|
+
return [False]
|
|
488
|
+
|
|
489
|
+
if name == "match":
|
|
490
|
+
if not isinstance(value, str) or not args:
|
|
491
|
+
return [None]
|
|
492
|
+
patterns = eval_fn(value, args[0], ctx)
|
|
493
|
+
pattern = str(patterns[0]) if patterns else ""
|
|
494
|
+
try:
|
|
495
|
+
flags = eval_fn(value, args[1], ctx)[0] if len(args) > 1 else ""
|
|
496
|
+
re_flags = _get_re_flags(flags)
|
|
497
|
+
m = re.search(pattern, value, re_flags)
|
|
498
|
+
if not m:
|
|
499
|
+
return []
|
|
500
|
+
return [
|
|
501
|
+
{
|
|
502
|
+
"offset": m.start(),
|
|
503
|
+
"length": len(m.group()),
|
|
504
|
+
"string": m.group(),
|
|
505
|
+
"captures": [
|
|
506
|
+
{
|
|
507
|
+
"offset": m.start(i + 1) if m.group(i + 1) else None,
|
|
508
|
+
"length": len(m.group(i + 1)) if m.group(i + 1) else 0,
|
|
509
|
+
"string": m.group(i + 1) or "",
|
|
510
|
+
"name": None,
|
|
511
|
+
}
|
|
512
|
+
for i in range(m.lastindex or 0)
|
|
513
|
+
],
|
|
514
|
+
}
|
|
515
|
+
]
|
|
516
|
+
except re.error:
|
|
517
|
+
return [None]
|
|
518
|
+
|
|
519
|
+
if name == "capture":
|
|
520
|
+
if not isinstance(value, str) or not args:
|
|
521
|
+
return [None]
|
|
522
|
+
patterns = eval_fn(value, args[0], ctx)
|
|
523
|
+
pattern = str(patterns[0]) if patterns else ""
|
|
524
|
+
try:
|
|
525
|
+
flags = eval_fn(value, args[1], ctx)[0] if len(args) > 1 else ""
|
|
526
|
+
re_flags = _get_re_flags(flags)
|
|
527
|
+
m = re.search(pattern, value, re_flags)
|
|
528
|
+
if not m or not m.groupdict():
|
|
529
|
+
return [{}]
|
|
530
|
+
return [m.groupdict()]
|
|
531
|
+
except re.error:
|
|
532
|
+
return [None]
|
|
533
|
+
|
|
534
|
+
if name == "sub":
|
|
535
|
+
if not isinstance(value, str) or len(args) < 2:
|
|
536
|
+
return [None]
|
|
537
|
+
patterns = eval_fn(value, args[0], ctx)
|
|
538
|
+
replacements = eval_fn(value, args[1], ctx)
|
|
539
|
+
pattern = str(patterns[0]) if patterns else ""
|
|
540
|
+
replacement = str(replacements[0]) if replacements else ""
|
|
541
|
+
try:
|
|
542
|
+
flags = eval_fn(value, args[2], ctx)[0] if len(args) > 2 else ""
|
|
543
|
+
re_flags = _get_re_flags(flags)
|
|
544
|
+
return [re.sub(pattern, replacement, value, count=1, flags=re_flags)]
|
|
545
|
+
except re.error:
|
|
546
|
+
return [value]
|
|
547
|
+
|
|
548
|
+
if name == "gsub":
|
|
549
|
+
if not isinstance(value, str) or len(args) < 2:
|
|
550
|
+
return [None]
|
|
551
|
+
patterns = eval_fn(value, args[0], ctx)
|
|
552
|
+
replacements = eval_fn(value, args[1], ctx)
|
|
553
|
+
pattern = str(patterns[0]) if patterns else ""
|
|
554
|
+
replacement = str(replacements[0]) if replacements else ""
|
|
555
|
+
try:
|
|
556
|
+
flags = eval_fn(value, args[2], ctx)[0] if len(args) > 2 else "g"
|
|
557
|
+
re_flags = _get_re_flags(flags)
|
|
558
|
+
return [re.sub(pattern, replacement, value, flags=re_flags)]
|
|
559
|
+
except re.error:
|
|
560
|
+
return [value]
|
|
561
|
+
|
|
562
|
+
if name == "ascii_downcase":
|
|
563
|
+
if isinstance(value, str):
|
|
564
|
+
return [value.lower()]
|
|
565
|
+
return [None]
|
|
566
|
+
|
|
567
|
+
if name == "ascii_upcase":
|
|
568
|
+
if isinstance(value, str):
|
|
569
|
+
return [value.upper()]
|
|
570
|
+
return [None]
|
|
571
|
+
|
|
572
|
+
if name == "ltrimstr":
|
|
573
|
+
if not isinstance(value, str) or not args:
|
|
574
|
+
return [value]
|
|
575
|
+
prefixes = eval_fn(value, args[0], ctx)
|
|
576
|
+
prefix = str(prefixes[0]) if prefixes else ""
|
|
577
|
+
return [value[len(prefix) :] if value.startswith(prefix) else value]
|
|
578
|
+
|
|
579
|
+
if name == "rtrimstr":
|
|
580
|
+
if not isinstance(value, str) or not args:
|
|
581
|
+
return [value]
|
|
582
|
+
suffixes = eval_fn(value, args[0], ctx)
|
|
583
|
+
suffix = str(suffixes[0]) if suffixes else ""
|
|
584
|
+
return [value[: -len(suffix)] if value.endswith(suffix) and suffix else value]
|
|
585
|
+
|
|
586
|
+
if name == "trim":
|
|
587
|
+
if isinstance(value, str):
|
|
588
|
+
return [value.strip()]
|
|
589
|
+
return [value]
|
|
590
|
+
|
|
591
|
+
if name == "startswith":
|
|
592
|
+
if not isinstance(value, str) or not args:
|
|
593
|
+
return [False]
|
|
594
|
+
prefixes = eval_fn(value, args[0], ctx)
|
|
595
|
+
prefix = str(prefixes[0]) if prefixes else ""
|
|
596
|
+
return [value.startswith(prefix)]
|
|
597
|
+
|
|
598
|
+
if name == "endswith":
|
|
599
|
+
if not isinstance(value, str) or not args:
|
|
600
|
+
return [False]
|
|
601
|
+
suffixes = eval_fn(value, args[0], ctx)
|
|
602
|
+
suffix = str(suffixes[0]) if suffixes else ""
|
|
603
|
+
return [value.endswith(suffix)]
|
|
604
|
+
|
|
605
|
+
if name == "index":
|
|
606
|
+
if not args:
|
|
607
|
+
return [None]
|
|
608
|
+
needles = eval_fn(value, args[0], ctx)
|
|
609
|
+
needle = needles[0] if needles else None
|
|
610
|
+
if isinstance(value, str) and isinstance(needle, str):
|
|
611
|
+
idx = value.find(needle)
|
|
612
|
+
return [idx if idx >= 0 else None]
|
|
613
|
+
if isinstance(value, list):
|
|
614
|
+
for i, item in enumerate(value):
|
|
615
|
+
if _deep_equal(item, needle):
|
|
616
|
+
return [i]
|
|
617
|
+
return [None]
|
|
618
|
+
return [None]
|
|
619
|
+
|
|
620
|
+
if name == "rindex":
|
|
621
|
+
if not args:
|
|
622
|
+
return [None]
|
|
623
|
+
needles = eval_fn(value, args[0], ctx)
|
|
624
|
+
needle = needles[0] if needles else None
|
|
625
|
+
if isinstance(value, str) and isinstance(needle, str):
|
|
626
|
+
idx = value.rfind(needle)
|
|
627
|
+
return [idx if idx >= 0 else None]
|
|
628
|
+
if isinstance(value, list):
|
|
629
|
+
for i in range(len(value) - 1, -1, -1):
|
|
630
|
+
if _deep_equal(value[i], needle):
|
|
631
|
+
return [i]
|
|
632
|
+
return [None]
|
|
633
|
+
return [None]
|
|
634
|
+
|
|
635
|
+
if name == "indices":
|
|
636
|
+
if not args:
|
|
637
|
+
return [[]]
|
|
638
|
+
needles = eval_fn(value, args[0], ctx)
|
|
639
|
+
needle = needles[0] if needles else None
|
|
640
|
+
result = []
|
|
641
|
+
if isinstance(value, str) and isinstance(needle, str):
|
|
642
|
+
idx = value.find(needle)
|
|
643
|
+
while idx != -1:
|
|
644
|
+
result.append(idx)
|
|
645
|
+
idx = value.find(needle, idx + 1)
|
|
646
|
+
elif isinstance(value, list):
|
|
647
|
+
for i, item in enumerate(value):
|
|
648
|
+
if _deep_equal(item, needle):
|
|
649
|
+
result.append(i)
|
|
650
|
+
return [result]
|
|
651
|
+
|
|
652
|
+
# Math functions
|
|
653
|
+
if name == "floor":
|
|
654
|
+
if isinstance(value, (int, float)):
|
|
655
|
+
return [math.floor(value)]
|
|
656
|
+
return [None]
|
|
657
|
+
|
|
658
|
+
if name == "ceil":
|
|
659
|
+
if isinstance(value, (int, float)):
|
|
660
|
+
return [math.ceil(value)]
|
|
661
|
+
return [None]
|
|
662
|
+
|
|
663
|
+
if name == "round":
|
|
664
|
+
if isinstance(value, (int, float)):
|
|
665
|
+
return [round(value)]
|
|
666
|
+
return [None]
|
|
667
|
+
|
|
668
|
+
if name == "sqrt":
|
|
669
|
+
if isinstance(value, (int, float)):
|
|
670
|
+
return [math.sqrt(value)]
|
|
671
|
+
return [None]
|
|
672
|
+
|
|
673
|
+
if name in ("fabs", "abs"):
|
|
674
|
+
if isinstance(value, (int, float)):
|
|
675
|
+
return [abs(value)]
|
|
676
|
+
return [None]
|
|
677
|
+
|
|
678
|
+
if name == "log":
|
|
679
|
+
if isinstance(value, (int, float)):
|
|
680
|
+
return [math.log(value)]
|
|
681
|
+
return [None]
|
|
682
|
+
|
|
683
|
+
if name == "log10":
|
|
684
|
+
if isinstance(value, (int, float)):
|
|
685
|
+
return [math.log10(value)]
|
|
686
|
+
return [None]
|
|
687
|
+
|
|
688
|
+
if name == "log2":
|
|
689
|
+
if isinstance(value, (int, float)):
|
|
690
|
+
return [math.log2(value)]
|
|
691
|
+
return [None]
|
|
692
|
+
|
|
693
|
+
if name == "exp":
|
|
694
|
+
if isinstance(value, (int, float)):
|
|
695
|
+
return [math.exp(value)]
|
|
696
|
+
return [None]
|
|
697
|
+
|
|
698
|
+
if name == "exp10":
|
|
699
|
+
if isinstance(value, (int, float)):
|
|
700
|
+
return [10**value]
|
|
701
|
+
return [None]
|
|
702
|
+
|
|
703
|
+
if name == "exp2":
|
|
704
|
+
if isinstance(value, (int, float)):
|
|
705
|
+
return [2**value]
|
|
706
|
+
return [None]
|
|
707
|
+
|
|
708
|
+
if name == "pow":
|
|
709
|
+
if not isinstance(value, (int, float)) or not args:
|
|
710
|
+
return [None]
|
|
711
|
+
exps = eval_fn(value, args[0], ctx)
|
|
712
|
+
exp = exps[0] if exps else 1
|
|
713
|
+
return [value**exp]
|
|
714
|
+
|
|
715
|
+
if name == "sin":
|
|
716
|
+
if isinstance(value, (int, float)):
|
|
717
|
+
return [math.sin(value)]
|
|
718
|
+
return [None]
|
|
719
|
+
|
|
720
|
+
if name == "cos":
|
|
721
|
+
if isinstance(value, (int, float)):
|
|
722
|
+
return [math.cos(value)]
|
|
723
|
+
return [None]
|
|
724
|
+
|
|
725
|
+
if name == "tan":
|
|
726
|
+
if isinstance(value, (int, float)):
|
|
727
|
+
return [math.tan(value)]
|
|
728
|
+
return [None]
|
|
729
|
+
|
|
730
|
+
if name == "asin":
|
|
731
|
+
if isinstance(value, (int, float)):
|
|
732
|
+
return [math.asin(value)]
|
|
733
|
+
return [None]
|
|
734
|
+
|
|
735
|
+
if name == "acos":
|
|
736
|
+
if isinstance(value, (int, float)):
|
|
737
|
+
return [math.acos(value)]
|
|
738
|
+
return [None]
|
|
739
|
+
|
|
740
|
+
if name == "atan":
|
|
741
|
+
if isinstance(value, (int, float)):
|
|
742
|
+
return [math.atan(value)]
|
|
743
|
+
return [None]
|
|
744
|
+
|
|
745
|
+
if name == "tostring":
|
|
746
|
+
if isinstance(value, str):
|
|
747
|
+
return [value]
|
|
748
|
+
return [json.dumps(value)]
|
|
749
|
+
|
|
750
|
+
if name == "tonumber":
|
|
751
|
+
if isinstance(value, (int, float)):
|
|
752
|
+
return [value]
|
|
753
|
+
if isinstance(value, str):
|
|
754
|
+
try:
|
|
755
|
+
return [float(value) if "." in value else int(value)]
|
|
756
|
+
except ValueError:
|
|
757
|
+
return [None]
|
|
758
|
+
return [None]
|
|
759
|
+
|
|
760
|
+
if name == "infinite":
|
|
761
|
+
return [not math.isfinite(value) if isinstance(value, (int, float)) else False]
|
|
762
|
+
|
|
763
|
+
if name == "nan":
|
|
764
|
+
return [math.isnan(value) if isinstance(value, (int, float)) else False]
|
|
765
|
+
|
|
766
|
+
if name == "isnan":
|
|
767
|
+
return [isinstance(value, (int, float)) and math.isnan(value)]
|
|
768
|
+
|
|
769
|
+
if name == "isinfinite":
|
|
770
|
+
return [isinstance(value, (int, float)) and not math.isfinite(value)]
|
|
771
|
+
|
|
772
|
+
if name == "isfinite":
|
|
773
|
+
return [isinstance(value, (int, float)) and math.isfinite(value)]
|
|
774
|
+
|
|
775
|
+
if name == "isnormal":
|
|
776
|
+
return [isinstance(value, (int, float)) and math.isfinite(value) and value != 0]
|
|
777
|
+
|
|
778
|
+
# Type filters
|
|
779
|
+
if name == "numbers":
|
|
780
|
+
# In Python, bool is a subclass of int, so we need to exclude bools
|
|
781
|
+
return [value] if isinstance(value, (int, float)) and not isinstance(value, bool) else []
|
|
782
|
+
|
|
783
|
+
if name == "strings":
|
|
784
|
+
return [value] if isinstance(value, str) else []
|
|
785
|
+
|
|
786
|
+
if name == "booleans":
|
|
787
|
+
return [value] if isinstance(value, bool) else []
|
|
788
|
+
|
|
789
|
+
if name == "nulls":
|
|
790
|
+
return [value] if value is None else []
|
|
791
|
+
|
|
792
|
+
if name == "arrays":
|
|
793
|
+
return [value] if isinstance(value, list) else []
|
|
794
|
+
|
|
795
|
+
if name == "objects":
|
|
796
|
+
return [value] if isinstance(value, dict) else []
|
|
797
|
+
|
|
798
|
+
if name == "iterables":
|
|
799
|
+
return [value] if isinstance(value, (list, dict)) else []
|
|
800
|
+
|
|
801
|
+
if name == "scalars":
|
|
802
|
+
return [value] if not isinstance(value, (list, dict)) else []
|
|
803
|
+
|
|
804
|
+
if name == "now":
|
|
805
|
+
import time
|
|
806
|
+
|
|
807
|
+
return [time.time()]
|
|
808
|
+
|
|
809
|
+
if name == "env":
|
|
810
|
+
return [ctx.env]
|
|
811
|
+
|
|
812
|
+
if name == "recurse":
|
|
813
|
+
if not args:
|
|
814
|
+
results = []
|
|
815
|
+
_walk_recurse(value, results)
|
|
816
|
+
return results
|
|
817
|
+
results = []
|
|
818
|
+
seen = set()
|
|
819
|
+
|
|
820
|
+
def walk(v: Any) -> None:
|
|
821
|
+
key = json.dumps(v, sort_keys=True) if isinstance(v, (dict, list)) else str(v)
|
|
822
|
+
if key in seen:
|
|
823
|
+
return
|
|
824
|
+
seen.add(key)
|
|
825
|
+
results.append(v)
|
|
826
|
+
nexts = eval_fn(v, args[0], ctx)
|
|
827
|
+
for n in nexts:
|
|
828
|
+
if n is not None:
|
|
829
|
+
walk(n)
|
|
830
|
+
|
|
831
|
+
walk(value)
|
|
832
|
+
return results
|
|
833
|
+
|
|
834
|
+
if name == "recurse_down":
|
|
835
|
+
return call_builtin(value, "recurse", args, ctx, eval_fn)
|
|
836
|
+
|
|
837
|
+
if name == "walk":
|
|
838
|
+
if not args:
|
|
839
|
+
return [value]
|
|
840
|
+
seen: set[int] = set()
|
|
841
|
+
|
|
842
|
+
def walk_fn(v: Any) -> Any:
|
|
843
|
+
if isinstance(v, (dict, list)):
|
|
844
|
+
obj_id = id(v)
|
|
845
|
+
if obj_id in seen:
|
|
846
|
+
return v
|
|
847
|
+
seen.add(obj_id)
|
|
848
|
+
|
|
849
|
+
if isinstance(v, list):
|
|
850
|
+
transformed = [walk_fn(item) for item in v]
|
|
851
|
+
elif isinstance(v, dict):
|
|
852
|
+
transformed = {k: walk_fn(val) for k, val in v.items()}
|
|
853
|
+
else:
|
|
854
|
+
transformed = v
|
|
855
|
+
|
|
856
|
+
results = eval_fn(transformed, args[0], ctx)
|
|
857
|
+
return results[0] if results else transformed
|
|
858
|
+
|
|
859
|
+
return [walk_fn(value)]
|
|
860
|
+
|
|
861
|
+
if name == "transpose":
|
|
862
|
+
if not isinstance(value, list):
|
|
863
|
+
return [None]
|
|
864
|
+
if not value:
|
|
865
|
+
return [[]]
|
|
866
|
+
max_len = max((len(row) if isinstance(row, list) else 0) for row in value)
|
|
867
|
+
result = []
|
|
868
|
+
for i in range(max_len):
|
|
869
|
+
row = [r[i] if isinstance(r, list) and i < len(r) else None for r in value]
|
|
870
|
+
result.append(row)
|
|
871
|
+
return [result]
|
|
872
|
+
|
|
873
|
+
if name == "ascii":
|
|
874
|
+
if isinstance(value, str) and value:
|
|
875
|
+
return [ord(value[0])]
|
|
876
|
+
return [None]
|
|
877
|
+
|
|
878
|
+
if name == "explode":
|
|
879
|
+
if isinstance(value, str):
|
|
880
|
+
return [[ord(c) for c in value]]
|
|
881
|
+
return [None]
|
|
882
|
+
|
|
883
|
+
if name == "implode":
|
|
884
|
+
if isinstance(value, list):
|
|
885
|
+
try:
|
|
886
|
+
return ["".join(chr(c) for c in value)]
|
|
887
|
+
except (TypeError, ValueError):
|
|
888
|
+
return [None]
|
|
889
|
+
return [None]
|
|
890
|
+
|
|
891
|
+
if name in ("tojson", "tojsonstream"):
|
|
892
|
+
return [json.dumps(value)]
|
|
893
|
+
|
|
894
|
+
if name == "fromjson":
|
|
895
|
+
if isinstance(value, str):
|
|
896
|
+
try:
|
|
897
|
+
return [json.loads(value)]
|
|
898
|
+
except json.JSONDecodeError:
|
|
899
|
+
return [None]
|
|
900
|
+
return [None]
|
|
901
|
+
|
|
902
|
+
if name == "limit":
|
|
903
|
+
if len(args) < 2:
|
|
904
|
+
return []
|
|
905
|
+
ns = eval_fn(value, args[0], ctx)
|
|
906
|
+
n = ns[0] if ns else 0
|
|
907
|
+
results = eval_fn(value, args[1], ctx)
|
|
908
|
+
return results[: int(n)]
|
|
909
|
+
|
|
910
|
+
if name == "until":
|
|
911
|
+
if len(args) < 2:
|
|
912
|
+
return [value]
|
|
913
|
+
current = value
|
|
914
|
+
max_iterations = ctx.limits.max_iterations
|
|
915
|
+
for _ in range(max_iterations):
|
|
916
|
+
conds = eval_fn(current, args[0], ctx)
|
|
917
|
+
if any(_is_truthy(c) for c in conds):
|
|
918
|
+
return [current]
|
|
919
|
+
nexts = eval_fn(current, args[1], ctx)
|
|
920
|
+
if not nexts:
|
|
921
|
+
return [current]
|
|
922
|
+
current = nexts[0]
|
|
923
|
+
raise ValueError(f"jq until: too many iterations ({max_iterations})")
|
|
924
|
+
|
|
925
|
+
if name == "while":
|
|
926
|
+
if len(args) < 2:
|
|
927
|
+
return [value]
|
|
928
|
+
results = []
|
|
929
|
+
current = value
|
|
930
|
+
max_iterations = ctx.limits.max_iterations
|
|
931
|
+
for _ in range(max_iterations):
|
|
932
|
+
conds = eval_fn(current, args[0], ctx)
|
|
933
|
+
if not any(_is_truthy(c) for c in conds):
|
|
934
|
+
break
|
|
935
|
+
results.append(current)
|
|
936
|
+
nexts = eval_fn(current, args[1], ctx)
|
|
937
|
+
if not nexts:
|
|
938
|
+
break
|
|
939
|
+
current = nexts[0]
|
|
940
|
+
return results
|
|
941
|
+
|
|
942
|
+
if name == "repeat":
|
|
943
|
+
if not args:
|
|
944
|
+
return [value]
|
|
945
|
+
results = []
|
|
946
|
+
current = value
|
|
947
|
+
max_iterations = ctx.limits.max_iterations
|
|
948
|
+
for _ in range(max_iterations):
|
|
949
|
+
results.append(current)
|
|
950
|
+
nexts = eval_fn(current, args[0], ctx)
|
|
951
|
+
if not nexts:
|
|
952
|
+
break
|
|
953
|
+
current = nexts[0]
|
|
954
|
+
return results
|
|
955
|
+
|
|
956
|
+
if name == "debug":
|
|
957
|
+
return [value]
|
|
958
|
+
|
|
959
|
+
if name == "input_line_number":
|
|
960
|
+
return [1]
|
|
961
|
+
|
|
962
|
+
# Format strings
|
|
963
|
+
if name == "@base64":
|
|
964
|
+
if isinstance(value, str):
|
|
965
|
+
return [base64.b64encode(value.encode("utf-8")).decode("utf-8")]
|
|
966
|
+
return [None]
|
|
967
|
+
|
|
968
|
+
if name == "@base64d":
|
|
969
|
+
if isinstance(value, str):
|
|
970
|
+
try:
|
|
971
|
+
return [base64.b64decode(value).decode("utf-8")]
|
|
972
|
+
except Exception:
|
|
973
|
+
return [None]
|
|
974
|
+
return [None]
|
|
975
|
+
|
|
976
|
+
if name == "@uri":
|
|
977
|
+
if isinstance(value, str):
|
|
978
|
+
return [uri_quote(value, safe="")]
|
|
979
|
+
return [None]
|
|
980
|
+
|
|
981
|
+
if name == "@csv":
|
|
982
|
+
if not isinstance(value, list):
|
|
983
|
+
return [None]
|
|
984
|
+
escaped = []
|
|
985
|
+
for v in value:
|
|
986
|
+
s = str(v) if v is not None else ""
|
|
987
|
+
if "," in s or '"' in s or "\n" in s:
|
|
988
|
+
escaped.append(f'"{s.replace(chr(34), chr(34) + chr(34))}"')
|
|
989
|
+
else:
|
|
990
|
+
escaped.append(s)
|
|
991
|
+
return [",".join(escaped)]
|
|
992
|
+
|
|
993
|
+
if name == "@tsv":
|
|
994
|
+
if not isinstance(value, list):
|
|
995
|
+
return [None]
|
|
996
|
+
escaped = []
|
|
997
|
+
for v in value:
|
|
998
|
+
s = str(v) if v is not None else ""
|
|
999
|
+
escaped.append(s.replace("\t", "\\t").replace("\n", "\\n"))
|
|
1000
|
+
return ["\t".join(escaped)]
|
|
1001
|
+
|
|
1002
|
+
if name == "@json":
|
|
1003
|
+
return [json.dumps(value)]
|
|
1004
|
+
|
|
1005
|
+
if name == "@html":
|
|
1006
|
+
if isinstance(value, str):
|
|
1007
|
+
return [
|
|
1008
|
+
value.replace("&", "&")
|
|
1009
|
+
.replace("<", "<")
|
|
1010
|
+
.replace(">", ">")
|
|
1011
|
+
.replace('"', """)
|
|
1012
|
+
.replace("'", "'")
|
|
1013
|
+
]
|
|
1014
|
+
return [None]
|
|
1015
|
+
|
|
1016
|
+
if name == "@sh":
|
|
1017
|
+
if isinstance(value, str):
|
|
1018
|
+
return [f"'{value.replace(chr(39), chr(39) + chr(92) + chr(39) + chr(39))}'"]
|
|
1019
|
+
return [None]
|
|
1020
|
+
|
|
1021
|
+
if name == "@text":
|
|
1022
|
+
if isinstance(value, str):
|
|
1023
|
+
return [value]
|
|
1024
|
+
if value is None:
|
|
1025
|
+
return [""]
|
|
1026
|
+
return [str(value)]
|
|
1027
|
+
|
|
1028
|
+
# Navigation operators
|
|
1029
|
+
if name == "parent":
|
|
1030
|
+
if ctx.root is None or not ctx.current_path:
|
|
1031
|
+
return []
|
|
1032
|
+
path = ctx.current_path
|
|
1033
|
+
if not path:
|
|
1034
|
+
return []
|
|
1035
|
+
levels = 1
|
|
1036
|
+
if args:
|
|
1037
|
+
levels_vals = eval_fn(value, args[0], ctx)
|
|
1038
|
+
levels = levels_vals[0] if levels_vals else 1
|
|
1039
|
+
|
|
1040
|
+
if levels >= 0:
|
|
1041
|
+
if levels > len(path):
|
|
1042
|
+
return []
|
|
1043
|
+
parent_path = path[: len(path) - levels]
|
|
1044
|
+
else:
|
|
1045
|
+
target_len = -levels - 1
|
|
1046
|
+
if target_len >= len(path):
|
|
1047
|
+
return [value]
|
|
1048
|
+
parent_path = path[:target_len]
|
|
1049
|
+
return [_get_value_at_path(ctx.root, parent_path)]
|
|
1050
|
+
|
|
1051
|
+
if name == "parents":
|
|
1052
|
+
if ctx.root is None or not ctx.current_path:
|
|
1053
|
+
return [[]]
|
|
1054
|
+
path = ctx.current_path
|
|
1055
|
+
parents = []
|
|
1056
|
+
for i in range(len(path) - 1, -1, -1):
|
|
1057
|
+
parents.append(_get_value_at_path(ctx.root, path[:i]))
|
|
1058
|
+
return [parents]
|
|
1059
|
+
|
|
1060
|
+
if name == "root":
|
|
1061
|
+
return [ctx.root] if ctx.root is not None else []
|
|
1062
|
+
|
|
1063
|
+
raise ValueError(f"Unknown function: {name}")
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
def _is_truthy(v: Any) -> bool:
|
|
1067
|
+
"""Check if a value is truthy in jq terms."""
|
|
1068
|
+
return v is not None and v is not False
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def _deep_equal(a: Any, b: Any) -> bool:
|
|
1072
|
+
"""Deep equality check."""
|
|
1073
|
+
return json.dumps(a, sort_keys=True) == json.dumps(b, sort_keys=True)
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
def _jq_sort_key(v: Any) -> tuple[int, Any]:
|
|
1077
|
+
"""Generate a sort key for jq-style sorting."""
|
|
1078
|
+
if v is None:
|
|
1079
|
+
return (0, 0)
|
|
1080
|
+
if isinstance(v, bool):
|
|
1081
|
+
return (1, int(v))
|
|
1082
|
+
if isinstance(v, (int, float)):
|
|
1083
|
+
return (2, v)
|
|
1084
|
+
if isinstance(v, str):
|
|
1085
|
+
return (3, v)
|
|
1086
|
+
if isinstance(v, list):
|
|
1087
|
+
return (4, json.dumps(v, sort_keys=True))
|
|
1088
|
+
if isinstance(v, dict):
|
|
1089
|
+
return (5, json.dumps(v, sort_keys=True))
|
|
1090
|
+
return (6, str(v))
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
def _flatten(lst: list[Any], depth: int | None) -> list[Any]:
|
|
1094
|
+
"""Flatten a list to a given depth."""
|
|
1095
|
+
if depth == 0:
|
|
1096
|
+
return lst
|
|
1097
|
+
result = []
|
|
1098
|
+
for item in lst:
|
|
1099
|
+
if isinstance(item, list):
|
|
1100
|
+
new_depth = None if depth is None else depth - 1
|
|
1101
|
+
result.extend(_flatten(item, new_depth))
|
|
1102
|
+
else:
|
|
1103
|
+
result.append(item)
|
|
1104
|
+
return result
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
def _contains_deep(a: Any, b: Any) -> bool:
|
|
1108
|
+
"""Check if a contains b (deep containment)."""
|
|
1109
|
+
if _deep_equal(a, b):
|
|
1110
|
+
return True
|
|
1111
|
+
if isinstance(a, list) and isinstance(b, list):
|
|
1112
|
+
return all(any(_contains_deep(a_item, b_item) for a_item in a) for b_item in b)
|
|
1113
|
+
if isinstance(a, dict) and isinstance(b, dict):
|
|
1114
|
+
return all(k in a and _contains_deep(a[k], v) for k, v in b.items())
|
|
1115
|
+
return False
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
def _set_path(value: Any, path: list[str | int], new_val: Any) -> Any:
|
|
1119
|
+
"""Set a value at a path."""
|
|
1120
|
+
if not path:
|
|
1121
|
+
return new_val
|
|
1122
|
+
head, *rest = path
|
|
1123
|
+
if isinstance(head, int):
|
|
1124
|
+
arr = list(value) if isinstance(value, list) else []
|
|
1125
|
+
while len(arr) <= head:
|
|
1126
|
+
arr.append(None)
|
|
1127
|
+
arr[head] = _set_path(arr[head], rest, new_val)
|
|
1128
|
+
return arr
|
|
1129
|
+
else:
|
|
1130
|
+
obj = dict(value) if isinstance(value, dict) else {}
|
|
1131
|
+
obj[head] = _set_path(obj.get(head), rest, new_val)
|
|
1132
|
+
return obj
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
def _delete_path(value: Any, path: list[str | int]) -> Any:
|
|
1136
|
+
"""Delete a value at a path."""
|
|
1137
|
+
if not path:
|
|
1138
|
+
return None
|
|
1139
|
+
if len(path) == 1:
|
|
1140
|
+
head = path[0]
|
|
1141
|
+
if isinstance(value, list) and isinstance(head, int):
|
|
1142
|
+
arr = list(value)
|
|
1143
|
+
if 0 <= head < len(arr):
|
|
1144
|
+
arr.pop(head)
|
|
1145
|
+
return arr
|
|
1146
|
+
if isinstance(value, dict) and isinstance(head, str):
|
|
1147
|
+
obj = dict(value)
|
|
1148
|
+
obj.pop(head, None)
|
|
1149
|
+
return obj
|
|
1150
|
+
return value
|
|
1151
|
+
head, *rest = path
|
|
1152
|
+
if isinstance(value, list) and isinstance(head, int):
|
|
1153
|
+
arr = list(value)
|
|
1154
|
+
if 0 <= head < len(arr):
|
|
1155
|
+
arr[head] = _delete_path(arr[head], rest)
|
|
1156
|
+
return arr
|
|
1157
|
+
if isinstance(value, dict) and isinstance(head, str):
|
|
1158
|
+
obj = dict(value)
|
|
1159
|
+
if head in obj:
|
|
1160
|
+
obj[head] = _delete_path(obj[head], rest)
|
|
1161
|
+
return obj
|
|
1162
|
+
return value
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
def _get_all_paths(value: Any, current: list[str | int]) -> list[list[str | int]]:
|
|
1166
|
+
"""Get all paths in a value."""
|
|
1167
|
+
paths = []
|
|
1168
|
+
if isinstance(value, dict):
|
|
1169
|
+
for k, v in value.items():
|
|
1170
|
+
new_path = current + [k]
|
|
1171
|
+
paths.append(new_path)
|
|
1172
|
+
paths.extend(_get_all_paths(v, new_path))
|
|
1173
|
+
elif isinstance(value, list):
|
|
1174
|
+
for i, v in enumerate(value):
|
|
1175
|
+
new_path = current + [i]
|
|
1176
|
+
paths.append(new_path)
|
|
1177
|
+
paths.extend(_get_all_paths(v, new_path))
|
|
1178
|
+
return paths
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def _get_leaf_paths(value: Any, current: list[str | int]) -> list[list[str | int]]:
|
|
1182
|
+
"""Get all leaf paths (paths to non-container values)."""
|
|
1183
|
+
paths = []
|
|
1184
|
+
if value is None or not isinstance(value, (dict, list)):
|
|
1185
|
+
return [current] if current else []
|
|
1186
|
+
if isinstance(value, dict):
|
|
1187
|
+
if not value:
|
|
1188
|
+
return [current] if current else []
|
|
1189
|
+
for k, v in value.items():
|
|
1190
|
+
paths.extend(_get_leaf_paths(v, current + [k]))
|
|
1191
|
+
elif isinstance(value, list):
|
|
1192
|
+
if not value:
|
|
1193
|
+
return [current] if current else []
|
|
1194
|
+
for i, v in enumerate(value):
|
|
1195
|
+
paths.extend(_get_leaf_paths(v, current + [i]))
|
|
1196
|
+
return paths
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
def _get_value_at_path(value: Any, path: list[str | int]) -> Any:
|
|
1200
|
+
"""Get the value at a path."""
|
|
1201
|
+
current = value
|
|
1202
|
+
for key in path:
|
|
1203
|
+
if isinstance(current, dict) and isinstance(key, str):
|
|
1204
|
+
current = current.get(key)
|
|
1205
|
+
elif isinstance(current, list) and isinstance(key, int):
|
|
1206
|
+
current = current[key] if 0 <= key < len(current) else None
|
|
1207
|
+
else:
|
|
1208
|
+
return None
|
|
1209
|
+
return current
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
def _collect_paths(
|
|
1213
|
+
value: Any,
|
|
1214
|
+
expr: AstNode,
|
|
1215
|
+
ctx: EvalContext,
|
|
1216
|
+
eval_fn: EvalFunc,
|
|
1217
|
+
current_path: list[str | int],
|
|
1218
|
+
paths: list[list[str | int]],
|
|
1219
|
+
) -> None:
|
|
1220
|
+
"""Collect paths that match an expression."""
|
|
1221
|
+
results = eval_fn(value, expr, ctx)
|
|
1222
|
+
if results:
|
|
1223
|
+
paths.append(current_path)
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
def _apply_del(value: Any, path_expr: AstNode, ctx: EvalContext, eval_fn: EvalFunc) -> Any:
|
|
1227
|
+
"""Apply deletion at a path."""
|
|
1228
|
+
if path_expr.type == "Identity":
|
|
1229
|
+
return None
|
|
1230
|
+
if path_expr.type == "Field":
|
|
1231
|
+
field_node = path_expr
|
|
1232
|
+
if isinstance(value, dict):
|
|
1233
|
+
result = dict(value)
|
|
1234
|
+
result.pop(field_node.name, None)
|
|
1235
|
+
return result
|
|
1236
|
+
return value
|
|
1237
|
+
if path_expr.type == "Index":
|
|
1238
|
+
index_node = path_expr
|
|
1239
|
+
indices = eval_fn(value, index_node.index, ctx)
|
|
1240
|
+
idx = indices[0] if indices else None
|
|
1241
|
+
if isinstance(idx, int) and isinstance(value, list):
|
|
1242
|
+
arr = list(value)
|
|
1243
|
+
i = idx if idx >= 0 else len(arr) + idx
|
|
1244
|
+
if 0 <= i < len(arr):
|
|
1245
|
+
arr.pop(i)
|
|
1246
|
+
return arr
|
|
1247
|
+
if isinstance(idx, str) and isinstance(value, dict):
|
|
1248
|
+
result = dict(value)
|
|
1249
|
+
result.pop(idx, None)
|
|
1250
|
+
return result
|
|
1251
|
+
return value
|
|
1252
|
+
if path_expr.type == "Iterate":
|
|
1253
|
+
if isinstance(value, list):
|
|
1254
|
+
return []
|
|
1255
|
+
if isinstance(value, dict):
|
|
1256
|
+
return {}
|
|
1257
|
+
return value
|
|
1258
|
+
return value
|
|
1259
|
+
|
|
1260
|
+
|
|
1261
|
+
def _walk_recurse(value: Any, results: list[Any]) -> None:
|
|
1262
|
+
"""Walk recursively through a value."""
|
|
1263
|
+
results.append(value)
|
|
1264
|
+
if isinstance(value, list):
|
|
1265
|
+
for item in value:
|
|
1266
|
+
_walk_recurse(item, results)
|
|
1267
|
+
elif isinstance(value, dict):
|
|
1268
|
+
for v in value.values():
|
|
1269
|
+
_walk_recurse(v, results)
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
def _get_re_flags(flags: str) -> int:
|
|
1273
|
+
"""Convert jq regex flag string to Python re flags."""
|
|
1274
|
+
result = 0
|
|
1275
|
+
if "i" in flags:
|
|
1276
|
+
result |= re.IGNORECASE
|
|
1277
|
+
if "m" in flags:
|
|
1278
|
+
result |= re.MULTILINE
|
|
1279
|
+
if "s" in flags:
|
|
1280
|
+
result |= re.DOTALL
|
|
1281
|
+
if "x" in flags:
|
|
1282
|
+
result |= re.VERBOSE
|
|
1283
|
+
return result
|