shellsim 0.1.27__tar.gz → 0.1.29__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 (344) hide show
  1. {shellsim-0.1.27 → shellsim-0.1.29}/Cargo.lock +1 -1
  2. {shellsim-0.1.27 → shellsim-0.1.29}/Cargo.toml +1 -1
  3. {shellsim-0.1.27 → shellsim-0.1.29}/PKG-INFO +2 -2
  4. {shellsim-0.1.27 → shellsim-0.1.29}/pyproject.toml +1 -1
  5. {shellsim-0.1.27 → shellsim-0.1.29}/python/native/Cargo.lock +2 -2
  6. {shellsim-0.1.27 → shellsim-0.1.29}/python/native/Cargo.toml +1 -1
  7. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/builtins.rs +211 -107
  8. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/find.rs +33 -15
  9. shellsim-0.1.29/src/commands/git/archive.rs +135 -0
  10. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/compare.rs +22 -4
  11. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/mod.rs +3 -0
  12. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/worktree.rs +29 -4
  13. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/mod.rs +1 -0
  14. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/pkg.rs +29 -2
  15. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/proc.rs +55 -15
  16. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/sort.rs +16 -6
  17. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/streams.rs +30 -6
  18. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/tarcmd.rs +6 -6
  19. {shellsim-0.1.27 → shellsim-0.1.29}/src/exec.rs +119 -22
  20. {shellsim-0.1.27 → shellsim-0.1.29}/src/interp.rs +33 -2
  21. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/find.rs +43 -39
  22. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/attributes.rs +103 -108
  23. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/bytecode.rs +58 -3
  24. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/compiler.rs +25 -7
  25. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/exception_types.rs +50 -1
  26. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/heap/gc.rs +28 -63
  27. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/heap/mod.rs +149 -221
  28. shellsim-0.1.29/src/python/heap/native_object.rs +56 -0
  29. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/heap/snapshot.rs +17 -105
  30. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/heap/stack.rs +13 -0
  31. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/mod.rs +56 -37
  32. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/native.rs +39 -6
  33. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/number.rs +68 -10
  34. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/object_model.rs +825 -554
  35. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/protocol.rs +43 -438
  36. shellsim-0.1.29/src/python/pytest_options.rs +198 -0
  37. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/scopes.rs +3 -2
  38. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/argparse.rs +191 -4
  39. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/core.rs +90 -27
  40. shellsim-0.1.29/src/python/stdlib/enum.rs +106 -0
  41. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/json.rs +0 -3
  42. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/convert.rs +9 -16
  43. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/mod.rs +1 -0
  44. shellsim-0.1.29/src/python/stdlib/numpy/storage.rs +116 -0
  45. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/pytest.rs +21 -0
  46. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/re.rs +104 -1
  47. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/unittest.rs +28 -0
  48. shellsim-0.1.29/src/python/string.rs +392 -0
  49. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/calls.rs +350 -254
  50. shellsim-0.1.29/src/python/vm/compare.rs +367 -0
  51. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/dispatch.rs +215 -61
  52. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/equality.rs +35 -117
  53. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/handles.rs +20 -0
  54. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/hashing.rs +43 -73
  55. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/host.rs +4 -1
  56. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/iteration.rs +55 -44
  57. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/mappings.rs +1 -1
  58. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/namespace.rs +79 -72
  59. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/native_runtime.rs +222 -196
  60. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/objects.rs +446 -577
  61. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/operations.rs +35 -183
  62. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm.rs +197 -62
  63. shellsim-0.1.27/src/python/stdlib/enum.rs +0 -16
  64. shellsim-0.1.27/src/python/string.rs +0 -175
  65. {shellsim-0.1.27 → shellsim-0.1.29}/LICENSE +0 -0
  66. {shellsim-0.1.27 → shellsim-0.1.29}/README.md +0 -0
  67. {shellsim-0.1.27 → shellsim-0.1.29}/python/native/src/lib.rs +0 -0
  68. {shellsim-0.1.27 → shellsim-0.1.29}/python/shellsim/__init__.py +0 -0
  69. {shellsim-0.1.27 → shellsim-0.1.29}/python/shellsim/__main__.py +0 -0
  70. {shellsim-0.1.27 → shellsim-0.1.29}/python/shellsim/_api.py +0 -0
  71. {shellsim-0.1.27 → shellsim-0.1.29}/python/shellsim/_cli.py +0 -0
  72. {shellsim-0.1.27 → shellsim-0.1.29}/python/shellsim/display_host.py +0 -0
  73. {shellsim-0.1.27 → shellsim-0.1.29}/python/shellsim/package.py +0 -0
  74. {shellsim-0.1.27 → shellsim-0.1.29}/python/shellsim/py.typed +0 -0
  75. {shellsim-0.1.27 → shellsim-0.1.29}/python/shellsim/python.py +0 -0
  76. {shellsim-0.1.27 → shellsim-0.1.29}/rust-toolchain.toml +0 -0
  77. {shellsim-0.1.27 → shellsim-0.1.29}/src/arith.rs +0 -0
  78. {shellsim-0.1.27 → shellsim-0.1.29}/src/bin/shellsim-python.rs +0 -0
  79. {shellsim-0.1.27 → shellsim-0.1.29}/src/clock.rs +0 -0
  80. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/archives.rs +0 -0
  81. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/arcmd.rs +0 -0
  82. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/awk/ast.rs +0 -0
  83. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/awk/eval.rs +0 -0
  84. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/awk/lexer.rs +0 -0
  85. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/awk/parser.rs +0 -0
  86. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/awk.rs +0 -0
  87. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/bc.rs +0 -0
  88. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/dd.rs +0 -0
  89. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/diff.rs +0 -0
  90. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/echo.rs +0 -0
  91. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/expr.rs +0 -0
  92. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/format.rs +0 -0
  93. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/fs.rs +0 -0
  94. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/apply.rs +0 -0
  95. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/commit.rs +0 -0
  96. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/config.rs +0 -0
  97. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/conflict.rs +0 -0
  98. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/diff.rs +0 -0
  99. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/ignore.rs +0 -0
  100. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/import.rs +0 -0
  101. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/log.rs +0 -0
  102. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/merge.rs +0 -0
  103. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/plumbing.rs +0 -0
  104. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/rebase.rs +0 -0
  105. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/refs.rs +0 -0
  106. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/repo.rs +0 -0
  107. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/stash.rs +0 -0
  108. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/git/switch.rs +0 -0
  109. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/grep.rs +0 -0
  110. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/hashing.rs +0 -0
  111. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/makecmd.rs +0 -0
  112. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/net.rs +0 -0
  113. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/options.rs +0 -0
  114. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/patch.rs +0 -0
  115. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/printf.rs +0 -0
  116. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/regex_compat.rs +0 -0
  117. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/rgcmd.rs +0 -0
  118. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/sed.rs +0 -0
  119. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/system.rs +0 -0
  120. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/text.rs +0 -0
  121. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/unavailable.rs +0 -0
  122. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/util.rs +0 -0
  123. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/wasm.rs +0 -0
  124. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/xargs.rs +0 -0
  125. {shellsim-0.1.27 → shellsim-0.1.29}/src/commands/zipcmd.rs +0 -0
  126. {shellsim-0.1.27 → shellsim-0.1.29}/src/corpus.rs +0 -0
  127. {shellsim-0.1.27 → shellsim-0.1.29}/src/descriptors.rs +0 -0
  128. {shellsim-0.1.27 → shellsim-0.1.29}/src/display.rs +0 -0
  129. {shellsim-0.1.27 → shellsim-0.1.29}/src/expand.rs +0 -0
  130. {shellsim-0.1.27 → shellsim-0.1.29}/src/harness.rs +0 -0
  131. {shellsim-0.1.27 → shellsim-0.1.29}/src/harness_manager.rs +0 -0
  132. {shellsim-0.1.27 → shellsim-0.1.29}/src/hashes.rs +0 -0
  133. {shellsim-0.1.27 → shellsim-0.1.29}/src/host_ingest.rs +0 -0
  134. {shellsim-0.1.27 → shellsim-0.1.29}/src/jqcmd.rs +0 -0
  135. {shellsim-0.1.27 → shellsim-0.1.29}/src/lib.rs +0 -0
  136. {shellsim-0.1.27 → shellsim-0.1.29}/src/main.rs +0 -0
  137. {shellsim-0.1.27 → shellsim-0.1.29}/src/mcp.rs +0 -0
  138. {shellsim-0.1.27 → shellsim-0.1.29}/src/net.rs +0 -0
  139. {shellsim-0.1.27 → shellsim-0.1.29}/src/netcmd.rs +0 -0
  140. {shellsim-0.1.27 → shellsim-0.1.29}/src/process.rs +0 -0
  141. {shellsim-0.1.27 → shellsim-0.1.29}/src/profile.rs +0 -0
  142. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/cat.rs +0 -0
  143. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/env.rs +0 -0
  144. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/head.rs +0 -0
  145. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/make.rs +0 -0
  146. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/nice.rs +0 -0
  147. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/nohup.rs +0 -0
  148. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/sleep.rs +0 -0
  149. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/tee.rs +0 -0
  150. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/timeout.rs +0 -0
  151. {shellsim-0.1.27 → shellsim-0.1.29}/src/program/xargs.rs +0 -0
  152. {shellsim-0.1.27 → shellsim-0.1.29}/src/program.rs +0 -0
  153. {shellsim-0.1.27 → shellsim-0.1.29}/src/pseudo_fs.rs +0 -0
  154. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/ast.rs +0 -0
  155. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/complex.rs +0 -0
  156. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/cpython_names.rs +0 -0
  157. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/definitions.rs +0 -0
  158. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/filesystem.rs +0 -0
  159. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/float_text.rs +0 -0
  160. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/hash.rs +0 -0
  161. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/heap/mapping.rs +0 -0
  162. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/heap/tests.rs +0 -0
  163. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/heap/value.rs +0 -0
  164. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/http.rs +0 -0
  165. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/lexer.rs +0 -0
  166. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/parser.rs +0 -0
  167. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/process.rs +0 -0
  168. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/slice.rs +0 -0
  169. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/sort.rs +0 -0
  170. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/source.rs +0 -0
  171. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/asyncio.rs +0 -0
  172. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/base64.rs +0 -0
  173. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/collections.rs +0 -0
  174. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/dataclasses.rs +0 -0
  175. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/frozen.rs +0 -0
  176. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/functools.rs +0 -0
  177. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/hashlib.rs +0 -0
  178. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/heapq.rs +0 -0
  179. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/http.rs +0 -0
  180. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/importlib.rs +0 -0
  181. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/introspect.rs +0 -0
  182. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/itertools.rs +0 -0
  183. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/mapping_views.rs +0 -0
  184. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/math.rs +0 -0
  185. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/mod.rs +0 -0
  186. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/args.rs +0 -0
  187. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/array.rs +0 -0
  188. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/construct.rs +0 -0
  189. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/dtype.rs +0 -0
  190. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/dtype_object.rs +0 -0
  191. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/element.rs +0 -0
  192. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/errstate.rs +0 -0
  193. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/format.rs +0 -0
  194. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/index.rs +0 -0
  195. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/io.rs +0 -0
  196. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/layout.rs +0 -0
  197. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/linalg/dense.rs +0 -0
  198. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/linalg.rs +0 -0
  199. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/math.rs +0 -0
  200. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/ndarray.rs +0 -0
  201. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/ops.rs +0 -0
  202. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/products.rs +0 -0
  203. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/random/bitgen.rs +0 -0
  204. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/random/discrete.rs +0 -0
  205. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/random/gamma.rs +0 -0
  206. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/random/integers.rs +0 -0
  207. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/random/normal.rs +0 -0
  208. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/random/sequence.rs +0 -0
  209. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/random.rs +0 -0
  210. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/reduce.rs +0 -0
  211. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/scalar.rs +0 -0
  212. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/select.rs +0 -0
  213. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/shape/broadcast.rs +0 -0
  214. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/shape/diagonal.rs +0 -0
  215. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/shape/join.rs +0 -0
  216. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/shape/repeat.rs +0 -0
  217. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/shape.rs +0 -0
  218. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/sort/bincount.rs +0 -0
  219. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/sort/search.rs +0 -0
  220. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/sort.rs +0 -0
  221. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/ufunc/masked.rs +0 -0
  222. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/ufunc.rs +0 -0
  223. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/numpy/underflow.rs +0 -0
  224. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/operator.rs +0 -0
  225. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/os.rs +0 -0
  226. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/scipy/mod.rs +0 -0
  227. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/scipy/special/erf.rs +0 -0
  228. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/scipy/special/gamma.rs +0 -0
  229. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/scipy/special/ibeta.rs +0 -0
  230. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/scipy/special/igam.rs +0 -0
  231. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/scipy/special/meter.rs +0 -0
  232. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/scipy/special/mod.rs +0 -0
  233. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/scipy/special/zeta.rs +0 -0
  234. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/_exceptions.py +0 -0
  235. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/_io.py +0 -0
  236. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/_iteration.py +0 -0
  237. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/abc.py +0 -0
  238. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/asyncio.py +0 -0
  239. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/base64.py +0 -0
  240. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/bisect.py +0 -0
  241. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/cmath.py +0 -0
  242. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/codecs.py +0 -0
  243. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/collections.py +0 -0
  244. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/collections_abc.py +0 -0
  245. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/contextlib.py +0 -0
  246. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/copy.py +0 -0
  247. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/csv.py +0 -0
  248. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/datetime.py +0 -0
  249. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/errno.py +0 -0
  250. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/fnmatch.py +0 -0
  251. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/fractions.py +0 -0
  252. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/functools.py +0 -0
  253. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/glob.py +0 -0
  254. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/hashlib.py +0 -0
  255. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/heapq.py +0 -0
  256. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/http.py +0 -0
  257. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/http_client.py +0 -0
  258. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/importlib.py +0 -0
  259. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/inspect.py +0 -0
  260. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/io.py +0 -0
  261. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/itertools.py +0 -0
  262. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/json.py +0 -0
  263. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/keyword.py +0 -0
  264. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/logging.py +0 -0
  265. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/__init__.py +0 -0
  266. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/_creation.py +0 -0
  267. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/_errors.py +0 -0
  268. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/_io.py +0 -0
  269. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/_math.py +0 -0
  270. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/_printing.py +0 -0
  271. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/_products.py +0 -0
  272. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/_sets.py +0 -0
  273. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/_shapes.py +0 -0
  274. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/_stats.py +0 -0
  275. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/exceptions.py +0 -0
  276. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/fft.py +0 -0
  277. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/linalg.py +0 -0
  278. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/random.py +0 -0
  279. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/numpy/testing.py +0 -0
  280. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/operator.py +0 -0
  281. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/os.py +0 -0
  282. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/pathlib.py +0 -0
  283. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/posixpath.py +0 -0
  284. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/pytest.py +0 -0
  285. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/random.py +0 -0
  286. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/re.py +0 -0
  287. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/__init__.py +0 -0
  288. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/integrate.py +0 -0
  289. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/interpolate.py +0 -0
  290. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/linalg.py +0 -0
  291. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/optimize.py +0 -0
  292. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/spatial/__init__.py +0 -0
  293. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/spatial/distance.py +0 -0
  294. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/special.py +0 -0
  295. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/stats/__init__.py +0 -0
  296. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/stats/_describe.py +0 -0
  297. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/stats/_distributions.py +0 -0
  298. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/scipy/stats/_tests.py +0 -0
  299. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/shutil.py +0 -0
  300. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/signal.py +0 -0
  301. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/stat.py +0 -0
  302. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/statistics.py +0 -0
  303. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/string.py +0 -0
  304. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/struct.py +0 -0
  305. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/subprocess.py +0 -0
  306. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/sys.py +0 -0
  307. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/tempfile.py +0 -0
  308. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/textwrap.py +0 -0
  309. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/time.py +0 -0
  310. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/types.py +0 -0
  311. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/urllib.py +0 -0
  312. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/urllib_error.py +0 -0
  313. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/urllib_parse.py +0 -0
  314. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/urllib_request.py +0 -0
  315. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/uuid.py +0 -0
  316. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/warnings.py +0 -0
  317. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/zipfile.py +0 -0
  318. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/source/zlib.py +0 -0
  319. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/string.rs +0 -0
  320. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/struct.rs +0 -0
  321. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/subprocess.rs +0 -0
  322. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/sys.rs +0 -0
  323. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/time.rs +0 -0
  324. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/typing.rs +0 -0
  325. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/vfs.rs +0 -0
  326. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/warnings.rs +0 -0
  327. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/stdlib/zlib.rs +0 -0
  328. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/symbols.rs +0 -0
  329. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/token.rs +0 -0
  330. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/unicode/tables.rs +0 -0
  331. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/unicode.rs +0 -0
  332. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/format.rs +0 -0
  333. {shellsim-0.1.27 → shellsim-0.1.29}/src/python/vm/summation.rs +0 -0
  334. {shellsim-0.1.27 → shellsim-0.1.29}/src/realtime.rs +0 -0
  335. {shellsim-0.1.27 → shellsim-0.1.29}/src/resources.rs +0 -0
  336. {shellsim-0.1.27 → shellsim-0.1.29}/src/sandbox.rs +0 -0
  337. {shellsim-0.1.27 → shellsim-0.1.29}/src/scenario.rs +0 -0
  338. {shellsim-0.1.27 → shellsim-0.1.29}/src/scheduler.rs +0 -0
  339. {shellsim-0.1.27 → shellsim-0.1.29}/src/shell.rs +0 -0
  340. {shellsim-0.1.27 → shellsim-0.1.29}/src/stack.rs +0 -0
  341. {shellsim-0.1.27 → shellsim-0.1.29}/src/syscalls.rs +0 -0
  342. {shellsim-0.1.27 → shellsim-0.1.29}/src/telemetry.rs +0 -0
  343. {shellsim-0.1.27 → shellsim-0.1.29}/src/variables.rs +0 -0
  344. {shellsim-0.1.27 → shellsim-0.1.29}/src/vfs.rs +0 -0
