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.
Files changed (122) hide show
  1. {shellsim-0.1.0 → shellsim-0.1.2}/Cargo.lock +1 -1
  2. {shellsim-0.1.0 → shellsim-0.1.2}/Cargo.toml +3 -2
  3. {shellsim-0.1.0 → shellsim-0.1.2}/PKG-INFO +15 -1
  4. {shellsim-0.1.0 → shellsim-0.1.2}/README.md +14 -0
  5. {shellsim-0.1.0 → shellsim-0.1.2}/pyproject.toml +3 -0
  6. {shellsim-0.1.0 → shellsim-0.1.2}/python/native/Cargo.lock +2 -2
  7. {shellsim-0.1.0 → shellsim-0.1.2}/python/native/Cargo.toml +5 -1
  8. shellsim-0.1.2/python/shellsim/__main__.py +6 -0
  9. shellsim-0.1.2/python/shellsim/_cli.py +182 -0
  10. {shellsim-0.1.0 → shellsim-0.1.2}/LICENSE +0 -0
  11. {shellsim-0.1.0 → shellsim-0.1.2}/python/native/src/lib.rs +0 -0
  12. {shellsim-0.1.0 → shellsim-0.1.2}/python/shellsim/__init__.py +0 -0
  13. {shellsim-0.1.0 → shellsim-0.1.2}/python/shellsim/_api.py +0 -0
  14. {shellsim-0.1.0 → shellsim-0.1.2}/python/shellsim/py.typed +0 -0
  15. {shellsim-0.1.0 → shellsim-0.1.2}/rust-toolchain.toml +0 -0
  16. {shellsim-0.1.0 → shellsim-0.1.2}/src/bin/shellsim-python.rs +0 -0
  17. {shellsim-0.1.0 → shellsim-0.1.2}/src/clock.rs +0 -0
  18. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/archives.rs +0 -0
  19. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/awk.rs +0 -0
  20. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/builtins.rs +0 -0
  21. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/echo.rs +0 -0
  22. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/fs.rs +0 -0
  23. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/git.rs +0 -0
  24. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/hashing.rs +0 -0
  25. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/makecmd.rs +0 -0
  26. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/mod.rs +0 -0
  27. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/net.rs +0 -0
  28. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/patch.rs +0 -0
  29. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/pkg.rs +0 -0
  30. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/printf.rs +0 -0
  31. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/proc.rs +0 -0
  32. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/rgcmd.rs +0 -0
  33. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/sort.rs +0 -0
  34. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/system.rs +0 -0
  35. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/tarcmd.rs +0 -0
  36. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/text.rs +0 -0
  37. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/util.rs +0 -0
  38. {shellsim-0.1.0 → shellsim-0.1.2}/src/commands/zipcmd.rs +0 -0
  39. {shellsim-0.1.0 → shellsim-0.1.2}/src/descriptors.rs +0 -0
  40. {shellsim-0.1.0 → shellsim-0.1.2}/src/exec.rs +0 -0
  41. {shellsim-0.1.0 → shellsim-0.1.2}/src/expand.rs +0 -0
  42. {shellsim-0.1.0 → shellsim-0.1.2}/src/harness.rs +0 -0
  43. {shellsim-0.1.0 → shellsim-0.1.2}/src/harness_manager.rs +0 -0
  44. {shellsim-0.1.0 → shellsim-0.1.2}/src/hashes.rs +0 -0
  45. {shellsim-0.1.0 → shellsim-0.1.2}/src/host_ingest.rs +0 -0
  46. {shellsim-0.1.0 → shellsim-0.1.2}/src/interp.rs +0 -0
  47. {shellsim-0.1.0 → shellsim-0.1.2}/src/jqcmd.rs +0 -0
  48. {shellsim-0.1.0 → shellsim-0.1.2}/src/lib.rs +0 -0
  49. {shellsim-0.1.0 → shellsim-0.1.2}/src/main.rs +0 -0
  50. {shellsim-0.1.0 → shellsim-0.1.2}/src/mcp.rs +0 -0
  51. {shellsim-0.1.0 → shellsim-0.1.2}/src/net.rs +0 -0
  52. {shellsim-0.1.0 → shellsim-0.1.2}/src/netcmd.rs +0 -0
  53. {shellsim-0.1.0 → shellsim-0.1.2}/src/process.rs +0 -0
  54. {shellsim-0.1.0 → shellsim-0.1.2}/src/pseudo_fs.rs +0 -0
  55. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/ast.rs +0 -0
  56. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/bytecode.rs +0 -0
  57. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/compiler.rs +0 -0
  58. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/filesystem.rs +0 -0
  59. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/heap.rs +0 -0
  60. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/lexer.rs +0 -0
  61. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/mod.rs +0 -0
  62. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/native.rs +0 -0
  63. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/number.rs +0 -0
  64. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/object_model.rs +0 -0
  65. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/parser.rs +0 -0
  66. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/process.rs +0 -0
  67. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/protocol.rs +0 -0
  68. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/source.rs +0 -0
  69. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/argparse.rs +0 -0
  70. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/base64.rs +0 -0
  71. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/bisect.rs +0 -0
  72. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/collections.rs +0 -0
  73. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/core.rs +0 -0
  74. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/dataclasses.rs +0 -0
  75. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/enum.rs +0 -0
  76. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/frozen.rs +0 -0
  77. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/functools.rs +0 -0
  78. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/hashlib.rs +0 -0
  79. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/heapq.rs +0 -0
  80. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/itertools.rs +0 -0
  81. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/json.rs +0 -0
  82. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/math.rs +0 -0
  83. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/mod.rs +0 -0
  84. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/os.rs +0 -0
  85. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/pytest.rs +0 -0
  86. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/re.rs +0 -0
  87. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/abc.py +0 -0
  88. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/base64.py +0 -0
  89. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/codecs.py +0 -0
  90. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/collections.py +0 -0
  91. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/csv.py +0 -0
  92. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/datetime.py +0 -0
  93. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/glob.py +0 -0
  94. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/hashlib.py +0 -0
  95. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/io.py +0 -0
  96. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/json.py +0 -0
  97. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/logging.py +0 -0
  98. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/os.py +0 -0
  99. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/pathlib.py +0 -0
  100. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/struct.py +0 -0
  101. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/subprocess.py +0 -0
  102. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/tempfile.py +0 -0
  103. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/uuid.py +0 -0
  104. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/source/zlib.py +0 -0
  105. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/string.rs +0 -0
  106. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/struct.rs +0 -0
  107. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/subprocess.rs +0 -0
  108. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/sys.rs +0 -0
  109. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/time.rs +0 -0
  110. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/typing.rs +0 -0
  111. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/unittest.rs +0 -0
  112. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/vfs.rs +0 -0
  113. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/stdlib/zlib.rs +0 -0
  114. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/token.rs +0 -0
  115. {shellsim-0.1.0 → shellsim-0.1.2}/src/python/vm.rs +0 -0
  116. {shellsim-0.1.0 → shellsim-0.1.2}/src/resources.rs +0 -0
  117. {shellsim-0.1.0 → shellsim-0.1.2}/src/sandbox.rs +0 -0
  118. {shellsim-0.1.0 → shellsim-0.1.2}/src/scenario.rs +0 -0
  119. {shellsim-0.1.0 → shellsim-0.1.2}/src/scheduler.rs +0 -0
  120. {shellsim-0.1.0 → shellsim-0.1.2}/src/shell.rs +0 -0
  121. {shellsim-0.1.0 → shellsim-0.1.2}/src/telemetry.rs +0 -0
  122. {shellsim-0.1.0 → shellsim-0.1.2}/src/vfs.rs +0 -0
@@ -330,7 +330,7 @@ dependencies = [
330
330
 
331
331
  [[package]]
332
332
  name = "shellsim"
333
- version = "0.1.0"
333
+ version = "0.1.2"
334
334
  dependencies = [
335
335
  "base64",
336
336
  "crc32fast",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "shellsim"
3
- version = "0.1.0"
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 = 2
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.0
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.0"
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.0"
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.0"
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,6 @@
1
+ """Run the shellsim package as a module."""
2
+
3
+ from ._cli import main
4
+
5
+
6
+ raise SystemExit(main())
@@ -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