Compare commits
63
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95cbd302fe | ||
|
|
1e390a63c0 | ||
|
|
452e249296 | ||
|
|
c8b5dc53e3 | ||
|
|
714ec8a7bf | ||
|
|
79e5df0df8 | ||
|
|
3f72f93c5d | ||
|
|
d837ddf398 | ||
|
|
b1ca27e82d | ||
|
|
ce711ac531 | ||
|
|
618a3f7c39 | ||
|
|
079eb9340e | ||
|
|
2ce633cb8d | ||
|
|
91465e566b | ||
|
|
d842aa305e | ||
|
|
d28a1f8e73 | ||
|
|
af5f8afdcb | ||
|
|
8f3f126816 | ||
|
|
c771318a7a | ||
|
|
36a1cf4954 | ||
|
|
13bb82ba13 | ||
|
|
cb9eb1da42 | ||
|
|
b101462d1b | ||
|
|
9f2e1a1ad4 | ||
|
|
2b092d53b0 | ||
|
|
550d545d72 | ||
|
|
a5d219764f | ||
|
|
397ab86302 | ||
|
|
2288789f33 | ||
|
|
84ae1b07a7 | ||
|
|
f32296fc22 | ||
|
|
7239fb0e08 | ||
|
|
c7e1f78486 | ||
|
|
fb907703a0 | ||
|
|
3d52077d7f | ||
|
|
e9792a8ef9 | ||
|
|
c732b60cfc | ||
|
|
c1e5d8ea08 | ||
|
|
6887201cce | ||
|
|
75d856e4c2 | ||
|
|
d7a2106258 | ||
|
|
a726ac6959 | ||
|
|
49590e63be | ||
|
|
99a9957e89 | ||
|
|
c521271ca8 | ||
|
|
7830c20bc1 | ||
|
|
b37e23fa98 | ||
|
|
07eada8531 | ||
|
|
15def35a2a | ||
|
|
30607c4ddc | ||
|
|
01c8412327 | ||
|
|
f3a79c5847 | ||
|
|
a9e5fe778c | ||
|
|
02d0239447 | ||
|
|
8b5dea703a | ||
|
|
34dfbc2ef1 | ||
|
|
8cb7090bbc | ||
|
|
2a5ab5a9a2 | ||
|
|
b14272c1d7 | ||
|
|
cd3b10db48 | ||
|
|
48c2f56358 | ||
|
|
dcd2df848a | ||
|
|
9efab796be |
No files matched your search
@@ -134,7 +134,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Retrieve reference artifacts
|
||||
uses: dawidd6/action-download-artifact@v21
|
||||
uses: dawidd6/action-download-artifact@v23
|
||||
continue-on-error: true
|
||||
with:
|
||||
workflow: GnuTests.yml
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
|
||||
- name: Install tools
|
||||
uses: taiki-e/install-action@v2
|
||||
@@ -47,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:
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
|
||||
Generated
+77
-338
File diff suppressed because it is too large.
Load diff
+19
-8
@@ -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"
|
||||
@@ -21,6 +20,19 @@ rust-version = "1.88"
|
||||
|
||||
build = "build.rs"
|
||||
|
||||
# cargo-binstall: fetch the archives published by `dist` in the GitHub releases
|
||||
# instead of building from source. The layout below has to stay in sync with
|
||||
# `dist-workspace.toml` (targets) and the archives it produces:
|
||||
# sed-<target>.tar.xz (zip on Windows), containing a sed-<target>/ directory.
|
||||
# Note that the release tags carry no `v` prefix, hence `{ version }`.
|
||||
[package.metadata.binstall]
|
||||
pkg-url = "{ repo }/releases/download/{ version }/{ name }-{ target }.{ archive-suffix }"
|
||||
bin-dir = "{ name }-{ target }/{ bin }{ binary-ext }"
|
||||
pkg-fmt = "txz"
|
||||
|
||||
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||
pkg-fmt = "zip"
|
||||
|
||||
[features]
|
||||
default = ["feat_common_core"]
|
||||
|
||||
@@ -29,7 +41,6 @@ sed = []
|
||||
|
||||
[workspace.dependencies]
|
||||
assert_fs = "1.1.3"
|
||||
bytesize = "2.0.0"
|
||||
chrono = { version = "0.4.37", default-features = false, features = [
|
||||
"clock",
|
||||
] }
|
||||
@@ -48,11 +59,11 @@ predicates = "3.1.3"
|
||||
rand = { version = "0.10.0" }
|
||||
regex = "1.10.4"
|
||||
sha2 = "0.11"
|
||||
sysinfo = "0.38"
|
||||
tempfile = "3.10.1"
|
||||
terminal_size = "0.4.2"
|
||||
textwrap = { version = "0.16.1", features = ["terminal_size"] }
|
||||
uucore = { version = "0.9.0", features = ["libc"] }
|
||||
uucore = { version = "0.10.0", features = ["libc"] }
|
||||
rustix = "1.1.4"
|
||||
xattr = "1.3.1"
|
||||
|
||||
|
||||
@@ -66,11 +77,11 @@ 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 }
|
||||
@@ -85,7 +96,7 @@ regex = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
|
||||
uutests = "0.9.0"
|
||||
uutests = "0.10.0"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
xattr = { workspace = true }
|
||||
@@ -135,7 +146,7 @@ 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
|
||||
# cast_possible_truncation = "allow" # 7
|
||||
unnecessary_wraps = "allow" # 6
|
||||
match_wildcard_for_single_variants = "allow" # 4
|
||||
cast_sign_loss = "allow" # 4
|
||||
|
||||
@@ -26,11 +26,18 @@ Further work aims to:
|
||||
* Implement more GNU extensions,
|
||||
* Improve performance where possible.
|
||||
|
||||
## Installation
|
||||
## Installation and Use
|
||||
|
||||
We provide a Linux x86_64 binary archive from the main branch at
|
||||
https://github.com/uutils/sed/releases/tag/latest-commit .
|
||||
|
||||
If you have [cargo-binstall](https://github.com/cargo-bins/cargo-binstall),
|
||||
the released binaries can be installed directly with:
|
||||
|
||||
```bash
|
||||
cargo binstall sed
|
||||
```
|
||||
|
||||
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:
|
||||
@@ -44,6 +51,10 @@ 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
|
||||
@@ -81,6 +92,7 @@ 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.
|
||||
@@ -89,14 +101,22 @@ cargo test
|
||||
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 &.
|
||||
* An `F` command outputs the name of the file currently being processed.
|
||||
* The `p` command adds a missing newline.
|
||||
(POSIX is silent; FreeBSD sed ignores the newline; original sed ignores
|
||||
the line).
|
||||
* A `Q` command (optionally followed by an exit code) quits immediately.
|
||||
* The `q` command can be optionally followed by an exit code.
|
||||
* An `R` schedules reading the next line from the specified file.
|
||||
* A `W` command writes to a file the pattern's first line.
|
||||
* 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.
|
||||
@@ -109,12 +129,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.
|
||||
|
||||
Generated
+25
-139
@@ -245,9 +245,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.3"
|
||||
version = "0.16.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
|
||||
checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
@@ -571,9 +571,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
@@ -646,15 +646,6 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
@@ -683,25 +674,6 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
@@ -725,9 +697,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.13.1"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||
checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"serde",
|
||||
@@ -735,9 +707,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.13.1"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
|
||||
checksum = "41b585a510fb76fdebead6897982ef2a03a21d8e6cbcca904999742a4afc6ffe"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
@@ -745,9 +717,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.13.1"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
||||
checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"phf_shared",
|
||||
@@ -755,9 +727,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.13.1"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||
checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
@@ -848,9 +820,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.1"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom 0.4.2",
|
||||
@@ -925,7 +897,7 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "sed"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
@@ -937,7 +909,7 @@ dependencies = [
|
||||
"phf_codegen",
|
||||
"predicates",
|
||||
"regex",
|
||||
"sysinfo",
|
||||
"rustix",
|
||||
"tempfile",
|
||||
"terminal_size",
|
||||
"textwrap",
|
||||
@@ -1013,9 +985,9 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "3.1.1"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6505efef05804732ed8a3f2d4f279429eb485bd69d5b0cc6b19cc02005cda16"
|
||||
checksum = "4f66ca1f7aca2474dc10c942eb22feffc897735f54cd1db90138c2fddb490987"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
]
|
||||
@@ -1055,20 +1027,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.38.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-kit",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
@@ -1194,9 +1152,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uucore"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "069b34217c27f611e1589f540f58118dbf226a9e407d38ab472052ff075a1dc2"
|
||||
checksum = "e97dd0fd8bb2ed12affba9f956c3b94b8ad3f9432746717475420e916ae0b8d4"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"clap",
|
||||
@@ -1212,6 +1170,7 @@ dependencies = [
|
||||
"rustix",
|
||||
"thiserror",
|
||||
"unic-langid",
|
||||
"unicode-width",
|
||||
"uucore_procs",
|
||||
"wild",
|
||||
]
|
||||
@@ -1233,9 +1192,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uucore_procs"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34337da211e7abfff7189b794afb3b5018fe356fb36474b73645458fc1201350"
|
||||
checksum = "907f1b828046ea42cdb4bd898f05b48ab873a41937bf75ebe12f09a5bf411f58"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1243,9 +1202,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uufuzz"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e4192b67d5e7f0a3300db353ec3217beee89442f02c11d8ae108867eee392d0"
|
||||
checksum = "72d4c5bac0f8ccabaa8e8ccbf2724446f8b007c06ad343fabfe1a1525a4ff4f2"
|
||||
dependencies = [
|
||||
"console",
|
||||
"rand",
|
||||
@@ -1361,49 +1320,6 @@ dependencies = [
|
||||
"glob",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
@@ -1417,17 +1333,6 @@ dependencies = [
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
@@ -1456,16 +1361,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
@@ -1493,15 +1388,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@ libc = "0.2.153"
|
||||
tempfile = "3.15.0"
|
||||
rand = { version = "0.10.0" }
|
||||
similar = "3.0.0"
|
||||
uucore = { version = "0.9.0", features = ["libc"] }
|
||||
uufuzz = "0.9.0"
|
||||
uucore = { version = "0.10.0", features = ["libc"] }
|
||||
uufuzz = "0.10.0"
|
||||
|
||||
sed = { path = ".." }
|
||||
|
||||
|
||||
+163
-82
File diff suppressed because it is too large.
Load diff
+821
-181
File diff suppressed because it is too large.
Load diff
+261
-92
File diff suppressed because it is too large.
Load diff
@@ -14,6 +14,7 @@ use crate::sed::script_line_provider::ScriptLineProvider;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -108,7 +109,7 @@ pub fn input_runtime_error<T>(
|
||||
location.input_name,
|
||||
location.line_number,
|
||||
location.column_number,
|
||||
context.input_name,
|
||||
context.input_name.quote(),
|
||||
context.line_number,
|
||||
msg.to_string()
|
||||
),
|
||||
|
||||
+234
-197
File diff suppressed because it is too large.
Load diff
+269
-87
File diff suppressed because it is too large.
Load diff
+106
-20
@@ -15,23 +15,25 @@ pub mod error_handling;
|
||||
pub mod fast_io;
|
||||
pub mod fast_regex;
|
||||
pub mod in_place;
|
||||
pub mod named_writer;
|
||||
pub mod named_io;
|
||||
pub mod processor;
|
||||
pub mod script_char_provider;
|
||||
pub mod script_line_provider;
|
||||
|
||||
use crate::sed::command::{ProcessingContext, StringSpace};
|
||||
use crate::sed::command::{ByteSpace, CharacterMode, ProcessingContext};
|
||||
use crate::sed::compiler::compile;
|
||||
use crate::sed::processor::process_all_files;
|
||||
use crate::sed::script_line_provider::ScriptValue;
|
||||
use clap::{Arg, ArgMatches, Command, arg, crate_version};
|
||||
use clap::{Arg, ArgMatches, Command, arg};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use uucore::error::{UResult, UUsageError};
|
||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||
use uucore::format_usage;
|
||||
|
||||
const ABOUT: &str = "Stream editor for filtering and transforming text";
|
||||
const ABOUT: &str = "Stream editor for filtering and transforming text (part of uutils)";
|
||||
const USAGE: &str = "sed [OPTION]... [script] [file]...";
|
||||
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (uutils)");
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
@@ -46,7 +48,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
}
|
||||
|
||||
let (scripts, files) = get_scripts_files(&matches)?;
|
||||
let mut context = build_context(&matches);
|
||||
let mut context = build_context(&matches)?;
|
||||
|
||||
let executable = compile(scripts, &mut context)?;
|
||||
process_all_files(executable, files, &mut context)?;
|
||||
@@ -61,7 +63,7 @@ pub fn uu_app() -> Command {
|
||||
let util_name = uucore::util_name();
|
||||
|
||||
Command::new(util_name)
|
||||
.version(crate_version!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.args_override_self(true)
|
||||
@@ -192,9 +194,40 @@ fn get_scripts_files(matches: &ArgMatches) -> UResult<(Vec<ScriptValue>, Vec<Pat
|
||||
Ok((scripts, files))
|
||||
}
|
||||
|
||||
// Parse CLI flag arguments and return a ProcessingContext struct based on them
|
||||
fn build_context(matches: &ArgMatches) -> ProcessingContext {
|
||||
ProcessingContext {
|
||||
/// Return the character interpretation mode implied by the process locale.
|
||||
///
|
||||
/// The mode is determined from the first non-empty of LC_ALL, LC_CTYPE, and
|
||||
/// LANG. The C and POSIX locales select byte mode. UTF-8 locales (including
|
||||
/// C.UTF-8) select UTF-8 mode. All other locales result in an error.
|
||||
pub fn character_mode_for_locale(locale: &str) -> UResult<CharacterMode> {
|
||||
if locale == "C" || locale == "POSIX" {
|
||||
Ok(CharacterMode::Byte)
|
||||
} else if locale.eq_ignore_ascii_case("C.UTF-8")
|
||||
|| locale.to_ascii_lowercase().ends_with(".utf-8")
|
||||
|| locale.to_ascii_lowercase().ends_with(".utf8")
|
||||
{
|
||||
Ok(CharacterMode::Utf8)
|
||||
} else {
|
||||
Err(USimpleError::new(
|
||||
1,
|
||||
format!("unsupported locale: {locale}"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a ProcessingContext based on parsed CLI flags and environment.
|
||||
fn build_context(matches: &ArgMatches) -> UResult<ProcessingContext> {
|
||||
let locale = ["LC_ALL", "LC_CTYPE", "LANG"]
|
||||
.into_iter()
|
||||
.find_map(|name| {
|
||||
let value = env::var(name).ok()?;
|
||||
(!value.is_empty()).then_some(value)
|
||||
})
|
||||
// Same default as GNU sed
|
||||
.unwrap_or_else(|| "C".to_string());
|
||||
|
||||
Ok(ProcessingContext {
|
||||
// CLI arguments
|
||||
all_output_files: matches.get_flag("all-output-files"),
|
||||
debug: matches.get_flag("debug"),
|
||||
regex_extended: matches.get_flag("regexp-extended"),
|
||||
@@ -211,8 +244,11 @@ fn build_context(matches: &ArgMatches) -> ProcessingContext {
|
||||
unbuffered: matches.get_flag("unbuffered"),
|
||||
null_data: matches.get_flag("null-data"),
|
||||
|
||||
// Environment
|
||||
character_mode: character_mode_for_locale(&locale)?,
|
||||
|
||||
// Other context
|
||||
input_name: "<stdin>".to_string(),
|
||||
input_name: PathBuf::from("-"),
|
||||
line_number: 0,
|
||||
last_address: false,
|
||||
last_line: false,
|
||||
@@ -220,8 +256,8 @@ fn build_context(matches: &ArgMatches) -> ProcessingContext {
|
||||
stop_processing: false,
|
||||
saved_regex: None,
|
||||
input_action: None,
|
||||
hold: StringSpace {
|
||||
content: String::new(),
|
||||
hold: ByteSpace {
|
||||
content: Vec::new(),
|
||||
has_newline: true,
|
||||
},
|
||||
parsed_block_nesting: 0,
|
||||
@@ -229,7 +265,8 @@ fn build_context(matches: &ArgMatches) -> ProcessingContext {
|
||||
range_commands: Vec::new(),
|
||||
substitution_made: false,
|
||||
append_elements: Vec::new(),
|
||||
}
|
||||
rss: processor::RecordSeparatorState::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -338,7 +375,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_defaults() {
|
||||
let matches = test_matches(&[]);
|
||||
let ctx = build_context(&matches);
|
||||
let ctx = build_context(&matches).unwrap();
|
||||
|
||||
assert!(!ctx.all_output_files);
|
||||
assert!(!ctx.debug);
|
||||
@@ -373,7 +410,7 @@ mod tests {
|
||||
"-z",
|
||||
]);
|
||||
|
||||
let ctx = build_context(&matches);
|
||||
let ctx = build_context(&matches).unwrap();
|
||||
|
||||
assert!(ctx.all_output_files);
|
||||
assert!(ctx.debug);
|
||||
@@ -393,7 +430,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_multiple_same_arguments() {
|
||||
let matches = test_matches(&["-E", "-r"]);
|
||||
let ctx = build_context(&matches);
|
||||
let ctx = build_context(&matches).unwrap();
|
||||
|
||||
assert!(ctx.regex_extended);
|
||||
}
|
||||
@@ -401,7 +438,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_in_place_with_suffix() {
|
||||
let matches = test_matches(&["-i", ".bak"]);
|
||||
let ctx = build_context(&matches);
|
||||
let ctx = build_context(&matches).unwrap();
|
||||
|
||||
assert!(ctx.in_place);
|
||||
assert_eq!(ctx.in_place_suffix, Some(".bak".to_string()));
|
||||
@@ -412,10 +449,59 @@ mod tests {
|
||||
let matches_default = test_matches(&[]);
|
||||
let matches_custom = test_matches(&["-l", "120"]);
|
||||
|
||||
let ctx_default = build_context(&matches_default);
|
||||
let ctx_custom = build_context(&matches_custom);
|
||||
let ctx_default = build_context(&matches_default).unwrap();
|
||||
let ctx_custom = build_context(&matches_custom).unwrap();
|
||||
|
||||
assert_eq!(ctx_default.length, 70);
|
||||
assert_eq!(ctx_custom.length, 120);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c_locale_selects_byte_mode() {
|
||||
assert_eq!(character_mode_for_locale("C").unwrap(), CharacterMode::Byte);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn posix_locale_selects_byte_mode() {
|
||||
assert_eq!(
|
||||
character_mode_for_locale("POSIX").unwrap(),
|
||||
CharacterMode::Byte
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c_utf8_locale_selects_utf8_mode() {
|
||||
assert_eq!(
|
||||
character_mode_for_locale("C.UTF-8").unwrap(),
|
||||
CharacterMode::Utf8
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_utf8_locale_selects_utf8_mode() {
|
||||
assert_eq!(
|
||||
character_mode_for_locale("en_US.UTF-8").unwrap(),
|
||||
CharacterMode::Utf8
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_utf8_locale_is_case_insensitive() {
|
||||
assert_eq!(
|
||||
character_mode_for_locale("el_GR.utf8").unwrap(),
|
||||
CharacterMode::Utf8
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_locale_reports_locale_name() {
|
||||
let err = character_mode_for_locale("el_GR.ISO-8859-7").unwrap_err();
|
||||
|
||||
assert_eq!(err.code(), 1);
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("unsupported locale: el_GR.ISO-8859-7"),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
// An abstraction for output files created on entry and flushed on exit
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2025 Diomidis Spinellis
|
||||
//
|
||||
// This file is part of the uutils sed package.
|
||||
// It is licensed under the MIT License.
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use crate::sed::error_handling::{ScriptLocation, runtime_error};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{BufRead, BufReader, BufWriter, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::UResult;
|
||||
|
||||
thread_local! {
|
||||
/// Readers indexed by canonical input path, used to share state
|
||||
/// between same-named path reads.
|
||||
static READERS: RefCell<HashMap<PathBuf, Rc<RefCell<NamedReader>>>> = RefCell::new(HashMap::new());
|
||||
/// Writers indexed by canonical output path, used to share same-named
|
||||
/// paths and to flush writes.
|
||||
static WRITERS: RefCell<HashMap<PathBuf, Rc<RefCell<NamedWriter>>>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Reader that shares line-by-line state for GNU sed's R command.
|
||||
pub struct NamedReader {
|
||||
path: PathBuf,
|
||||
reader: Option<BufReader<File>>,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl NamedReader {
|
||||
/// Create or retrieve the reader associated with `path`.
|
||||
pub fn new(path: PathBuf) -> Rc<RefCell<Self>> {
|
||||
let canonical_path = fs::canonicalize(&path).unwrap_or(path);
|
||||
READERS.with(|readers| {
|
||||
readers
|
||||
.borrow_mut()
|
||||
.entry(canonical_path.clone())
|
||||
.or_insert_with(|| {
|
||||
Rc::new(RefCell::new(Self {
|
||||
path: canonical_path,
|
||||
reader: None,
|
||||
done: false,
|
||||
}))
|
||||
})
|
||||
.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the path associated with this reader.
|
||||
pub fn original_path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Read the next line, including its newline. Missing files and read errors
|
||||
/// are treated as end-of-file, as required by the R command.
|
||||
pub fn read_line(&mut self) -> Option<Vec<u8>> {
|
||||
if self.done {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.reader.is_none() {
|
||||
if let Ok(file) = File::open(&self.path) {
|
||||
self.reader = Some(BufReader::new(file));
|
||||
} else {
|
||||
self.done = true;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut line = Vec::new();
|
||||
match self.reader.as_mut().unwrap().read_until(b'\n', &mut line) {
|
||||
Ok(0) | Err(_) => {
|
||||
self.done = true;
|
||||
None
|
||||
}
|
||||
Ok(_) => Some(line),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Writer that tracks its file name for better error messages
|
||||
pub struct NamedWriter {
|
||||
pub path: PathBuf,
|
||||
writer: BufWriter<File>,
|
||||
location: ScriptLocation,
|
||||
}
|
||||
|
||||
impl NamedWriter {
|
||||
/// Create a new writer, truncate the file, and register it for flushing.
|
||||
pub fn new(path: PathBuf, location: ScriptLocation) -> UResult<Rc<RefCell<Self>>> {
|
||||
let canonical_path = canonicalize_output_path(&path, &location)?;
|
||||
|
||||
if let Some(writer) = WRITERS.with(|writers| writers.borrow().get(&canonical_path).cloned())
|
||||
{
|
||||
return Ok(writer);
|
||||
}
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&path)
|
||||
.map_err(|e| {
|
||||
runtime_error::<()>(&location, format!("creating file {}: {}", path.quote(), e))
|
||||
.unwrap_err()
|
||||
})?;
|
||||
|
||||
let writer = Rc::new(RefCell::new(NamedWriter {
|
||||
path,
|
||||
writer: BufWriter::new(file),
|
||||
location,
|
||||
}));
|
||||
|
||||
WRITERS.with(|writers| {
|
||||
writers
|
||||
.borrow_mut()
|
||||
.insert(canonical_path, Rc::clone(&writer));
|
||||
});
|
||||
Ok(writer)
|
||||
}
|
||||
|
||||
/// Return the path used when this writer was first opened.
|
||||
pub fn original_path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Write String to the file, possibly with a newline, returning errors.
|
||||
pub fn write_line(&mut self, line: &str, newline: bool) -> UResult<()> {
|
||||
self.write_line_bytes(line.as_bytes(), newline)
|
||||
}
|
||||
|
||||
/// Write bytes to the file, possibly with a newline, returning errors.
|
||||
pub fn write_line_bytes(&mut self, line: &[u8], newline: bool) -> UResult<()> {
|
||||
self.writer
|
||||
.write_all(line)
|
||||
.and_then(|()| {
|
||||
if newline {
|
||||
self.writer.write_all(b"\n")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.map_err(|e| {
|
||||
runtime_error::<()>(
|
||||
&self.location,
|
||||
format!("writing to file {}: {e}", self.path.quote()),
|
||||
)
|
||||
.unwrap_err()
|
||||
})
|
||||
}
|
||||
|
||||
/// Flush the writer, returning a descriptive error.
|
||||
pub fn flush(&mut self) -> UResult<()> {
|
||||
self.writer.flush().map_err(|e| {
|
||||
runtime_error::<()>(
|
||||
&self.location,
|
||||
format!("writing to file {}: {}", self.path.quote(), e),
|
||||
)
|
||||
.unwrap_err()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalize_output_path(path: &Path, location: &ScriptLocation) -> UResult<PathBuf> {
|
||||
let parent = path
|
||||
.parent()
|
||||
.filter(|parent| !parent.as_os_str().is_empty())
|
||||
.unwrap_or_else(|| Path::new("."));
|
||||
let canonical_parent = fs::canonicalize(parent).map_err(|e| {
|
||||
runtime_error::<()>(location, format!("creating file {}: {}", path.quote(), e)).unwrap_err()
|
||||
})?;
|
||||
|
||||
Ok(match path.file_name() {
|
||||
Some(file_name) => canonical_parent.join(file_name),
|
||||
None => canonical_parent,
|
||||
})
|
||||
}
|
||||
|
||||
/// Flush buffered content to all open files, returning descriptive errors.
|
||||
pub fn flush_all() -> UResult<()> {
|
||||
WRITERS.with(|writers| {
|
||||
for writer in writers.borrow().values() {
|
||||
writer.borrow_mut().flush()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempfile::{NamedTempFile, tempdir};
|
||||
|
||||
#[test]
|
||||
fn test_reader_reads_lines_as_bytes() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
let path = file.path().to_path_buf();
|
||||
fs::write(&path, b"first\nsecond\xE9").unwrap();
|
||||
let reader = NamedReader::new(path);
|
||||
|
||||
assert_eq!(reader.borrow_mut().read_line(), Some(b"first\n".to_vec()));
|
||||
assert_eq!(
|
||||
reader.borrow_mut().read_line(),
|
||||
Some(b"second\xE9".to_vec())
|
||||
);
|
||||
assert_eq!(reader.borrow_mut().read_line(), None);
|
||||
assert_eq!(reader.borrow_mut().read_line(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_reuses_reader_and_shared_position_for_same_path() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
let path = file.path().to_path_buf();
|
||||
fs::write(&path, b"first\nsecond\n").unwrap();
|
||||
let first = NamedReader::new(path.clone());
|
||||
let second = NamedReader::new(path);
|
||||
|
||||
assert!(Rc::ptr_eq(&first, &second));
|
||||
assert_eq!(first.borrow_mut().read_line(), Some(b"first\n".to_vec()));
|
||||
assert_eq!(second.borrow_mut().read_line(), Some(b"second\n".to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_reuses_reader_for_canonical_duplicate_path() {
|
||||
let dir = tempdir().unwrap();
|
||||
let path = dir.path().join("input");
|
||||
fs::write(&path, b"first\nsecond\n").unwrap();
|
||||
let duplicate_path = dir.path().join(".").join("input");
|
||||
let first = NamedReader::new(path.clone());
|
||||
let second = NamedReader::new(duplicate_path);
|
||||
|
||||
assert!(Rc::ptr_eq(&first, &second));
|
||||
assert_eq!(
|
||||
first.borrow().original_path(),
|
||||
fs::canonicalize(path).unwrap()
|
||||
);
|
||||
assert_eq!(first.borrow_mut().read_line(), Some(b"first\n".to_vec()));
|
||||
assert_eq!(second.borrow_mut().read_line(), Some(b"second\n".to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reader_silently_ignores_missing_file() {
|
||||
let dir = tempdir().unwrap();
|
||||
let reader = NamedReader::new(dir.path().join("missing"));
|
||||
|
||||
assert_eq!(reader.borrow_mut().read_line(), None);
|
||||
assert_eq!(reader.borrow_mut().read_line(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_line_bytes_appends_newline() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
let path = file.path().to_path_buf();
|
||||
let writer = NamedWriter::new(path.clone(), ScriptLocation::default()).unwrap();
|
||||
|
||||
writer
|
||||
.borrow_mut()
|
||||
.write_line_bytes(b"a\xE9", true)
|
||||
.unwrap();
|
||||
writer.borrow_mut().flush().unwrap();
|
||||
|
||||
assert_eq!(fs::read(path).unwrap(), b"a\xE9\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_line_bytes_appends_no_newline() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
let path = file.path().to_path_buf();
|
||||
let writer = NamedWriter::new(path.clone(), ScriptLocation::default()).unwrap();
|
||||
|
||||
writer
|
||||
.borrow_mut()
|
||||
.write_line_bytes(b"a\xE9", false)
|
||||
.unwrap();
|
||||
writer.borrow_mut().flush().unwrap();
|
||||
|
||||
assert_eq!(fs::read(path).unwrap(), b"a\xE9");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_reuses_writer_for_same_path() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
let path = file.path().to_path_buf();
|
||||
let first = NamedWriter::new(path.clone(), ScriptLocation::default()).unwrap();
|
||||
let second = NamedWriter::new(path.clone(), ScriptLocation::default()).unwrap();
|
||||
|
||||
assert!(Rc::ptr_eq(&first, &second));
|
||||
assert_eq!(first.borrow().original_path(), path.as_path());
|
||||
|
||||
first.borrow_mut().write_line("first", true).unwrap();
|
||||
second.borrow_mut().write_line("second", true).unwrap();
|
||||
first.borrow_mut().flush().unwrap();
|
||||
|
||||
assert_eq!(fs::read_to_string(path).unwrap(), "first\nsecond\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_reuses_writer_for_canonical_duplicate_path() {
|
||||
let dir = tempdir().unwrap();
|
||||
let subdir = dir.path().join("subdir");
|
||||
fs::create_dir(&subdir).unwrap();
|
||||
|
||||
let path = subdir.join("output");
|
||||
let duplicate_path = subdir.join(".").join("output");
|
||||
let first = NamedWriter::new(path.clone(), ScriptLocation::default()).unwrap();
|
||||
let second = NamedWriter::new(duplicate_path, ScriptLocation::default()).unwrap();
|
||||
|
||||
assert!(Rc::ptr_eq(&first, &second));
|
||||
assert_eq!(first.borrow().original_path(), path.as_path());
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
// An abstraction for output files created on entry and flushed on exit
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2025 Diomidis Spinellis
|
||||
//
|
||||
// This file is part of the uutils sed package.
|
||||
// It is licensed under the MIT License.
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use crate::sed::error_handling::{ScriptLocation, runtime_error};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::UResult;
|
||||
|
||||
thread_local! {
|
||||
/// Global list of all writers that should be flushed at shutdown
|
||||
static FLUSH_LIST: RefCell<Vec<Rc<RefCell<NamedWriter>>>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Writer that tracks its file name for better error messages
|
||||
pub struct NamedWriter {
|
||||
pub path: PathBuf,
|
||||
writer: BufWriter<File>,
|
||||
location: ScriptLocation,
|
||||
}
|
||||
|
||||
impl NamedWriter {
|
||||
/// Create a new writer, truncate the file, and register it for flushing.
|
||||
pub fn new(path: PathBuf, location: ScriptLocation) -> UResult<Rc<RefCell<Self>>> {
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&path)
|
||||
.map_err(|e| {
|
||||
runtime_error::<()>(&location, format!("creating file {}: {}", path.quote(), e))
|
||||
.unwrap_err()
|
||||
})?;
|
||||
|
||||
let writer = Rc::new(RefCell::new(NamedWriter {
|
||||
path,
|
||||
writer: BufWriter::new(file),
|
||||
location,
|
||||
}));
|
||||
|
||||
FLUSH_LIST.with(|list| list.borrow_mut().push(Rc::clone(&writer)));
|
||||
Ok(writer)
|
||||
}
|
||||
|
||||
/// Write a line to the file with a newline, returning descriptive errors.
|
||||
pub fn write_line(&mut self, line: &str) -> UResult<()> {
|
||||
writeln!(self.writer, "{line}").map_err(|e| {
|
||||
runtime_error::<()>(
|
||||
&self.location,
|
||||
format!("writing to file {}: {e}", self.path.quote()),
|
||||
)
|
||||
.unwrap_err()
|
||||
})
|
||||
}
|
||||
|
||||
/// Flush the writer, returning a descriptive error.
|
||||
pub fn flush(&mut self) -> UResult<()> {
|
||||
self.writer.flush().map_err(|e| {
|
||||
runtime_error::<()>(
|
||||
&self.location,
|
||||
format!("writing to file {}: {}", self.path.quote(), e),
|
||||
)
|
||||
.unwrap_err()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Flush buffered content to the file, returning descriptive errors.
|
||||
pub fn flush_all() -> UResult<()> {
|
||||
FLUSH_LIST.with(|cell| {
|
||||
for handle in cell.borrow().iter() {
|
||||
handle.borrow_mut().flush()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
+624
-107
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,4 @@
|
||||
// Provide the script contents character by character
|
||||
// Provide the script contents byte by byte
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2025 Diomidis Spinellis
|
||||
@@ -10,19 +10,20 @@
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScriptCharProvider {
|
||||
line: Vec<char>,
|
||||
line: Vec<u8>,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl ScriptCharProvider {
|
||||
pub fn new(line_string: &str) -> Self {
|
||||
/// Build from raw script bytes.
|
||||
pub fn new(line: impl AsRef<[u8]>) -> Self {
|
||||
Self {
|
||||
line: line_string.chars().collect(),
|
||||
line: line.as_ref().to_vec(),
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances to the next character, if not at end of line.
|
||||
/// Advances to the next byte, if not at end of line.
|
||||
pub fn advance(&mut self) {
|
||||
if self.pos < self.line.len() {
|
||||
self.pos += 1;
|
||||
@@ -39,8 +40,13 @@ impl ScriptCharProvider {
|
||||
self.pos = pos;
|
||||
}
|
||||
|
||||
/// Returns the current character. Panics if out of bounds.
|
||||
/// Returns the current byte as a character. Panics if out of bounds.
|
||||
pub fn current(&self) -> char {
|
||||
char::from(self.line[self.pos])
|
||||
}
|
||||
|
||||
/// Returns the current script byte. Panics if out of bounds.
|
||||
pub fn current_byte(&self) -> u8 {
|
||||
self.line[self.pos]
|
||||
}
|
||||
|
||||
@@ -51,8 +57,8 @@ impl ScriptCharProvider {
|
||||
|
||||
/// Advances the position past any whitespace characters.
|
||||
pub fn eat_spaces(&mut self) {
|
||||
while self.pos < self.line.len() && self.line[self.pos].is_whitespace() {
|
||||
self.pos += 1;
|
||||
while self.pos < self.line.len() && self.current().is_whitespace() {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,4 +145,28 @@ mod tests {
|
||||
assert_eq!(chars.get_pos(), 2);
|
||||
assert_eq!(chars.current(), 'c');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_current_for_utf8_bytes() {
|
||||
let mut chars = ScriptCharProvider::new("αb");
|
||||
|
||||
assert_eq!(chars.current(), char::from(0xCE));
|
||||
assert_eq!(chars.current_byte(), 0xCE);
|
||||
chars.advance();
|
||||
assert_eq!(chars.current(), char::from(0xB1));
|
||||
assert_eq!(chars.current_byte(), 0xB1);
|
||||
chars.advance();
|
||||
assert_eq!(chars.current(), 'b');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_current_for_invalid_utf8_byte() {
|
||||
let mut chars = ScriptCharProvider::new(vec![0xC2, b'a']);
|
||||
|
||||
assert_eq!(chars.current(), char::from(0xC2));
|
||||
assert_eq!(chars.current_byte(), 0xC2);
|
||||
chars.advance();
|
||||
assert_eq!(chars.current(), 'a');
|
||||
assert_eq!(chars.current_byte(), b'a');
|
||||
}
|
||||
}
|
||||
@@ -69,8 +69,8 @@ impl ScriptLineProvider {
|
||||
}
|
||||
|
||||
/// Return the next script line to process across all scripts.
|
||||
pub fn next_line(&mut self) -> UResult<Option<String>> {
|
||||
let mut line = String::new();
|
||||
pub fn next_line(&mut self) -> UResult<Option<Vec<u8>>> {
|
||||
let mut line = Vec::new();
|
||||
|
||||
loop {
|
||||
let advance = match &mut self.state {
|
||||
@@ -82,13 +82,13 @@ impl ScriptLineProvider {
|
||||
..
|
||||
} => {
|
||||
line.clear();
|
||||
let bytes = reader.read_line(&mut line)?;
|
||||
let bytes = reader.read_until(b'\n', &mut line)?;
|
||||
if bytes == 0 {
|
||||
Some(*index + 1) // finished reading this source
|
||||
} else {
|
||||
*line_number += 1;
|
||||
// Remove trailing newline
|
||||
if line.ends_with('\n') {
|
||||
if line.ends_with(b"\n") {
|
||||
line.pop();
|
||||
}
|
||||
return Ok(Some(line));
|
||||
@@ -114,7 +114,7 @@ impl ScriptLineProvider {
|
||||
|
||||
match &self.sources[next_index] {
|
||||
ScriptValue::StringVal(s) => {
|
||||
let cursor = std::io::Cursor::new(s.clone());
|
||||
let cursor = std::io::Cursor::new(s.as_bytes().to_vec());
|
||||
self.state = State::Active {
|
||||
index: next_index,
|
||||
reader: Box::new(BufReader::new(cursor)),
|
||||
@@ -199,7 +199,7 @@ mod tests {
|
||||
|
||||
let mut lines = Vec::new();
|
||||
while let Some(line) = provider.next_line().unwrap() {
|
||||
lines.push(line.trim_end().to_string());
|
||||
lines.push(String::from_utf8(line).unwrap().trim_end().to_string());
|
||||
}
|
||||
|
||||
assert_eq!(lines, vec!["line one", "line two", "line three"]);
|
||||
@@ -216,7 +216,7 @@ mod tests {
|
||||
|
||||
let mut lines = Vec::new();
|
||||
while let Some(line) = provider.next_line().unwrap() {
|
||||
lines.push(line.trim_end().to_string());
|
||||
lines.push(String::from_utf8(line).unwrap().trim_end().to_string());
|
||||
}
|
||||
|
||||
assert_eq!(lines, vec!["file line 1", "file line 2"]);
|
||||
@@ -241,7 +241,7 @@ mod tests {
|
||||
|
||||
let mut lines = Vec::new();
|
||||
while let Some(line) = provider.next_line().unwrap() {
|
||||
lines.push(line.trim_end().to_string());
|
||||
lines.push(String::from_utf8(line).unwrap().trim_end().to_string());
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
@@ -266,7 +266,7 @@ mod tests {
|
||||
let mut provider = ScriptLineProvider::new(input);
|
||||
|
||||
if let Some(line) = provider.next_line().unwrap() {
|
||||
assert_eq!(line.trim(), "l1");
|
||||
assert_eq!(String::from_utf8(line).unwrap().trim(), "l1");
|
||||
assert_eq!(provider.get_line_number(), 1);
|
||||
assert_eq!(provider.get_input_name(), "<script argument 1>");
|
||||
} else {
|
||||
@@ -274,7 +274,7 @@ mod tests {
|
||||
}
|
||||
|
||||
if let Some(line) = provider.next_line().unwrap() {
|
||||
assert_eq!(line.trim(), "l2");
|
||||
assert_eq!(String::from_utf8(line).unwrap().trim(), "l2");
|
||||
assert_eq!(provider.get_line_number(), 2);
|
||||
assert_eq!(provider.get_input_name(), "<script argument 1>");
|
||||
} else {
|
||||
@@ -282,11 +282,22 @@ mod tests {
|
||||
}
|
||||
|
||||
if let Some(line) = provider.next_line().unwrap() {
|
||||
assert_eq!(line.trim(), "l3");
|
||||
assert_eq!(String::from_utf8(line).unwrap().trim(), "l3");
|
||||
assert_eq!(provider.get_line_number(), 1);
|
||||
assert_eq!(provider.get_input_name(), "<script argument 2>");
|
||||
} else {
|
||||
panic!("Expected a line");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_source_preserves_invalid_utf8_bytes() {
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
temp_file.write_all(b"s/\xC2\xE7/X/\n").unwrap();
|
||||
|
||||
let input = vec![ScriptValue::PathVal(temp_file.path().to_path_buf())];
|
||||
let mut provider = ScriptLineProvider::new(input);
|
||||
|
||||
assert_eq!(provider.next_line().unwrap().unwrap(), b"s/\xC2\xE7/X/");
|
||||
}
|
||||
}
|
||||
Loaded 20 of 41 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user