@@ -889,7 +889,7 @@ dependencies = [
889
889
 
890
890
  [[package]]
891
891
  name = "shellsim"
892
- version = "0.1.27"
892
+ version = "0.1.29"
893
893
  dependencies = [
894
894
  "base64",
895
895
  "crc32fast",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "shellsim"
3
- version = "0.1.27"
3
+ version = "0.1.29"
4
4
  edition = "2021"
5
5
  description = "In-process BusyBox-like environment for RL rollouts and agent tests"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shellsim
3
- Version: 0.1.27
3
+ Version: 0.1.29
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Operating System :: MacOS :: MacOS X
6
6
  Classifier: Operating System :: Microsoft :: Windows
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
10
10
  Classifier: Programming Language :: Rust
11
11
  Classifier: Topic :: Software Development :: Testing
12
12
  Classifier: Typing :: Typed
13
- Requires-Dist: shellsim-c-toolchain==0.1.27 ; extra == 'c'
13
+ Requires-Dist: shellsim-c-toolchain==0.1.29 ; extra == 'c'
14
14
  Provides-Extra: c
15
15
  License-File: LICENSE
16
16
  Summary: In-process BusyBox-like environment for RL rollouts and agent tests
@@ -25,7 +25,7 @@ classifiers = [
25
25
  keywords = ["agents", "containers", "reinforcement-learning", "sandbox", "simulation", "testing"]
26
26
 
27
27
  [project.optional-dependencies]
28
- c = ["shellsim-c-toolchain==0.1.27"]
28
+ c = ["shellsim-c-toolchain==0.1.29"]
29
29
 
30
30
  [project.urls]
31
31
  Homepage = "https://github.com/rjpower/shellsim"
@@ -953,7 +953,7 @@ dependencies = [
953
953
 
954
954
  [[package]]
955
955
  name = "shellsim"
956
- version = "0.1.27"
956
+ version = "0.1.29"
957
957
  dependencies = [
958
958
  "base64",
959
959
  "crc32fast",
@@ -978,7 +978,7 @@ dependencies = [
978
978
 
979
979
  [[package]]
980
980
  name = "shellsim-python-bindings"
981
- version = "0.1.27"
981
+ version = "0.1.29"
982
982
  dependencies = [
983
983
  "base64",
984
984
  "pyo3",
@@ -2,7 +2,7 @@
2
2
 
3
3
  [package]
4
4
  name = "shellsim-python-bindings"
5
- version = "0.1.27"
5
+ version = "0.1.29"
6
6
  edition = "2021"
7
7
  description = "Python bindings for shellsim"
8
8
  license = "Apache-2.0"
@@ -4,7 +4,7 @@
4
4
 
5
5
  use std::collections::HashMap;
6
6
 
7
- use crate::commands::util::{ewln, split_flags, wln};
7
+ use crate::commands::util::{ewln, wln};
8
8
  use crate::commands::{CommandContext, CommandPoll, CommandResume, CommandSpec, Io, Trust};
9
9
  use crate::interp::Interp;
10
10
  use crate::syscalls::System;
@@ -357,7 +357,7 @@ fn cmd_flock(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i
357
357
 
358
358
  fn cmd_trap(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
359
359
  if args.is_empty() {
360
- print_traps(interp, &[], true, io);
360
+ print_traps(interp, &[], true, true, true, io);
361
361
  return 0;
362
362
  }
363
363
  if args[0] == "-l" {
@@ -368,14 +368,21 @@ fn cmd_trap(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i3
368
368
  return list_signal(None, io);
369
369
  }
370
370
  if args[0] == "-p" {
371
- let (include_exit, signals) = match parse_trap_targets(&args[1..]) {
371
+ let (include_exit, include_err, signals) = match parse_trap_targets(&args[1..]) {
372
372
  Ok(targets) => targets,
373
373
  Err(error) => {
374
374
  ewln(io.err, &format!("trap: {error}"));
375
375
  return 2;
376
376
  }
377
377
  };
378
- print_traps(interp, &signals, include_exit, io);
378
+ print_traps(
379
+ interp,
380
+ &signals,
381
+ include_exit,
382
+ include_err,
383
+ args.len() == 1,
384
+ io,
385
+ );
379
386
  return 0;
380
387
  }
381
388
 
@@ -389,7 +396,7 @@ fn cmd_trap(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i3
389
396
  ewln(io.err, "trap: missing signal operand");
390
397
  return 2;
391
398
  }
392
- let (has_exit, signals) = match parse_trap_targets(signal_args) {
399
+ let (has_exit, has_err, signals) = match parse_trap_targets(signal_args) {
393
400
  Ok(targets) => targets,
394
401
  Err(error) => {
395
402
  ewln(io.err, &format!("trap: {error}"));
@@ -439,6 +446,11 @@ fn cmd_trap(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i3
439
446
  if has_exit {
440
447
  updated_exit = disposition.clone();
441
448
  }
449
+ let updated_err = if has_err {
450
+ disposition.clone()
451
+ } else {
452
+ interp.err_disposition.clone()
453
+ };
442
454
  let state_bytes = updated
443
455
  .values()
444
456
  .fold(0_u64, |total, disposition| {
@@ -450,61 +462,81 @@ fn cmd_trap(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i3
450
462
  .saturating_add(32),
451
463
  })
452
464
  })
453
- .saturating_add(updated_exit.as_ref().map_or(0, |disposition| {
454
- match disposition {
455
- crate::interp::ShellSignalDisposition::Ignore => 16,
456
- crate::interp::ShellSignalDisposition::Handler { source, body } => (source.len()
457
- as u64)
458
- .saturating_add(body.estimated_bytes())
459
- .saturating_add(32),
460
- }
461
- }));
465
+ .saturating_add(updated_exit.iter().chain(updated_err.iter()).fold(
466
+ 0_u64,
467
+ |total, disposition| {
468
+ total.saturating_add(match disposition {
469
+ crate::interp::ShellSignalDisposition::Ignore => 16,
470
+ crate::interp::ShellSignalDisposition::Handler { source, body } => {
471
+ (source.len() as u64)
472
+ .saturating_add(body.estimated_bytes())
473
+ .saturating_add(32)
474
+ }
475
+ })
476
+ },
477
+ ));
462
478
  if state_bytes > MAX_TRAP_STATE_BYTES {
463
479
  ewln(io.err, "trap: signal handler state exceeds the 1 MiB limit");
464
480
  return 2;
465
481
  }
466
482
  interp.signal_dispositions = updated;
467
483
  interp.exit_disposition = updated_exit;
484
+ interp.err_disposition = updated_err;
485
+ if has_err {
486
+ interp.err_trap_active = true;
487
+ }
468
488
  0
469
489
  }
470
490
 
471
- fn parse_trap_targets(values: &[String]) -> Result<(bool, Vec<crate::process::Signal>), String> {
491
+ fn parse_trap_targets(
492
+ values: &[String],
493
+ ) -> Result<(bool, bool, Vec<crate::process::Signal>), String> {
472
494
  let mut has_exit = values.is_empty();
495
+ let mut has_err = values.is_empty();
473
496
  let mut signals = Vec::with_capacity(values.len());
474
497
  for value in values {
475
498
  if value == "0" || value.eq_ignore_ascii_case("EXIT") {
476
499
  has_exit = true;
477
500
  continue;
478
501
  }
502
+ if value.eq_ignore_ascii_case("ERR") {
503
+ has_err = true;
504
+ continue;
505
+ }
479
506
  let signal = crate::process::Signal::parse(value)
480
507
  .ok_or_else(|| format!("invalid signal: {value}"))?;
481
508
  if !signals.contains(&signal) {
482
509
  signals.push(signal);
483
510
  }
484
511
  }
485
- Ok((has_exit, signals))
512
+ Ok((has_exit, has_err, signals))
486
513
  }
487
514
 
488
515
  fn print_traps(
489
516
  interp: &CommandContext<'_>,
490
517
  selected: &[crate::process::Signal],
491
518
  include_exit: bool,
519
+ include_err: bool,
520
+ all: bool,
492
521
  io: &mut Io,
493
522
  ) {
494
- if include_exit {
495
- if let Some(disposition) = &interp.exit_disposition {
523
+ for (include, name, disposition) in [
524
+ (include_exit, "EXIT", &interp.exit_disposition),
525
+ (include_err, "ERR", &interp.err_disposition),
526
+ ] {
527
+ if let Some(disposition) = disposition.as_ref().filter(|_| include) {
496
528
  let action = match disposition {
497
529
  crate::interp::ShellSignalDisposition::Ignore => String::new(),
498
530
  crate::interp::ShellSignalDisposition::Handler { source, .. } => source.clone(),
499
531
  };
500
532
  wln(
501
533
  io.out,
502
- &format!("trap -- '{}' EXIT", action.replace('\'', "'\\''")),
534
+ &format!("trap -- '{}' {name}", action.replace('\'', "'\\''")),
503
535
  );
504
536
  }
505
537
  }
506
538
  for (signal, disposition) in &interp.signal_dispositions {
507
- if !selected.is_empty() && !selected.contains(signal) {
539
+ if !all && !selected.contains(signal) {
508
540
  continue;
509
541
  }
510
542
  let action = match disposition {
@@ -1091,6 +1123,8 @@ fn cmd_set(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32
1091
1123
  match a.as_str() {
1092
1124
  "-e" => interp.opt_errexit = true,
1093
1125
  "+e" => interp.opt_errexit = false,
1126
+ "-E" => interp.opt_errtrace = true,
1127
+ "+E" => interp.opt_errtrace = false,
1094
1128
  "-u" => interp.opt_nounset = true,
1095
1129
  "+u" => interp.opt_nounset = false,
1096
1130
  "-x" | "+x" => {
@@ -1102,6 +1136,7 @@ fn cmd_set(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32
1102
1136
  match opt.as_str() {
1103
1137
  "pipefail" => interp.opt_pipefail = true,
1104
1138
  "errexit" => interp.opt_errexit = true,
1139
+ "errtrace" => interp.opt_errtrace = true,
1105
1140
  "nounset" => interp.opt_nounset = true,
1106
1141
  _ => {
1107
1142
  ewln(io.err, &format!("set: unimplemented option '{opt}'"));
@@ -1116,6 +1151,7 @@ fn cmd_set(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32
1116
1151
  match opt.as_str() {
1117
1152
  "pipefail" => interp.opt_pipefail = false,
1118
1153
  "errexit" => interp.opt_errexit = false,
1154
+ "errtrace" => interp.opt_errtrace = false,
1119
1155
  "nounset" => interp.opt_nounset = false,
1120
1156
  _ => {
1121
1157
  ewln(io.err, &format!("set: unimplemented option '{opt}'"));
@@ -1129,17 +1165,19 @@ fn cmd_set(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32
1129
1165
  interp.positional = args[i + 1..].to_vec().into();
1130
1166
  break;
1131
1167
  }
1132
- s if s.starts_with('-') && s.len() > 1 => {
1168
+ s if (s.starts_with('-') || s.starts_with('+')) && s.len() > 1 => {
1169
+ let enable = s.starts_with('-');
1133
1170
  for option in s[1..].chars() {
1134
1171
  match option {
1135
- 'e' => interp.opt_errexit = true,
1136
- 'u' => interp.opt_nounset = true,
1172
+ 'e' => interp.opt_errexit = enable,
1173
+ 'E' => interp.opt_errtrace = enable,
1174
+ 'u' => interp.opt_nounset = enable,
1137
1175
  'x' => {
1138
1176
  ewln(io.err, "set: xtrace is unimplemented");
1139
1177
  return 2;
1140
1178
  }
1141
1179
  'o' if args.get(i + 1).map(String::as_str) == Some("pipefail") => {
1142
- interp.opt_pipefail = true;
1180
+ interp.opt_pipefail = enable;
1143
1181
  i += 1;
1144
1182
  }
1145
1183
  'o' => {
@@ -1750,42 +1788,129 @@ fn num(s: &str) -> i64 {
1750
1788
  s.trim().parse().unwrap_or(0)
1751
1789
  }
1752
1790
 
1753
- fn cmd_read(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
1754
- let (flags, ops, long) = split_flags(args);
1755
- if !long.is_empty() || flags.iter().any(|flag| !matches!(flag, 'a' | 'r')) {
1756
- ewln(io.err, "read: supported options are -a and -r");
1757
- return 2;
1791
+ /// Parsed record framing and assignment options for the shell `read` builtin.
1792
+ struct ReadOptions<'a> {
1793
+ raw: bool,
1794
+ delimiter: u8,
1795
+ array: Option<&'a str>,
1796
+ names: Vec<&'a str>,
1797
+ }
1798
+
1799
+ fn parse_read_options(args: &[String]) -> Result<ReadOptions<'_>, &'static str> {
1800
+ let mut options = ReadOptions {
1801
+ raw: false,
1802
+ delimiter: b'\n',
1803
+ array: None,
1804
+ names: Vec::new(),
1805
+ };
1806
+ let mut index = 0;
1807
+ while let Some(argument) = args.get(index) {
1808
+ if argument == "--" {
1809
+ index += 1;
1810
+ break;
1811
+ }
1812
+ if !argument.starts_with('-') || argument == "-" {
1813
+ break;
1814
+ }
1815
+ let bytes = argument.as_bytes();
1816
+ let mut offset = 1;
1817
+ while offset < bytes.len() {
1818
+ match bytes[offset] {
1819
+ b'r' => options.raw = true,
1820
+ option @ (b'a' | b'd') => {
1821
+ let value = if offset + 1 < bytes.len() {
1822
+ &argument[offset + 1..]
1823
+ } else {
1824
+ index += 1;
1825
+ args.get(index)
1826
+ .ok_or("read: option requires a value")?
1827
+ .as_str()
1828
+ };
1829
+ if option == b'a' {
1830
+ options.array = Some(value);
1831
+ } else {
1832
+ options.delimiter = value.as_bytes().first().copied().unwrap_or(0);
1833
+ }
1834
+ break;
1835
+ }
1836
+ _ => return Err("read: supported options are -a, -r and -d"),
1837
+ }
1838
+ offset += 1;
1839
+ }
1840
+ index += 1;
1758
1841
  }
1759
- // `read -a arr`: split the line into an indexed array (the name follows `-a`).
1760
- if flags.contains(&'a') {
1761
- let line = read_one_line(interp, io);
1762
- let Some(line) = line else { return 1 };
1763
- let line = if flags.contains(&'r') {
1764
- line
1765
- } else {
1766
- decode_read_backslashes(&line)
1842
+ options
1843
+ .names
1844
+ .extend(args[index..].iter().map(String::as_str));
1845
+ if options.names.iter().any(|name| !shell_identifier(name))
1846
+ || options.array.is_some_and(|name| !shell_identifier(name))
1847
+ {
1848
+ return Err("read: expected valid variable names");
1849
+ }
1850
+ Ok(options)
1851
+ }
1852
+
1853
+ /// Identify a completed buffered record without consuming bytes after its delimiter.
1854
+ /// Invalid options are complete immediately so they fail without waiting for input.
1855
+ #[derive(Clone, Copy)]
1856
+ pub(crate) struct ReadFraming {
1857
+ delimiter: Option<u8>,
1858
+ raw: bool,
1859
+ }
1860
+
1861
+ impl ReadFraming {
1862
+ pub(crate) fn new(args: &[String]) -> Self {
1863
+ match parse_read_options(args) {
1864
+ Ok(options) => Self {
1865
+ delimiter: Some(options.delimiter),
1866
+ raw: options.raw,
1867
+ },
1868
+ Err(_) => Self {
1869
+ delimiter: None,
1870
+ raw: true,
1871
+ },
1872
+ }
1873
+ }
1874
+
1875
+ pub(crate) fn complete(self, input: &[u8]) -> bool {
1876
+ let Some(delimiter) = self.delimiter else {
1877
+ return true;
1767
1878
  };
1768
- let arr = ops.first().map(|s| s.as_str()).unwrap_or("REPLY");
1879
+ if input.last().copied() != Some(delimiter) {
1880
+ return false;
1881
+ }
1882
+ self.raw
1883
+ || input[..input.len() - 1]
1884
+ .iter()
1885
+ .rev()
1886
+ .take_while(|byte| **byte == b'\\')
1887
+ .count()
1888
+ % 2
1889
+ == 0
1890
+ }
1891
+ }
1892
+
1893
+ fn cmd_read(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
1894
+ let options = match parse_read_options(args) {
1895
+ Ok(options) => options,
1896
+ Err(message) => {
1897
+ ewln(io.err, message);
1898
+ return 2;
1899
+ }
1900
+ };
1901
+ let (line, terminated) = read_one_record(interp, io, options.delimiter, options.raw);
1902
+ let status = i32::from(!terminated);
1903
+ if let Some(array) = options.array {
1769
1904
  let ifs = interp.get_var("IFS").unwrap_or_else(|| " \t\n".to_string());
1770
- let elems: Vec<String> = if ifs.is_empty() {
1905
+ let elems = if ifs.is_empty() {
1771
1906
  vec![line]
1772
1907
  } else {
1773
1908
  split_read_fields(&line, &ifs)
1774
1909
  };
1775
- interp.set_array(arr, elems);
1776
- return 0;
1910
+ interp.set_array(array, elems);
1911
+ return status;
1777
1912
  }
1778
- // Obtain a line: from explicit stdin (pipe) if present, else from the persistent input
1779
- // cursor (set up by a `< file` redirect on an enclosing loop).
1780
- let Some(line) = read_one_line(interp, io) else {
1781
- return 1;
1782
- };
1783
- let line = if flags.contains(&'r') {
1784
- line
1785
- } else {
1786
- decode_read_backslashes(&line)
1787
- };
1788
-
1913
+ let ops = options.names;
1789
1914
  let ifs = interp.get_var("IFS").unwrap_or_else(|| " \t\n".to_string());
1790
1915
  if ops.is_empty() {
1791
1916
  interp.set_var("REPLY", line);
@@ -1805,7 +1930,7 @@ fn cmd_read(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i3
1805
1930
  }
1806
1931
  }
1807
1932
  }
1808
- 0
1933
+ status
1809
1934
  }
1810
1935
 
1811
1936
  /// Split a `read` record according to POSIX IFS classes.
@@ -1840,21 +1965,6 @@ fn split_read_fields(line: &str, ifs: &str) -> Vec<String> {
1840
1965
  fields
1841
1966
  }
1842
1967
 
1843
- fn decode_read_backslashes(line: &str) -> String {
1844
- let mut decoded = String::with_capacity(line.len());
1845
- let mut characters = line.chars();
1846
- while let Some(character) = characters.next() {
1847
- if character == '\\' {
1848
- if let Some(escaped) = characters.next() {
1849
- decoded.push(escaped);
1850
- }
1851
- } else {
1852
- decoded.push(character);
1853
- }
1854
- }
1855
- decoded
1856
- }
1857
-
1858
1968
  const MAX_MAPFILE_RECORDS: usize = 100_000;
1859
1969
 
1860
1970
  fn cmd_mapfile(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
@@ -1997,49 +2107,43 @@ fn shell_identifier(value: &str) -> bool {
1997
2107
  && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric())
1998
2108
  }
1999
2109
 
2000
- /// Read one line for `read`: from an explicit stdin pipe if present, else the persistent input
2001
- /// cursor (a `< file` redirect on an enclosing loop). Returns None at EOF.
2002
- fn read_one_line(interp: &mut Interp, io: &Io) -> Option<String> {
2003
- if !io.stdin.is_empty() {
2004
- return Some(
2005
- String::from_utf8_lossy(&io.stdin)
2006
- .lines()
2007
- .next()
2008
- .unwrap_or("")
2009
- .to_string(),
2010
- );
2011
- }
2012
- if interp.input_pos < interp.input_stream.len() {
2013
- let rest = &interp.input_stream[interp.input_pos..];
2014
- let nl = rest.iter().position(|&b| b == b'\n');
2015
- let (line_bytes, adv) = match nl {
2016
- Some(i) => (&rest[..i], i + 1),
2017
- None => (rest, rest.len()),
2110
+ /// Read a bounded record and report whether its delimiter was encountered. Shell variables
2111
+ /// discard embedded NUL bytes; an empty `-d` instead uses NUL as the record delimiter.
2112
+ fn read_one_record(interp: &mut Interp, io: &Io, delimiter: u8, raw: bool) -> (String, bool) {
2113
+ let mut record = Vec::new();
2114
+ let mut cursor = 0usize;
2115
+ let mut escaped = false;
2116
+ let mut terminated = false;
2117
+ while cursor < crate::descriptors::MAX_CAPTURE_BYTES {
2118
+ let byte = if !io.stdin.is_empty() {
2119
+ io.stdin.get(cursor).copied()
2120
+ } else if interp.input_pos < interp.input_stream.len() {
2121
+ let byte = interp.input_stream[interp.input_pos];
2122
+ interp.input_pos += 1;
2123
+ Some(byte)
2124
+ } else {
2125
+ match interp.read_fd(0, 1) {
2126
+ Ok(crate::descriptors::IoPoll::Ready(bytes)) => bytes.first().copied(),
2127
+ _ => None,
2128
+ }
2018
2129
  };
2019
- let l = String::from_utf8_lossy(line_bytes).into_owned();
2020
- interp.input_pos += adv;
2021
- return Some(l);
2022
- }
2023
- let mut line = Vec::new();
2024
- let mut consumed = false;
2025
- while line.len() < crate::descriptors::MAX_CAPTURE_BYTES {
2026
- match interp.read_fd(0, 1).ok()? {
2027
- crate::descriptors::IoPoll::Ready(bytes) if bytes.is_empty() => break,
2028
- crate::descriptors::IoPoll::Ready(bytes) => {
2029
- consumed = true;
2030
- if bytes[0] == b'\n' {
2031
- break;
2032
- }
2033
- line.push(bytes[0]);
2130
+ let Some(byte) = byte else { break };
2131
+ cursor += 1;
2132
+ if escaped {
2133
+ if byte != b'\n' && byte != 0 {
2134
+ record.push(byte);
2034
2135
  }
2035
- crate::descriptors::IoPoll::Blocked(_) => return None,
2136
+ escaped = false;
2137
+ } else if byte == delimiter {
2138
+ terminated = true;
2139
+ break;
2140
+ } else if !raw && byte == b'\\' {
2141
+ escaped = true;
2142
+ } else if byte != 0 {
2143
+ record.push(byte);
2036
2144
  }
2037
2145
  }
2038
- if !consumed {
2039
- None
2040
- } else {
2041
- Some(String::from_utf8_lossy(&line).into_owned())
2042
- }
2146
+ (String::from_utf8_lossy(&record).into_owned(), terminated)
2043
2147
  }
2044
2148
 
2045
2149
  fn cmd_which(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {