219 Commits
Author SHA1 Message Date
Sylvestre Ledru 2ce633cb8d prepare version 0.2.0 (#519) 2026-08-09 01:57:25 +02:00
Mukunda Rao Katta 91465e566b fix: identify uutils in cli output (#435) 2026-08-09 00:55:37 +02:00
Sylvestre Ledru d842aa305e sed: fix clippy cast_possible_truncation errors (#518) 2026-08-09 00:52:26 +02:00
xtqqczze d28a1f8e73 chore(cargo): remove deprecated package authors field (#444) 2026-08-08 23:28:22 +02:00
renovate[bot] af5f8afdcb Update Rust crate clap_complete to v4.6.9 2026-08-07 08:07:56 +02:00
renovate[bot] 8f3f126816 Update Rust crate clap_mangen to v0.3.2 2026-08-07 08:05:08 +02:00
renovate[bot] c771318a7a Update Rust crate clap to v4.6.6 2026-08-07 08:03:52 +02:00
renovate[bot] 36a1cf4954 Update Rust crate uucore to 0.10.0 2026-08-06 09:02:46 +02:00
renovate[bot] 13bb82ba13 Update Rust crate clap_mangen to v0.3.1 2026-08-06 08:32:10 +02:00
renovate[bot] cb9eb1da42 Update Rust crate uufuzz to 0.10.0 2026-08-06 08:15:55 +02:00
renovate[bot] b101462d1b Update Rust crate uutests to 0.10.0 2026-08-06 08:10:31 +02:00
oech3 9f2e1a1ad4 remove sysinfo dep 2026-08-05 14:18:30 +02:00
Franklin-Qi 2b092d53b0 fast_io.rs: replace the unsafe libc::copy_file_range call by rustix (#458)
Replace the unsafe `libc::copy_file_range` call with the safe `rustix::fs::copy_file_range`.

Closes: #442
2026-08-05 13:32:45 +02:00
renovate[bot] 550d545d72 Update Rust crate similar to v3.1.2 2026-08-05 08:01:27 +02:00
Diomidis Spinellisandmukunda katta a5d219764f feat: add T command (branch on no-substitution) (#508)
* feat: add T command (branch on no-substitution)

Implements the GNU `T LABEL` command: branch to LABEL if no s///
substitution has succeeded since the last input line was read or the
last t/T was taken. It is the inverse of `t` and shares the same
address and label parsing.

Wires 'T' into get_cmd_spec, resolve_branch_targets, and the processor
branch logic, resetting the substitution flag on evaluation just like
't'. Adds integration tests (simple branch + flag-clear) and a cmd-spec
unit test.

Closes #395

Co-authored-by: Diomidis Spinellis <dds@aueb.gr>

* ci: re-trigger checks (windows runner crates.io download flake)

* Disable T in POSIX mode

---------

Co-authored-by: mukunda katta <mukunda.vjcs6@gmail.com>
2026-08-02 17:47:18 +03:00
Develandpre-commit-ci[bot] 397ab86302 Implement v command #397 (#420)
* implement version command

* add tests for version option

* fix patch comparsion

* absurd extreme comparsion fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* accept missing minor or patch

* guard 'v' with if posix

* add a few tests

* add test_default_version

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* check if minor is empty and push zero instead of shadowing

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-01 18:18:05 +03:00
Mukunda Rao Katta 2288789f33 fix: allow closing brace as command terminator (#427) 2026-08-01 14:03:34 +03:00
Pranav Ram Joshi 84ae1b07a7 Implement e (execute) command (#481)
* chore: fix typo

* add: standalone e command

Implement the 'e' command. No argument implies execution of pattern space and any respective argument is to run with the output written to stream. Rejected under --posix and --sandbox.

Also stop silently swallowing shell stderr for e and s///e.

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* add: e command tests

Includes 17 new tests. Cover both forms of the new 'e' command: with-argument execution (including shell side effects, addressing, ';' not separating the argument, escape decoding, and backslash continuation) and the no-argument pattern-space form. Also cover --posix/--sandbox rejection and stderr surfacing on command failure.

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* chore: gate LF-related tests to UNIIX only

* address e command review

Validate shell output as utf-8 and error out instead of lossily converting it. Make CRLF strip conditionally compile to Windows only. Rename execute_pattern_as_shell to execute_pattern_as_shell_command. Add tests for non-utf-8 error paths.

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* add: Windows tests for e command with argument

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* fix: broken pipe race in sandbox e command test

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* Apply s/// p and e flags in written order

GNU sed applies the 'p' and 'e' substitution flags in the order they appear. 'pe' prints the pre-execution text then executes, while 'ep' executes then prints the result. Record whether 'p' precedes 'e' and apply them accordingly.

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

---------

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>
2026-08-01 13:29:42 +03:00
renovate[bot] f32296fc22 Update Rust crate clap to v4.6.5 2026-08-01 07:12:40 +02:00
renovate[bot] 7239fb0e08 Update Rust crate ctor to v1.0.12 2026-07-30 07:57:26 +02:00
renovate[bot] c7e1f78486 Update mozilla-actions/sccache-action action to v0.0.11 2026-07-29 15:19:54 +02:00
renovate[bot] fb907703a0 Update CodSpeedHQ/action action to v5 2026-07-29 14:01:03 +02:00
renovate[bot] 3d52077d7f Update Rust crate clap_complete to v4.6.8 2026-07-27 17:37:39 +02:00
Diomidis Spinellis e9792a8ef9 Improve GNU compatibility through byte processing (#487)
The original Rust implementation assummed a UTF-8 environment, as is
common in modern systems.  This commit improves GNU sed compatibility
by changing most processing to run on bytes rather than Rust's Unicode
characters and strings. This change also improves the handling of binary
files. The commit adds a simple POSIX-compatible locale configuration
to support UTF-8 processing when specified. While at it, also fix
script-specified file handling to take place through OsString and
add a few missing unit tests.

The change fixes two failing GNU test suite cases.
2026-07-26 19:44:54 +02:00
renovate[bot] c732b60cfc Update Rust crate ctor to v1.0.11 (#501)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-26 19:43:52 +02:00
renovate[bot] c1e5d8ea08 Update Rust crate ctor to v1.0.10 2026-07-22 08:23:11 +02:00
renovate[bot] 6887201cce Update Rust crate clap to v4.6.4 2026-07-22 08:09:28 +02:00
renovate[bot] 75d856e4c2 Update Rust crate libc to v0.2.189 2026-07-22 08:00:30 +02:00
renovate[bot] d7a2106258 Update Rust crate libc to v0.2.188 2026-07-21 14:00:30 +02:00
renovate[bot] a726ac6959 Update Rust crate clap to v4.6.3 2026-07-21 08:13:36 +02:00
renovate[bot] 49590e63be Update Rust crate libc to v0.2.187 2026-07-21 08:03:39 +02:00
renovate[bot] 99a9957e89 Update Rust crate ctor to v1.0.9 2026-07-16 08:48:11 +02:00
renovate[bot] c521271ca8 Update Rust crate regex to v1.13.1 2026-07-16 08:19:19 +02:00
renovate[bot] 7830c20bc1 Update Rust crate clap to v4.6.2 2026-07-16 08:08:20 +02:00
renovate[bot] b37e23fa98 Update Rust crate regex to v1.13.0 2026-07-10 10:55:46 +02:00
Daniel Hofstetter 07eada8531 Bump crossbeam-epoch from 0.9.18 to 0.9.20 (#486)
https://rustsec.org/advisories/RUSTSEC-2026-0204
2026-07-08 18:57:10 +03:00
renovate[bot] 15def35a2a chore(deps): update rust crate memchr to v2.8.3 2026-07-08 07:50:55 +02:00
renovate[bot] 30607c4ddc chore(deps): update rust crate ctor to v1.0.8 2026-07-06 08:07:50 +02:00
Diomidis Spinellis 01c8412327 Optimize multiline matching (#483)
The multiline matching resulted in a large performance regression due to
the indiscriminate use of the dotall (?s) flag.  Change the regex
selection process to use only the flag when probably needed.
2026-07-05 15:13:53 +02:00
Diomidis SpinellisandMukunda Katta f3a79c5847 Support multiline substitution flag (#482)
While at it fix . matching of embedded newlines.

This commit increases GNU passed tests by 2.

Co-authored-by: Mukunda Katta <mukunda.vjcs6@gmail.com>
2026-07-05 10:48:39 +02:00
Diomidis Spinellis a9e5fe778c Add sandbox checks (#477)
Following the introduction of the first sandbox check in b14272c1
complete the checks for the remaining commands.
2026-07-03 16:55:50 +02:00
renovate[bot] 02d0239447 chore(deps): update rust crate rand to v0.10.2 2026-07-02 16:50:42 +02:00
renovate[bot] 8b5dea703a chore(deps): update rust crate console to v0.16.4 2026-07-02 08:05:54 +02:00
renovate[bot] 34dfbc2ef1 chore(deps): update rust crate clap_complete to v4.6.7 2026-07-02 08:05:18 +02:00
Develandpre-commit-ci[bot] 8cb7090bbc --posix should reject GNU substitute flags i/I #401 (#419)
* reject 'i', 'I', 'm', 'M' and 'E' when --posix flag is set

* add test for issue 401

* formatting

* add a test for 'm' flag in test_posix_reject_flags

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update compiler.rs

* cargo fmt

* add a const variable with the err msg

* Update compiler.rs

* cargo fmt

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-01 19:30:25 +03:00
Mukunda Rao Katta 2a5ab5a9a2 feat: add z command (#428)
* feat: add z command

* test: avoid stdin race in z POSIX rejection
2026-07-01 12:51:11 +03:00
Pranav Ram Joshi b14272c1d7 Add e substitution flag
* feat: introduce e substituion for sed

When the s command is requested for sed, the e substitution flag enables execution of the result of substitution as a shell command. It matches the GNU sed behavior.

On src/sed/command.rs, add 'execute' field to 'Substitution' structure.

On src/sed/compiler.rs, handle 'e' in 'compile_subst_flags' and reject it if --posix or --sandbox flag is used in sed.

On src/sed/processor.rs, spawn /bin/sh with -c flag for a successful match and replace the pattern space with the command's standard output stream.

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* chore: comply with rustfmt

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* chore: support for Windows command-line

Instead of calling bourne shell, define a new function: shell_command() with two definitions; conditionally compiling to UNIX-like and Windows. On UNIX-like, executes the bourne shell, whereas command prompt is executed on Windows.

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* chore: check newline for Windows

Unix-like system only uses the newline character whereas Windows uses the carriage return as well as newline. Hence, when working on Windows, we need to truncate the last two characters.

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* add: 'e' flag tests

Nine new tests have been introduced in regards to new 'e' substitution option. 'test_subst_e_flag_multiline_outuput' is conditionally compiled as printf command is not guaranteed to be present on all Windows systems.

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* chore: fallback for shell_command

Executing a shell or a command prompt requires appropriate platforms. WASM does not currently support it.

Also fix a linting issue.

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* chore: linting cleanup

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

---------

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>
2026-07-01 11:18:02 +03:00
Diomidis Spinellis cd3b10db48 Document uutils Playground 2026-07-01 10:48:54 +03:00
Diomidis Spinellis 48c2f56358 Document r command extension 2026-07-01 10:42:22 +03:00
Diomidis SpinellisandPranavRJoshi dcd2df848a Implement zero-address r command to insert file before first line (#474)
* feat: support zero-address file insertion

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>

* Polish the handling of 0r

- Use common constant for error message.
- Simplify command check at compile and runtime.
- Separate runtime handling into a function.

---------

Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>
Co-authored-by: PranavRJoshi <pranavrjoshi1@gmail.com>
2026-07-01 08:36:03 +02:00
renovate[bot] 9efab796be chore(deps): update rust crate clap_complete to v4.6.6 2026-07-01 07:05:51 +02:00
Daniel Hofstetter b74925283f Bump anyhow from 1.0.102 to 1.0.103 (#473) 2026-06-30 11:20:26 +02:00
renovate[bot] 8baab710c3 chore(deps): update rust crate divan to v5.0.1 2026-06-27 10:53:11 +02:00
renovate[bot] 5ca2636ce3 chore(deps): update actions/cache action to v6 2026-06-24 09:09:57 +02:00
renovate[bot] 8d28215c1b chore(deps): update rust crate divan to v5 2026-06-23 15:08:10 +02:00
renovate[bot] 88d188aa03 chore(deps): update rust crate memmap2 to v0.9.11 2026-06-23 07:59:32 +02:00
renovate[bot] 5e5281be03 chore(deps): update rust crate phf_codegen to 0.14.0 2026-06-22 11:21:53 +02:00
renovate[bot] 4eda475d5c chore(deps): update rust crate phf to 0.14.0 2026-06-21 10:08:39 +02:00
renovate[bot] 9b388fca34 chore(deps): update actions/checkout action to v7 2026-06-19 08:13:11 +02:00
Sylvestre Ledru b90c599fac sed: fix brace quantifier delimiter and {,n} bound edge cases (#463) 2026-06-14 16:09:18 +02:00
Loukas Papadopoulos d9cd53883c Add validations for brace quantifiers {m}, {m,} and {m,n} (#328) 2026-06-14 12:08:11 +02:00
renovate[bot] 3eb27c7831 chore(deps): update rust crate memchr to v2.8.2 2026-06-12 07:08:51 +02:00
renovate[bot] 3c7488ba1f chore(deps): update rust crate regex to v1.12.4 2026-06-10 07:21:44 +02:00
renovate[bot] 5122acc896 chore(deps): update codecov/codecov-action action to v7 2026-06-07 09:05:21 +02:00
renovate[bot] a9a332af1c chore(deps): update rust crate libfuzzer-sys to v0.4.13 2026-06-05 07:20:26 +02:00
renovate[bot] b765a4b0c1 chore(deps): update rust crate chrono to v0.4.45 2026-06-04 17:05:21 +02:00
Devel 508d74fe4a Show usage when invoked without argments
* exit with code 1 when no arguments
* add a test for no arguments.
* Update test_sed.rs
* cargo fmt
* pass -n for missing script arguments test

Issue: #274
PR: #409
2026-06-02 18:37:35 +03:00
Diomidis Spinellis c45b2f5516 Merge pull request #437 from MukundaKatta/codex/reject-empty-text-command
fix: reject empty text commands
2026-06-01 22:49:16 +03:00
Diomidis Spinellis 3b846498f4 Add current POSIX specification link
Link to POSIX document to help contributors.
2026-06-01 21:41:25 +03:00
renovate[bot] 238648ddbf chore(deps): update rust crate uutests to 0.9.0 2026-05-31 14:41:47 +02:00
renovate[bot] d37963afcb fix(deps): update rust crate uucore to 0.9.0 2026-05-31 10:38:54 +02:00
renovate[bot] 9ebf4e2ba2 fix(deps): update rust crate uufuzz to 0.9.0 2026-05-31 10:13:44 +02:00
Sylvestre Ledru 9d0d86a8f1 Add CONTRIBUTING.md pointing to the review guidelines 2026-05-30 10:41:37 +02:00
renovate[bot] 7c6cbf9dba chore(deps): update rust crate ctor to v1.0.7 2026-05-29 08:29:01 +02:00
renovate[bot] a65af95b5c chore(deps): update rust crate memchr to v2.8.1 2026-05-27 08:10:43 +02:00
renovate[bot] 47cebdd562 chore(deps): update rust crate assert_fs to v1.1.4 2026-05-27 07:53:21 +02:00
mukunda katta f6e69e734b fix: preserve latest binary executable mode 2026-05-24 17:57:32 +02:00
renovate[bot] 2dbeaea2e1 chore(deps): update rust crate similar to v3.1.1 2026-05-24 13:02:25 +02:00
renovate[bot] 5cef1c4327 chore(deps): update rust crate divan to v4.7.0 2026-05-23 07:09:32 +02:00
renovate[bot] 4171a7fe61 chore(deps): update rust crate ctor to v1.0.6 2026-05-17 10:35:56 +02:00
mukunda katta 142e70bd13 fix: reject empty text commands 2026-05-15 13:11:51 -07:00
renovate[bot] f083fa0634 chore(deps): update rust crate ctor to v1.0.5 2026-05-12 07:12:43 +02:00
renovate[bot] ff28e7d83d chore(deps): update rust crate clap_complete to v4.6.5 2026-05-12 07:08:52 +02:00
renovate[bot] 6c00065099 chore(deps): update rust crate clap_complete to v4.6.4 2026-05-10 10:11:34 +02:00
renovate[bot] cd2e3cba30 chore(deps): update rust crate ctor to v1.0.4 2026-05-09 16:14:15 +02:00
Daniel Hofstetter 7f00ab2f16 Remove trailing whitespace 2026-05-09 16:05:32 +02:00
Daniel Hofstetter 249a4f4a26 Add newline at the end of two files 2026-05-09 16:05:32 +02:00
Daniel Hofstetter 5fcb15dba5 Make test_compare_test_results.py executable 2026-05-09 16:05:32 +02:00
Sylvestre Ledru f783eb975c add a precommit conf 2026-05-06 09:50:57 +02:00
renovate[bot] 5856b37cea chore(deps): update rust crate ctor to v1.0.1 2026-05-05 07:19:02 +02:00
renovate[bot] 84c32f8ef0 chore(deps): update rust crate ctor to v1 2026-05-04 07:35:50 +02:00
LoukasPap 433635b583 Fix --separate flag 2026-05-03 13:28:03 +02:00
xtqqczze c5cbd62f9b clippy: fix redundant_else lint
https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#redundant_else
2026-05-03 13:27:20 +02:00
xtqqczze b94b148638 clippy: update Cargo.toml 2026-05-03 13:27:20 +02:00
Daniel Hofstetter a6c028b782 Move ctor to dev-dependencies 2026-05-03 13:22:23 +02:00
Daniel Hofstetter 7e7abc9407 tests.rs: adapt to ctor change 2026-05-03 13:22:23 +02:00
Daniel Hofstetter fef7a40433 Bump ctor from 0.12.0 to 0.13.0 2026-05-03 13:22:23 +02:00
xtqqczze 60243b4fa7 clippy: fix assigning_clones lint
https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#assigning_clones
2026-04-30 22:59:01 +02:00
LoukasPap b7072cb4a5 Fix \?, \+ and \| to be treated as ERE in BRE mode 2026-04-30 22:05:25 +02:00
xtqqczze 6215664b2e actions: add security audit workflow 2026-04-30 22:03:29 +02:00
renovate[bot] ffe4130654 fix(deps): update rust crate ctor to 0.12.0 2026-04-30 22:02:17 +02:00
renovate[bot] e435b9be6c chore(deps): update rust crate divan to v4.6.0 2026-04-29 07:32:05 +02:00
renovate[bot] 5d9f08af1c fix(deps): update rust crate ctor to 0.11.0 2026-04-29 07:28:37 +02:00
renovate[bot] 14b156995e chore(deps): update dawidd6/action-download-artifact action to v21 2026-04-28 13:31:38 +02:00
renovate[bot] 187ae761b1 chore(deps): update rust crate clap_complete to v4.6.3 2026-04-27 18:34:32 +02:00
renovate[bot] b96e0e78d8 chore(deps): update rust crate fancy-regex to 0.18.0 2026-04-24 07:34:42 +02:00
renovate[bot] 442c2ed0b9 chore(deps): update rust crate libc to v0.2.186 2026-04-24 07:10:01 +02:00
pocopepe d7a713e3c8 fix: correct newline handling for p and x commands 2026-04-23 08:44:58 +02:00
renovate[bot] 0cf23a8e34 chore(deps): update mozilla-actions/sccache-action action to v0.0.10 2026-04-23 07:28:32 +02:00
renovate[bot] 2724f4ad75 chore(deps): update rust crate ctor to v0.10.1 2026-04-23 07:14:50 +02:00
oech3 ebb321e90e Add CI for wasm32 2026-04-21 14:20:02 +02:00
xtqqczze 17a2fece6f chore(deps): update rust crates 2026-04-21 11:27:56 +02:00
d389e51925 Add SECURITY.md
Copied from https://github.com/uutils/coreutils/blob/5e974797bd8050c2d425a706670254ad0323404d/SECURITY.md

Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
2026-04-21 11:21:45 +02:00
pocopepe a2da4c1a3b assert_fs belongs in dev-dependencies 2026-04-21 11:12:10 +02:00
renovate[bot] 7b204c022b chore(deps): update rust crate divan to v4.5.0 2026-04-17 14:00:06 +02:00
Daniel Hofstetter af30aa40a4 clippy: fix warnings from collapsible_match lint 2026-04-17 13:37:02 +02:00
renovate[bot] 4cd9858bfd chore(deps): update rust crate clap to v4.6.1 2026-04-16 14:51:38 +02:00
renovate[bot] b4c567e43e chore(deps): update rust crate clap_complete to v4.6.2 2026-04-14 07:16:04 +02:00
renovate[bot] fc2db1e335 chore(deps): update rust crate libc to v0.2.185 2026-04-13 15:59:24 +02:00
renovate[bot] 35bc81a5cf fix(deps): update rust crate ctor to 0.10.0 2026-04-13 07:11:26 +02:00
YumeYuka 59d24ac8dd ci: add Android build check for 32-bit and 64-bit targets 2026-04-12 17:29:50 +02:00
白彩恋 37df236504 fix: fix integer type conversion to support Android and 32-bit
Signed-off-by: 白彩恋 <shiro@oom-wg.dev>
(cherry picked from commit 48e6431869424d6d2d39aaf4c0ad08a07a46bfe0)
2026-04-12 17:29:50 +02:00
renovate[bot] 2e76232e05 chore(deps): update rust crate similar to v3.1.0 2026-04-12 10:24:00 +02:00
renovate[bot] b72279afb3 chore(deps): update softprops/action-gh-release action to v3 2026-04-12 10:20:28 +02:00
renovate[bot] 08d3454b8c chore(deps): update rust crate rand to v0.10.1 2026-04-11 15:17:59 +02:00
renovate[bot] 17f36e91c1 chore(deps): update rust crate clap_complete to v4.6.1 2026-04-11 07:17:20 +02:00
renovate[bot] 5e2eb59959 fix(deps): update rust crate ctor to 0.9.0 2026-04-10 07:06:31 +02:00
renovate[bot] e3ff6cc268 chore(deps): update actions/github-script action to v9 2026-04-10 07:04:56 +02:00
renovate[bot] e012c4b5c0 chore(deps): update rust crate uutests to 0.8.0 2026-04-07 08:02:43 +02:00
renovate[bot] 59551c580b fix(deps): update rust crate uucore to 0.8.0 2026-04-07 07:52:39 +02:00
renovate[bot] deb64e0ccd fix(deps): update rust crate uufuzz to 0.8.0 2026-04-07 07:12:53 +02:00
oech3 68b5883b0e Override latest-commit's body with corresponding commit 2026-04-03 11:23:13 +02:00
renovate[bot] 1b747a66a8 chore(deps): update dawidd6/action-download-artifact action to v20 2026-04-03 07:15:16 +02:00
renovate[bot] 7fade42bb5 fix(deps): update rust crate similar to v3 2026-04-02 07:05:23 +02:00
renovate[bot] f334e1000c chore(deps): update rust crate libc to v0.2.184 2026-04-01 15:56:01 +02:00
renovate[bot] ed6869944f chore(deps): update rust crate clap_mangen to 0.3 2026-03-28 07:18:48 +01:00
renovate[bot] ad01bedff8 fix(deps): update rust crate ctor to 0.8.0 2026-03-28 07:07:46 +01:00
renovate[bot] 34651493c1 chore(deps): update codecov/codecov-action action to v6 2026-03-27 07:24:50 +01:00
renovate[bot] bf7bed170a chore(deps): update rust crate sha2 to 0.11 2026-03-25 18:36:34 +01:00
renovate[bot] 1469cccfde chore(deps): update rust crate terminal_size to v0.4.4 2026-03-24 07:29:39 +01:00
LoukasPap 5e9b821d80 Set max addresses to 2 for a, i and = and make n_addr conditional, based on POSIX flag
- Update `get_cmd_spec()` and `get_verified_cmd_spec()` to accept a boolean POSIX param
- Add unit and high level tests
- Update README.md
2026-03-19 22:21:56 +01:00
xtqqczze 27da0276ec clippy: fix single_match_else lint 2026-03-19 22:05:47 +01:00
xtqqczze 2836cb7257 clippy: fix map_unwrap_or lint 2026-03-19 22:05:47 +01:00
xtqqczze 8c09b0f5a5 clippy: fix manual_assert lint 2026-03-19 22:05:47 +01:00
xtqqczze 5f85e267db clippy: fix ptr_as_ptr lint 2026-03-19 22:05:47 +01:00
xtqqczze 1535cfdd9b clippy: fix ref_option lint 2026-03-19 22:05:47 +01:00
xtqqczze 8772c166d9 clippy: fix manual_let_else lint 2026-03-19 22:05:47 +01:00
xtqqczze 6b6067075b clippy: fix if_not_else lint 2026-03-19 22:05:47 +01:00
xtqqczze dc9be6018c clippy: fix cast_lossless lint 2026-03-19 22:05:47 +01:00
xtqqczze b295d213e8 clippy: fix borrow_as_ptr 2026-03-19 22:05:47 +01:00
xtqqczze d3ca9d27b4 clippy: warn pedantic lints by default 2026-03-19 22:05:47 +01:00
Kevin BurkeandClaude Opus 4.6 16f11456b3 fix: rewrite GNU testsuite harness to run upstream test scripts directly
The previous harness tried to extract sed commands from GNU test scripts
via regex pattern matching, which produced false negatives (comparing
against empty expected output) and false positives. This led to inflated
test counts and unreliable pass/fail signals.

The new approach:
- Provides a lightweight shim for the gnulib test framework (init.sh)
  with implementations of compare_, returns_, skip_, framework_failure_,
  and all require_* functions
- Executes each .sh test script from the GNU testsuite directly,
  injecting our Rust sed binary via PATH
- Uses a clean srcdir with symlinks to real test data files
- Adds per-test timeout (10s) to catch infinite loops, with SIGTERM
  isolation so timeout signals don't kill the parent script
- Properly propagates exit codes (0=pass, 77=skip, 99=framework failure)

Results are now consistent with CI tracking (~12% pass rate) with
clear PASS/FAIL/SKIP/timeout categorization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 22:04:56 +01:00
renovate[bot] 81d98b9049 chore(deps): update dawidd6/action-download-artifact action to v19 2026-03-18 10:55:40 +01:00
renovate[bot] 25efbc0ab7 chore(deps): update dawidd6/action-download-artifact action to v18 2026-03-15 10:47:13 +01:00
renovate[bot] ad1f3d22dc chore(deps): update rust crate console to v0.16.3 2026-03-13 11:26:07 +01:00
renovate[bot] 3f3dd1270e chore(deps): update rust crate clap_mangen to v0.2.33 2026-03-13 07:08:17 +01:00
renovate[bot] af7131fdb2 chore(deps): update rust crate clap_complete to v4.6.0 2026-03-13 07:07:41 +01:00
renovate[bot] 97e70434b9 chore(deps): update rust crate clap to v4.6.0 2026-03-13 07:07:07 +01:00
renovate[bot] 19363fa810 chore(deps): update dawidd6/action-download-artifact action to v17 2026-03-12 15:33:11 +01:00
renovate[bot] 56e6ef5d2f chore(deps): update rust crate divan to v4.4.1 2026-03-12 15:32:38 +01:00
renovate[bot] fadc36512d chore(deps): update rust crate tempfile to v3.27.0 2026-03-11 07:04:15 +01:00
oech3 61152af62b Quick codspeed setup 2026-03-10 09:06:10 +01:00
renovate[bot] 7f16a008c9 fix(deps): update rust crate uucore to 0.7.0 2026-03-10 07:41:48 +01:00
renovate[bot] a6d85d8e42 chore(deps): update rust crate sysinfo to v0.38.4 2026-03-10 07:24:50 +01:00
renovate[bot] dc91d3709c chore(deps): update rust crate uutests to 0.7.0 2026-03-10 07:16:06 +01:00
renovate[bot] 8a3f63db27 fix(deps): update rust crate uufuzz to 0.7.0 2026-03-10 07:10:06 +01:00
renovate[bot] 1bb2f89e8d chore(deps): update rust crate libc to v0.2.183 2026-03-08 14:18:24 +01:00
renovate[bot] b7df132ba1 chore(deps): update rust crate sysinfo to v0.38.3 2026-03-02 14:36:11 +01:00
renovate[bot] 9f6f052c27 chore(deps): update github artifact actions 2026-02-27 06:57:50 +01:00
renovate[bot] 569ec711ac chore(deps): update dawidd6/action-download-artifact action to v16 2026-02-26 07:08:58 +01:00
renovate[bot] e075bff043 chore(deps): update rust crate tempfile to v3.26.0 2026-02-24 11:04:35 +01:00
renovate[bot] 628d320e5b chore(deps): update rust crate chrono to v0.4.44 2026-02-23 14:11:54 +01:00
oech3 d102d0be73 reproducible fuzzing, fast setup 2026-02-20 10:50:47 +01:00
renovate[bot] 01b190c59a chore(deps): update rust crate clap to v4.5.60 2026-02-20 07:21:35 +01:00
renovate[bot] 5ecd6afedb chore(deps): update dawidd6/action-download-artifact action to v15 2026-02-18 13:38:50 +01:00
renovate[bot] 81e71bcb49 chore(deps): update rust crate clap to v4.5.59 2026-02-17 07:06:36 +01:00
renovate[bot] b169b4dfc2 chore(deps): update rust crate sysinfo to v0.38.2 2026-02-15 18:47:48 +01:00
renovate[bot] 049e149ea5 chore(deps): update rust crate memmap2 to v0.9.10 2026-02-15 13:47:06 +01:00
Sylvestre Ledru 91affcf90b Update CodeCov badge to point to main branch 2026-02-15 12:11:40 +01:00
oech3 99c8574fc5 Reduce codegen-units to 7 2026-02-15 11:24:26 +01:00
oech3 b5b4cefb77 Drop apt-get 2026-02-13 14:36:53 +01:00
renovate[bot] fee1ff33ae chore(deps): update rust crate libc to v0.2.182 2026-02-13 10:48:45 +01:00
Alexander Wang 22abe5ea6c Preserve new line on exchanges (x) with empty hold spaces. (#259)
* fix: remove newline swap in exchange and swap content only

* fix: support non empty hold spaces and newline appended hold spaces

* nit: period in comment

* nit: useless comment removal

* chore: clarify newline swap logic for `x`
2026-02-13 10:36:51 +01:00
Daniel Hofstetter c0c85e65fa Bump bigdecimal from 0.4.8 to 0.4.10 2026-02-13 09:46:20 +01:00
Daniel Hofstetter ab6f9d9810 Bump uufuzz from 0.5.0 to 0.6.0 2026-02-13 09:46:20 +01:00
renovate[bot] 1d04c82eb8 chore(deps): update rust crate clap_complete to v4.5.66 2026-02-12 07:17:08 +01:00
renovate[bot] cdae35308d chore(deps): update rust crate clap to v4.5.58 2026-02-12 07:16:55 +01:00
renovate[bot] b926499ed4 chore(deps): update rust crate predicates to v3.1.4 2026-02-12 07:09:00 +01:00
renovate[bot] 4be480767c chore(deps): update rust crate libfuzzer-sys to v0.4.12 2026-02-11 07:01:08 +01:00
renovate[bot] aeac7d1e63 chore(deps): update rust crate tempfile to v3.25.0 2026-02-10 07:18:27 +01:00
renovate[bot] 3c7b8ff4ba chore(deps): update rust crate libc to v0.2.181 2026-02-10 07:09:28 +01:00
Daniel Hofstetter a177e97cfb Bump rand from 0.9.0 to 0.10.0 2026-02-09 16:42:11 +01:00
LoukasPap 361c45e6c3 Add test cases 2026-02-08 18:48:17 +01:00
LoukasPap 1c57aaa8e2 Add check for empty text with a, c and i commands in posix mode 2026-02-08 18:48:17 +01:00
renovate[bot] 1bb2609f16 fix(deps): update rust crate uucore to 0.6.0 2026-02-08 15:18:37 +01:00
LoukasPap e3684f0029 Add test case 2026-02-08 15:17:59 +01:00
LoukasPap f27060898a Allow multiple uses of same CLI argument 2026-02-08 15:17:59 +01:00
Daniel Hofstetter 1dc91b0082 Merge pull request #292 from uutils/renovate/memchr-2.x-lockfile
chore(deps): update rust crate memchr to v2.8.0
2026-02-07 07:12:24 +01:00
Daniel Hofstetter c3bbd572e9 Merge pull request #293 from uutils/renovate/sysinfo-0.x-lockfile
chore(deps): update rust crate sysinfo to v0.38.1
2026-02-07 07:09:05 +01:00
renovate[bot] 8d7559b52d chore(deps): update rust crate sysinfo to v0.38.1 2026-02-07 01:49:07 +00:00
renovate[bot] 995e32a70d chore(deps): update rust crate memchr to v2.8.0 2026-02-06 20:46:42 +00:00
Daniel Hofstetter 3f26fc2936 Merge pull request #291 from uutils/dependabot/cargo/time-0.3.47
Bump time from 0.3.44 to 0.3.47
2026-02-06 09:03:05 +01:00
dependabot[bot] 907d01b847 Bump time from 0.3.44 to 0.3.47
Bumps [time](https://github.com/time-rs/time) from 0.3.44 to 0.3.47.
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.44...v0.3.47)

---
updated-dependencies:
- dependency-name: time
  dependency-version: 0.3.47
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-05 18:59:26 +00:00
Daniel Hofstetter 3504109373 Merge pull request #285 from uutils/renovate/clap-4.x-lockfile
chore(deps): update rust crate clap to v4.5.57
2026-02-04 07:17:02 +01:00
Daniel Hofstetter 837f2cd2ea Merge pull request #286 from uutils/renovate/regex-1.x-lockfile
chore(deps): update rust crate regex to v1.12.3
2026-02-04 07:14:01 +01:00
renovate[bot] 0a09d32b00 chore(deps): update rust crate regex to v1.12.3 2026-02-03 18:31:23 +00:00
renovate[bot] da2715dcb5 chore(deps): update rust crate clap to v4.5.57 2026-02-03 18:31:18 +00:00
Daniel Hofstetter 40925c8b4f Merge pull request #283 from uutils/renovate/uutests-0.x
chore(deps): update rust crate uutests to 0.6.0
2026-02-03 14:56:16 +01:00
renovate[bot] 2eeaa80c3b chore(deps): update rust crate uutests to 0.6.0 2026-02-03 13:35:42 +00:00
Daniel Hofstetter a6207cbebc Bump rlimit from 0.10.1 to 0.11.0 2026-02-02 08:57:25 +01:00
Daniel Hofstetter 98f7338960 Merge pull request #280 from uutils/renovate/dawidd6-action-download-artifact-14.x
chore(deps): update dawidd6/action-download-artifact action to v14
2026-02-01 16:14:38 +01:00
renovate[bot] 003b579967 chore(deps): update dawidd6/action-download-artifact action to v14 2026-02-01 15:00:12 +00:00
Daniel Hofstetter d497e881af Merge pull request #279 from uutils/renovate/dawidd6-action-download-artifact-13.x
chore(deps): update dawidd6/action-download-artifact action to v13
2026-01-30 16:21:32 +01:00
renovate[bot] 98c4dab5dd chore(deps): update dawidd6/action-download-artifact action to v13 2026-01-30 15:15:24 +00:00
Daniel Hofstetter 8e8c2bf9f7 Merge pull request #278 from uutils/renovate/clap-4.x-lockfile
chore(deps): update rust crate clap to v4.5.56
2026-01-30 07:17:31 +01:00
renovate[bot] 3cf5ea5beb chore(deps): update rust crate clap to v4.5.56 2026-01-29 18:36:39 +00:00
oech3 1617d32d5d README.md: Link tag/latest-commit 2026-01-28 11:12:45 +01:00
Sylvestre Ledru b3350a21c4 python coding style on the file 2026-01-28 09:32:24 +01:00
Sylvestre Ledru 8c46bec97f Skip PR comments when test results are identical 2026-01-28 09:32:24 +01:00
45 changed files with 6134 additions and 2464 deletions

No files matched your search

+8 -8
View File
@@ -18,7 +18,7 @@ jobs:
github.event.workflow_run.event == 'pull_request'
steps:
- name: 'Download artifact'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
// List all artifacts from GnuTests
@@ -32,12 +32,12 @@ jobs:
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "comment"
})[0];
if (!matchArtifact) {
console.log('No comment artifact found');
return;
}
var download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -49,12 +49,12 @@ jobs:
- run: unzip comment.zip || echo "Failed to unzip comment artifact"
- name: 'Comment on PR'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var fs = require('fs');
// Check if files exist
if (!fs.existsSync('./NR')) {
console.log('No NR file found, skipping comment');
@@ -64,10 +64,10 @@ jobs:
console.log('No result.txt file found, skipping comment');
return;
}
var issue_number = Number(fs.readFileSync('./NR'));
var content = fs.readFileSync('./result.txt');
if (content.toString().trim().length > 7) { // 7 because we have backquote + \n
await github.rest.issues.createComment({
owner: context.repo.owner,
@@ -77,4 +77,4 @@ jobs:
});
} else {
console.log('Comment content too short, skipping');
}
}
+18 -16
View File
@@ -28,7 +28,7 @@ jobs:
steps:
#### Get the code, setup cache
- name: Checkout code (sed)
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
path: 'sed'
persist-credentials: false
@@ -40,7 +40,7 @@ jobs:
with:
workspaces: "./sed -> target"
- name: Checkout code (GNU sed testsuite)
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
repository: 'mirror/sed'
path: 'gnu.sed'
@@ -56,16 +56,18 @@ jobs:
## Build Rust sed binary
cd 'sed'
cargo build --config=profile.release.strip=true --profile=release #-fast
zstd -19 target/release/sed -o ../sed-x86_64-unknown-linux-gnu.zst
tar -C target/release -cf - sed | zstd -19 -o ../sed-x86_64-unknown-linux-gnu.tar.zst
- name: Publish latest commit
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
tag_name: latest-commit
body: |
commit: ${{ github.sha }}
draft: false
prerelease: true
files: |
sed-x86_64-unknown-linux-gnu.zst
sed-x86_64-unknown-linux-gnu.tar.zst
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -75,8 +77,8 @@ jobs:
run: |
## Run GNU sed testsuite using our script
cd 'sed'
# Set GNU testsuite directory
export GNU_TESTSUITE_DIR="../gnu.sed/testsuite"
# Set GNU sed source directory (testsuite is at gnu.sed/testsuite/)
export GNU_SED_DIR="../gnu.sed"
# Run tests with JSON output
./util/run-gnu-testsuite.sh --json-output "${{ env.TEST_FULL_SUMMARY_FILE }}" || true
@@ -91,7 +93,7 @@ jobs:
find . -name "*json*" -type f || echo "No JSON files found"
- name: Upload full json results
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: sed-gnu-full-result
path: sed/${{ env.TEST_FULL_SUMMARY_FILE }}
@@ -99,7 +101,7 @@ jobs:
- name: Upload test logs
if: always()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: test-logs
path: |
@@ -126,13 +128,13 @@ jobs:
outputs TEST_SUMMARY_FILE
- name: Checkout code (sed)
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
path: 'sed'
persist-credentials: false
- name: Retrieve reference artifacts
uses: dawidd6/action-download-artifact@v12
uses: dawidd6/action-download-artifact@v21
continue-on-error: true
with:
workflow: GnuTests.yml
@@ -142,7 +144,7 @@ jobs:
if_no_artifact_found: warn
- name: Download full json results
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: sed-gnu-full-result
path: results
@@ -203,13 +205,13 @@ jobs:
outputs HASH TOTAL PASS FAIL SKIP
- name: Upload SHA1/ID of 'test-summary'
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: "${{ steps.summary.outputs.HASH }}"
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
- name: Upload test results summary
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: test-summary
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
@@ -259,10 +261,10 @@ jobs:
echo "ONLY_INTERMITTENT=true" >> $GITHUB_ENV
echo "::notice ::No new test failures detected"
fi
- name: Upload comparison log (for GnuComment workflow)
if: success() || failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: comment
path: reference/comment/
+17
View File
@@ -0,0 +1,17 @@
name: Security audit
# spell-checker:ignore (misc) rustsec
on:
schedule:
- cron: "0 0 * * *"
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
+7 -12
View File
@@ -25,25 +25,20 @@ jobs:
benchmark-target:
- { package: sed }
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Install system dependencies
shell: bash
run: |
sudo apt-get -y update
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.9
uses: mozilla-actions/sccache-action@v0.0.11
- name: Install cargo-codspeed
shell: bash
run: cargo install cargo-codspeed --locked
- name: Install tools
uses: taiki-e/install-action@v2
with:
tool: cargo-codspeed
- name: Build benchmarks for ${{ matrix.benchmark-target.package }}
shell: bash
@@ -52,7 +47,7 @@ jobs:
cargo codspeed build -p ${{ matrix.benchmark-target.package }}
- name: Run benchmarks for ${{ matrix.benchmark-target.package }}
uses: CodSpeedHQ/action@v4
uses: CodSpeedHQ/action@v5
env:
CODSPEED_LOG: debug
with:
+25 -4
View File
@@ -13,10 +13,31 @@ jobs:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- run: cargo check
check_android:
name: cargo check (Android ${{ matrix.target }})
runs-on: ubuntu-latest
strategy:
matrix:
target: [aarch64-linux-android, armv7-linux-androideabi]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: android-actions/setup-android@v4
- name: Install Android NDK
run: sdkmanager "ndk;29.0.14206865"
- name: Install cargo-ndk
run: cargo install cargo-ndk
- name: Check Android target
run: cargo ndk --platform 21 --target ${{ matrix.target }} check
env:
ANDROID_NDK_HOME: ${{ env.ANDROID_SDK_ROOT }}/ndk/29.0.14206865
test:
name: cargo test
runs-on: ${{ matrix.os }}
@@ -28,7 +49,7 @@ jobs:
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- run: cargo test --all
@@ -47,7 +68,7 @@ jobs:
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Initialize workflow variables
id: vars
shell: bash
@@ -109,7 +130,7 @@ jobs:
ls -al
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
- name: Upload coverage results (to Codecov.io)
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ${{ steps.coverage.outputs.report }}
+3 -3
View File
@@ -25,7 +25,7 @@ jobs:
name: cargo fmt --all -- --check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- run: rustup component add rustfmt
- run: cargo fmt --all -- --check
@@ -44,14 +44,14 @@ jobs:
- { os: macos-latest }
- { os: windows-latest }
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.9
uses: mozilla-actions/sccache-action@v0.0.11
- name: Initialize workflow variables
id: vars
shell: bash
+18 -14
View File
@@ -21,18 +21,20 @@ jobs:
name: Build the fuzzers
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@nightly
- name: Install `cargo-fuzz`
run: cargo install cargo-fuzz
uses: taiki-e/install-action@v2
with:
tool: cargo-fuzz
- uses: Swatinem/rust-cache@v2
with:
shared-key: "cargo-fuzz-cache-key"
cache-directories: "fuzz/target"
- name: Run `cargo-fuzz build`
run: cargo +nightly fuzz build
# https://github.com/rust-fuzz/cargo-fuzz/issues/398
run: env RUSTC_BOOTSTRAP=1 cargo fuzz build --target $(rustc --print host-tuple)
fuzz-run:
needs: fuzz-build
@@ -47,18 +49,19 @@ jobs:
- { name: fuzz_sed, should_pass: false }
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@nightly
- name: Install `cargo-fuzz`
run: cargo install cargo-fuzz
uses: taiki-e/install-action@v2
with:
tool: cargo-fuzz
- uses: Swatinem/rust-cache@v2
with:
shared-key: "cargo-fuzz-cache-key"
cache-directories: "fuzz/target"
- name: Restore Cached Corpus
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
key: corpus-cache-${{ matrix.test-target.name }}
path: |
@@ -70,7 +73,8 @@ jobs:
run: |
mkdir -p fuzz/stats
STATS_FILE="fuzz/stats/${{ matrix.test-target.name }}.txt"
cargo +nightly fuzz run ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -timeout=${{ env.RUN_FOR }} -detect_leaks=0 -print_final_stats=1 2>&1 | tee "$STATS_FILE"
# https://github.com/rust-fuzz/cargo-fuzz/issues/398
env RUSTC_BOOTSTRAP=1 cargo fuzz run --target $(rustc --print host-tuple) ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -timeout=${{ env.RUN_FOR }} -detect_leaks=0 -print_final_stats=1 2>&1 | tee "$STATS_FILE"
# Extract key stats from the output
if grep -q "stat::number_of_executed_units" "$STATS_FILE"; then
@@ -140,13 +144,13 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
- name: Save Corpus Cache
uses: actions/cache/save@v5
uses: actions/cache/save@v6
with:
key: corpus-cache-${{ matrix.test-target.name }}
path: |
fuzz/corpus/${{ matrix.test-target.name }}
- name: Upload Stats
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: fuzz-stats-${{ matrix.test-target.name }}
path: |
@@ -159,11 +163,11 @@ jobs:
runs-on: ubuntu-latest
if: always()
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Download all stats
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
path: fuzz/stats-artifacts
pattern: fuzz-stats-*
@@ -257,7 +261,7 @@ jobs:
run: |
cat fuzzing_summary.md
- name: Upload Summary
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: fuzzing-summary
path: fuzzing_summary.md
+1 -1
View File
@@ -4,4 +4,4 @@
# Add test names that are known to be flaky or environment-dependent
# Example:
# basic_substitution
# line_address_test
# line_address_test
+16 -16
View File
@@ -56,7 +56,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
@@ -66,7 +66,7 @@ jobs:
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.3/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
@@ -82,7 +82,7 @@ jobs:
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
@@ -116,7 +116,7 @@ jobs:
- name: enable windows longpaths
run: |
git config --global core.longpaths true
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
@@ -131,7 +131,7 @@ jobs:
run: ${{ matrix.install_dist.run }}
# Get the dist-manifest
- name: Fetch local artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: target/distrib/
@@ -158,7 +158,7 @@ jobs:
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
@@ -175,19 +175,19 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: target/distrib/
@@ -205,7 +205,7 @@ jobs:
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: artifacts-build-global
path: |
@@ -225,19 +225,19 @@ jobs:
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: target/distrib/
@@ -250,14 +250,14 @@ jobs:
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
path: dist-manifest.json
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: artifacts
@@ -290,7 +290,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
+28
View File
@@ -0,0 +1,28 @@
# spell-checker:ignore wasip
name: WASI
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
# End the current execution if there is a new changeset in the PR.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
test_wasi:
name: Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip1
- name: check
run: cargo check --target wasm32-wasip1
+55
View File
@@ -0,0 +1,55 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
exclude: ^tests/fixtures/
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-added-large-files
- id: check-executables-have-shebangs
- id: check-json
exclude: '\.vscode/(cSpell|extensions)\.json' # cSpell.json and extensions.json use comments
- id: check-shebang-scripts-are-executable
exclude: '.+\.rs' # would be triggered by #![some_attribute]
- id: check-symlinks
- id: check-toml
- id: check-yaml
args: [ --allow-multiple-documents ]
- id: destroyed-symlinks
- id: end-of-file-fixer
- id: mixed-line-ending
args: [ --fix=lf ]
- id: trailing-whitespace
- repo: local
hooks:
- id: rust-linting
name: Rust linting
description: Run cargo fmt on files included in the commit.
entry: cargo +stable fmt --
pass_filenames: true
types: [file, rust]
language: system
- id: rust-clippy
name: Rust clippy
description: Run cargo clippy on files included in the commit.
entry: cargo +stable clippy --workspace --all-targets --all-features -- -D warnings
pass_filenames: false
types: [file, rust]
language: system
- id: cargo-lock-check
name: Cargo.lock sync check
description: Ensure Cargo.lock and fuzz/Cargo.lock are up-to-date.
entry: bash -c 'for dir in . fuzz; do ( cd "$dir" && cargo fetch --quiet ); done'
pass_filenames: false
files: 'Cargo\.(toml|lock)$'
language: system
- id: cspell
name: Code spell checker (cspell)
description: Run cspell to check for spelling errors (if available).
entry: bash -c 'if command -v cspell >/dev/null 2>&1; then cspell --no-must-find-files -- "$@"; else echo "cspell not found, skipping spell check"; exit 0; fi' --
pass_filenames: true
language: system
ci:
skip: [rust-linting, rust-clippy, cargo-lock-check, cspell]
+32
View File
@@ -0,0 +1,32 @@
# Contributing to sed
Hi! Welcome to uutils/sed, and thanks for wanting to contribute!
This project follows the shared conventions of the [uutils](https://github.com/uutils)
organization. Before opening a pull request, please read:
- Our **[Review Guidelines](https://uutils.github.io/reviews/)** — what we expect
from a pull request and how reviews are carried out.
- Our community's [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md), if present.
Finally, feel free to join our [Discord](https://discord.gg/wQVJbvJ)!
> [!WARNING]
> uutils is original code and cannot contain any code from GNU or other
> strongly-licensed (GPL/LGPL) implementations. We **cannot** accept changes
> based on the GNU source code, and you **must not link** to it either. You may
> look at permissively-licensed implementations (MIT/BSD) and read the GNU
> *manuals* — never the GNU *source*.
## In short
- Discuss non-trivial changes in an issue **before** writing the code.
- Keep pull requests **small, self-contained, and descriptively titled**
(e.g. `sed: fix ...`).
- Make sure CI passes: tests are green, `rustfmt` is satisfied, and there are
no `clippy` warnings.
- Add tests for new behavior; don't let coverage regress.
- Write small, atomic commits annotated with the component you touched.
See the [Review Guidelines](https://uutils.github.io/reviews/) for the full
details.
Generated
+489 -574
View File
File diff suppressed because it is too large. Load diff
+55 -35
View File
@@ -5,8 +5,7 @@
[package]
name = "sed"
version = "0.1.1"
authors = ["uutils developers"]
version = "0.2.0"
license = "MIT"
description = "sed ~ implemented as universal (cross-platform) utils, written in Rust"
default-run = "sed"
@@ -29,53 +28,52 @@ sed = []
[workspace.dependencies]
assert_fs = "1.1.3"
bytesize = "2.0.0"
chrono = { version = "0.4.37", default-features = false, features = [
"clock",
] }
clap = { version = "4.4", features = ["wrap_help", "cargo"] }
clap_complete = "4.5"
clap_mangen = "0.2"
divan = { package = "codspeed-divan-compat", version = "4.0.5" }
fancy-regex = "0.17.0"
clap_mangen = "0.3"
divan = { package = "codspeed-divan-compat", version = "5.0.0" }
fancy-regex = "0.18.0"
hex = "0.4"
libc = "0.2.153"
memchr = "2.7.4"
memmap2 = "0.9"
phf = "0.13.0"
phf_codegen = "0.13.0"
phf = "0.14.0"
phf_codegen = "0.14.0"
predicates = "3.1.3"
rand = { version = "0.9", features = ["small_rng"] }
rand = { version = "0.10.0" }
regex = "1.10.4"
sha2 = "0.10"
sysinfo = "0.38"
sha2 = "0.11"
tempfile = "3.10.1"
textwrap = { version = "0.16.1", features = ["terminal_size"] }
terminal_size = "0.4.2"
uucore = { version = "0.5.0", features = ["libc"] }
textwrap = { version = "0.16.1", features = ["terminal_size"] }
uucore = { version = "0.10.0", features = ["libc"] }
rustix = "1.1.4"
xattr = "1.3.1"
[dependencies]
assert_fs = { workspace = true }
clap = { workspace = true }
clap_complete = { workspace = true }
clap_mangen = { workspace = true }
ctor = "0.6.0"
fancy-regex = { workspace = true }
memchr = { workspace = true }
memmap2.workspace = true
phf = { workspace = true }
predicates = { workspace = true }
regex = { workspace = true }
sysinfo = { workspace = true }
tempfile = { workspace = true }
terminal_size = { workspace = true }
textwrap = { workspace = true }
uucore = { workspace = true }
rustix = { workspace = true }
[dev-dependencies]
assert_fs = { workspace = true }
chrono = { workspace = true }
ctor = "1.0.0"
divan = { workspace = true }
hex = { workspace = true }
libc = { workspace = true }
@@ -85,13 +83,13 @@ regex = { workspace = true }
sha2 = { workspace = true }
tempfile = { workspace = true }
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
uutests = "0.5.0"
uutests = "0.10.0"
[target.'cfg(unix)'.dev-dependencies]
xattr = { workspace = true }
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
rlimit = "0.10.1"
rlimit = "0.11.0"
[build-dependencies]
phf_codegen = { workspace = true }
@@ -107,31 +105,53 @@ path = "src/bin/sed.rs"
name = "sed_bench"
harness = false
# The default release profile. It contains all optimizations, without
# sacrificing debug info. With this profile (like in the standard
# release profile), the debug info and the stack traces will still be available.
[profile.release]
lto = true
# A release-like profile that is tuned to be fast, even when being fast
# compromises on binary size. This includes aborting on panic.
[profile.release-fast]
inherits = "release"
panic = "abort"
codegen-units = 1 # should be moved to release without regression
# should be dropped to 1 for binary size without performance drop
codegen-units = 7
# A release-like profile that is as small as possible.
[profile.release-small]
inherits = "release-fast"
inherits = "release"
opt-level = "z"
strip = true
[lints.clippy]
default_trait_access = "warn"
manual_string_new = "warn"
all = { level = "warn", priority = -1 }
cargo = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
# The counts were generated with this command:
# cargo +nightly clippy --all-targets --workspace --message-format=json --quiet \
# | jq -r '.message.code.code | select(. != null and startswith("clippy::"))' \
# | sort | uniq -c | sort -h -r
missing_errors_doc = "allow" # 69
doc_markdown = "allow" # 63
must_use_candidate = "allow" # 56
needless_raw_string_hashes = "allow" # 20
needless_pass_by_value = "allow" # 15
missing_panics_doc = "allow" # 12
# cast_possible_truncation = "allow" # 7
unnecessary_wraps = "allow" # 6
match_wildcard_for_single_variants = "allow" # 4
cast_sign_loss = "allow" # 4
cast_possible_wrap = "allow" # 4
uninlined_format_args = "allow" # 3
similar_names = "allow" # 3
used_underscore_binding = "allow" # 2
too_many_lines = "allow" # 2
struct_excessive_bools = "allow" # 2
match_same_arms = "allow" # 2
ignore_without_reason = "allow" # 2
format_push_string = "allow" # 2
should_panic_without_expect = "allow" # 1
many_single_char_names = "allow" # 1
comparison_chain = "allow"
multiple_crate_versions = "allow"
unnested_or_patterns = "allow"
restriction = { level = "allow", priority = -1 }
cognitive_complexity = "warn"
implicit_clone = "warn"
range-plus-one = "warn"
redundant-clone = "warn"
match_bool = "warn"
semicolon_if_nothing_returned = "warn"
redundant_clone = "warn"
+37 -13
View File
@@ -3,7 +3,7 @@
[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/sed/blob/main/LICENSE)
[![dependency status](https://deps.rs/repo/github/uutils/sed/status.svg)](https://deps.rs/repo/github/uutils/sed)
[![CodeCov](https://codecov.io/gh/uutils/sed/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/sed)
[![CodeCov](https://codecov.io/gh/uutils/sed/branch/main/graph/badge.svg)](https://codecov.io/gh/uutils/sed)
# sed
@@ -14,7 +14,7 @@ and other extensions.
## Status
At this state _sed_ implements all POSIX commands
At this state _sed_ implements all [POSIX features](https://pubs.opengroup.org/onlinepubs/9799919799/)
and can run correctly the two complex scripts of its integration tests:
[hanoi.sed](https://github.com/uutils/sed/blob/main/tests/fixtures/sed/script/hanoi.sed) (solves the Towers of Hanoi puzzle) and
[math.sed](https://github.com/uutils/sed/blob/main/tests/fixtures/sed/script/math.sed) (implements an arbitrary precision integer math calculator).
@@ -26,9 +26,12 @@ Further work aims to:
* Implement more GNU extensions,
* Improve performance where possible.
## Installation
## Installation and Use
Ensure you have Rust installed on your system. You can install Rust through [rustup](https://rustup.rs/).
We provide a Linux x86_64 binary archive from the main branch at
https://github.com/uutils/sed/releases/tag/latest-commit .
For other platforms, ensure you have Rust installed on your system. You can install Rust through [rustup](https://rustup.rs/).
Clone the repository and build the project using Cargo:
@@ -41,11 +44,16 @@ cargo run --release
The binary is named `sed` in `target/release/sed`.
You can also try *sed* on the web
through the [uutils Playground](https://uutils.org//playground/)
by clicking on the `Load sed` button.
## Testing
### GNU sed Compatibility Testing
Test compatibility against GNU sed using the comprehensive testsuite (47+ tests, ~10% pass rate):
Test compatibility against GNU sed by running the upstream testsuite shell scripts
with a lightweight gnulib test-framework shim:
```bash
# Clone GNU sed testsuite (one time setup)
@@ -54,11 +62,16 @@ git clone https://github.com/mirror/sed.git ../gnu.sed
# Run compatibility tests
./util/run-gnu-testsuite.sh
# Verbose mode shows failure details
./util/run-gnu-testsuite.sh -v
# Generate JSON results for CI
./util/run-gnu-testsuite.sh --json-output results.json
```
The testsuite extracts test cases from the GNU sed repository and tests them against expected outputs.
The harness executes each `.sh` test from the GNU sed testsuite directly, injecting
our Rust sed binary via `PATH` and providing shim implementations of the gnulib test
framework functions (`compare_`, `returns_`, `skip_`, etc.).
### Unit Tests
@@ -72,21 +85,25 @@ cargo test
* Spaces can precede a regular expression modifier.
* `I` can be used in as a synonym for the `i` (case insensitive) substitution
flag.
* `M` and `m` substitution flags allow multi-line matching.
* In addition to `\n`, other escape sequences (octal, hex, C) are supported
in the strings of the `y` command.
Under POSIX these yield undefined behavior.
* The `a`, `c`, and `i` commands do not require an initial backslash,
allow text to appear on the same line, and support escape sequences
in the specified text.
* The `a`, `i`, `=`, `l`, `q` and `r` commands support address range as an extension to POSIX.
* The substitution command replacement group `\0` is a synonym for &.
* A `Q` command (optionally followed by an exit code) quits immediately.
* The `q` command can be optionally followed by an exit code.
* The `l` command can be optionally followed by the output width.
* The `--follow-symlinks` flag for in-place editing.
* The `--follow-symlinks` option for in-place editing.
* The `--sandbox` option that limits potentially destructive commands.
* Address 0 can be used to specify an address range that is already
active on line 1 and can finish with the specified regular expression.
* Address steps can be specified in the form of start~step and start,~step
ranges.
* Address 0 can be used in the `r` command to prepend a file.
### Supported BSD and GNU extensions
* The second address in a range can be specified as a relative address with +N.
@@ -99,12 +116,19 @@ cargo test
sequences.
### Incompatibilities
* The input is assumed to be valid UTF-8 (this includes 7-bit ASCII).
If the input is in another code page, consider converting it through UTF-8
in order to avoid errors on invalid UTF-8 sequences and for the correct
handling of regular expressions.
This _sed_ program can also handle arbitrary byte sequences if no part of the
input is treated as string.
* Similarly to GNU _sed_, input is processed as raw bytes or as valid UTF-8
(this includes 7-bit ASCII) based on the locale as specified by the
`LC_ALL`, `LC_CTYPE`, and `LANG` environment variables,
with the default being byte processing.
However, in contrast with GNU _sed_, other locales (e.g. ISO-8859-1)
are not supported. If the input is in another code page or encoding
and requires locale-specific processing (e.g. ignore/map case,
character classes), consider converting it through UTF-8 to ensure
the correct handling of locale-specific regular expressions.
This _sed_ program can also handle arbitrary byte sequences
if no part of the input requires treating it as a Rust String.
* Back-references aren't supported when input is processed as bytes
(`LC_ALL=C`).
* The command will report an error and fail if duplicate labels are found
in the script.
This matches the BSD behavior. The GNU version accepts duplicate labels.
+44
View File
@@ -0,0 +1,44 @@
# Security Policy
## Supported Versions
We provide security updates only for the latest released version of `uutils/sed`.
Older versions may not receive patches.
If you are using a version packaged by your Linux distribution, please check with your distribution maintainers for their update policy.
---
## Reporting a Vulnerability
**Do not open public GitHub issues for security vulnerabilities.**
This prevents accidental disclosure before a fix is available.
Instead, please use the following method:
- **Email:** [sylvestre@debian.org](mailto:Sylvestre@debian.org)
- **Encryption (optional):** You may encrypt your report using our PGP key:
Fingerprint: B60D B599 4D39 BEC4 D1A9 5CCF 7E65 28DA 752F 1BE1
---
### What to Include in Your Report
To help us investigate and resolve the issue quickly, please include as much detail as possible:
- **Type of issue:** e.g. privilege escalation, information disclosure.
- **Location in the source:** file path, commit hash, branch, or tag.
- **Steps to reproduce:** exact commands, test cases, or scripts.
- **Special configuration:** any flags, environment variables, or system setup required.
- **Affected systems:** OS/distribution and version(s) where the issue occurs.
- **Impact:** your assessment of the potential severity (DoS, RCE, data leak, etc.).
---
## Disclosure Policy
We follow a **Coordinated Vulnerability Disclosure (CVD)** process:
1. We will acknowledge receipt of your report within **10 days**.
2. We will investigate, reproduce, and assess the issue.
3. We will provide a timeline for developing and releasing a fix.
4. Once a fix is available, we will publish a GitHub Security Advisory.
5. You will be credited in the advisory unless you request anonymity.
+476 -583
View File
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -12,10 +12,10 @@ console = "0.16.0"
libfuzzer-sys = "0.4.7"
libc = "0.2.153"
tempfile = "3.15.0"
rand = { version = "0.9.0", features = ["small_rng"] }
similar = "2.5.0"
uucore = { version = "0.5.0", features = ["libc"] }
uufuzz = "0.5.0"
rand = { version = "0.10.0" }
similar = "3.0.0"
uucore = { version = "0.10.0", features = ["libc"] }
uufuzz = "0.10.0"
sed = { path = ".." }
+164 -78
View File
@@ -52,6 +52,8 @@ pub struct ProcessingContext {
pub last_file: bool,
/// Stop processing further input.
pub stop_processing: bool,
/// Whether sed operates on bytes or UTF-8 characters
pub character_mode: CharacterMode,
/// Previously compiled RE, saved for reuse when specifying an empty RE
pub saved_regex: Option<Regex>,
/// Modification of input processing action
@@ -59,7 +61,7 @@ pub struct ProcessingContext {
// command.
pub input_action: Option<InputAction>,
/// Hold space
pub hold: StringSpace,
pub hold: ByteSpace,
/// Nesting of { } at compile time
pub parsed_block_nesting: usize,
/// Command associated with each label
@@ -75,14 +77,22 @@ pub struct ProcessingContext {
#[derive(Clone, Debug)]
/// Elements that shall be appended at the end of each command processing cycle
pub enum AppendElement {
Text(Rc<str>), // The specified text string
Path(PathBuf), // The contents of the specified file path
Text(Rc<[u8]>), // The specified text bytes
Path(PathBuf), // The contents of the specified file path
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
/// Whether sed operates on bytes or UTF-8 characters
pub enum CharacterMode {
Byte, // Interpret data as arbitrary bytes (C/POSIX locale).
#[default]
Utf8, // Interpret data as UTF-8 characters (UTF-8 locale).
}
#[derive(Clone, Debug, Default, PartialEq)]
/// A space mirroring IOChunk, but only with a String
pub struct StringSpace {
pub content: String, // Line content without newline
/// A space mirroring IOChunk without mmap-backed storage.
pub struct ByteSpace {
pub content: Vec<u8>, // Line content without newline
pub has_newline: bool, // True if \n-terminated
}
@@ -100,9 +110,19 @@ pub enum Address {
#[derive(Debug)]
/// A single part of an RE replacement
pub enum ReplacementPart {
Literal(String), // Normal text
WholeMatch, // &
Group(u32), // \1 to \9
Literal(Vec<u8>), // Normal text
WholeMatch, // &
Group(u32), // \1 to \9
}
// The maximum value allowed in regex quantifier
pub const RE_DUP_MAX: usize = 32767;
/// Regex modes (BRE or ERE)
#[derive(Copy, Clone, Debug)]
pub enum RegexMode {
Basic,
Extended,
}
#[derive(Debug)]
@@ -142,8 +162,8 @@ impl ReplacementTemplate {
/// let result = regex.replace_all(input, |caps: &Captures| {
/// template.apply_captures(&command, caps) });
/// Returns an error if a backreference in the template was not matched by the RE.
pub fn apply_captures(&self, command: &Command, caps: &Captures) -> UResult<String> {
let mut result = String::new();
pub fn apply_captures(&self, command: &Command, caps: &Captures) -> UResult<Vec<u8>> {
let mut result = Vec::new();
// Invalid group numbers may end here through (unkown at compile time)
// reused REs.
@@ -159,15 +179,17 @@ impl ReplacementTemplate {
for part in &self.parts {
match part {
ReplacementPart::Literal(s) => result.push_str(s),
ReplacementPart::Literal(s) => result.extend_from_slice(s),
ReplacementPart::WholeMatch => {
result.push_str(caps.get(0)?.map(|m| m.as_str()).unwrap_or(""));
result
.extend_from_slice(caps.get(0)?.map(|m| m.as_bytes()).unwrap_or_default());
}
ReplacementPart::Group(n) => {
let i: usize = (*n).try_into().unwrap();
result.push_str(caps.get(i)?.map(|m| m.as_str()).unwrap_or(""));
result
.extend_from_slice(caps.get(i)?.map(|m| m.as_bytes()).unwrap_or_default());
}
}
}
@@ -176,14 +198,14 @@ impl ReplacementTemplate {
}
/// Apply the template to the given RE single match.
pub fn apply_match(&self, m: &Match) -> String {
let mut result = String::new();
pub fn apply_match(&self, m: &Match) -> Vec<u8> {
let mut result = Vec::new();
for part in &self.parts {
match part {
ReplacementPart::Literal(s) => result.push_str(s),
ReplacementPart::Literal(s) => result.extend_from_slice(s),
ReplacementPart::WholeMatch => result.push_str(m.as_str()),
ReplacementPart::WholeMatch => result.extend_from_slice(m.as_bytes()),
ReplacementPart::Group(_) => {
panic!("unexpected Regex group replacement")
@@ -201,10 +223,20 @@ pub struct Substitution {
pub replacement: ReplacementTemplate, // Specified broken-down replacement
pub occurrence: usize, // Which occurrence to substitute
pub print_flag: bool, // True if 'p' flag
pub p_before_e: bool, // True if 'p' appears before 'e'
pub ignore_case: bool, // True if 'I' flag
pub execute: bool, // True if 'e' flag (GNU extension)
pub multiline: bool, // True if 'm' or 'M' flag (GNU extension)
pub write_file: Option<Rc<RefCell<NamedWriter>>>, // Writer to file if 'w' flag is used
}
#[derive(Debug, PartialEq, Eq)]
/// Result of parsing a transliteration string in byte or character mode.
pub enum ParsedTransliteration {
Bytes(Vec<u8>),
Text(String),
}
/// The block of the first and most common Unicode characters:
/// ASCII, Latin Extended, Greek, Curillic, Coptic, Arabic, etc.
/// It comprises all UCS-2 characters. We use a fast lookup array for these.
@@ -213,20 +245,28 @@ const COMMON_UNICODE: usize = 2048;
#[derive(Debug)]
/// Transliteration command (y)
pub struct Transliteration {
fast: [char; COMMON_UNICODE],
slow: HashMap<char, char>,
byte_fast: [u8; 256],
unicode_fast: [char; COMMON_UNICODE],
unicode_slow: HashMap<char, char>,
pub(crate) is_byte_identity: bool,
}
impl Default for Transliteration {
/// Create a new Transliteration with identity mapping for the fast-path.
fn default() -> Self {
let mut fast = ['\0'; COMMON_UNICODE];
for (i, slot) in fast.iter_mut().enumerate() {
*slot = char::from_u32(i as u32).unwrap_or('\0');
let mut fast = [0u8; 256];
for (slot, value) in fast.iter_mut().zip(0..=u8::MAX) {
*slot = value;
}
let mut unicode_fast = ['\0'; COMMON_UNICODE];
for (slot, cp) in unicode_fast.iter_mut().zip(0u32..) {
*slot = char::from_u32(cp).unwrap_or('\0');
}
Self {
fast,
slow: HashMap::new(),
byte_fast: fast,
unicode_fast,
unicode_slow: HashMap::new(),
is_byte_identity: true,
}
}
}
@@ -241,25 +281,44 @@ impl Transliteration {
result
}
/// Create through byte mappings from `source` to `target`.
pub fn from_bytes(source: &[u8], target: &[u8]) -> Self {
let mut result = Self::default();
for (&from, &to) in source.iter().zip(target) {
result.byte_fast[from as usize] = to;
}
result
}
/// Set a transliteration mapping from one character to another.
fn insert(&mut self, from: char, to: char) {
let cp = from as usize;
if cp < COMMON_UNICODE {
self.fast[cp] = to;
self.unicode_fast[cp] = to;
} else {
self.slow.insert(from, to);
self.unicode_slow.insert(from, to);
}
if from.is_ascii() && to.is_ascii() {
self.byte_fast[from as usize] = to as u8;
} else {
self.is_byte_identity = false;
}
}
/// Look up a character transliteration.
pub fn lookup(&self, ch: char) -> char {
pub fn lookup_char(&self, ch: char) -> char {
let cp = ch as usize;
if cp < COMMON_UNICODE {
self.fast[cp]
self.unicode_fast[cp]
} else {
self.slow.get(&ch).copied().unwrap_or(ch)
self.unicode_slow.get(&ch).copied().unwrap_or(ch)
}
}
/// Fast byte lookup for pure ASCII transliterations.
pub fn lookup_byte(&self, byte: u8) -> u8 {
self.byte_fast[byte as usize]
}
}
#[derive(Debug)]
@@ -302,16 +361,16 @@ impl Command {
#[derive(Debug)]
/// Command-specific data
/// After parsing, t, b Label elements are converted into BranchTarget ones.
/// After parsing, t, T, b Label elements are converted into BranchTarget ones.
pub enum CommandData {
None,
BranchTarget(Option<Rc<RefCell<Command>>>), // Commands for 'b', 't', '{'
Label(Option<String>), // Label name for 'b', 't', ':'
BranchTarget(Option<Rc<RefCell<Command>>>), // Commands for 'b', 't', 'T', '{'
Label(Option<String>), // Label name for 'b', 't', 'T', ':'
Path(PathBuf), // File path for 'r'
NamedWriter(Rc<RefCell<NamedWriter>>), // File output for 'w'
Number(usize), // Number for 'l', 'q', 'Q' (GNU)
Substitution(Box<Substitution>), // Substitute command 's'
Text(Rc<str>), // Text for 'a', 'c', 'i'
Text(Rc<[u8]>), // Text for 'a', 'c', 'i'
Transliteration(Box<Transliteration>), // Transliteration command 'y'
}
@@ -328,7 +387,7 @@ pub struct InputAction {
/// Next command to execute (rather than commands from start)
pub next_command: Option<Rc<RefCell<Command>>>,
/// Data to prepend to the read contents
pub prepend: String,
pub prepend: Vec<u8>,
}
#[cfg(test)]
@@ -338,7 +397,7 @@ mod tests {
// Return the captures for the RE applied to the specified string
fn caps_for<'a>(re: &str, chunk: &'a mut IOChunk) -> Captures<'a> {
Regex::new(re)
Regex::new(re, CharacterMode::Utf8)
.unwrap()
.captures(chunk)
.unwrap()
@@ -354,26 +413,26 @@ mod tests {
let cmd = Command::default();
let result = template.apply_captures(&cmd, &caps).unwrap();
assert_eq!(result, "");
assert_eq!(result, b"");
}
#[test]
// s/abc/hello/
fn test_literal_only() {
let template = ReplacementTemplate::new(vec![ReplacementPart::Literal("hello".into())]);
let template = ReplacementTemplate::new(vec![ReplacementPart::Literal(b"hello".to_vec())]);
let input = &mut IOChunk::new_from_str("abc");
let caps = caps_for("abc", input);
let cmd = Command::default();
let result = template.apply_captures(&cmd, &caps).unwrap();
assert_eq!(result, "hello");
assert_eq!(result, b"hello");
}
#[test]
// s/foo\d+/got: &/
fn test_whole_match() {
let template = ReplacementTemplate::new(vec![
ReplacementPart::Literal("got: ".into()),
ReplacementPart::Literal(b"got: ".to_vec()),
ReplacementPart::WholeMatch,
]);
let input = &mut IOChunk::new_from_str("foo42");
@@ -381,14 +440,26 @@ mod tests {
let cmd = Command::default();
let result = template.apply_captures(&cmd, &caps).unwrap();
assert_eq!(result, "got: foo42");
assert_eq!(result, b"got: foo42");
}
#[test]
fn test_apply_match_uses_matched_bytes() {
let template = ReplacementTemplate::new(vec![
ReplacementPart::Literal(b"<".to_vec()),
ReplacementPart::WholeMatch,
ReplacementPart::Literal(b">".to_vec()),
]);
let m = Match::from_bytes(1, 3, b"\xE9x");
assert_eq!(template.apply_match(&m), b"<\xE9x>");
}
#[test]
// s/foo(\d+)/number: \1/
fn test_backreference() {
let template = ReplacementTemplate::new(vec![
ReplacementPart::Literal("number: ".into()),
ReplacementPart::Literal(b"number: ".to_vec()),
ReplacementPart::Group(1),
]);
let input = &mut IOChunk::new_from_str("foo42");
@@ -396,16 +467,16 @@ mod tests {
let cmd = Command::default();
let result = template.apply_captures(&cmd, &caps).unwrap();
assert_eq!(result, "number: 42");
assert_eq!(result, b"number: 42");
}
#[test]
// s/(\w+):(\d+)/key: \1, value: \2/
fn test_multiple_parts() {
let template = ReplacementTemplate::new(vec![
ReplacementPart::Literal("key: ".into()),
ReplacementPart::Literal(b"key: ".to_vec()),
ReplacementPart::Group(1),
ReplacementPart::Literal(", value: ".into()),
ReplacementPart::Literal(b", value: ".to_vec()),
ReplacementPart::Group(2),
]);
let input = &mut IOChunk::new_from_str("x:123");
@@ -413,16 +484,16 @@ mod tests {
let cmd = Command::default();
let result = template.apply_captures(&cmd, &caps).unwrap();
assert_eq!(result, "key: x, value: 123");
assert_eq!(result, b"key: x, value: 123");
}
#[test]
// s/(\w+):(\d+)/key: \1, value: \3/
fn test_invalid_group() {
let template = ReplacementTemplate::new(vec![
ReplacementPart::Literal("key: ".into()),
ReplacementPart::Literal(b"key: ".to_vec()),
ReplacementPart::Group(1),
ReplacementPart::Literal(", value: ".into()),
ReplacementPart::Literal(b", value: ".to_vec()),
ReplacementPart::Group(3),
]);
let input = &mut IOChunk::new_from_str("x:123");
@@ -440,11 +511,11 @@ mod tests {
#[test]
fn test_max_group_number_with_groups() {
let template = ReplacementTemplate::new(vec![
ReplacementPart::Literal("a".into()),
ReplacementPart::Literal(b"a".to_vec()),
ReplacementPart::Group(2),
ReplacementPart::WholeMatch,
ReplacementPart::Group(5),
ReplacementPart::Literal("z".into()),
ReplacementPart::Literal(b"z".to_vec()),
]);
assert_eq!(template.max_group_number, 5);
}
@@ -452,9 +523,9 @@ mod tests {
#[test]
fn test_max_group_number_without_groups() {
let template = ReplacementTemplate::new(vec![
ReplacementPart::Literal("no".into()),
ReplacementPart::Literal(b"no".to_vec()),
ReplacementPart::WholeMatch,
ReplacementPart::Literal("groups".into()),
ReplacementPart::Literal(b"groups".to_vec()),
]);
assert_eq!(template.max_group_number, 0);
}
@@ -464,16 +535,31 @@ mod tests {
#[test]
fn test_identity_lookup_fast_path() {
let t = Transliteration::default();
assert_eq!(t.lookup('A'), 'A');
assert_eq!(t.lookup('z'), 'z');
assert_eq!(t.lookup('\u{07FF}'), '\u{07FF}'); // highest 2-byte UTF-8 char
assert_eq!(t.lookup_char('A'), 'A');
assert_eq!(t.lookup_char('z'), 'z');
assert_eq!(t.lookup_char('\u{07FF}'), '\u{07FF}'); // highest 2-byte UTF-8 char
}
#[test]
fn test_identity_lookup_slow_path() {
let t = Transliteration::default();
assert_eq!(t.lookup('\u{0800}'), '\u{0800}'); // just outside fast path
assert_eq!(t.lookup('\u{1F600}'), '\u{1F600}'); // 😀
assert_eq!(t.lookup_char('\u{0800}'), '\u{0800}'); // just outside fast path
assert_eq!(t.lookup_char('\u{1F600}'), '\u{1F600}'); // 😀
}
#[test]
fn test_from_bytes_and_lookup_byte() {
let t = Transliteration::from_bytes(b"a\xE9", b"Z!");
assert_eq!(t.lookup_byte(b'a'), b'Z');
assert_eq!(t.lookup_byte(0xE9), b'!');
assert_eq!(t.lookup_byte(b'b'), b'b');
}
#[test]
fn test_is_byte_identity_tracks_non_ascii_character_mappings() {
assert!(Transliteration::from_strings("ab", "xy").is_byte_identity);
assert!(!Transliteration::from_strings("aé", "xy").is_byte_identity);
assert!(!Transliteration::from_strings("ab", "xé").is_byte_identity);
}
#[test]
@@ -481,38 +567,38 @@ mod tests {
let mut t = Transliteration::default();
t.insert('a', 'α');
t.insert('b', 'β');
assert_eq!(t.lookup('a'), 'α');
assert_eq!(t.lookup('b'), 'β');
assert_eq!(t.lookup('c'), 'c'); // unchanged
assert_eq!(t.lookup_char('a'), 'α');
assert_eq!(t.lookup_char('b'), 'β');
assert_eq!(t.lookup_char('c'), 'c'); // unchanged
}
#[test]
fn test_insert_and_lookup_slow_path() {
let mut t = Transliteration::default();
t.insert('🦀', 'c'); // U+1F980 Crab emoji -> 'c'
assert_eq!(t.lookup('🦀'), 'c');
assert_eq!(t.lookup('🦁'), '🦁'); // unchanged
assert_eq!(t.lookup_char('🦀'), 'c');
assert_eq!(t.lookup_char('🦁'), '🦁'); // unchanged
}
#[test]
fn test_overwrite_mapping() {
let mut t = Transliteration::default();
t.insert('x', '1');
assert_eq!(t.lookup('x'), '1');
assert_eq!(t.lookup_char('x'), '1');
t.insert('x', '2');
assert_eq!(t.lookup('x'), '2');
assert_eq!(t.lookup_char('x'), '2');
}
#[test]
fn test_all_fast_path_mapped_to_space() {
let mut t = Transliteration::default();
for cp in 0..COMMON_UNICODE {
if let Some(ch) = char::from_u32(cp as u32) {
if let Some(ch) = u32::try_from(cp).ok().and_then(char::from_u32) {
t.insert(ch, ' ');
}
}
assert_eq!(t.lookup('A'), ' ');
assert_eq!(t.lookup('\u{07FF}'), ' ');
assert_eq!(t.lookup_char('A'), ' ');
assert_eq!(t.lookup_char('\u{07FF}'), ' ');
}
// from_strings
@@ -520,11 +606,11 @@ mod tests {
fn test_basic_transliteration() {
let t = Transliteration::from_strings("abcδ", "1234");
assert_eq!(t.lookup('a'), '1');
assert_eq!(t.lookup('b'), '2');
assert_eq!(t.lookup('c'), '3');
assert_eq!(t.lookup('δ'), '4');
assert_eq!(t.lookup('e'), 'e'); // not mapped, fallback
assert_eq!(t.lookup_char('a'), '1');
assert_eq!(t.lookup_char('b'), '2');
assert_eq!(t.lookup_char('c'), '3');
assert_eq!(t.lookup_char('δ'), '4');
assert_eq!(t.lookup_char('e'), 'e'); // not mapped, fallback
}
#[test]
@@ -533,16 +619,16 @@ mod tests {
let target = "e文c";
let t = Transliteration::from_strings(source, target);
assert_eq!(t.lookup('é'), 'e');
assert_eq!(t.lookup('漢'), '文');
assert_eq!(t.lookup('🦀'), 'c');
assert_eq!(t.lookup('x'), 'x'); // fast fallback
assert_eq!(t.lookup('文'), '文'); // slow fallback
assert_eq!(t.lookup_char('é'), 'e');
assert_eq!(t.lookup_char('漢'), '文');
assert_eq!(t.lookup_char('🦀'), 'c');
assert_eq!(t.lookup_char('x'), 'x'); // fast fallback
assert_eq!(t.lookup_char('文'), '文'); // slow fallback
}
#[test]
fn test_overwrite_fast_path() {
let t = Transliteration::from_strings("aa", "12");
assert_eq!(t.lookup('a'), '2'); // last mapping wins
assert_eq!(t.lookup_char('a'), '2'); // last mapping wins
}
}
+934 -222
View File
File diff suppressed because it is too large. Load diff
Loaded 20 of 45 files, more files were not shown because too many files have changed in this diff. Show more