157 Commits
Author SHA1 Message Date
renovate[bot]andDaniel Hofstetter 3c7488ba1f chore(deps): update rust crate regex to v1.12.4 2026-06-10 07:21:44 +02:00
renovate[bot]andSylvestre Ledru 5122acc896 chore(deps): update codecov/codecov-action action to v7 2026-06-07 09:05:21 +02:00
renovate[bot]andDaniel Hofstetter a9a332af1c chore(deps): update rust crate libfuzzer-sys to v0.4.13 2026-06-05 07:20:26 +02:00
renovate[bot]andDaniel Hofstetter b765a4b0c1 chore(deps): update rust crate chrono to v0.4.45 2026-06-04 17:05:21 +02:00
DevelandGitHub 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 SpinellisandGitHub 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 SpinellisandGitHub 3b846498f4 Add current POSIX specification link
Link to POSIX document to help contributors.
2026-06-01 21:41:25 +03:00
renovate[bot]andDaniel Hofstetter 238648ddbf chore(deps): update rust crate uutests to 0.9.0 2026-05-31 14:41:47 +02:00
renovate[bot]andDaniel Hofstetter d37963afcb fix(deps): update rust crate uucore to 0.9.0 2026-05-31 10:38:54 +02:00
renovate[bot]andDaniel Hofstetter 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]andDaniel Hofstetter 7c6cbf9dba chore(deps): update rust crate ctor to v1.0.7 2026-05-29 08:29:01 +02:00
renovate[bot]andDaniel Hofstetter a65af95b5c chore(deps): update rust crate memchr to v2.8.1 2026-05-27 08:10:43 +02:00
renovate[bot]andDaniel Hofstetter 47cebdd562 chore(deps): update rust crate assert_fs to v1.1.4 2026-05-27 07:53:21 +02:00
mukunda kattaandSylvestre Ledru f6e69e734b fix: preserve latest binary executable mode 2026-05-24 17:57:32 +02:00
renovate[bot]andDaniel Hofstetter 2dbeaea2e1 chore(deps): update rust crate similar to v3.1.1 2026-05-24 13:02:25 +02:00
renovate[bot]andDaniel Hofstetter 5cef1c4327 chore(deps): update rust crate divan to v4.7.0 2026-05-23 07:09:32 +02:00
renovate[bot]andDaniel Hofstetter 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]andDaniel Hofstetter f083fa0634 chore(deps): update rust crate ctor to v1.0.5 2026-05-12 07:12:43 +02:00
renovate[bot]andDaniel Hofstetter ff28e7d83d chore(deps): update rust crate clap_complete to v4.6.5 2026-05-12 07:08:52 +02:00
renovate[bot]andDaniel Hofstetter 6c00065099 chore(deps): update rust crate clap_complete to v4.6.4 2026-05-10 10:11:34 +02:00
renovate[bot]andDaniel Hofstetter 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]andDaniel Hofstetter 5856b37cea chore(deps): update rust crate ctor to v1.0.1 2026-05-05 07:19:02 +02:00
renovate[bot]andDaniel Hofstetter 84c32f8ef0 chore(deps): update rust crate ctor to v1 2026-05-04 07:35:50 +02:00
LoukasPapandSylvestre Ledru 433635b583 Fix --separate flag 2026-05-03 13:28:03 +02:00
xtqqczzeandSylvestre Ledru 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
xtqqczzeandSylvestre Ledru b94b148638 clippy: update Cargo.toml 2026-05-03 13:27:20 +02:00
Daniel HofstetterandSylvestre Ledru a6c028b782 Move ctor to dev-dependencies 2026-05-03 13:22:23 +02:00
Daniel HofstetterandSylvestre Ledru 7e7abc9407 tests.rs: adapt to ctor change 2026-05-03 13:22:23 +02:00
Daniel HofstetterandSylvestre Ledru fef7a40433 Bump ctor from 0.12.0 to 0.13.0 2026-05-03 13:22:23 +02:00
xtqqczzeandSylvestre Ledru 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
LoukasPapandSylvestre Ledru b7072cb4a5 Fix \?, \+ and \| to be treated as ERE in BRE mode 2026-04-30 22:05:25 +02:00
xtqqczzeandSylvestre Ledru 6215664b2e actions: add security audit workflow 2026-04-30 22:03:29 +02:00
renovate[bot]andSylvestre Ledru ffe4130654 fix(deps): update rust crate ctor to 0.12.0 2026-04-30 22:02:17 +02:00
renovate[bot]andDaniel Hofstetter e435b9be6c chore(deps): update rust crate divan to v4.6.0 2026-04-29 07:32:05 +02:00
renovate[bot]andDaniel Hofstetter 5d9f08af1c fix(deps): update rust crate ctor to 0.11.0 2026-04-29 07:28:37 +02:00
renovate[bot]andDaniel Hofstetter 14b156995e chore(deps): update dawidd6/action-download-artifact action to v21 2026-04-28 13:31:38 +02:00
renovate[bot]andDaniel Hofstetter 187ae761b1 chore(deps): update rust crate clap_complete to v4.6.3 2026-04-27 18:34:32 +02:00
renovate[bot]andDaniel Hofstetter b96e0e78d8 chore(deps): update rust crate fancy-regex to 0.18.0 2026-04-24 07:34:42 +02:00
renovate[bot]andDaniel Hofstetter 442c2ed0b9 chore(deps): update rust crate libc to v0.2.186 2026-04-24 07:10:01 +02:00
pocopepeandSylvestre Ledru d7a713e3c8 fix: correct newline handling for p and x commands 2026-04-23 08:44:58 +02:00
renovate[bot]andDaniel Hofstetter 0cf23a8e34 chore(deps): update mozilla-actions/sccache-action action to v0.0.10 2026-04-23 07:28:32 +02:00
renovate[bot]andDaniel Hofstetter 2724f4ad75 chore(deps): update rust crate ctor to v0.10.1 2026-04-23 07:14:50 +02:00
oech3andDaniel Hofstetter ebb321e90e Add CI for wasm32 2026-04-21 14:20:02 +02:00
xtqqczzeandDaniel Hofstetter 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
pocopepeandDaniel Hofstetter a2da4c1a3b assert_fs belongs in dev-dependencies 2026-04-21 11:12:10 +02:00
renovate[bot]andDaniel Hofstetter 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]andDaniel Hofstetter 4cd9858bfd chore(deps): update rust crate clap to v4.6.1 2026-04-16 14:51:38 +02:00
renovate[bot]andDaniel Hofstetter b4c567e43e chore(deps): update rust crate clap_complete to v4.6.2 2026-04-14 07:16:04 +02:00
renovate[bot]andDaniel Hofstetter fc2db1e335 chore(deps): update rust crate libc to v0.2.185 2026-04-13 15:59:24 +02:00
renovate[bot]andDaniel Hofstetter 35bc81a5cf fix(deps): update rust crate ctor to 0.10.0 2026-04-13 07:11:26 +02:00
YumeYukaandSylvestre Ledru 59d24ac8dd ci: add Android build check for 32-bit and 64-bit targets 2026-04-12 17:29:50 +02:00
白彩恋andSylvestre Ledru 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]andDaniel Hofstetter 2e76232e05 chore(deps): update rust crate similar to v3.1.0 2026-04-12 10:24:00 +02:00
renovate[bot]andDaniel Hofstetter b72279afb3 chore(deps): update softprops/action-gh-release action to v3 2026-04-12 10:20:28 +02:00
renovate[bot]andDaniel Hofstetter 08d3454b8c chore(deps): update rust crate rand to v0.10.1 2026-04-11 15:17:59 +02:00
renovate[bot]andDaniel Hofstetter 17f36e91c1 chore(deps): update rust crate clap_complete to v4.6.1 2026-04-11 07:17:20 +02:00
renovate[bot]andDaniel Hofstetter 5e2eb59959 fix(deps): update rust crate ctor to 0.9.0 2026-04-10 07:06:31 +02:00
renovate[bot]andDaniel Hofstetter e3ff6cc268 chore(deps): update actions/github-script action to v9 2026-04-10 07:04:56 +02:00
renovate[bot]andDaniel Hofstetter e012c4b5c0 chore(deps): update rust crate uutests to 0.8.0 2026-04-07 08:02:43 +02:00
renovate[bot]andDaniel Hofstetter 59551c580b fix(deps): update rust crate uucore to 0.8.0 2026-04-07 07:52:39 +02:00
renovate[bot]andDaniel Hofstetter deb64e0ccd fix(deps): update rust crate uufuzz to 0.8.0 2026-04-07 07:12:53 +02:00
oech3andSylvestre Ledru 68b5883b0e Override latest-commit's body with corresponding commit 2026-04-03 11:23:13 +02:00
renovate[bot]andDaniel Hofstetter 1b747a66a8 chore(deps): update dawidd6/action-download-artifact action to v20 2026-04-03 07:15:16 +02:00
renovate[bot]andDaniel Hofstetter 7fade42bb5 fix(deps): update rust crate similar to v3 2026-04-02 07:05:23 +02:00
renovate[bot]andDaniel Hofstetter f334e1000c chore(deps): update rust crate libc to v0.2.184 2026-04-01 15:56:01 +02:00
renovate[bot]andDaniel Hofstetter ed6869944f chore(deps): update rust crate clap_mangen to 0.3 2026-03-28 07:18:48 +01:00
renovate[bot]andDaniel Hofstetter ad01bedff8 fix(deps): update rust crate ctor to 0.8.0 2026-03-28 07:07:46 +01:00
renovate[bot]andDaniel Hofstetter 34651493c1 chore(deps): update codecov/codecov-action action to v6 2026-03-27 07:24:50 +01:00
renovate[bot]andSylvestre Ledru bf7bed170a chore(deps): update rust crate sha2 to 0.11 2026-03-25 18:36:34 +01:00
renovate[bot]andDaniel Hofstetter 1469cccfde chore(deps): update rust crate terminal_size to v0.4.4 2026-03-24 07:29:39 +01:00
LoukasPapandSylvestre Ledru 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
xtqqczzeandSylvestre Ledru 27da0276ec clippy: fix single_match_else lint 2026-03-19 22:05:47 +01:00
xtqqczzeandSylvestre Ledru 2836cb7257 clippy: fix map_unwrap_or lint 2026-03-19 22:05:47 +01:00
xtqqczzeandSylvestre Ledru 8c09b0f5a5 clippy: fix manual_assert lint 2026-03-19 22:05:47 +01:00
xtqqczzeandSylvestre Ledru 5f85e267db clippy: fix ptr_as_ptr lint 2026-03-19 22:05:47 +01:00
xtqqczzeandSylvestre Ledru 1535cfdd9b clippy: fix ref_option lint 2026-03-19 22:05:47 +01:00
xtqqczzeandSylvestre Ledru 8772c166d9 clippy: fix manual_let_else lint 2026-03-19 22:05:47 +01:00
xtqqczzeandSylvestre Ledru 6b6067075b clippy: fix if_not_else lint 2026-03-19 22:05:47 +01:00
xtqqczzeandSylvestre Ledru dc9be6018c clippy: fix cast_lossless lint 2026-03-19 22:05:47 +01:00
xtqqczzeandSylvestre Ledru b295d213e8 clippy: fix borrow_as_ptr 2026-03-19 22:05:47 +01:00
xtqqczzeandSylvestre Ledru d3ca9d27b4 clippy: warn pedantic lints by default 2026-03-19 22:05:47 +01:00
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]andDaniel Hofstetter 81d98b9049 chore(deps): update dawidd6/action-download-artifact action to v19 2026-03-18 10:55:40 +01:00
renovate[bot]andDaniel Hofstetter 25efbc0ab7 chore(deps): update dawidd6/action-download-artifact action to v18 2026-03-15 10:47:13 +01:00
renovate[bot]andDaniel Hofstetter ad1f3d22dc chore(deps): update rust crate console to v0.16.3 2026-03-13 11:26:07 +01:00
renovate[bot]andDaniel Hofstetter 3f3dd1270e chore(deps): update rust crate clap_mangen to v0.2.33 2026-03-13 07:08:17 +01:00
renovate[bot]andDaniel Hofstetter af7131fdb2 chore(deps): update rust crate clap_complete to v4.6.0 2026-03-13 07:07:41 +01:00
renovate[bot]andDaniel Hofstetter 97e70434b9 chore(deps): update rust crate clap to v4.6.0 2026-03-13 07:07:07 +01:00
renovate[bot]andDaniel Hofstetter 19363fa810 chore(deps): update dawidd6/action-download-artifact action to v17 2026-03-12 15:33:11 +01:00
renovate[bot]andDaniel Hofstetter 56e6ef5d2f chore(deps): update rust crate divan to v4.4.1 2026-03-12 15:32:38 +01:00
renovate[bot]andDaniel Hofstetter fadc36512d chore(deps): update rust crate tempfile to v3.27.0 2026-03-11 07:04:15 +01:00
oech3andSylvestre Ledru 61152af62b Quick codspeed setup 2026-03-10 09:06:10 +01:00
renovate[bot]andDaniel Hofstetter 7f16a008c9 fix(deps): update rust crate uucore to 0.7.0 2026-03-10 07:41:48 +01:00
renovate[bot]andDaniel Hofstetter a6d85d8e42 chore(deps): update rust crate sysinfo to v0.38.4 2026-03-10 07:24:50 +01:00
renovate[bot]andDaniel Hofstetter dc91d3709c chore(deps): update rust crate uutests to 0.7.0 2026-03-10 07:16:06 +01:00
renovate[bot]andDaniel Hofstetter 8a3f63db27 fix(deps): update rust crate uufuzz to 0.7.0 2026-03-10 07:10:06 +01:00
renovate[bot]andDaniel Hofstetter 1bb2f89e8d chore(deps): update rust crate libc to v0.2.183 2026-03-08 14:18:24 +01:00
renovate[bot]andDaniel Hofstetter b7df132ba1 chore(deps): update rust crate sysinfo to v0.38.3 2026-03-02 14:36:11 +01:00
renovate[bot]andDaniel Hofstetter 9f6f052c27 chore(deps): update github artifact actions 2026-02-27 06:57:50 +01:00
renovate[bot]andDaniel Hofstetter 569ec711ac chore(deps): update dawidd6/action-download-artifact action to v16 2026-02-26 07:08:58 +01:00
renovate[bot]andDaniel Hofstetter e075bff043 chore(deps): update rust crate tempfile to v3.26.0 2026-02-24 11:04:35 +01:00
renovate[bot]andDaniel Hofstetter 628d320e5b chore(deps): update rust crate chrono to v0.4.44 2026-02-23 14:11:54 +01:00
oech3andDaniel Hofstetter d102d0be73 reproducible fuzzing, fast setup 2026-02-20 10:50:47 +01:00
renovate[bot]andDaniel Hofstetter 01b190c59a chore(deps): update rust crate clap to v4.5.60 2026-02-20 07:21:35 +01:00
renovate[bot]andDaniel Hofstetter 5ecd6afedb chore(deps): update dawidd6/action-download-artifact action to v15 2026-02-18 13:38:50 +01:00
renovate[bot]andDaniel Hofstetter 81e71bcb49 chore(deps): update rust crate clap to v4.5.59 2026-02-17 07:06:36 +01:00
renovate[bot]andSylvestre Ledru b169b4dfc2 chore(deps): update rust crate sysinfo to v0.38.2 2026-02-15 18:47:48 +01:00
renovate[bot]andDaniel Hofstetter 049e149ea5 chore(deps): update rust crate memmap2 to v0.9.10 2026-02-15 13:47:06 +01:00
Sylvestre LedruandGitHub 91affcf90b Update CodeCov badge to point to main branch 2026-02-15 12:11:40 +01:00
oech3andSylvestre Ledru 99c8574fc5 Reduce codegen-units to 7 2026-02-15 11:24:26 +01:00
oech3andSylvestre Ledru b5b4cefb77 Drop apt-get 2026-02-13 14:36:53 +01:00
renovate[bot]andDaniel Hofstetter fee1ff33ae chore(deps): update rust crate libc to v0.2.182 2026-02-13 10:48:45 +01:00
Alexander WangandGitHub 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 HofstetterandSylvestre Ledru c0c85e65fa Bump bigdecimal from 0.4.8 to 0.4.10 2026-02-13 09:46:20 +01:00
Daniel HofstetterandSylvestre Ledru ab6f9d9810 Bump uufuzz from 0.5.0 to 0.6.0 2026-02-13 09:46:20 +01:00
renovate[bot]andDaniel Hofstetter 1d04c82eb8 chore(deps): update rust crate clap_complete to v4.5.66 2026-02-12 07:17:08 +01:00
renovate[bot]andDaniel Hofstetter cdae35308d chore(deps): update rust crate clap to v4.5.58 2026-02-12 07:16:55 +01:00
renovate[bot]andDaniel Hofstetter b926499ed4 chore(deps): update rust crate predicates to v3.1.4 2026-02-12 07:09:00 +01:00
renovate[bot]andDaniel Hofstetter 4be480767c chore(deps): update rust crate libfuzzer-sys to v0.4.12 2026-02-11 07:01:08 +01:00
renovate[bot]andDaniel Hofstetter aeac7d1e63 chore(deps): update rust crate tempfile to v3.25.0 2026-02-10 07:18:27 +01:00
renovate[bot]andDaniel Hofstetter 3c7b8ff4ba chore(deps): update rust crate libc to v0.2.181 2026-02-10 07:09:28 +01:00
Daniel HofstetterandSylvestre Ledru a177e97cfb Bump rand from 0.9.0 to 0.10.0 2026-02-09 16:42:11 +01:00
LoukasPapandSylvestre Ledru 361c45e6c3 Add test cases 2026-02-08 18:48:17 +01:00
LoukasPapandSylvestre Ledru 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]andSylvestre Ledru 1bb2609f16 fix(deps): update rust crate uucore to 0.6.0 2026-02-08 15:18:37 +01:00
LoukasPapandSylvestre Ledru e3684f0029 Add test case 2026-02-08 15:17:59 +01:00
LoukasPapandSylvestre Ledru f27060898a Allow multiple uses of same CLI argument 2026-02-08 15:17:59 +01:00
Daniel HofstetterandGitHub 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 HofstetterandGitHub 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]andGitHub 8d7559b52d chore(deps): update rust crate sysinfo to v0.38.1 2026-02-07 01:49:07 +00:00
renovate[bot]andGitHub 995e32a70d chore(deps): update rust crate memchr to v2.8.0 2026-02-06 20:46:42 +00:00
Daniel HofstetterandGitHub 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]andGitHub 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 HofstetterandGitHub 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 HofstetterandGitHub 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]andGitHub 0a09d32b00 chore(deps): update rust crate regex to v1.12.3 2026-02-03 18:31:23 +00:00
renovate[bot]andGitHub da2715dcb5 chore(deps): update rust crate clap to v4.5.57 2026-02-03 18:31:18 +00:00
Daniel HofstetterandGitHub 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]andGitHub 2eeaa80c3b chore(deps): update rust crate uutests to 0.6.0 2026-02-03 13:35:42 +00:00
Daniel HofstetterandSylvestre Ledru a6207cbebc Bump rlimit from 0.10.1 to 0.11.0 2026-02-02 08:57:25 +01:00
Daniel HofstetterandGitHub 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]andGitHub 003b579967 chore(deps): update dawidd6/action-download-artifact action to v14 2026-02-01 15:00:12 +00:00
Daniel HofstetterandGitHub 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]andGitHub 98c4dab5dd chore(deps): update dawidd6/action-download-artifact action to v13 2026-01-30 15:15:24 +00:00
Daniel HofstetterandGitHub 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]andGitHub 3cf5ea5beb chore(deps): update rust crate clap to v4.5.56 2026-01-29 18:36:39 +00:00
oech3andSylvestre Ledru 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
37 changed files with 2202 additions and 1461 deletions
+2 -2
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
@@ -49,7 +49,7 @@ 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: |
+14 -12
View File
@@ -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: |
@@ -132,7 +134,7 @@ jobs:
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 }}"
@@ -262,7 +264,7 @@ jobs:
- 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@v6
with:
persist-credentials: false
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
+5 -10
View File
@@ -29,21 +29,16 @@ jobs:
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.10
- 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
+22 -1
View File
@@ -17,6 +17,27 @@ jobs:
- 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@v6
- 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 }}
@@ -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 }}
+1 -1
View File
@@ -51,7 +51,7 @@ jobs:
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.10
- name: Initialize workflow variables
id: vars
shell: bash
+13 -9
View File
@@ -24,15 +24,17 @@ jobs:
- uses: actions/checkout@v6
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
@@ -50,9 +52,10 @@ jobs:
- uses: actions/checkout@v6
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"
@@ -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
@@ -146,7 +150,7 @@ jobs:
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: |
@@ -163,7 +167,7 @@ jobs:
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
+11 -11
View File
@@ -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
@@ -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: |
@@ -180,14 +180,14 @@ jobs:
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: |
@@ -230,14 +230,14 @@ jobs:
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
+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@v6
- 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
+463 -356
View File
File diff suppressed because it is too large Load Diff
+49 -27
View File
@@ -35,9 +35,9 @@ chrono = { version = "0.4.37", default-features = false, features = [
] }
clap = { version = "4.4", features = ["wrap_help", "cargo"] }
clap_complete = "4.5"
clap_mangen = "0.2"
clap_mangen = "0.3"
divan = { package = "codspeed-divan-compat", version = "4.0.5" }
fancy-regex = "0.17.0"
fancy-regex = "0.18.0"
hex = "0.4"
libc = "0.2.153"
memchr = "2.7.4"
@@ -45,23 +45,21 @@ memmap2 = "0.9"
phf = "0.13.0"
phf_codegen = "0.13.0"
predicates = "3.1.3"
rand = { version = "0.9", features = ["small_rng"] }
rand = { version = "0.10.0" }
regex = "1.10.4"
sha2 = "0.10"
sha2 = "0.11"
sysinfo = "0.38"
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.9.0", features = ["libc"] }
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
@@ -75,7 +73,9 @@ textwrap = { workspace = true }
uucore = { 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 +85,13 @@ regex = { workspace = true }
sha2 = { workspace = true }
tempfile = { workspace = true }
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
uutests = "0.5.0"
uutests = "0.9.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 +107,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"
+15 -5
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).
@@ -28,7 +28,10 @@ Further work aims to:
## Installation
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:
@@ -45,7 +48,8 @@ The binary is named `sed` in `target/release/sed`.
### 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 +58,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
@@ -78,6 +87,7 @@ cargo test
* 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.
+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.
+488 -481
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.9.0", features = ["libc"] }
uufuzz = "0.9.0"
sed = { path = ".." }
+2 -2
View File
@@ -162,12 +162,12 @@ impl ReplacementTemplate {
ReplacementPart::Literal(s) => result.push_str(s),
ReplacementPart::WholeMatch => {
result.push_str(caps.get(0)?.map(|m| m.as_str()).unwrap_or(""));
result.push_str(caps.get(0)?.map(|m| m.as_str()).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.push_str(caps.get(i)?.map(|m| m.as_str()).unwrap_or_default());
}
}
}
+105 -56
View File
@@ -120,7 +120,7 @@ fn patch_block_endings(head: Option<Rc<RefCell<Command>>>) {
}
// 3) splice the tails `.next` to splice_target
tail.borrow_mut().next = splice_target.clone();
tail.borrow_mut().next.clone_from(&splice_target);
}
// drop the borrow before moving on
@@ -140,7 +140,7 @@ fn populate_label_map(
mut cur: Option<Rc<RefCell<Command>>>,
context: &mut ProcessingContext,
) -> UResult<()> {
while let Some(rc_cmd) = cur {
while let Some(rc_cmd) = cur.take() {
// Borrow mutably just long enough to inspect/rewire this node
let cmd = rc_cmd.borrow_mut();
@@ -163,14 +163,14 @@ fn populate_label_map(
context.label_to_command_map.insert(label, rc_cmd.clone());
}
cur = cmd.next.clone();
cur.clone_from(&cmd.next);
}
Ok(())
}
/// Populate the context's address range command list with references to associated commands.
fn populate_range_commands(mut cur: Option<Rc<RefCell<Command>>>, context: &mut ProcessingContext) {
while let Some(rc_cmd) = cur {
while let Some(rc_cmd) = cur.take() {
// Borrow mutably just long enough to inspect/rewire this node
let cmd = rc_cmd.borrow_mut();
@@ -184,7 +184,7 @@ fn populate_range_commands(mut cur: Option<Rc<RefCell<Command>>>, context: &mut
context.range_commands.push(Rc::clone(&rc_cmd));
}
cur = cmd.next.clone();
cur.clone_from(&cmd.next);
}
}
@@ -194,7 +194,7 @@ fn resolve_branch_targets(
mut cur: Option<Rc<RefCell<Command>>>,
context: &mut ProcessingContext,
) -> UResult<()> {
while let Some(rc_cmd) = cur {
while let Some(rc_cmd) = cur.take() {
// Borrow mutably just long enough to inspect/rewire this node
let mut cmd = rc_cmd.borrow_mut();
@@ -233,7 +233,7 @@ fn resolve_branch_targets(
}
// Advance to the next sibling
cur = cmd.next.clone();
cur.clone_from(&cmd.next);
}
Ok(())
}
@@ -285,14 +285,14 @@ fn compile_sequence(
let mut cmd = Rc::new(RefCell::new(Command::at_position(lines, line)));
let n_addr = compile_address_range(lines, line, &mut cmd, context)?;
line.eat_spaces();
let mut cmd_spec = get_verified_cmd_spec(lines, line, n_addr)?;
let mut cmd_spec = get_verified_cmd_spec(lines, line, n_addr, context.posix)?;
// Compile the command according to its specification.
let mut cmd_mut = cmd.borrow_mut();
cmd_mut.code = line.current();
match (cmd_spec.handler)(lines, line, &mut cmd_mut, context)? {
CommandHandling::GetNext => {
cmd_spec = get_verified_cmd_spec(lines, line, n_addr)?;
cmd_spec = get_verified_cmd_spec(lines, line, n_addr, context.posix)?;
cmd_mut.code = line.current();
(cmd_spec.handler)(lines, line, &mut cmd_mut, context)?;
}
@@ -498,9 +498,8 @@ fn parse_number(
if num_str.is_empty() {
if required {
return compilation_error(lines, line, "number expected");
} else {
return Ok(None);
}
return Ok(None);
}
num_str
@@ -533,7 +532,7 @@ fn parse_command_ending(
}
/// Convert a primitive BRE pattern to a safe ERE-compatible pattern string.
/// - Replaces `\(` and `\)` with `(` and `)`.
/// - Replaces `\(`, `\)`, `\?`, `\+` and `\|` with `(`, `)`, `?`, `+` and `|`.
/// - Puts single-digit back-references in non-capturing groups..
/// - Escapes ERE-only metacharacters: `+ ? { } | ( )`.
/// - Leaves all other characters as-is.
@@ -554,6 +553,18 @@ fn bre_to_ere(pattern: &str) -> String {
chars.next();
result.push(')'); // Group end
}
Some('?') => {
chars.next();
result.push('?'); // Quantifier 0 or 1
}
Some('+') => {
chars.next();
result.push('+'); // Quantifier 1 or more
}
Some('|') => {
chars.next();
result.push('|'); // Alternation operator
}
Some(v) if v.is_ascii_digit() => {
// Back-reference. In sed BREs these are single-digit
// (\1-\9) whereas fancy_regex supports multi-digit
@@ -668,13 +679,12 @@ pub fn compile_replacement(
literal.push('\n');
*line = ScriptCharProvider::new(&next_line_string);
continue;
} else {
return compilation_error(
lines,
line,
"unterminated substitute replacement (unexpected EOF)",
);
}
return compilation_error(
lines,
line,
"unterminated substitute replacement (unexpected EOF)",
);
}
match line.current() {
@@ -706,14 +716,15 @@ pub fn compile_replacement(
}
// other escape sequences
_ => match parse_char_escape(line) {
Some(decoded) => literal.push(decoded),
None => {
_ => {
if let Some(decoded) = parse_char_escape(line) {
literal.push(decoded);
} else {
literal.push('\\');
literal.push(line.current());
line.advance();
}
},
}
}
}
@@ -1118,7 +1129,6 @@ fn compile_text_command(
context: &mut ProcessingContext,
) -> UResult<CommandHandling> {
line.advance(); // Skip the command character.
line.eat_spaces(); // Skip any leading whitespace.
if context.posix {
compile_text_command_posix(lines, line, cmd, context)
@@ -1141,6 +1151,14 @@ fn compile_text_command_gnu(
// True after a \ at the end of a line
let mut escaped_newline = false;
if line.eol() {
return compilation_error(
lines,
line,
format!("command `{}' expects \\ followed by text", cmd.code),
);
}
// Skip optional \.
if !line.eol() && line.current() == '\\' {
line.advance();
@@ -1177,13 +1195,12 @@ fn compile_text_command_gnu(
continue 'text_content;
}
match parse_char_escape(line) {
Some(decoded) => text.push(decoded),
None => {
// Invalid escapes result in the escaped character.
text.push(line.current());
line.advance();
}
if let Some(decoded) = parse_char_escape(line) {
text.push(decoded);
} else {
// Invalid escapes result in the escaped character.
text.push(line.current());
line.advance();
}
} else {
text.push(line.current());
@@ -1236,6 +1253,11 @@ fn compile_text_command_posix(
break;
}
}
if text.is_empty() {
compilation_error(lines, line, "incomplete command")?;
}
cmd.data = CommandData::Text(Rc::from(text));
Ok(CommandHandling::Continue)
}
@@ -1246,13 +1268,14 @@ fn get_verified_cmd_spec(
lines: &ScriptLineProvider,
line: &ScriptCharProvider,
n_addr: usize,
posix: bool,
) -> UResult<CommandSpec> {
if line.eol() {
return compilation_error(lines, line, "command expected");
}
let ch = line.current();
let cmd_spec = get_cmd_spec(lines, line, ch)?;
let cmd_spec = get_cmd_spec(lines, line, ch, posix)?;
if n_addr > cmd_spec.n_addr {
return compilation_error(
@@ -1273,6 +1296,7 @@ fn get_cmd_spec(
lines: &ScriptLineProvider,
line: &ScriptCharProvider,
cmd_code: char,
posix: bool,
) -> UResult<CommandSpec> {
match cmd_code {
'!' => Ok(CommandSpec {
@@ -1280,7 +1304,7 @@ fn get_cmd_spec(
handler: compile_negation_command,
}),
'=' => Ok(CommandSpec {
n_addr: 1,
n_addr: if posix { 1 } else { 2 },
handler: compile_empty_command,
}),
':' => Ok(CommandSpec {
@@ -1296,7 +1320,7 @@ fn get_cmd_spec(
handler: compile_end_group_command,
}),
'a' | 'i' => Ok(CommandSpec {
n_addr: 1,
n_addr: if posix { 1 } else { 2 },
handler: compile_text_command,
}),
'b' | 't' => Ok(CommandSpec {
@@ -1315,13 +1339,17 @@ fn get_cmd_spec(
n_addr: 2,
handler: compile_number_command,
}),
'q' => Ok(CommandSpec {
n_addr: if posix { 1 } else { 2 },
handler: compile_number_command,
}),
// Q is a GNU extension
'q' | 'Q' => Ok(CommandSpec {
'Q' => Ok(CommandSpec {
n_addr: 1,
handler: compile_number_command,
}),
'r' => Ok(CommandSpec {
n_addr: 1,
n_addr: if posix { 1 } else { 2 },
handler: compile_read_file_command,
}),
's' => Ok(CommandSpec {
@@ -1373,35 +1401,35 @@ mod tests {
#[test]
fn test_lookup_empty_command() {
let (lines, line) = make_providers("123abc");
let cmd = get_cmd_spec(&lines, &line, 'd').unwrap();
let cmd = get_cmd_spec(&lines, &line, 'd', false).unwrap();
assert_eq!(cmd.n_addr, 2);
}
#[test]
fn test_lookup_text_command() {
let (lines, line) = make_providers("123abc");
let cmd = get_cmd_spec(&lines, &line, 'a').unwrap();
assert_eq!(cmd.n_addr, 1);
let cmd = get_cmd_spec(&lines, &line, 'a', false).unwrap();
assert_eq!(cmd.n_addr, 2);
}
#[test]
fn test_lookup_nonselect_command() {
let (lines, line) = make_providers("123abc");
let cmd = get_cmd_spec(&lines, &line, '!').unwrap();
let cmd = get_cmd_spec(&lines, &line, '!', false).unwrap();
assert_eq!(cmd.n_addr, 2);
}
#[test]
fn test_lookup_endgroup_command() {
let (lines, line) = make_providers("123abc");
let cmd = get_cmd_spec(&lines, &line, '}').unwrap();
let cmd = get_cmd_spec(&lines, &line, '}', false).unwrap();
assert_eq!(cmd.n_addr, 0);
}
#[test]
fn test_lookup_invalid_command() {
let (lines, line) = make_providers("123abc");
let result = get_cmd_spec(&lines, &line, 'Z');
let result = get_cmd_spec(&lines, &line, 'Z', false);
assert!(result.is_err());
}
@@ -1453,7 +1481,7 @@ mod tests {
fn test_missing_command_character() {
let lines = ScriptLineProvider::with_active_state("test.sed", 1);
let line = char_provider_from("");
let result = get_verified_cmd_spec(&lines, &line, 0);
let result = get_verified_cmd_spec(&lines, &line, 0, ctx().posix);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
@@ -1464,7 +1492,7 @@ mod tests {
fn test_invalid_command_character() {
let lines = ScriptLineProvider::with_active_state("script.sed", 2);
let line = char_provider_from("@");
let result = get_verified_cmd_spec(&lines, &line, 0);
let result = get_verified_cmd_spec(&lines, &line, 0, ctx().posix);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
@@ -1475,7 +1503,7 @@ mod tests {
fn test_too_many_addresses() {
let lines = ScriptLineProvider::with_active_state("input.sed", 3);
let line = char_provider_from("q"); // q takes one address
let result = get_verified_cmd_spec(&lines, &line, 2);
let result = get_verified_cmd_spec(&lines, &line, 2, true);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
@@ -1488,11 +1516,22 @@ mod tests {
fn test_valid_command_spec() {
let lines = ScriptLineProvider::with_active_state("input.sed", 4);
let line = char_provider_from("a"); // valid command
let result = get_verified_cmd_spec(&lines, &line, 1);
let result = get_verified_cmd_spec(&lines, &line, 2, ctx().posix);
assert!(result.is_ok());
let spec = result.unwrap();
assert_eq!(spec.n_addr, 1);
assert_eq!(spec.n_addr, 2);
}
#[test]
fn test_invalid_address_range_posix() {
let lines = ScriptLineProvider::with_active_state("input.sed", 1);
let line = char_provider_from("i"); // valid command
let result = get_verified_cmd_spec(&lines, &line, 2, true);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("input.sed:1:1: error: command i expects up to 1 address(es), found 2")
);
}
// parse_number
@@ -2171,7 +2210,7 @@ mod tests {
// bre_to_ere
#[test]
fn test_bre_group_translation() {
assert_eq!(bre_to_ere(r"\(abc\)"), "(abc)");
assert_eq!(bre_to_ere(r"\(a\?b\+c\|\)"), "(a?b+c|)");
assert_eq!(bre_to_ere(r"a\(b\)c"), "a(b)c");
}
@@ -2720,6 +2759,19 @@ mod tests {
}
}
#[test]
fn test_compile_text_command_posix_incomplete() {
let (mut lines, mut chars) = make_providers("i\\");
let mut cmd = Command::default();
let mut context = ProcessingContext {
posix: true,
..Default::default()
};
let result = compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context);
let err = result.unwrap_err().to_string();
assert!(err.contains("incomplete command"));
}
#[test]
fn test_compile_text_command_gnu_optional_backslash() {
let mut chars = make_char_provider("athere");
@@ -2753,19 +2805,16 @@ mod tests {
}
#[test]
fn test_compile_text_command_gnu_optional_backslash_eol_eof() {
fn test_compile_text_command_gnu_no_text() {
let mut chars = make_char_provider("a");
let mut lines = make_line_provider(&[]);
let mut cmd = Command::default();
let mut context = ProcessingContext::default();
compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context).unwrap();
match &cmd.data {
CommandData::Text(text) => {
assert_eq!(text.to_string(), "\n");
}
_ => panic!("Expected CommandData::Text"),
}
let result = compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("expects \\ followed by text"));
}
#[test]
+72 -77
View File
@@ -77,7 +77,7 @@ fn create_control_char(x: char) -> Option<char> {
let c = x.to_ascii_uppercase();
let transformed = (c as u8) ^ 0x40;
char::from_u32(transformed as u32)
char::from_u32(u32::from(transformed))
}
/// Parse a character escape valid in all contexts (RE pattern, substitution,
@@ -186,9 +186,10 @@ fn parse_character_class(
) -> UResult<String> {
let mut result = String::new();
if line.eol() || line.current() != '[' {
panic!("Invalid character class.");
}
assert!(
!line.eol() && line.current() == '[',
"Invalid character class."
);
line.advance();
result.push('[');
@@ -216,59 +217,56 @@ fn parse_character_class(
if ch == '[' {
line.advance();
if !line.eol() {
let marker = line.current();
// POSIX character class, collating symbol, or equivalence
if marker == ':' || marker == '.' || marker == '=' {
line.advance();
result.push('[');
result.push(marker);
let mut inner = String::new();
let mut terminated = false;
while !line.eol() {
let c = line.current();
if c == marker {
line.advance();
if !line.eol() && line.current() == ']' {
line.advance();
result.push_str(&inner);
result.push(marker);
result.push(']');
terminated = true;
break;
} else {
// False alarm, just part of the inner name
inner.push(marker);
}
} else {
inner.push(c);
line.advance();
}
}
if !terminated {
return compilation_error(
lines,
line,
"Unterminated POSIX character class, equivalence or collating symbol",
);
}
continue;
} else {
// Not a POSIX construct — treat as literal
result.push('[');
result.push(marker);
line.advance();
continue;
}
} else {
if line.eol() {
result.push('[');
continue;
}
let marker = line.current();
// POSIX character class, collating symbol, or equivalence
if marker == ':' || marker == '.' || marker == '=' {
line.advance();
result.push('[');
result.push(marker);
let mut inner = String::new();
let mut terminated = false;
while !line.eol() {
let c = line.current();
if c == marker {
line.advance();
if !line.eol() && line.current() == ']' {
line.advance();
result.push_str(&inner);
result.push(marker);
result.push(']');
terminated = true;
break;
}
// False alarm, just part of the inner name
inner.push(marker);
} else {
inner.push(c);
line.advance();
}
}
if !terminated {
return compilation_error(
lines,
line,
"Unterminated POSIX character class, equivalence or collating symbol",
);
}
continue;
}
// Not a POSIX construct — treat as literal
result.push('[');
result.push(marker);
line.advance();
continue;
}
if ch == '\\' {
@@ -277,13 +275,12 @@ fn parse_character_class(
if line.eol() {
break;
}
match parse_char_escape(line) {
Some(decoded) => result.push(decoded),
None => {
result.push('\\');
result.push(line.current());
line.advance();
}
if let Some(decoded) = parse_char_escape(line) {
result.push(decoded);
} else {
result.push('\\');
result.push(line.current());
line.advance();
}
} else {
result.push(ch);
@@ -335,14 +332,13 @@ pub fn parse_regex(lines: &ScriptLineProvider, line: &mut ScriptCharProvider) ->
line.advance();
continue;
}
match parse_char_escape(line) {
Some(decoded) => result.push(decoded),
None => {
// Pass through \<any> to RE engine for further treatment
result.push('\\');
result.push(line.current());
line.advance();
}
if let Some(decoded) = parse_char_escape(line) {
result.push(decoded);
} else {
// Pass through \<any> to RE engine for further treatment
result.push('\\');
result.push(line.current());
line.advance();
}
continue;
}
@@ -377,14 +373,13 @@ pub fn parse_transliteration(
line.advance();
continue;
}
match parse_char_escape(line) {
Some(decoded) => result.push(decoded),
None => {
// Pass through \<any> to tr for literal use
result.push('\\');
result.push(line.current());
line.advance();
}
if let Some(decoded) = parse_char_escape(line) {
result.push(decoded);
} else {
// Pass through \<any> to tr for literal use
result.push('\\');
result.push(line.current());
line.advance();
}
continue;
}

Some files were not shown because too many files have changed in this diff Show More