strands-shell 0.4.0__tar.gz → 0.5.0__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.
- {strands_shell-0.4.0 → strands_shell-0.5.0}/Cargo.lock +25 -25
- {strands_shell-0.4.0 → strands_shell-0.5.0}/Cargo.toml +1 -1
- {strands_shell-0.4.0 → strands_shell-0.5.0}/PKG-INFO +1 -1
- {strands_shell-0.4.0 → strands_shell-0.5.0}/package.json +1 -1
- {strands_shell-0.4.0 → strands_shell-0.5.0}/pyproject.toml +1 -1
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/grep.rs +186 -67
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/shell_integration.rs +341 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.cargo/config.toml +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/CODEOWNERS +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/actions/stamp-version/action.yml +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/dependabot.yml +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/scripts/group-release-notes.py +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/workflows/ci.yml +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/workflows/pr-title.yml +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.github/workflows/release.yml +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/.gitignore +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/AGENTS.md +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/CODE_OF_CONDUCT.md +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/CONTRIBUTING.md +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/LICENSE +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/NOTICE +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/README.md +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/SECURITY.md +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/build.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/index.d.ts +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/index.js +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/python/strands_shell/__init__.py +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/scripts/build-wasm.sh +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/alias.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/cd.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/colon.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/echo.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/export.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/find.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/getopts.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/hash.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/local.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/lua.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/mod.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/printf.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/pwd.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/read.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/set.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/shift.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/test.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/trap.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/type_cmd.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/umask.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/unset.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/wait.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/builtins/xargs.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/cli.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/basename.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/cat.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/chmod.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/cp.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/curl.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/cut.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/date.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/dirname.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/echo.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/env.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/false.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/head.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/jq.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/ln.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/ls.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/mkdir.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/mktemp.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/mod.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/mv.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/pwd.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/readlink.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/rm.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/rmdir.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/sed.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/sleep.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/sort.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/tail.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/tee.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/touch.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/tr.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/true.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/uniq.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/commands/wc.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/exec.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/io.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/js.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/lib.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/main.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/mcp.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/mcp_client.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/os.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/parser.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/prelude.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/python.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/shell.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/vfs.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/vfs_config.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/vfs_kernel.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/src/wasm_main.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/strands-shell-macros/Cargo.toml +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/strands-shell-macros/src/lib.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/config.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/curl_integration.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/js/test_bindings.mjs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/js/test_builder.mjs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/js/test_config.mjs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/lua_integration.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/mcp_integration.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/python/test_bindings.py +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/python/test_builder.py +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/python/test_config.py +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/ts/api.types.ts +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tests/vfs_unit.rs +0 -0
- {strands_shell-0.4.0 → strands_shell-0.5.0}/tsconfig.json +0 -0
|
@@ -214,9 +214,9 @@ dependencies = [
|
|
|
214
214
|
|
|
215
215
|
[[package]]
|
|
216
216
|
name = "clap"
|
|
217
|
-
version = "4.6.
|
|
217
|
+
version = "4.6.7"
|
|
218
218
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
219
|
-
checksum = "
|
|
219
|
+
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
|
|
220
220
|
dependencies = [
|
|
221
221
|
"clap_builder",
|
|
222
222
|
"clap_derive",
|
|
@@ -224,9 +224,9 @@ dependencies = [
|
|
|
224
224
|
|
|
225
225
|
[[package]]
|
|
226
226
|
name = "clap_builder"
|
|
227
|
-
version = "4.6.
|
|
227
|
+
version = "4.6.7"
|
|
228
228
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
229
|
-
checksum = "
|
|
229
|
+
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
|
|
230
230
|
dependencies = [
|
|
231
231
|
"anstream",
|
|
232
232
|
"anstyle",
|
|
@@ -236,9 +236,9 @@ dependencies = [
|
|
|
236
236
|
|
|
237
237
|
[[package]]
|
|
238
238
|
name = "clap_derive"
|
|
239
|
-
version = "4.6.
|
|
239
|
+
version = "4.6.7"
|
|
240
240
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
241
|
-
checksum = "
|
|
241
|
+
checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
|
|
242
242
|
dependencies = [
|
|
243
243
|
"heck",
|
|
244
244
|
"proc-macro2",
|
|
@@ -880,9 +880,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
|
|
880
880
|
|
|
881
881
|
[[package]]
|
|
882
882
|
name = "lua-src"
|
|
883
|
-
version = "551.0.
|
|
883
|
+
version = "551.0.2"
|
|
884
884
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
885
|
-
checksum = "
|
|
885
|
+
checksum = "d400ffef0e3d4d29287092bdc5a276d0bf468b2c69709d5bab0d469312d9f947"
|
|
886
886
|
dependencies = [
|
|
887
887
|
"cc",
|
|
888
888
|
]
|
|
@@ -928,9 +928,9 @@ dependencies = [
|
|
|
928
928
|
|
|
929
929
|
[[package]]
|
|
930
930
|
name = "mlua"
|
|
931
|
-
version = "0.12.
|
|
931
|
+
version = "0.12.2"
|
|
932
932
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
933
|
-
checksum = "
|
|
933
|
+
checksum = "7a7ae6b65bf54b7fdd4cf5b6f44f60862350ebf3fe3779b3a9cc005e7fa14f81"
|
|
934
934
|
dependencies = [
|
|
935
935
|
"bstr",
|
|
936
936
|
"either",
|
|
@@ -944,9 +944,9 @@ dependencies = [
|
|
|
944
944
|
|
|
945
945
|
[[package]]
|
|
946
946
|
name = "mlua-sys"
|
|
947
|
-
version = "0.
|
|
947
|
+
version = "0.13.0"
|
|
948
948
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
949
|
-
checksum = "
|
|
949
|
+
checksum = "1631cda17bd6486410ab751d91f5fd4484f441589278cdad879eb3afc721a285"
|
|
950
950
|
dependencies = [
|
|
951
951
|
"cc",
|
|
952
952
|
"cfg-if",
|
|
@@ -958,9 +958,9 @@ dependencies = [
|
|
|
958
958
|
|
|
959
959
|
[[package]]
|
|
960
960
|
name = "napi"
|
|
961
|
-
version = "3.
|
|
961
|
+
version = "3.14.2"
|
|
962
962
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
963
|
-
checksum = "
|
|
963
|
+
checksum = "a4e2cf85c02d9b057898c485b824c3c416b65193689e2b7f517efc94b86ed24c"
|
|
964
964
|
dependencies = [
|
|
965
965
|
"bitflags",
|
|
966
966
|
"ctor",
|
|
@@ -975,42 +975,42 @@ dependencies = [
|
|
|
975
975
|
|
|
976
976
|
[[package]]
|
|
977
977
|
name = "napi-build"
|
|
978
|
-
version = "2.
|
|
978
|
+
version = "2.6.0"
|
|
979
979
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
980
|
-
checksum = "
|
|
980
|
+
checksum = "b899b545d3aa6dca985939059f258c5488d34e4ecf39c274e20009748f4b846d"
|
|
981
981
|
|
|
982
982
|
[[package]]
|
|
983
983
|
name = "napi-derive"
|
|
984
|
-
version = "3.6.
|
|
984
|
+
version = "3.6.11"
|
|
985
985
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
986
|
-
checksum = "
|
|
986
|
+
checksum = "903796e3a3f79eb7e52b1548c11f1817cddec62b0e347a3e97af88b1bd76fd53"
|
|
987
987
|
dependencies = [
|
|
988
988
|
"convert_case",
|
|
989
989
|
"ctor",
|
|
990
990
|
"napi-derive-backend",
|
|
991
991
|
"proc-macro2",
|
|
992
992
|
"quote",
|
|
993
|
-
"syn
|
|
993
|
+
"syn 3.0.2",
|
|
994
994
|
]
|
|
995
995
|
|
|
996
996
|
[[package]]
|
|
997
997
|
name = "napi-derive-backend"
|
|
998
|
-
version = "6.1.
|
|
998
|
+
version = "6.1.5"
|
|
999
999
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1000
|
-
checksum = "
|
|
1000
|
+
checksum = "fae2444f4ba7333754bb73414ff8059dec2cb23b83a95bd1a0bf08169eed1be0"
|
|
1001
1001
|
dependencies = [
|
|
1002
1002
|
"convert_case",
|
|
1003
1003
|
"proc-macro2",
|
|
1004
1004
|
"quote",
|
|
1005
1005
|
"semver",
|
|
1006
|
-
"syn
|
|
1006
|
+
"syn 3.0.2",
|
|
1007
1007
|
]
|
|
1008
1008
|
|
|
1009
1009
|
[[package]]
|
|
1010
1010
|
name = "napi-sys"
|
|
1011
|
-
version = "3.
|
|
1011
|
+
version = "3.4.0"
|
|
1012
1012
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1013
|
-
checksum = "
|
|
1013
|
+
checksum = "e22a4f25c16a5c5411d987cd6fbd48313522ae1789ade2d6dd3efdc6d40a0fc8"
|
|
1014
1014
|
dependencies = [
|
|
1015
1015
|
"libloading",
|
|
1016
1016
|
]
|
|
@@ -1606,7 +1606,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
|
|
1606
1606
|
|
|
1607
1607
|
[[package]]
|
|
1608
1608
|
name = "strands-shell"
|
|
1609
|
-
version = "0.
|
|
1609
|
+
version = "0.5.0"
|
|
1610
1610
|
dependencies = [
|
|
1611
1611
|
"async-trait",
|
|
1612
1612
|
"axum",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "strands-shell"
|
|
3
3
|
# Placeholder — the real version comes from the release git tag, stamped at
|
|
4
4
|
# build time by .github/actions/stamp-version. Do not bump this by hand.
|
|
5
|
-
version = "0.
|
|
5
|
+
version = "0.5.0"
|
|
6
6
|
edition = "2024"
|
|
7
7
|
description = "A virtual shell sandbox for AI agents"
|
|
8
8
|
license = "Apache-2.0"
|
|
@@ -6,7 +6,7 @@ build-backend = "maturin"
|
|
|
6
6
|
name = "strands-shell"
|
|
7
7
|
# Placeholder — the real version comes from the release git tag, stamped at
|
|
8
8
|
# build time by .github/actions/stamp-version. Do not bump this by hand.
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.5.0"
|
|
10
10
|
description = "A virtual shell for AI agents"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.10"
|
|
@@ -21,6 +21,9 @@ Options:
|
|
|
21
21
|
-H, --with-filename print filename with matches
|
|
22
22
|
-h, --no-filename suppress filename prefix
|
|
23
23
|
-o, --only-matching show only the matching part
|
|
24
|
+
-a, --text process a binary file as if it were text
|
|
25
|
+
-I ignore binary files (treat as non-matching)
|
|
26
|
+
--binary-files=TYPE binary, text, or without-match
|
|
24
27
|
-m, --max-count NUM stop after NUM matches per file
|
|
25
28
|
-A, --after-context NUM print NUM lines after match
|
|
26
29
|
-B, --before-context NUM print NUM lines before match
|
|
@@ -29,6 +32,17 @@ Options:
|
|
|
29
32
|
--exclude=GLOB skip files matching GLOB
|
|
30
33
|
--exclude-dir=DIR skip directories matching DIR";
|
|
31
34
|
|
|
35
|
+
/// How to treat input that looks binary (NUL byte or invalid UTF-8).
|
|
36
|
+
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
37
|
+
enum BinaryFiles {
|
|
38
|
+
/// Report "binary file matches" instead of printing the lines.
|
|
39
|
+
Binary,
|
|
40
|
+
/// Print every line, decoding invalid bytes lossily (captured output must be UTF-8).
|
|
41
|
+
Text,
|
|
42
|
+
/// Treat files containing NUL as if they had no matches.
|
|
43
|
+
WithoutMatch,
|
|
44
|
+
}
|
|
45
|
+
|
|
32
46
|
struct Opts {
|
|
33
47
|
patterns: Vec<String>,
|
|
34
48
|
files: Vec<String>,
|
|
@@ -46,6 +60,7 @@ struct Opts {
|
|
|
46
60
|
with_filename: Option<bool>,
|
|
47
61
|
only_matching: bool,
|
|
48
62
|
max_count: Option<u64>,
|
|
63
|
+
binary_files: BinaryFiles,
|
|
49
64
|
after_context: usize,
|
|
50
65
|
before_context: usize,
|
|
51
66
|
include: Vec<String>,
|
|
@@ -71,6 +86,7 @@ fn parse_args(args: &[String]) -> Result<Option<Opts>, Box<dyn std::error::Error
|
|
|
71
86
|
with_filename: None,
|
|
72
87
|
only_matching: false,
|
|
73
88
|
max_count: None,
|
|
89
|
+
binary_files: BinaryFiles::Binary,
|
|
74
90
|
after_context: 0,
|
|
75
91
|
before_context: 0,
|
|
76
92
|
include: Vec::new(),
|
|
@@ -97,6 +113,20 @@ fn parse_args(args: &[String]) -> Result<Option<Opts>, Box<dyn std::error::Error
|
|
|
97
113
|
Short('h') | Long("no-filename") => opts.with_filename = Some(false),
|
|
98
114
|
Short('o') | Long("only-matching") => opts.only_matching = true,
|
|
99
115
|
Short('m') | Long("max-count") => opts.max_count = Some(parser.value()?.parse()?),
|
|
116
|
+
Short('a') | Long("text") => opts.binary_files = BinaryFiles::Text,
|
|
117
|
+
Short('I') => opts.binary_files = BinaryFiles::WithoutMatch,
|
|
118
|
+
Long("binary-files") => {
|
|
119
|
+
opts.binary_files = match parser.value()?.string()?.as_str() {
|
|
120
|
+
"binary" => BinaryFiles::Binary,
|
|
121
|
+
"text" => BinaryFiles::Text,
|
|
122
|
+
"without-match" => BinaryFiles::WithoutMatch,
|
|
123
|
+
other => {
|
|
124
|
+
return Err(
|
|
125
|
+
format!("invalid argument '{other}' for '--binary-files'").into()
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
100
130
|
Short('A') | Long("after-context") => opts.after_context = parser.value()?.parse()?,
|
|
101
131
|
Short('B') | Long("before-context") => opts.before_context = parser.value()?.parse()?,
|
|
102
132
|
Short('C') | Long("context") => {
|
|
@@ -125,7 +155,9 @@ fn parse_args(args: &[String]) -> Result<Option<Opts>, Box<dyn std::error::Error
|
|
|
125
155
|
Ok(Some(opts))
|
|
126
156
|
}
|
|
127
157
|
|
|
128
|
-
fn build_regex(
|
|
158
|
+
fn build_regex(
|
|
159
|
+
opts: &Opts,
|
|
160
|
+
) -> Result<regex::bytes::Regex, Box<dyn std::error::Error + Send + Sync>> {
|
|
129
161
|
let combined = if opts.fixed {
|
|
130
162
|
opts.patterns
|
|
131
163
|
.iter()
|
|
@@ -142,7 +174,7 @@ fn build_regex(opts: &Opts) -> Result<regex::Regex, Box<dyn std::error::Error +
|
|
|
142
174
|
if opts.line_regexp {
|
|
143
175
|
pat = format!("^(?:{})$", pat);
|
|
144
176
|
}
|
|
145
|
-
let re = regex::RegexBuilder::new(&pat)
|
|
177
|
+
let re = regex::bytes::RegexBuilder::new(&pat)
|
|
146
178
|
.case_insensitive(opts.ignore_case)
|
|
147
179
|
.build()?;
|
|
148
180
|
Ok(re)
|
|
@@ -211,87 +243,160 @@ async fn collect_files_recursive(os: &dyn Kernel, path: &str, opts: &Opts, out:
|
|
|
211
243
|
}
|
|
212
244
|
}
|
|
213
245
|
|
|
246
|
+
async fn write_context(
|
|
247
|
+
w: &mut os::FdWriter,
|
|
248
|
+
prefix: &str,
|
|
249
|
+
line_number: bool,
|
|
250
|
+
lineno: u64,
|
|
251
|
+
text: &[u8],
|
|
252
|
+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
253
|
+
if !prefix.is_empty() {
|
|
254
|
+
wprint!(w, "{}-", prefix)?;
|
|
255
|
+
}
|
|
256
|
+
if line_number {
|
|
257
|
+
wprint!(w, "{}-", lineno)?;
|
|
258
|
+
}
|
|
259
|
+
wprintln!(w, "{}", String::from_utf8_lossy(text))?;
|
|
260
|
+
Ok(())
|
|
261
|
+
}
|
|
262
|
+
|
|
214
263
|
async fn grep_reader<R: tokio::io::AsyncRead + Unpin + Send>(
|
|
215
264
|
reader: R,
|
|
216
|
-
re: ®ex::Regex,
|
|
265
|
+
re: ®ex::bytes::Regex,
|
|
217
266
|
opts: &Opts,
|
|
218
267
|
prefix: &str,
|
|
268
|
+
name: &str,
|
|
219
269
|
w: &mut os::FdWriter,
|
|
270
|
+
ew: &mut os::FdWriter,
|
|
220
271
|
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
|
221
|
-
let mut buf_reader = BufReader::
|
|
222
|
-
let mut line =
|
|
272
|
+
let mut buf_reader = BufReader::with_capacity(96 * 1024, reader);
|
|
273
|
+
let mut line: Vec<u8> = Vec::new();
|
|
223
274
|
let mut lineno: u64 = 0;
|
|
224
275
|
let mut match_count: u64 = 0;
|
|
225
276
|
let mut found = false;
|
|
277
|
+
let detect_binary = opts.binary_files != BinaryFiles::Text;
|
|
278
|
+
// A NUL in the first 96 KiB (GNU's first buffer) marks the whole input binary; a later NUL
|
|
279
|
+
// does so from its line on (GNU decides per buffer there).
|
|
280
|
+
let mut file_binary = detect_binary && buf_reader.fill_buf().await?.contains(&0);
|
|
281
|
+
// A matched or context line was hidden as binary, so report it on stderr.
|
|
282
|
+
let mut binary_matched = false;
|
|
283
|
+
let summary_only = opts.quiet || opts.list || opts.list_non_matching || opts.count;
|
|
226
284
|
|
|
227
285
|
let use_context = opts.before_context > 0 || opts.after_context > 0;
|
|
228
|
-
//
|
|
229
|
-
let mut before_buf: std::collections::VecDeque<(u64,
|
|
286
|
+
// Last B lines not yet written (hidden ones included), as (lineno, bytes, hidden).
|
|
287
|
+
let mut before_buf: std::collections::VecDeque<(u64, Vec<u8>, bool)> =
|
|
230
288
|
std::collections::VecDeque::new();
|
|
231
|
-
//
|
|
232
|
-
let mut
|
|
233
|
-
|
|
234
|
-
|
|
289
|
+
// Line number of the last line written (0 = none); GNU's lastout.
|
|
290
|
+
let mut last_printed: u64 = 0;
|
|
291
|
+
let mut used = false;
|
|
292
|
+
// After-context lines still owed.
|
|
293
|
+
let mut pending: usize = 0;
|
|
294
|
+
|
|
295
|
+
// A binary file is treated as non-matching under -I.
|
|
296
|
+
let skip_file = file_binary && opts.binary_files == BinaryFiles::WithoutMatch;
|
|
235
297
|
|
|
236
298
|
loop {
|
|
299
|
+
if skip_file {
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
237
302
|
line.clear();
|
|
238
|
-
if buf_reader.
|
|
303
|
+
if buf_reader.read_until(b'\n', &mut line).await? == 0 {
|
|
239
304
|
break;
|
|
240
305
|
}
|
|
241
306
|
lineno += 1;
|
|
242
|
-
let
|
|
243
|
-
|
|
307
|
+
let mut raw = line.as_slice();
|
|
308
|
+
raw = raw.strip_suffix(b"\n").unwrap_or(raw);
|
|
309
|
+
raw = raw.strip_suffix(b"\r").unwrap_or(raw);
|
|
310
|
+
if detect_binary && !file_binary && raw.contains(&0) {
|
|
311
|
+
file_binary = true;
|
|
312
|
+
if opts.binary_files == BinaryFiles::WithoutMatch {
|
|
313
|
+
// GNU counts the whole file as non-matching; printed lines stay printed.
|
|
314
|
+
match_count = 0;
|
|
315
|
+
found = false;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Invalid UTF-8 is matched on raw bytes but never printed unless -a.
|
|
320
|
+
let invalid = detect_binary && std::str::from_utf8(raw).is_err();
|
|
321
|
+
let is_binary = file_binary || invalid;
|
|
322
|
+
// -o hides only matches that are themselves invalid UTF-8.
|
|
323
|
+
let per_match = opts.only_matching && !opts.invert && !file_binary;
|
|
324
|
+
let matched = re.is_match(raw) ^ opts.invert;
|
|
325
|
+
// Hidden lines are never written but otherwise behave like normal lines.
|
|
326
|
+
let hidden = is_binary && !per_match;
|
|
244
327
|
|
|
245
328
|
if matched {
|
|
246
329
|
found = true;
|
|
247
330
|
match_count += 1;
|
|
248
331
|
|
|
249
|
-
if
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
{
|
|
332
|
+
if summary_only {
|
|
333
|
+
// -q/-l/-L stop at the first match (GNU done_on_match).
|
|
334
|
+
let first_match_done = opts.quiet || opts.list || opts.list_non_matching;
|
|
335
|
+
if first_match_done || opts.max_count.is_some_and(|max| match_count >= max) {
|
|
253
336
|
break;
|
|
254
337
|
}
|
|
255
338
|
continue;
|
|
256
339
|
}
|
|
257
340
|
|
|
258
|
-
if
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
341
|
+
if hidden && file_binary {
|
|
342
|
+
binary_matched = true;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Region start; a gap from the last written line starts a new group.
|
|
347
|
+
let rs = (last_printed + 1).max(lineno.saturating_sub(opts.before_context as u64));
|
|
348
|
+
// GNU's `used` is set by any selected line, hidden or not. With nothing written
|
|
349
|
+
// and no -A, its lastout is still unset and never matches, so the group gets `--`.
|
|
350
|
+
let lastout_unset = last_printed == 0 && opts.after_context == 0;
|
|
351
|
+
if use_context && used && (lastout_unset || rs != last_printed + 1) {
|
|
352
|
+
wprintln!(w, "--")?;
|
|
353
|
+
}
|
|
354
|
+
used = true;
|
|
355
|
+
for (bno, btext, bhidden) in before_buf.drain(..) {
|
|
356
|
+
if bno < rs {
|
|
357
|
+
continue;
|
|
262
358
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if !
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
if opts.line_number {
|
|
270
|
-
wprint!(w, "{}-", bno)?;
|
|
359
|
+
if bhidden {
|
|
360
|
+
binary_matched |= !opts.only_matching;
|
|
361
|
+
} else {
|
|
362
|
+
if !opts.only_matching {
|
|
363
|
+
write_context(w, prefix, opts.line_number, bno, &btext).await?;
|
|
271
364
|
}
|
|
272
|
-
|
|
365
|
+
last_printed = bno;
|
|
273
366
|
}
|
|
274
|
-
after_remaining = opts.after_context;
|
|
275
367
|
}
|
|
276
368
|
|
|
277
|
-
if
|
|
278
|
-
|
|
369
|
+
if hidden {
|
|
370
|
+
// A hidden match starts no after-context (GNU lastout does not advance).
|
|
371
|
+
binary_matched = true;
|
|
372
|
+
pending = 0;
|
|
373
|
+
} else {
|
|
374
|
+
if opts.only_matching && !opts.invert {
|
|
375
|
+
for m in re.find_iter(raw) {
|
|
376
|
+
// GNU drops the rest of the line once a match is suppressed.
|
|
377
|
+
if invalid && std::str::from_utf8(m.as_bytes()).is_err() {
|
|
378
|
+
binary_matched = true;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
if !prefix.is_empty() {
|
|
382
|
+
wprint!(w, "{}:", prefix)?;
|
|
383
|
+
}
|
|
384
|
+
if opts.line_number {
|
|
385
|
+
wprint!(w, "{}:", lineno)?;
|
|
386
|
+
}
|
|
387
|
+
wprintln!(w, "{}", String::from_utf8_lossy(m.as_bytes()))?;
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
279
390
|
if !prefix.is_empty() {
|
|
280
391
|
wprint!(w, "{}:", prefix)?;
|
|
281
392
|
}
|
|
282
393
|
if opts.line_number {
|
|
283
394
|
wprint!(w, "{}:", lineno)?;
|
|
284
395
|
}
|
|
285
|
-
wprintln!(w, "{}",
|
|
286
|
-
}
|
|
287
|
-
} else {
|
|
288
|
-
if !prefix.is_empty() {
|
|
289
|
-
wprint!(w, "{}:", prefix)?;
|
|
396
|
+
wprintln!(w, "{}", String::from_utf8_lossy(raw))?;
|
|
290
397
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
wprintln!(w, "{}", text)?;
|
|
398
|
+
last_printed = lineno;
|
|
399
|
+
pending = opts.after_context;
|
|
295
400
|
}
|
|
296
401
|
|
|
297
402
|
if let Some(max) = opts.max_count
|
|
@@ -299,25 +404,21 @@ async fn grep_reader<R: tokio::io::AsyncRead + Unpin + Send>(
|
|
|
299
404
|
{
|
|
300
405
|
break;
|
|
301
406
|
}
|
|
302
|
-
} else if
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
} else if use_context {
|
|
316
|
-
// Buffer for before-context
|
|
317
|
-
if after_remaining == 0 && found && !need_sep {
|
|
318
|
-
need_sep = true;
|
|
407
|
+
} else if pending > 0 {
|
|
408
|
+
if hidden {
|
|
409
|
+
// GNU lastout never passes a hidden line, so it spends the rest of the context.
|
|
410
|
+
pending = 0;
|
|
411
|
+
binary_matched |= !opts.only_matching;
|
|
412
|
+
} else {
|
|
413
|
+
pending -= 1;
|
|
414
|
+
// -o prints no context, but the line still counts as written.
|
|
415
|
+
if !opts.only_matching {
|
|
416
|
+
write_context(w, prefix, opts.line_number, lineno, raw).await?;
|
|
417
|
+
}
|
|
418
|
+
last_printed = lineno;
|
|
319
419
|
}
|
|
320
|
-
|
|
420
|
+
} else if opts.before_context > 0 {
|
|
421
|
+
before_buf.push_back((lineno, raw.to_vec(), hidden));
|
|
321
422
|
while before_buf.len() > opts.before_context {
|
|
322
423
|
before_buf.pop_front();
|
|
323
424
|
}
|
|
@@ -336,6 +437,9 @@ async fn grep_reader<R: tokio::io::AsyncRead + Unpin + Send>(
|
|
|
336
437
|
if opts.list_non_matching && !found {
|
|
337
438
|
wprintln!(w, "{}", prefix)?;
|
|
338
439
|
}
|
|
440
|
+
if binary_matched && !summary_only && opts.binary_files == BinaryFiles::Binary {
|
|
441
|
+
wprintln!(ew, "grep: {}: binary file matches", name)?;
|
|
442
|
+
}
|
|
339
443
|
|
|
340
444
|
Ok(found)
|
|
341
445
|
}
|
|
@@ -377,12 +481,14 @@ async fn cmd_grep(os: &dyn Kernel, args: &[String]) -> CommandResult {
|
|
|
377
481
|
let show_name = opts.with_filename.unwrap_or(multi);
|
|
378
482
|
|
|
379
483
|
let mut w = io::stdout()?;
|
|
484
|
+
// stderr can only be taken once per process, so share one writer across files.
|
|
485
|
+
let mut ew = io::stderr()?;
|
|
380
486
|
let mut any_match = false;
|
|
381
487
|
|
|
382
488
|
if files.is_empty() {
|
|
383
489
|
// Read from stdin
|
|
384
490
|
let reader = io::stdin()?;
|
|
385
|
-
if grep_reader(reader, &re, &opts, "", &mut w).await? {
|
|
491
|
+
if grep_reader(reader, &re, &opts, "", "(standard input)", &mut w, &mut ew).await? {
|
|
386
492
|
any_match = true;
|
|
387
493
|
}
|
|
388
494
|
} else {
|
|
@@ -391,7 +497,6 @@ async fn cmd_grep(os: &dyn Kernel, args: &[String]) -> CommandResult {
|
|
|
391
497
|
Ok(fd) => fd,
|
|
392
498
|
Err(e) => {
|
|
393
499
|
if !opts.quiet {
|
|
394
|
-
let mut ew = io::stderr()?;
|
|
395
500
|
wprintln!(ew, "grep: {}: {}", path, e)?;
|
|
396
501
|
}
|
|
397
502
|
continue;
|
|
@@ -399,10 +504,24 @@ async fn cmd_grep(os: &dyn Kernel, args: &[String]) -> CommandResult {
|
|
|
399
504
|
};
|
|
400
505
|
let reader = io::take_reader(fd)?;
|
|
401
506
|
let prefix = if show_name { path.as_str() } else { "" };
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
507
|
+
match grep_reader(reader, &re, &opts, prefix, path, &mut w, &mut ew).await {
|
|
508
|
+
Ok(true) => {
|
|
509
|
+
any_match = true;
|
|
510
|
+
if opts.quiet {
|
|
511
|
+
return Ok(0);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
Ok(false) => {}
|
|
515
|
+
Err(e) => {
|
|
516
|
+
// A closed stdout must stop grep, not repeat per file.
|
|
517
|
+
if e.downcast_ref::<std::io::Error>()
|
|
518
|
+
.is_some_and(|io| io.kind() == std::io::ErrorKind::BrokenPipe)
|
|
519
|
+
{
|
|
520
|
+
return Err(e);
|
|
521
|
+
}
|
|
522
|
+
if !opts.quiet {
|
|
523
|
+
wprintln!(ew, "grep: {}: {}", path, e)?;
|
|
524
|
+
}
|
|
406
525
|
}
|
|
407
526
|
}
|
|
408
527
|
}
|