shellsim 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {shellsim-0.1.0 → shellsim-0.1.2}/Cargo.lock +1 -1
- {shellsim-0.1.0 → shellsim-0.1.2}/Cargo.toml +3 -2
- {shellsim-0.1.0 → shellsim-0.1.2}/PKG-INFO +15 -1
- {shellsim-0.1.0 → shellsim-0.1.2}/README.md +14 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/pyproject.toml +3 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/python/native/Cargo.lock +2 -2
- {shellsim-0.1.0 → shellsim-0.1.2}/python/native/Cargo.toml +5 -1
- shellsim-0.1.2/python/shellsim/__main__.py +6 -0
- shellsim-0.1.2/python/shellsim/_cli.py +182 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/LICENSE +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/python/native/src/lib.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/python/shellsim/__init__.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/python/shellsim/_api.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/python/shellsim/py.typed +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/rust-toolchain.toml +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/bin/shellsim-python.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/clock.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/archives.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/awk.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/builtins.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/echo.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/fs.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/git.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/hashing.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/makecmd.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/mod.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/net.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/patch.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/pkg.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/printf.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/proc.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/rgcmd.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/sort.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/system.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/tarcmd.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/text.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/util.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/zipcmd.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/descriptors.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/exec.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/expand.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/harness.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/harness_manager.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/hashes.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/host_ingest.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/interp.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/jqcmd.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/lib.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/main.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/mcp.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/net.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/netcmd.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/process.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/pseudo_fs.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/ast.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/bytecode.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/compiler.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/filesystem.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/heap.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/lexer.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/mod.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/native.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/number.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/object_model.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/parser.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/process.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/protocol.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/source.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/argparse.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/base64.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/bisect.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/collections.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/core.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/dataclasses.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/enum.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/frozen.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/functools.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/hashlib.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/heapq.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/itertools.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/json.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/math.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/mod.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/os.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/pytest.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/re.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/abc.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/base64.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/codecs.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/collections.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/csv.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/datetime.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/glob.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/hashlib.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/io.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/json.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/logging.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/os.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/pathlib.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/struct.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/subprocess.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/tempfile.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/uuid.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/zlib.py +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/string.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/struct.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/subprocess.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/sys.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/time.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/typing.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/unittest.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/vfs.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/zlib.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/token.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/python/vm.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/resources.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/sandbox.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/scenario.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/scheduler.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/shell.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/telemetry.rs +0 -0
- {shellsim-0.1.0 → shellsim-0.1.2}/src/vfs.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "shellsim"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "Deterministic, resource-constrained BusyBox-like environment for evaluating agents"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -58,4 +58,5 @@ python = []
|
|
|
58
58
|
opt-level = 1
|
|
59
59
|
|
|
60
60
|
[profile.release]
|
|
61
|
-
opt-level =
|
|
61
|
+
opt-level = "s"
|
|
62
|
+
lto = false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shellsim
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
6
6
|
Classifier: Operating System :: MacOS :: MacOS X
|
|
@@ -119,6 +119,15 @@ environment.write_file("/work/main.py", "print(6 * 7)\n")
|
|
|
119
119
|
result = environment.run("python3.14 /work/main.py")
|
|
120
120
|
assert result.stdout == b"42\n"
|
|
121
121
|
PY
|
|
122
|
+
|
|
123
|
+
# Run the PyPI package as an ephemeral tool
|
|
124
|
+
uvx shellsim -c 'printf "b\na\n" | sort'
|
|
125
|
+
|
|
126
|
+
# Copy a trusted host project into /work, then modify only the disposable VFS snapshot
|
|
127
|
+
uvx shellsim --root ./project -c 'pwd; find . -type f; make test'
|
|
128
|
+
|
|
129
|
+
# Open a persistent interactive simulator rooted at a host-project snapshot
|
|
130
|
+
uvx shellsim --root ./project
|
|
122
131
|
```
|
|
123
132
|
|
|
124
133
|
Limit values accept `k`, `m`, and `g` binary suffixes. Arguments after `--` in `eval` mode become
|
|
@@ -153,6 +162,11 @@ process-wide seccomp filter, so importing or using it does not restrict the embe
|
|
|
153
162
|
process. Simulated programs still execute through the capability-free Rust library and cannot
|
|
154
163
|
reach ambient host resources.
|
|
155
164
|
|
|
165
|
+
The installed `shellsim` console command uses the same facade. With a terminal it opens a
|
|
166
|
+
persistent simulated shell; with piped input it executes that input as shell source, and `-c`
|
|
167
|
+
executes one action. `--root DIR` performs a bounded, symlink-rejecting snapshot copy into `/work`.
|
|
168
|
+
Changes made by simulated commands are never written back to the host directory.
|
|
169
|
+
|
|
156
170
|
## Virtual time
|
|
157
171
|
|
|
158
172
|
An environment owns deterministic monotonic, wall, and process-CPU clocks. Sleeps and deadlines
|
|
@@ -96,6 +96,15 @@ environment.write_file("/work/main.py", "print(6 * 7)\n")
|
|
|
96
96
|
result = environment.run("python3.14 /work/main.py")
|
|
97
97
|
assert result.stdout == b"42\n"
|
|
98
98
|
PY
|
|
99
|
+
|
|
100
|
+
# Run the PyPI package as an ephemeral tool
|
|
101
|
+
uvx shellsim -c 'printf "b\na\n" | sort'
|
|
102
|
+
|
|
103
|
+
# Copy a trusted host project into /work, then modify only the disposable VFS snapshot
|
|
104
|
+
uvx shellsim --root ./project -c 'pwd; find . -type f; make test'
|
|
105
|
+
|
|
106
|
+
# Open a persistent interactive simulator rooted at a host-project snapshot
|
|
107
|
+
uvx shellsim --root ./project
|
|
99
108
|
```
|
|
100
109
|
|
|
101
110
|
Limit values accept `k`, `m`, and `g` binary suffixes. Arguments after `--` in `eval` mode become
|
|
@@ -130,6 +139,11 @@ process-wide seccomp filter, so importing or using it does not restrict the embe
|
|
|
130
139
|
process. Simulated programs still execute through the capability-free Rust library and cannot
|
|
131
140
|
reach ambient host resources.
|
|
132
141
|
|
|
142
|
+
The installed `shellsim` console command uses the same facade. With a terminal it opens a
|
|
143
|
+
persistent simulated shell; with piped input it executes that input as shell source, and `-c`
|
|
144
|
+
executes one action. `--root DIR` performs a bounded, symlink-rejecting snapshot copy into `/work`.
|
|
145
|
+
Changes made by simulated commands are never written back to the host directory.
|
|
146
|
+
|
|
133
147
|
## Virtual time
|
|
134
148
|
|
|
135
149
|
An environment owns deterministic monotonic, wall, and process-CPU clocks. Sleeps and deadlines
|
|
@@ -29,6 +29,9 @@ Homepage = "https://github.com/rjpower/shellsim"
|
|
|
29
29
|
Issues = "https://github.com/rjpower/shellsim/issues"
|
|
30
30
|
Repository = "https://github.com/rjpower/shellsim"
|
|
31
31
|
|
|
32
|
+
[project.scripts]
|
|
33
|
+
shellsim = "shellsim._cli:main"
|
|
34
|
+
|
|
32
35
|
[dependency-groups]
|
|
33
36
|
test = ["pytest>=8"]
|
|
34
37
|
|
|
@@ -406,7 +406,7 @@ dependencies = [
|
|
|
406
406
|
|
|
407
407
|
[[package]]
|
|
408
408
|
name = "shellsim"
|
|
409
|
-
version = "0.1.
|
|
409
|
+
version = "0.1.2"
|
|
410
410
|
dependencies = [
|
|
411
411
|
"base64",
|
|
412
412
|
"crc32fast",
|
|
@@ -427,7 +427,7 @@ dependencies = [
|
|
|
427
427
|
|
|
428
428
|
[[package]]
|
|
429
429
|
name = "shellsim-python-bindings"
|
|
430
|
-
version = "0.1.
|
|
430
|
+
version = "0.1.2"
|
|
431
431
|
dependencies = [
|
|
432
432
|
"pyo3",
|
|
433
433
|
"serde",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[package]
|
|
4
4
|
name = "shellsim-python-bindings"
|
|
5
|
-
version = "0.1.
|
|
5
|
+
version = "0.1.2"
|
|
6
6
|
edition = "2021"
|
|
7
7
|
description = "Python bindings for shellsim"
|
|
8
8
|
license = "Apache-2.0"
|
|
@@ -18,3 +18,7 @@ pyo3 = { version = "0.28", features = ["abi3-py39", "extension-module"] }
|
|
|
18
18
|
serde = { version = "1", features = ["derive"] }
|
|
19
19
|
serde_json = "1"
|
|
20
20
|
shellsim = { path = "../.." }
|
|
21
|
+
|
|
22
|
+
[profile.release]
|
|
23
|
+
opt-level = "s"
|
|
24
|
+
lto = false
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Command-line interface for the installed shellsim Python package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
from ._api import Environment, RunResult, SimulationError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_MAX_U64 = (1 << 64) - 1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _quantity(value: str) -> int:
|
|
17
|
+
"""Parse a non-negative count with an optional binary k, m, or g suffix."""
|
|
18
|
+
|
|
19
|
+
suffixes = {"k": 1024, "m": 1024**2, "g": 1024**3}
|
|
20
|
+
suffix = value[-1:].lower()
|
|
21
|
+
multiplier = suffixes.get(suffix, 1)
|
|
22
|
+
digits = value[:-1] if suffix in suffixes else value
|
|
23
|
+
try:
|
|
24
|
+
number = int(digits)
|
|
25
|
+
except ValueError as error:
|
|
26
|
+
raise argparse.ArgumentTypeError(
|
|
27
|
+
"expected a count, optionally suffixed with k, m, or g"
|
|
28
|
+
) from error
|
|
29
|
+
result = number * multiplier
|
|
30
|
+
if number < 0 or result > _MAX_U64:
|
|
31
|
+
raise argparse.ArgumentTypeError(f"expected a count between 0 and {_MAX_U64}")
|
|
32
|
+
return result
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _parser() -> argparse.ArgumentParser:
|
|
36
|
+
"""Build the command-line parser shared by the console and module entry points."""
|
|
37
|
+
|
|
38
|
+
parser = argparse.ArgumentParser(
|
|
39
|
+
prog="shellsim",
|
|
40
|
+
description="Run shell commands in a deterministic in-memory simulation.",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument("-c", "--command", metavar="SOURCE", help="execute one shell action")
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--root",
|
|
45
|
+
metavar="DIR",
|
|
46
|
+
help="copy this trusted host directory into the simulated /work tree",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument("--cpu", type=_quantity, help="cumulative CPU fuel limit")
|
|
49
|
+
parser.add_argument("--memory", type=_quantity, help="modeled memory limit")
|
|
50
|
+
parser.add_argument("--disk", type=_quantity, help="simulated filesystem limit")
|
|
51
|
+
parser.add_argument("--output", type=_quantity, help="cumulative output limit")
|
|
52
|
+
return parser
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _binary_stream(stream: Any) -> Any:
|
|
56
|
+
"""Return a stream that accepts bytes, including under in-process test doubles."""
|
|
57
|
+
|
|
58
|
+
return getattr(stream, "buffer", stream)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _write_bytes(stream: Any, data: bytes) -> None:
|
|
62
|
+
"""Write exact simulator bytes to a host console stream."""
|
|
63
|
+
|
|
64
|
+
binary = _binary_stream(stream)
|
|
65
|
+
try:
|
|
66
|
+
binary.write(data)
|
|
67
|
+
except TypeError:
|
|
68
|
+
binary.write(data.decode("utf-8", errors="replace"))
|
|
69
|
+
binary.flush()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _emit_result(result: RunResult, stdout: Any, stderr: Any) -> None:
|
|
73
|
+
"""Forward one simulated action's byte-preserving output."""
|
|
74
|
+
|
|
75
|
+
_write_bytes(stdout, result.stdout)
|
|
76
|
+
_write_bytes(stderr, result.stderr)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _execute_action(
|
|
80
|
+
environment: Environment,
|
|
81
|
+
source: str,
|
|
82
|
+
stdin: bytes,
|
|
83
|
+
stdout: Any,
|
|
84
|
+
stderr: Any,
|
|
85
|
+
) -> Optional[RunResult]:
|
|
86
|
+
"""Execute and forward one action, reporting adapter failures without a traceback."""
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
result = environment.run(source, stdin)
|
|
90
|
+
except SimulationError as error:
|
|
91
|
+
print(f"shellsim: {error}", file=stderr)
|
|
92
|
+
return None
|
|
93
|
+
_emit_result(result, stdout, stderr)
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _read_bytes(stream: Any) -> bytes:
|
|
98
|
+
"""Read all remaining input without depending on a text-stream encoding."""
|
|
99
|
+
|
|
100
|
+
data = _binary_stream(stream).read()
|
|
101
|
+
return data.encode() if isinstance(data, str) else data
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _prepare_environment(
|
|
105
|
+
options: argparse.Namespace, stdout: Any, stderr: Any
|
|
106
|
+
) -> tuple[Optional[Environment], int]:
|
|
107
|
+
"""Create a simulated machine, import an optional host snapshot, and enter `/work`."""
|
|
108
|
+
|
|
109
|
+
environment = Environment(
|
|
110
|
+
cpu=options.cpu,
|
|
111
|
+
memory=options.memory,
|
|
112
|
+
disk=options.disk,
|
|
113
|
+
output=options.output,
|
|
114
|
+
)
|
|
115
|
+
try:
|
|
116
|
+
if options.root is not None:
|
|
117
|
+
environment.mount(options.root)
|
|
118
|
+
prepared = environment.run("cd /work")
|
|
119
|
+
except SimulationError as error:
|
|
120
|
+
print(f"shellsim: {error}", file=stderr)
|
|
121
|
+
return None, 2
|
|
122
|
+
|
|
123
|
+
if prepared.returncode != 0:
|
|
124
|
+
_emit_result(prepared, stdout, stderr)
|
|
125
|
+
return None, prepared.returncode
|
|
126
|
+
return environment, 0
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _interactive(environment: Environment, stdin: Any, stdout: Any, stderr: Any) -> int:
|
|
130
|
+
"""Run one action per input line while retaining simulated state."""
|
|
131
|
+
|
|
132
|
+
status = 0
|
|
133
|
+
while not environment.terminated:
|
|
134
|
+
stdout.write("shellsim$ ")
|
|
135
|
+
stdout.flush()
|
|
136
|
+
try:
|
|
137
|
+
line = stdin.readline()
|
|
138
|
+
except KeyboardInterrupt:
|
|
139
|
+
stdout.write("\n")
|
|
140
|
+
stdout.flush()
|
|
141
|
+
continue
|
|
142
|
+
if not line:
|
|
143
|
+
stdout.write("\n")
|
|
144
|
+
stdout.flush()
|
|
145
|
+
break
|
|
146
|
+
result = _execute_action(environment, line, b"", stdout, stderr)
|
|
147
|
+
if result is None:
|
|
148
|
+
return 1
|
|
149
|
+
status = result.returncode
|
|
150
|
+
return status
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main(arguments: Optional[Sequence[str]] = None) -> int:
|
|
154
|
+
"""Run the package CLI and return the simulated or usage exit status."""
|
|
155
|
+
|
|
156
|
+
options = _parser().parse_args(sys.argv[1:] if arguments is None else arguments)
|
|
157
|
+
environment, preparation_status = _prepare_environment(options, sys.stdout, sys.stderr)
|
|
158
|
+
if environment is None:
|
|
159
|
+
return preparation_status
|
|
160
|
+
|
|
161
|
+
if options.command is not None:
|
|
162
|
+
stdin = b"" if sys.stdin.isatty() else _read_bytes(sys.stdin)
|
|
163
|
+
result = _execute_action(
|
|
164
|
+
environment, options.command, stdin, sys.stdout, sys.stderr
|
|
165
|
+
)
|
|
166
|
+
if result is None:
|
|
167
|
+
return 1
|
|
168
|
+
return result.returncode
|
|
169
|
+
|
|
170
|
+
if sys.stdin.isatty():
|
|
171
|
+
return _interactive(environment, sys.stdin, sys.stdout, sys.stderr)
|
|
172
|
+
|
|
173
|
+
source_bytes = _read_bytes(sys.stdin)
|
|
174
|
+
try:
|
|
175
|
+
source = source_bytes.decode("utf-8")
|
|
176
|
+
except UnicodeDecodeError as error:
|
|
177
|
+
print(f"shellsim: stdin is not valid UTF-8 shell source: {error}", file=sys.stderr)
|
|
178
|
+
return 2
|
|
179
|
+
result = _execute_action(environment, source, b"", sys.stdout, sys.stderr)
|
|
180
|
+
if result is None:
|
|
181
|
+
return 1
|
|
182
|
+
return result.returncode
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|