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
just_bash/bash.py ADDED
@@ -0,0 +1,220 @@
1
+ """Main Bash class - the primary API for just-bash.
2
+
3
+ Example usage:
4
+ from just_bash import Bash
5
+
6
+ # Synchronous usage (for REPL, scripts)
7
+ bash = Bash()
8
+ result = bash.run("echo hello world")
9
+ print(result.stdout) # "hello world\n"
10
+
11
+ # Async usage (for async applications)
12
+ bash = Bash()
13
+ result = await bash.exec("echo hello world")
14
+ print(result.stdout) # "hello world\n"
15
+
16
+ # With initial files
17
+ bash = Bash(files={"/data.txt": "hello\\n"})
18
+ result = bash.run("cat /data.txt")
19
+
20
+ # With execution limits
21
+ bash = Bash(limits=ExecutionLimits(max_command_count=1000))
22
+ """
23
+
24
+ import asyncio
25
+ from typing import Optional
26
+
27
+ import nest_asyncio # type: ignore[import-untyped]
28
+
29
+ from .commands import create_command_registry
30
+ from .fs import InMemoryFs
31
+ from .interpreter import Interpreter, InterpreterState, ShellOptions
32
+ from .parser import parse
33
+ from .types import (
34
+ Command,
35
+ ExecResult,
36
+ ExecutionLimits,
37
+ IFileSystem,
38
+ NetworkConfig,
39
+ )
40
+
41
+
42
+ class Bash:
43
+ """Main Bash interpreter class.
44
+
45
+ Provides a high-level API for executing bash scripts in a sandboxed
46
+ environment with an in-memory virtual filesystem.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ *,
52
+ fs: Optional[IFileSystem] = None,
53
+ files: Optional[dict[str, str | bytes]] = None,
54
+ cwd: str = "/home/user",
55
+ env: Optional[dict[str, str]] = None,
56
+ limits: Optional[ExecutionLimits] = None,
57
+ network: Optional[NetworkConfig] = None,
58
+ commands: Optional[dict[str, Command]] = None,
59
+ errexit: bool = False,
60
+ pipefail: bool = False,
61
+ nounset: bool = False,
62
+ ):
63
+ """Initialize the Bash interpreter.
64
+
65
+ Args:
66
+ fs: Filesystem to use. If not provided, creates an InMemoryFs.
67
+ files: Initial files to create (requires default InMemoryFs).
68
+ cwd: Initial working directory.
69
+ env: Additional environment variables.
70
+ limits: Execution limits for security.
71
+ network: Network configuration (for curl command).
72
+ commands: Custom command registry. If not provided, uses built-in commands.
73
+ errexit: Enable errexit (set -e) mode.
74
+ pipefail: Enable pipefail mode.
75
+ nounset: Enable nounset (set -u) mode.
76
+ """
77
+ # Set up filesystem
78
+ if fs is not None:
79
+ self._fs = fs
80
+ else:
81
+ self._fs = InMemoryFs(initial_files=files or {})
82
+
83
+ # Set up limits
84
+ self._limits = limits or ExecutionLimits()
85
+
86
+ # Set up commands
87
+ self._commands = commands or create_command_registry()
88
+
89
+ # Set up network config
90
+ self._network = network
91
+
92
+ # Set up initial state
93
+ default_env = {
94
+ "PATH": "/usr/local/bin:/usr/bin:/bin",
95
+ "HOME": "/home/user",
96
+ "USER": "user",
97
+ "SHELL": "/bin/bash",
98
+ "PWD": cwd,
99
+ "?": "0",
100
+ }
101
+ if env:
102
+ default_env.update(env)
103
+
104
+ self._initial_state = InterpreterState(
105
+ env=default_env,
106
+ cwd=cwd,
107
+ previous_dir=cwd,
108
+ options=ShellOptions(
109
+ errexit=errexit,
110
+ pipefail=pipefail,
111
+ nounset=nounset,
112
+ ),
113
+ )
114
+
115
+ # Create interpreter
116
+ self._interpreter = Interpreter(
117
+ fs=self._fs,
118
+ commands=self._commands,
119
+ limits=self._limits,
120
+ state=self._initial_state,
121
+ )
122
+
123
+ @property
124
+ def fs(self) -> IFileSystem:
125
+ """Get the filesystem."""
126
+ return self._fs
127
+
128
+ @property
129
+ def cwd(self) -> str:
130
+ """Get the current working directory."""
131
+ return self._interpreter.state.cwd
132
+
133
+ @property
134
+ def env(self) -> dict[str, str]:
135
+ """Get the environment variables."""
136
+ return self._interpreter.state.env
137
+
138
+ async def exec(
139
+ self,
140
+ script: str,
141
+ *,
142
+ env: Optional[dict[str, str]] = None,
143
+ cwd: Optional[str] = None,
144
+ ) -> ExecResult:
145
+ """Execute a bash script.
146
+
147
+ Args:
148
+ script: The bash script to execute.
149
+ env: Additional environment variables for this execution.
150
+ cwd: Working directory for this execution.
151
+
152
+ Returns:
153
+ ExecResult with stdout, stderr, exit_code, and final env.
154
+ """
155
+ # Parse the script
156
+ ast = parse(script)
157
+
158
+ # Update state if env/cwd provided
159
+ if env:
160
+ self._interpreter.state.env.update(env)
161
+ if cwd:
162
+ self._interpreter.state.cwd = cwd
163
+ self._interpreter.state.env["PWD"] = cwd
164
+
165
+ # Execute
166
+ return await self._interpreter.execute_script(ast)
167
+
168
+ def run(
169
+ self,
170
+ script: str,
171
+ *,
172
+ env: Optional[dict[str, str]] = None,
173
+ cwd: Optional[str] = None,
174
+ ) -> ExecResult:
175
+ """Execute a bash script synchronously.
176
+
177
+ This is a convenience wrapper around exec() that works in any context,
178
+ including Jupyter notebooks and async frameworks.
179
+
180
+ Args:
181
+ script: The bash script to execute.
182
+ env: Additional environment variables for this execution.
183
+ cwd: Working directory for this execution.
184
+
185
+ Returns:
186
+ ExecResult with stdout, stderr, exit_code, and final env.
187
+
188
+ Example:
189
+ >>> bash = Bash()
190
+ >>> result = bash.run('echo "Hello, World!"')
191
+ >>> print(result.stdout)
192
+ Hello, World!
193
+ """
194
+ try:
195
+ asyncio.get_running_loop()
196
+ # We're in an existing event loop (Jupyter, async framework, etc.)
197
+ # Apply nest_asyncio to allow nested event loops
198
+ nest_asyncio.apply()
199
+ except RuntimeError:
200
+ # No running event loop, asyncio.run() will work fine
201
+ pass
202
+ return asyncio.run(self.exec(script, env=env, cwd=cwd))
203
+
204
+ def reset(self) -> None:
205
+ """Reset the interpreter state to initial values."""
206
+ self._interpreter = Interpreter(
207
+ fs=self._fs,
208
+ commands=self._commands,
209
+ limits=self._limits,
210
+ state=InterpreterState(
211
+ env=dict(self._initial_state.env),
212
+ cwd=self._initial_state.cwd,
213
+ previous_dir=self._initial_state.previous_dir,
214
+ options=ShellOptions(
215
+ errexit=self._initial_state.options.errexit,
216
+ pipefail=self._initial_state.options.pipefail,
217
+ nounset=self._initial_state.options.nounset,
218
+ ),
219
+ ),
220
+ )
@@ -0,0 +1,23 @@
1
+ """Command implementations for just-bash."""
2
+
3
+ from .registry import (
4
+ create_command_registry,
5
+ create_lazy_commands,
6
+ get_command_names,
7
+ get_network_command_names,
8
+ clear_command_cache,
9
+ get_loaded_command_count,
10
+ COMMAND_NAMES,
11
+ NETWORK_COMMAND_NAMES,
12
+ )
13
+
14
+ __all__ = [
15
+ "create_command_registry",
16
+ "create_lazy_commands",
17
+ "get_command_names",
18
+ "get_network_command_names",
19
+ "clear_command_cache",
20
+ "get_loaded_command_count",
21
+ "COMMAND_NAMES",
22
+ "NETWORK_COMMAND_NAMES",
23
+ ]
@@ -0,0 +1,5 @@
1
+ """Argv command module."""
2
+
3
+ from .argv import ArgvCommand
4
+
5
+ __all__ = ["ArgvCommand"]
@@ -0,0 +1,21 @@
1
+ """Argv command implementation.
2
+
3
+ Usage: argv.py [arg ...]
4
+
5
+ Print arguments as a Python-style list.
6
+ Used for testing word splitting and expansion.
7
+ """
8
+
9
+ from ...types import CommandContext, ExecResult
10
+
11
+
12
+ class ArgvCommand:
13
+ """The argv.py command for testing argument handling."""
14
+
15
+ name = "argv.py"
16
+
17
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
18
+ """Execute the argv.py command."""
19
+ # Format as Python list
20
+ output = repr(args) + "\n"
21
+ return ExecResult(stdout=output, stderr="", exit_code=0)
@@ -0,0 +1,5 @@
1
+ """Awk command implementation."""
2
+
3
+ from .awk import AwkCommand
4
+
5
+ __all__ = ["AwkCommand"]