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.
Files changed (193) hide show
  1. just_bash/__init__.py +55 -0
  2. just_bash/ast/__init__.py +213 -0
  3. just_bash/ast/factory.py +320 -0
  4. just_bash/ast/types.py +953 -0
  5. just_bash/bash.py +220 -0
  6. just_bash/commands/__init__.py +23 -0
  7. just_bash/commands/argv/__init__.py +5 -0
  8. just_bash/commands/argv/argv.py +21 -0
  9. just_bash/commands/awk/__init__.py +5 -0
  10. just_bash/commands/awk/awk.py +1168 -0
  11. just_bash/commands/base64/__init__.py +5 -0
  12. just_bash/commands/base64/base64.py +138 -0
  13. just_bash/commands/basename/__init__.py +5 -0
  14. just_bash/commands/basename/basename.py +72 -0
  15. just_bash/commands/bash/__init__.py +5 -0
  16. just_bash/commands/bash/bash.py +188 -0
  17. just_bash/commands/cat/__init__.py +5 -0
  18. just_bash/commands/cat/cat.py +173 -0
  19. just_bash/commands/checksum/__init__.py +5 -0
  20. just_bash/commands/checksum/checksum.py +179 -0
  21. just_bash/commands/chmod/__init__.py +5 -0
  22. just_bash/commands/chmod/chmod.py +216 -0
  23. just_bash/commands/column/__init__.py +5 -0
  24. just_bash/commands/column/column.py +180 -0
  25. just_bash/commands/comm/__init__.py +5 -0
  26. just_bash/commands/comm/comm.py +150 -0
  27. just_bash/commands/compression/__init__.py +5 -0
  28. just_bash/commands/compression/compression.py +298 -0
  29. just_bash/commands/cp/__init__.py +5 -0
  30. just_bash/commands/cp/cp.py +149 -0
  31. just_bash/commands/curl/__init__.py +5 -0
  32. just_bash/commands/curl/curl.py +801 -0
  33. just_bash/commands/cut/__init__.py +5 -0
  34. just_bash/commands/cut/cut.py +327 -0
  35. just_bash/commands/date/__init__.py +5 -0
  36. just_bash/commands/date/date.py +258 -0
  37. just_bash/commands/diff/__init__.py +5 -0
  38. just_bash/commands/diff/diff.py +118 -0
  39. just_bash/commands/dirname/__init__.py +5 -0
  40. just_bash/commands/dirname/dirname.py +56 -0
  41. just_bash/commands/du/__init__.py +5 -0
  42. just_bash/commands/du/du.py +150 -0
  43. just_bash/commands/echo/__init__.py +5 -0
  44. just_bash/commands/echo/echo.py +125 -0
  45. just_bash/commands/env/__init__.py +5 -0
  46. just_bash/commands/env/env.py +163 -0
  47. just_bash/commands/expand/__init__.py +5 -0
  48. just_bash/commands/expand/expand.py +299 -0
  49. just_bash/commands/expr/__init__.py +5 -0
  50. just_bash/commands/expr/expr.py +273 -0
  51. just_bash/commands/file/__init__.py +5 -0
  52. just_bash/commands/file/file.py +274 -0
  53. just_bash/commands/find/__init__.py +5 -0
  54. just_bash/commands/find/find.py +623 -0
  55. just_bash/commands/fold/__init__.py +5 -0
  56. just_bash/commands/fold/fold.py +160 -0
  57. just_bash/commands/grep/__init__.py +5 -0
  58. just_bash/commands/grep/grep.py +418 -0
  59. just_bash/commands/head/__init__.py +5 -0
  60. just_bash/commands/head/head.py +167 -0
  61. just_bash/commands/help/__init__.py +5 -0
  62. just_bash/commands/help/help.py +67 -0
  63. just_bash/commands/hostname/__init__.py +5 -0
  64. just_bash/commands/hostname/hostname.py +21 -0
  65. just_bash/commands/html_to_markdown/__init__.py +5 -0
  66. just_bash/commands/html_to_markdown/html_to_markdown.py +191 -0
  67. just_bash/commands/join/__init__.py +5 -0
  68. just_bash/commands/join/join.py +252 -0
  69. just_bash/commands/jq/__init__.py +5 -0
  70. just_bash/commands/jq/jq.py +280 -0
  71. just_bash/commands/ln/__init__.py +5 -0
  72. just_bash/commands/ln/ln.py +127 -0
  73. just_bash/commands/ls/__init__.py +5 -0
  74. just_bash/commands/ls/ls.py +280 -0
  75. just_bash/commands/mkdir/__init__.py +5 -0
  76. just_bash/commands/mkdir/mkdir.py +92 -0
  77. just_bash/commands/mv/__init__.py +5 -0
  78. just_bash/commands/mv/mv.py +142 -0
  79. just_bash/commands/nl/__init__.py +5 -0
  80. just_bash/commands/nl/nl.py +180 -0
  81. just_bash/commands/od/__init__.py +5 -0
  82. just_bash/commands/od/od.py +157 -0
  83. just_bash/commands/paste/__init__.py +5 -0
  84. just_bash/commands/paste/paste.py +100 -0
  85. just_bash/commands/printf/__init__.py +5 -0
  86. just_bash/commands/printf/printf.py +157 -0
  87. just_bash/commands/pwd/__init__.py +5 -0
  88. just_bash/commands/pwd/pwd.py +23 -0
  89. just_bash/commands/read/__init__.py +5 -0
  90. just_bash/commands/read/read.py +185 -0
  91. just_bash/commands/readlink/__init__.py +5 -0
  92. just_bash/commands/readlink/readlink.py +86 -0
  93. just_bash/commands/registry.py +844 -0
  94. just_bash/commands/rev/__init__.py +5 -0
  95. just_bash/commands/rev/rev.py +74 -0
  96. just_bash/commands/rg/__init__.py +5 -0
  97. just_bash/commands/rg/rg.py +1048 -0
  98. just_bash/commands/rm/__init__.py +5 -0
  99. just_bash/commands/rm/rm.py +106 -0
  100. just_bash/commands/search_engine/__init__.py +13 -0
  101. just_bash/commands/search_engine/matcher.py +170 -0
  102. just_bash/commands/search_engine/regex.py +159 -0
  103. just_bash/commands/sed/__init__.py +5 -0
  104. just_bash/commands/sed/sed.py +863 -0
  105. just_bash/commands/seq/__init__.py +5 -0
  106. just_bash/commands/seq/seq.py +190 -0
  107. just_bash/commands/shell/__init__.py +5 -0
  108. just_bash/commands/shell/shell.py +206 -0
  109. just_bash/commands/sleep/__init__.py +5 -0
  110. just_bash/commands/sleep/sleep.py +62 -0
  111. just_bash/commands/sort/__init__.py +5 -0
  112. just_bash/commands/sort/sort.py +411 -0
  113. just_bash/commands/split/__init__.py +5 -0
  114. just_bash/commands/split/split.py +237 -0
  115. just_bash/commands/sqlite3/__init__.py +5 -0
  116. just_bash/commands/sqlite3/sqlite3_cmd.py +505 -0
  117. just_bash/commands/stat/__init__.py +5 -0
  118. just_bash/commands/stat/stat.py +150 -0
  119. just_bash/commands/strings/__init__.py +5 -0
  120. just_bash/commands/strings/strings.py +150 -0
  121. just_bash/commands/tac/__init__.py +5 -0
  122. just_bash/commands/tac/tac.py +158 -0
  123. just_bash/commands/tail/__init__.py +5 -0
  124. just_bash/commands/tail/tail.py +180 -0
  125. just_bash/commands/tar/__init__.py +5 -0
  126. just_bash/commands/tar/tar.py +1067 -0
  127. just_bash/commands/tee/__init__.py +5 -0
  128. just_bash/commands/tee/tee.py +63 -0
  129. just_bash/commands/timeout/__init__.py +5 -0
  130. just_bash/commands/timeout/timeout.py +188 -0
  131. just_bash/commands/touch/__init__.py +5 -0
  132. just_bash/commands/touch/touch.py +91 -0
  133. just_bash/commands/tr/__init__.py +5 -0
  134. just_bash/commands/tr/tr.py +297 -0
  135. just_bash/commands/tree/__init__.py +5 -0
  136. just_bash/commands/tree/tree.py +139 -0
  137. just_bash/commands/true/__init__.py +5 -0
  138. just_bash/commands/true/true.py +32 -0
  139. just_bash/commands/uniq/__init__.py +5 -0
  140. just_bash/commands/uniq/uniq.py +323 -0
  141. just_bash/commands/wc/__init__.py +5 -0
  142. just_bash/commands/wc/wc.py +169 -0
  143. just_bash/commands/which/__init__.py +5 -0
  144. just_bash/commands/which/which.py +52 -0
  145. just_bash/commands/xan/__init__.py +5 -0
  146. just_bash/commands/xan/xan.py +1663 -0
  147. just_bash/commands/xargs/__init__.py +5 -0
  148. just_bash/commands/xargs/xargs.py +136 -0
  149. just_bash/commands/yq/__init__.py +5 -0
  150. just_bash/commands/yq/yq.py +848 -0
  151. just_bash/fs/__init__.py +29 -0
  152. just_bash/fs/in_memory_fs.py +621 -0
  153. just_bash/fs/mountable_fs.py +504 -0
  154. just_bash/fs/overlay_fs.py +894 -0
  155. just_bash/fs/read_write_fs.py +455 -0
  156. just_bash/interpreter/__init__.py +37 -0
  157. just_bash/interpreter/builtins/__init__.py +92 -0
  158. just_bash/interpreter/builtins/alias.py +154 -0
  159. just_bash/interpreter/builtins/cd.py +76 -0
  160. just_bash/interpreter/builtins/control.py +127 -0
  161. just_bash/interpreter/builtins/declare.py +336 -0
  162. just_bash/interpreter/builtins/export.py +56 -0
  163. just_bash/interpreter/builtins/let.py +44 -0
  164. just_bash/interpreter/builtins/local.py +57 -0
  165. just_bash/interpreter/builtins/mapfile.py +152 -0
  166. just_bash/interpreter/builtins/misc.py +378 -0
  167. just_bash/interpreter/builtins/readonly.py +80 -0
  168. just_bash/interpreter/builtins/set.py +234 -0
  169. just_bash/interpreter/builtins/shopt.py +201 -0
  170. just_bash/interpreter/builtins/source.py +136 -0
  171. just_bash/interpreter/builtins/test.py +290 -0
  172. just_bash/interpreter/builtins/unset.py +53 -0
  173. just_bash/interpreter/conditionals.py +387 -0
  174. just_bash/interpreter/control_flow.py +381 -0
  175. just_bash/interpreter/errors.py +116 -0
  176. just_bash/interpreter/expansion.py +1156 -0
  177. just_bash/interpreter/interpreter.py +813 -0
  178. just_bash/interpreter/types.py +134 -0
  179. just_bash/network/__init__.py +1 -0
  180. just_bash/parser/__init__.py +39 -0
  181. just_bash/parser/lexer.py +948 -0
  182. just_bash/parser/parser.py +2162 -0
  183. just_bash/py.typed +0 -0
  184. just_bash/query_engine/__init__.py +83 -0
  185. just_bash/query_engine/builtins/__init__.py +1283 -0
  186. just_bash/query_engine/evaluator.py +578 -0
  187. just_bash/query_engine/parser.py +525 -0
  188. just_bash/query_engine/tokenizer.py +329 -0
  189. just_bash/query_engine/types.py +373 -0
  190. just_bash/types.py +180 -0
  191. just_bash-0.1.5.dist-info/METADATA +410 -0
  192. just_bash-0.1.5.dist-info/RECORD +193 -0
  193. 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)