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,290 @@
|
|
|
1
|
+
"""Test / [ builtin implementation.
|
|
2
|
+
|
|
3
|
+
The test command evaluates conditional expressions and returns
|
|
4
|
+
exit code 0 (true) or 1 (false).
|
|
5
|
+
|
|
6
|
+
Usage: test expression
|
|
7
|
+
[ expression ]
|
|
8
|
+
|
|
9
|
+
File operators:
|
|
10
|
+
-f FILE True if FILE exists and is a regular file
|
|
11
|
+
-d FILE True if FILE exists and is a directory
|
|
12
|
+
-e FILE True if FILE exists
|
|
13
|
+
-s FILE True if FILE exists and has size > 0
|
|
14
|
+
-r FILE True if FILE exists and is readable
|
|
15
|
+
-w FILE True if FILE exists and is writable
|
|
16
|
+
-x FILE True if FILE exists and is executable
|
|
17
|
+
-h/-L FILE True if FILE exists and is a symbolic link
|
|
18
|
+
|
|
19
|
+
String operators:
|
|
20
|
+
-z STRING True if STRING is empty
|
|
21
|
+
-n STRING True if STRING is not empty
|
|
22
|
+
STRING True if STRING is not empty
|
|
23
|
+
S1 = S2 True if strings are equal
|
|
24
|
+
S1 != S2 True if strings are not equal
|
|
25
|
+
S1 < S2 True if S1 sorts before S2
|
|
26
|
+
S1 > S2 True if S1 sorts after S2
|
|
27
|
+
|
|
28
|
+
Numeric operators:
|
|
29
|
+
N1 -eq N2 True if N1 equals N2
|
|
30
|
+
N1 -ne N2 True if N1 does not equal N2
|
|
31
|
+
N1 -lt N2 True if N1 is less than N2
|
|
32
|
+
N1 -le N2 True if N1 is less or equal to N2
|
|
33
|
+
N1 -gt N2 True if N1 is greater than N2
|
|
34
|
+
N1 -ge N2 True if N1 is greater or equal to N2
|
|
35
|
+
|
|
36
|
+
Logical operators:
|
|
37
|
+
! EXPR True if EXPR is false
|
|
38
|
+
( EXPR ) Grouping
|
|
39
|
+
EXPR -a EXPR True if both EXPRs are true (AND)
|
|
40
|
+
EXPR -o EXPR True if either EXPR is true (OR)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from typing import TYPE_CHECKING
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from ..types import InterpreterContext
|
|
47
|
+
from ...types import ExecResult
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Known unary operators that require an operand
|
|
51
|
+
_UNARY_OPS = {
|
|
52
|
+
"-f", "-d", "-e", "-s", "-r", "-w", "-x", "-h", "-L",
|
|
53
|
+
"-z", "-n", "-b", "-c", "-g", "-G", "-k", "-O", "-p",
|
|
54
|
+
"-S", "-t", "-u", "-N", "-v", "-o",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def handle_test(ctx: "InterpreterContext", args: list[str]) -> "ExecResult":
|
|
59
|
+
"""Execute the test builtin."""
|
|
60
|
+
from ...types import ExecResult
|
|
61
|
+
|
|
62
|
+
# Empty test is false
|
|
63
|
+
if not args:
|
|
64
|
+
return ExecResult(stdout="", stderr="", exit_code=1)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
result = await _evaluate(ctx, args)
|
|
68
|
+
return ExecResult(stdout="", stderr="", exit_code=0 if result else 1)
|
|
69
|
+
except ValueError as e:
|
|
70
|
+
return ExecResult(stdout="", stderr=f"bash: test: {e}\n", exit_code=2)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def handle_bracket(ctx: "InterpreterContext", args: list[str]) -> "ExecResult":
|
|
74
|
+
"""Execute the [ builtin (requires closing ])."""
|
|
75
|
+
from ...types import ExecResult
|
|
76
|
+
|
|
77
|
+
# [ requires closing ]
|
|
78
|
+
if not args or args[-1] != "]":
|
|
79
|
+
return ExecResult(stdout="", stderr="bash: [: missing `]'\n", exit_code=2)
|
|
80
|
+
args = args[:-1]
|
|
81
|
+
|
|
82
|
+
# Empty test is false
|
|
83
|
+
if not args:
|
|
84
|
+
return ExecResult(stdout="", stderr="", exit_code=1)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
result = await _evaluate(ctx, args)
|
|
88
|
+
return ExecResult(stdout="", stderr="", exit_code=0 if result else 1)
|
|
89
|
+
except ValueError as e:
|
|
90
|
+
return ExecResult(stdout="", stderr=f"bash: [: {e}\n", exit_code=2)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def _evaluate(ctx: "InterpreterContext", args: list[str]) -> bool:
|
|
94
|
+
"""Evaluate a test expression."""
|
|
95
|
+
if not args:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
# Handle negation
|
|
99
|
+
if args[0] == "!":
|
|
100
|
+
return not await _evaluate(ctx, args[1:])
|
|
101
|
+
|
|
102
|
+
# Handle parentheses
|
|
103
|
+
if args[0] == "(":
|
|
104
|
+
# Find matching )
|
|
105
|
+
depth = 1
|
|
106
|
+
end_idx = 1
|
|
107
|
+
while end_idx < len(args) and depth > 0:
|
|
108
|
+
if args[end_idx] == "(":
|
|
109
|
+
depth += 1
|
|
110
|
+
elif args[end_idx] == ")":
|
|
111
|
+
depth -= 1
|
|
112
|
+
end_idx += 1
|
|
113
|
+
|
|
114
|
+
if depth != 0:
|
|
115
|
+
raise ValueError("missing ')'")
|
|
116
|
+
|
|
117
|
+
inner_result = await _evaluate(ctx, args[1:end_idx - 1])
|
|
118
|
+
|
|
119
|
+
# Check for -a or -o after the parentheses
|
|
120
|
+
if end_idx < len(args):
|
|
121
|
+
return await _evaluate_compound(ctx, inner_result, args[end_idx:])
|
|
122
|
+
|
|
123
|
+
return inner_result
|
|
124
|
+
|
|
125
|
+
# Handle -a and -o (lowest precedence)
|
|
126
|
+
for i, arg in enumerate(args):
|
|
127
|
+
if arg == "-a" and i > 0:
|
|
128
|
+
left = await _evaluate(ctx, args[:i])
|
|
129
|
+
right = await _evaluate(ctx, args[i + 1:])
|
|
130
|
+
return left and right
|
|
131
|
+
if arg == "-o" and i > 0:
|
|
132
|
+
left = await _evaluate(ctx, args[:i])
|
|
133
|
+
right = await _evaluate(ctx, args[i + 1:])
|
|
134
|
+
return left or right
|
|
135
|
+
|
|
136
|
+
# Single argument: non-empty string is true, but check for misused operators
|
|
137
|
+
if len(args) == 1:
|
|
138
|
+
arg = args[0]
|
|
139
|
+
# If it looks like an operator (starts with -) but isn't followed by
|
|
140
|
+
# an operand, it could be a misused unary operator
|
|
141
|
+
if arg.startswith("-") and len(arg) > 1:
|
|
142
|
+
# Check if this looks like an operator that needs an operand
|
|
143
|
+
if arg in _UNARY_OPS:
|
|
144
|
+
raise ValueError(f"{arg}: unary operator expected")
|
|
145
|
+
return arg != ""
|
|
146
|
+
|
|
147
|
+
# Two arguments: unary operators
|
|
148
|
+
if len(args) == 2:
|
|
149
|
+
return await _unary_test(ctx, args[0], args[1])
|
|
150
|
+
|
|
151
|
+
# Three arguments: binary operators
|
|
152
|
+
if len(args) == 3:
|
|
153
|
+
return await _binary_test(ctx, args[0], args[1], args[2])
|
|
154
|
+
|
|
155
|
+
# More than 3 args should be handled by -a/-o above
|
|
156
|
+
raise ValueError("too many arguments")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
async def _evaluate_compound(
|
|
160
|
+
ctx: "InterpreterContext", left_result: bool, remaining: list[str]
|
|
161
|
+
) -> bool:
|
|
162
|
+
"""Evaluate compound expression with -a or -o."""
|
|
163
|
+
if not remaining:
|
|
164
|
+
return left_result
|
|
165
|
+
|
|
166
|
+
op = remaining[0]
|
|
167
|
+
rest = remaining[1:]
|
|
168
|
+
|
|
169
|
+
if op == "-a":
|
|
170
|
+
if not left_result:
|
|
171
|
+
return False
|
|
172
|
+
return await _evaluate(ctx, rest)
|
|
173
|
+
elif op == "-o":
|
|
174
|
+
if left_result:
|
|
175
|
+
return True
|
|
176
|
+
return await _evaluate(ctx, rest)
|
|
177
|
+
else:
|
|
178
|
+
raise ValueError(f"unexpected '{op}'")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def _unary_test(ctx: "InterpreterContext", op: str, arg: str) -> bool:
|
|
182
|
+
"""Evaluate a unary test."""
|
|
183
|
+
# File tests
|
|
184
|
+
if op == "-f":
|
|
185
|
+
return await _file_test(ctx, arg, "file")
|
|
186
|
+
if op == "-d":
|
|
187
|
+
return await _file_test(ctx, arg, "directory")
|
|
188
|
+
if op == "-e":
|
|
189
|
+
return await _file_test(ctx, arg, "exists")
|
|
190
|
+
if op == "-s":
|
|
191
|
+
return await _file_test(ctx, arg, "size")
|
|
192
|
+
if op == "-r":
|
|
193
|
+
return await _file_test(ctx, arg, "readable")
|
|
194
|
+
if op == "-w":
|
|
195
|
+
return await _file_test(ctx, arg, "writable")
|
|
196
|
+
if op == "-x":
|
|
197
|
+
return await _file_test(ctx, arg, "executable")
|
|
198
|
+
if op in ("-h", "-L"):
|
|
199
|
+
return await _file_test(ctx, arg, "symlink")
|
|
200
|
+
|
|
201
|
+
# String tests
|
|
202
|
+
if op == "-z":
|
|
203
|
+
return arg == ""
|
|
204
|
+
if op == "-n":
|
|
205
|
+
return arg != ""
|
|
206
|
+
|
|
207
|
+
# Default: two non-operator args means binary comparison
|
|
208
|
+
raise ValueError(f"unknown unary operator '{op}'")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def _binary_test(
|
|
212
|
+
ctx: "InterpreterContext", left: str, op: str, right: str
|
|
213
|
+
) -> bool:
|
|
214
|
+
"""Evaluate a binary test."""
|
|
215
|
+
# String comparisons
|
|
216
|
+
if op == "=":
|
|
217
|
+
return left == right
|
|
218
|
+
if op == "==":
|
|
219
|
+
return left == right
|
|
220
|
+
if op == "!=":
|
|
221
|
+
return left != right
|
|
222
|
+
if op == "<":
|
|
223
|
+
return left < right
|
|
224
|
+
if op == ">":
|
|
225
|
+
return left > right
|
|
226
|
+
|
|
227
|
+
# Numeric comparisons
|
|
228
|
+
if op in ("-eq", "-ne", "-lt", "-le", "-gt", "-ge"):
|
|
229
|
+
try:
|
|
230
|
+
left_num = int(left)
|
|
231
|
+
right_num = int(right)
|
|
232
|
+
except ValueError:
|
|
233
|
+
raise ValueError(f"integer expression expected")
|
|
234
|
+
|
|
235
|
+
if op == "-eq":
|
|
236
|
+
return left_num == right_num
|
|
237
|
+
if op == "-ne":
|
|
238
|
+
return left_num != right_num
|
|
239
|
+
if op == "-lt":
|
|
240
|
+
return left_num < right_num
|
|
241
|
+
if op == "-le":
|
|
242
|
+
return left_num <= right_num
|
|
243
|
+
if op == "-gt":
|
|
244
|
+
return left_num > right_num
|
|
245
|
+
if op == "-ge":
|
|
246
|
+
return left_num >= right_num
|
|
247
|
+
|
|
248
|
+
raise ValueError(f"unknown binary operator '{op}'")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
async def _file_test(ctx: "InterpreterContext", path: str, test_type: str) -> bool:
|
|
252
|
+
"""Perform a file test."""
|
|
253
|
+
# Resolve path relative to cwd
|
|
254
|
+
full_path = ctx.fs.resolve_path(ctx.state.cwd, path)
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
# For symlink test, use lstat (doesn't follow symlinks)
|
|
258
|
+
if test_type == "symlink":
|
|
259
|
+
try:
|
|
260
|
+
stat_info = await ctx.fs.lstat(full_path)
|
|
261
|
+
return stat_info.is_symbolic_link
|
|
262
|
+
except FileNotFoundError:
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
exists = await ctx.fs.exists(full_path)
|
|
266
|
+
|
|
267
|
+
if test_type == "exists":
|
|
268
|
+
return exists
|
|
269
|
+
|
|
270
|
+
if not exists:
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
if test_type == "directory":
|
|
274
|
+
return await ctx.fs.is_directory(full_path)
|
|
275
|
+
|
|
276
|
+
if test_type == "file":
|
|
277
|
+
return not await ctx.fs.is_directory(full_path)
|
|
278
|
+
|
|
279
|
+
if test_type == "size":
|
|
280
|
+
content = await ctx.fs.read_file(full_path)
|
|
281
|
+
return len(content) > 0
|
|
282
|
+
|
|
283
|
+
# For readable/writable/executable, we assume true if exists
|
|
284
|
+
# (virtual filesystem doesn't track permissions)
|
|
285
|
+
if test_type in ("readable", "writable", "executable"):
|
|
286
|
+
return exists
|
|
287
|
+
|
|
288
|
+
return False
|
|
289
|
+
except Exception:
|
|
290
|
+
return False
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Unset builtin implementation.
|
|
2
|
+
|
|
3
|
+
Usage: unset [-f] [-v] [name ...]
|
|
4
|
+
|
|
5
|
+
Remove variables or functions.
|
|
6
|
+
|
|
7
|
+
Options:
|
|
8
|
+
-v Treat each name as a variable name (default)
|
|
9
|
+
-f Treat each name as a function name
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ..types import InterpreterContext
|
|
16
|
+
from ...types import ExecResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def handle_unset(ctx: "InterpreterContext", args: list[str]) -> "ExecResult":
|
|
20
|
+
"""Execute the unset builtin."""
|
|
21
|
+
from ...types import ExecResult
|
|
22
|
+
|
|
23
|
+
mode = "variable"
|
|
24
|
+
names = []
|
|
25
|
+
|
|
26
|
+
for arg in args:
|
|
27
|
+
if arg == "-v":
|
|
28
|
+
mode = "variable"
|
|
29
|
+
elif arg == "-f":
|
|
30
|
+
mode = "function"
|
|
31
|
+
elif arg == "-n":
|
|
32
|
+
# -n treats name as a nameref - we don't support this but ignore
|
|
33
|
+
pass
|
|
34
|
+
elif arg.startswith("-"):
|
|
35
|
+
# Skip unknown options
|
|
36
|
+
pass
|
|
37
|
+
else:
|
|
38
|
+
names.append(arg)
|
|
39
|
+
|
|
40
|
+
for name in names:
|
|
41
|
+
if mode == "function":
|
|
42
|
+
ctx.state.functions.pop(name, None)
|
|
43
|
+
else:
|
|
44
|
+
# Check if variable is readonly
|
|
45
|
+
if name in ctx.state.readonly_vars:
|
|
46
|
+
return ExecResult(
|
|
47
|
+
stdout="",
|
|
48
|
+
stderr=f"bash: unset: {name}: cannot unset: readonly variable\n",
|
|
49
|
+
exit_code=1,
|
|
50
|
+
)
|
|
51
|
+
ctx.state.env.pop(name, None)
|
|
52
|
+
|
|
53
|
+
return ExecResult(stdout="", stderr="", exit_code=0)
|