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
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,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)
|