1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

Merge branch 'main' into ls-version-cmp

This commit is contained in:
Sylvestre Ledru 2023-07-09 09:24:39 +02:00 committed by GitHub
commit 10bca71b09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 1336 additions and 660 deletions

View file

@ -31,12 +31,12 @@ jobs:
id: vars id: vars
shell: bash shell: bash
run: | run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# surface MSRV from CICD workflow # surface MSRV from CICD workflow
RUST_MIN_SRV=$(grep -P "^\s+RUST_MIN_SRV:" .github/workflows/CICD.yml | grep -Po "(?<=\x22)\d+[.]\d+(?:[.]\d+)?(?=\x22)" ) RUST_MIN_SRV=$(grep -P "^\s+RUST_MIN_SRV:" .github/workflows/CICD.yml | grep -Po "(?<=\x22)\d+[.]\d+(?:[.]\d+)?(?=\x22)" )
outputs RUST_MIN_SRV echo "RUST_MIN_SRV=${RUST_MIN_SRV}" >> $GITHUB_OUTPUT
- uses: dtolnay/rust-toolchain@${{ steps.vars.outputs.RUST_MIN_SRV }} - uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ steps.vars.outputs.RUST_MIN_SRV }}
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: Ensure updated 'Cargo.lock' - name: Ensure updated 'Cargo.lock'
shell: bash shell: bash
@ -67,7 +67,7 @@ jobs:
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}') - name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
uses: EndBug/add-and-commit@v9 uses: EndBug/add-and-commit@v9
with: with:
branch: ${{ env.BRANCH_TARGET }} new_branch: ${{ env.BRANCH_TARGET }}
default_author: github_actions default_author: github_actions
message: "maint ~ refresh 'Cargo.lock'" message: "maint ~ refresh 'Cargo.lock'"
add: Cargo.lock add: Cargo.lock
@ -90,13 +90,11 @@ jobs:
id: vars id: vars
shell: bash shell: bash
run: | run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# target-specific options # target-specific options
# * CARGO_FEATURES_OPTION # * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ; CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
outputs CARGO_FEATURES_OPTION echo "CARGO_FEATURES_OPTION=${CARGO_FEATURES_OPTION}" >> $GITHUB_OUTPUT
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
toolchain: stable toolchain: stable
@ -114,7 +112,7 @@ jobs:
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}') - name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
uses: EndBug/add-and-commit@v9 uses: EndBug/add-and-commit@v9
with: with:
branch: ${{ env.BRANCH_TARGET }} new_branch: ${{ env.BRANCH_TARGET }}
default_author: github_actions default_author: github_actions
message: "maint ~ rustfmt (`cargo fmt`)" message: "maint ~ rustfmt (`cargo fmt`)"
env: env:

View file

@ -205,7 +205,7 @@ jobs:
path_UUTILS='${{ steps.vars.outputs.path_UUTILS }}' path_UUTILS='${{ steps.vars.outputs.path_UUTILS }}'
# https://github.com/uutils/coreutils/issues/4294 # https://github.com/uutils/coreutils/issues/4294
# https://github.com/uutils/coreutils/issues/4295 # https://github.com/uutils/coreutils/issues/4295
IGNORE_INTERMITTENT='${path_UUTILS}/.github/workflows/ignore-intermittent.txt' IGNORE_INTERMITTENT="${path_UUTILS}/.github/workflows/ignore-intermittent.txt"
mkdir -p ${{ steps.vars.outputs.path_reference }} mkdir -p ${{ steps.vars.outputs.path_reference }}

View file

@ -35,7 +35,7 @@ jobs:
- name: Run sccache-cache - name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3 uses: mozilla-actions/sccache-action@v0.0.3
- name: Prepare, build and test - name: Prepare, build and test
uses: vmactions/freebsd-vm@v0.3.0 uses: vmactions/freebsd-vm@v0.3.1
with: with:
usesh: true usesh: true
# We need jq to run show-utils.sh and bash to use inline shell string replacement # We need jq to run show-utils.sh and bash to use inline shell string replacement
@ -125,7 +125,7 @@ jobs:
- name: Run sccache-cache - name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3 uses: mozilla-actions/sccache-action@v0.0.3
- name: Prepare, build and test - name: Prepare, build and test
uses: vmactions/freebsd-vm@v0.3.0 uses: vmactions/freebsd-vm@v0.3.1
with: with:
usesh: true usesh: true
# sync: sshfs # sync: sshfs

1
.gitignore vendored
View file

@ -5,6 +5,7 @@ target/
/busybox/ /busybox/
/.vscode/ /.vscode/
/.vs/ /.vs/
/public/
*~ *~
.*.swp .*.swp
.*.swo .*.swo

1
.vscode/cSpell.json vendored
View file

@ -19,6 +19,7 @@
// files to ignore (globs supported) // files to ignore (globs supported)
"ignorePaths": [ "ignorePaths": [
"Cargo.lock", "Cargo.lock",
"oranda.json",
"target/**", "target/**",
"tests/**/fixtures/**", "tests/**/fixtures/**",
"src/uu/dd/test-resources/**", "src/uu/dd/test-resources/**",

370
Cargo.lock generated
View file

@ -2,12 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "Inflector"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]] [[package]]
name = "adler" name = "adler"
version = "1.0.2" version = "1.0.2"
@ -43,12 +37,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "aliasable"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]] [[package]]
name = "android-tzdata" name = "android-tzdata"
version = "0.1.1" version = "0.1.1"
@ -133,10 +121,11 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "bigdecimal" name = "bigdecimal"
version = "0.3.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" checksum = "5274a6b6e0ee020148397245b973e30163b7bffbc6d473613f850cb99888581e"
dependencies = [ dependencies = [
"libm",
"num-bigint", "num-bigint",
"num-integer", "num-integer",
"num-traits", "num-traits",
@ -169,7 +158,7 @@ dependencies = [
"regex", "regex",
"rustc-hash", "rustc-hash",
"shlex", "shlex",
"syn", "syn 1.0.109",
"which", "which",
] ]
@ -215,12 +204,11 @@ dependencies = [
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.5.0" version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
dependencies = [ dependencies = [
"memchr", "memchr",
"once_cell",
"regex-automata", "regex-automata",
"serde", "serde",
] ]
@ -245,9 +233,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.77" version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]] [[package]]
name = "cexpr" name = "cexpr"
@ -336,16 +324,6 @@ dependencies = [
"roff", "roff",
] ]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.0" version = "1.0.0"
@ -371,6 +349,28 @@ dependencies = [
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
[[package]]
name = "const-random"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e"
dependencies = [
"const-random-macro",
"proc-macro-hack",
]
[[package]]
name = "const-random-macro"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb"
dependencies = [
"getrandom",
"once_cell",
"proc-macro-hack",
"tiny-keccak",
]
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.2.4" version = "0.2.4"
@ -562,7 +562,7 @@ dependencies = [
"lazy_static", "lazy_static",
"proc-macro2", "proc-macro2",
"regex", "regex",
"syn", "syn 1.0.109",
"unicode-xid", "unicode-xid",
] ]
@ -574,7 +574,7 @@ checksum = "76071bb9c8c4dd2b5eb209907deab7b031323cf1be3dfdc6ec5d37f4f187d8a1"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"proc-macro2", "proc-macro2",
"syn", "syn 1.0.109",
] ]
[[package]] [[package]]
@ -589,7 +589,7 @@ dependencies = [
"lazy_static", "lazy_static",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.109",
] ]
[[package]] [[package]]
@ -701,7 +701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn 1.0.109",
] ]
[[package]] [[package]]
@ -720,50 +720,6 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
[[package]]
name = "cxx"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71"
[[package]]
name = "cxxbridge-macro"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.4.0" version = "2.4.0"
@ -787,7 +743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772"
dependencies = [ dependencies = [
"data-encoding", "data-encoding",
"syn", "syn 1.0.109",
] ]
[[package]] [[package]]
@ -809,9 +765,12 @@ dependencies = [
[[package]] [[package]]
name = "dlv-list" name = "dlv-list"
version = "0.3.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" checksum = "d529fd73d344663edfd598ccb3f344e46034db51ebd103518eae34338248ad73"
dependencies = [
"const-random",
]
[[package]] [[package]]
name = "dns-lookup" name = "dns-lookup"
@ -956,9 +915,18 @@ dependencies = [
[[package]] [[package]]
name = "fundu" name = "fundu"
version = "1.0.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47af3b646bdd738395be2db903fc11a5923b5e206016b8d4ad6db890bcae9bd5" checksum = "d579dcb632d86591bdd7fc445e705b96cb2a7fb5488d918d956f392b6148e898"
dependencies = [
"fundu-core",
]
[[package]]
name = "fundu-core"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a363b75dd1e4b5bd2cdc305c47399c524cae24638b368b66b1a4c2a36482801f"
[[package]] [[package]]
name = "futures" name = "futures"
@ -1016,7 +984,7 @@ checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.109",
] ]
[[package]] [[package]]
@ -1107,19 +1075,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "hermit-abi" name = "hashbrown"
version = "0.1.19" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]] [[package]]
name = "hex" name = "hex"
@ -1144,16 +1109,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "humantime_to_duration"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714764645f21cc70c4c151d7798dd158409641f37ad820bed65224aae403cbed"
dependencies = [
"regex",
"time",
]
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.53" version = "0.1.53"
@ -1170,12 +1125,11 @@ dependencies = [
[[package]] [[package]]
name = "iana-time-zone-haiku" name = "iana-time-zone-haiku"
version = "0.1.1" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [ dependencies = [
"cxx", "cc",
"cxx-build",
] ]
[[package]] [[package]]
@ -1225,7 +1179,7 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [ dependencies = [
"hermit-abi 0.3.1", "hermit-abi",
"libc", "libc",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -1236,7 +1190,7 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [ dependencies = [
"hermit-abi 0.3.1", "hermit-abi",
"io-lifetimes", "io-lifetimes",
"rustix 0.37.19", "rustix 0.37.19",
"windows-sys 0.48.0", "windows-sys 0.48.0",
@ -1244,9 +1198,9 @@ dependencies = [
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [ dependencies = [
"either", "either",
] ]
@ -1309,9 +1263,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.146" version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -1324,13 +1278,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "link-cplusplus" name = "libm"
version = "1.0.7" version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
@ -1519,11 +1470,11 @@ dependencies = [
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.14.0" version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [ dependencies = [
"hermit-abi 0.1.19", "hermit-abi",
"libc", "libc",
] ]
@ -1572,12 +1523,12 @@ dependencies = [
[[package]] [[package]]
name = "ordered-multimap" name = "ordered-multimap"
version = "0.4.3" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e"
dependencies = [ dependencies = [
"dlv-list", "dlv-list",
"hashbrown", "hashbrown 0.13.2",
] ]
[[package]] [[package]]
@ -1589,29 +1540,6 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "ouroboros"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db"
dependencies = [
"aliasable",
"ouroboros_macro",
]
[[package]]
name = "ouroboros_macro"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7"
dependencies = [
"Inflector",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "output_vt100" name = "output_vt100"
version = "0.1.3" version = "0.1.3"
@ -1662,18 +1590,18 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.11.1" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [ dependencies = [
"phf_shared", "phf_shared",
] ]
[[package]] [[package]]
name = "phf_codegen" name = "phf_codegen"
version = "0.11.1" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [ dependencies = [
"phf_generator", "phf_generator",
"phf_shared", "phf_shared",
@ -1691,9 +1619,9 @@ dependencies = [
[[package]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.11.1" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [ dependencies = [
"siphasher", "siphasher",
] ]
@ -1751,34 +1679,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-hack"
version = "1.0.4" version = "0.5.20+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.47" version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1815,9 +1725,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.21" version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -1909,9 +1819,21 @@ checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f"
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.8.4" version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [
"aho-corasick 1.0.1",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9aaecc05d5c4b5f7da074b9a0d1a0867e71fd36e7fc0482d8bcfe8e8fc56290"
dependencies = [ dependencies = [
"aho-corasick 1.0.1", "aho-corasick 1.0.1",
"memchr", "memchr",
@ -1919,22 +1841,22 @@ dependencies = [
] ]
[[package]] [[package]]
name = "regex-automata" name = "regex-syntax"
version = "0.1.10" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846"
[[package]] [[package]]
name = "regex-syntax" name = "relative-path"
version = "0.7.2" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" checksum = "4bf2521270932c3c7bed1a59151222bd7643c79310f2916f01925e1e16255698"
[[package]] [[package]]
name = "rlimit" name = "rlimit"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8a29d87a652dc4d43c586328706bb5cdff211f3f39a530f240b53f7221dab8e" checksum = "9b5b8be0bc0ef630d24f8fa836b3a3463479b2343b29f9a8fa905c71a8c7b69b"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -1947,9 +1869,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
[[package]] [[package]]
name = "rstest" name = "rstest"
version = "0.17.0" version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962" checksum = "2b96577ca10cb3eade7b337eb46520108a67ca2818a24d0b63f41fd62bc9651c"
dependencies = [ dependencies = [
"futures", "futures",
"futures-timer", "futures-timer",
@ -1959,23 +1881,26 @@ dependencies = [
[[package]] [[package]]
name = "rstest_macros" name = "rstest_macros"
version = "0.17.0" version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8" checksum = "225e674cf31712b8bb15fdbca3ec0c1b9d825c5a24407ff2b7e005fb6a29ba03"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"glob",
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex",
"relative-path",
"rustc_version", "rustc_version",
"syn", "syn 2.0.23",
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "rust-ini" name = "rust-ini"
version = "0.18.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"ordered-multimap", "ordered-multimap",
@ -2040,10 +1965,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "scratch" name = "self_cell"
version = "1.0.2" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6"
[[package]] [[package]]
name = "selinux" name = "selinux"
@ -2177,9 +2102,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.10.0" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
[[package]] [[package]]
name = "smawk" name = "smawk"
@ -2217,9 +2142,20 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.103" version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2249,15 +2185,6 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "terminal_size" name = "terminal_size"
version = "0.2.6" version = "0.2.6"
@ -2297,7 +2224,7 @@ checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.109",
] ]
[[package]] [[package]]
@ -2329,6 +2256,15 @@ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.15.0" version = "1.15.0"
@ -2347,7 +2283,7 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
dependencies = [ dependencies = [
"hashbrown", "hashbrown 0.12.3",
"regex", "regex",
] ]
@ -3099,9 +3035,9 @@ dependencies = [
"fnv", "fnv",
"itertools", "itertools",
"memchr", "memchr",
"ouroboros",
"rand", "rand",
"rayon", "rayon",
"self_cell",
"tempfile", "tempfile",
"unicode-width", "unicode-width",
"uucore", "uucore",
@ -3235,7 +3171,7 @@ version = "0.0.19"
dependencies = [ dependencies = [
"clap", "clap",
"filetime", "filetime",
"humantime_to_duration", "parse_datetime",
"time", "time",
"uucore", "uucore",
"windows-sys 0.48.0", "windows-sys 0.48.0",
@ -3484,7 +3420,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.109",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -3506,7 +3442,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.109",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]

View file

@ -1,7 +1,7 @@
# coreutils (uutils) # coreutils (uutils)
# * see the repository LICENSE, README, and CONTRIBUTING files for more information # * see the repository LICENSE, README, and CONTRIBUTING files for more information
# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu mangen datetime uuhelp # spell-checker:ignore (libs) bigdecimal datetime fundu gethostid kqueue libselinux mangen memmap procfs uuhelp
[package] [package]
name = "coreutils" name = "coreutils"
@ -257,9 +257,9 @@ feat_os_windows_legacy = [
test = ["uu_test"] test = ["uu_test"]
[workspace.dependencies] [workspace.dependencies]
bigdecimal = "0.3" bigdecimal = "0.4"
binary-heap-plus = "0.5.0" binary-heap-plus = "0.5.0"
bstr = "1.5" bstr = "1.6"
bytecount = "0.6.3" bytecount = "0.6.3"
byteorder = "1.4.3" byteorder = "1.4.3"
chrono = { version = "^0.4.26", default-features = false, features = [ chrono = { version = "^0.4.26", default-features = false, features = [
@ -280,18 +280,19 @@ filetime = "0.2"
fnv = "1.0.7" fnv = "1.0.7"
fs_extra = "1.3.0" fs_extra = "1.3.0"
fts-sys = "0.2" fts-sys = "0.2"
fundu = "1.0.0" fundu = "1.1.0"
gcd = "2.3" gcd = "2.3"
glob = "0.3.1" glob = "0.3.1"
half = "2.2" half = "2.2"
indicatif = "0.17" indicatif = "0.17"
is-terminal = "0.4.7" is-terminal = "0.4.7"
itertools = "0.10.5" itertools = "0.11.0"
libc = "0.2.146" libc = "0.2.147"
lscolors = { version = "0.14.0", default-features = false, features = [ lscolors = { version = "0.14.0", default-features = false, features = [
"nu-ansi-term", "nu-ansi-term",
] } ] }
memchr = "2" memchr = "2"
memmap2 = "0.7"
nix = { version = "0.26", default-features = false } nix = { version = "0.26", default-features = false }
nom = "7.1.3" nom = "7.1.3"
notify = { version = "=6.0.1", features = ["macos_kqueue"] } notify = { version = "=6.0.1", features = ["macos_kqueue"] }
@ -300,23 +301,23 @@ num-traits = "0.2.15"
number_prefix = "0.4" number_prefix = "0.4"
once_cell = "1.18.0" once_cell = "1.18.0"
onig = { version = "~6.4", default-features = false } onig = { version = "~6.4", default-features = false }
ouroboros = "0.15.6"
parse_datetime = "0.4.0" parse_datetime = "0.4.0"
phf = "0.11.1" phf = "0.11.2"
phf_codegen = "0.11.1" phf_codegen = "0.11.2"
platform-info = "2.0.1" platform-info = "2.0.1"
quick-error = "2.0.1" quick-error = "2.0.1"
rand = { version = "0.8", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
rand_core = "0.6" rand_core = "0.6"
rayon = "1.7" rayon = "1.7"
redox_syscall = "0.3" redox_syscall = "0.3"
regex = "1.8.4" regex = "1.9.1"
rstest = "0.17.0" rstest = "0.18.1"
rust-ini = "0.18.0" rust-ini = "0.19.0"
same-file = "1.0.6" same-file = "1.0.6"
self_cell = "1.0.1"
selinux = "0.4" selinux = "0.4"
signal-hook = "0.3.15" signal-hook = "0.3.15"
smallvec = { version = "1.10", features = ["union"] } smallvec = { version = "1.11", features = ["union"] }
tempfile = "3.6.0" tempfile = "3.6.0"
term_grid = "0.1.5" term_grid = "0.1.5"
terminal_size = "0.2.6" terminal_size = "0.2.6"
@ -491,11 +492,11 @@ uucore = { workspace = true, features = ["entries", "process", "signals"] }
walkdir = { workspace = true } walkdir = { workspace = true }
is-terminal = { workspace = true } is-terminal = { workspace = true }
hex-literal = "0.4.1" hex-literal = "0.4.1"
rstest = "0.17.0" rstest = { workspace = true }
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies] [target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
procfs = { version = "0.15", default-features = false } procfs = { version = "0.15", default-features = false }
rlimit = "0.9.1" rlimit = "0.10.0"
[target.'cfg(unix)'.dev-dependencies] [target.'cfg(unix)'.dev-dependencies]
nix = { workspace = true, features = ["process", "signal", "user"] } nix = { workspace = true, features = ["process", "signal", "user"] }

View file

@ -1,6 +1,7 @@
<!-- markdownlint-disable MD033 MD041 MD002 --> <!-- markdownlint-disable MD033 MD041 MD002 -->
<!-- markdownlint-disable commands-show-output no-duplicate-heading --> <!-- markdownlint-disable commands-show-output no-duplicate-heading -->
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR UTILNAME manpages reimplementation --> <!-- spell-checker:ignore markdownlint ; (options) DESTDIR UTILNAME manpages reimplementation oranda -->
<div class="oranda-hide">
<div align="center"> <div align="center">
![uutils logo](docs/src/logo.svg) ![uutils logo](docs/src/logo.svg)
@ -19,11 +20,14 @@
--- ---
</div>
uutils coreutils is a cross-platform reimplementation of the GNU coreutils in uutils coreutils is a cross-platform reimplementation of the GNU coreutils in
[Rust](http://www.rust-lang.org). While all programs have been implemented, some [Rust](http://www.rust-lang.org). While all programs have been implemented, some
options might be missing or different behavior might be experienced. options might be missing or different behavior might be experienced.
<div class="oranda-hide">
To install it: To install it:
```shell ```shell
@ -31,6 +35,8 @@ cargo install coreutils
~/.cargo/bin/coreutils ~/.cargo/bin/coreutils
``` ```
</div>
<!-- markdownlint-disable-next-line MD026 --> <!-- markdownlint-disable-next-line MD026 -->
## Goals ## Goals
@ -42,6 +48,8 @@ uutils aims to work on as many platforms as possible, to be able to use the same
utils on Linux, Mac, Windows and other platforms. This ensures, for example, utils on Linux, Mac, Windows and other platforms. This ensures, for example,
that scripts can be easily transferred between platforms. that scripts can be easily transferred between platforms.
<div class="oranda-hide">
## Documentation ## Documentation
uutils has both user and developer documentation available: uutils has both user and developer documentation available:
@ -52,6 +60,7 @@ uutils has both user and developer documentation available:
Both can also be generated locally, the instructions for that can be found in Both can also be generated locally, the instructions for that can be found in
the [coreutils docs](https://github.com/uutils/uutils.github.io) repository. the [coreutils docs](https://github.com/uutils/uutils.github.io) repository.
<!-- ANCHOR: build (this mark is needed for mdbook) --> <!-- ANCHOR: build (this mark is needed for mdbook) -->
## Requirements ## Requirements
@ -301,6 +310,8 @@ See <https://github.com/uutils/coreutils/issues/3336> for the main meta bugs
![Evolution over time](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.png?raw=true) ![Evolution over time](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.png?raw=true)
</div> <!-- close oranda-hide div -->
## Contributing ## Contributing
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).

View file

@ -59,8 +59,6 @@ highlight = "all"
# introduces it. # introduces it.
# spell-checker: disable # spell-checker: disable
skip = [ skip = [
# is-terminal
{ name = "hermit-abi", version = "0.3.1" },
# procfs # procfs
{ name = "rustix", version = "0.36.14" }, { name = "rustix", version = "0.36.14" },
# rustix # rustix
@ -87,6 +85,10 @@ skip = [
{ name = "redox_syscall", version = "0.3.5" }, { name = "redox_syscall", version = "0.3.5" },
# cpp_macros # cpp_macros
{ name = "aho-corasick", version = "0.7.19" }, { name = "aho-corasick", version = "0.7.19" },
# ordered-multimap (via rust-ini)
{ name = "hashbrown", version = "0.13.2" },
# various crates
{ name = "syn", version = "1.0.109" },
] ]
# spell-checker: enable # spell-checker: enable

View file

@ -61,7 +61,20 @@ feature is adopted from [FreeBSD](https://www.freebsd.org/cgi/man.cgi?cut).
## `fmt` ## `fmt`
`fmt` has additional flags for prefixes: `-P/--skip-prefix`, `-x/--exact-prefix`, and `fmt` has additional flags for prefixes: `-P`/`--skip-prefix`, `-x`/`--exact-prefix`, and
`-X/--exact-skip-prefix`. With `-m/--preserve-headers`, an attempt is made to detect and preserve `-X`/`--exact-skip-prefix`. With `-m`/`--preserve-headers`, an attempt is made to detect and preserve
mail headers in the input. `-q/--quick` breaks lines more quickly. And `-T/--tab-width` defines the mail headers in the input. `-q`/`--quick` breaks lines more quickly. And `-T`/`--tab-width` defines the
number of spaces representing a tab when determining the line length. number of spaces representing a tab when determining the line length.
## `seq`
`seq` provides `-t`/`--terminator` to set the terminator character.
## `ls`
GNU `ls` provides two ways to use a long listing format: `-l` and `--format=long`. We support a
third way: `--long`.
## `du`
`du` allows `birth` and `creation` as values for the `--time` argument to show the creation time.

4
docs/src/oranda.css Normal file
View file

@ -0,0 +1,4 @@
.logo {
display: block;
height: 170px;
}

13
oranda.json Normal file
View file

@ -0,0 +1,13 @@
{
"project": {
"name": "uutils coreutils"
},
"components": {
"changelog": true
},
"styles": {
"theme": "light",
"logo": "docs/src/logo.svg",
"additional_css": ["docs/src/oranda.css"]
}
}

View file

@ -383,6 +383,7 @@ pub fn uu_app() -> Command {
backup_control::BACKUP_CONTROL_LONG_HELP backup_control::BACKUP_CONTROL_LONG_HELP
)) ))
.infer_long_args(true) .infer_long_args(true)
.args_override_self(true)
.arg( .arg(
Arg::new(options::TARGET_DIRECTORY) Arg::new(options::TARGET_DIRECTORY)
.short('t') .short('t')

View file

@ -26,14 +26,13 @@ use uucore::{format_usage, help_about, help_usage, show};
#[cfg(windows)] #[cfg(windows)]
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime}; use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
use uucore::shortcut_value_parser::ShortcutValueParser;
// Options // Options
const DATE: &str = "date"; const DATE: &str = "date";
const HOURS: &str = "hours"; const HOURS: &str = "hours";
const MINUTES: &str = "minutes"; const MINUTES: &str = "minutes";
const SECONDS: &str = "seconds"; const SECONDS: &str = "seconds";
const HOUR: &str = "hour";
const MINUTE: &str = "minute";
const SECOND: &str = "second";
const NS: &str = "ns"; const NS: &str = "ns";
const ABOUT: &str = help_about!("date.md"); const ABOUT: &str = help_about!("date.md");
@ -110,9 +109,9 @@ enum Iso8601Format {
impl<'a> From<&'a str> for Iso8601Format { impl<'a> From<&'a str> for Iso8601Format {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
match s { match s {
HOURS | HOUR => Self::Hours, HOURS => Self::Hours,
MINUTES | MINUTE => Self::Minutes, MINUTES => Self::Minutes,
SECONDS | SECOND => Self::Seconds, SECONDS => Self::Seconds,
NS => Self::Ns, NS => Self::Ns,
DATE => Self::Date, DATE => Self::Date,
// Note: This is caught by clap via `possible_values` // Note: This is caught by clap via `possible_values`
@ -131,7 +130,7 @@ impl<'a> From<&'a str> for Rfc3339Format {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
match s { match s {
DATE => Self::Date, DATE => Self::Date,
SECONDS | SECOND => Self::Seconds, SECONDS => Self::Seconds,
NS => Self::Ns, NS => Self::Ns,
// Should be caught by clap // Should be caught by clap
_ => panic!("Invalid format: {s}"), _ => panic!("Invalid format: {s}"),
@ -317,7 +316,9 @@ pub fn uu_app() -> Command {
.short('I') .short('I')
.long(OPT_ISO_8601) .long(OPT_ISO_8601)
.value_name("FMT") .value_name("FMT")
.value_parser([DATE, HOUR, HOURS, MINUTE, MINUTES, SECOND, SECONDS, NS]) .value_parser(ShortcutValueParser::new([
DATE, HOURS, MINUTES, SECONDS, NS,
]))
.num_args(0..=1) .num_args(0..=1)
.default_missing_value(OPT_DATE) .default_missing_value(OPT_DATE)
.help(ISO_8601_HELP_STRING), .help(ISO_8601_HELP_STRING),
@ -333,7 +334,7 @@ pub fn uu_app() -> Command {
Arg::new(OPT_RFC_3339) Arg::new(OPT_RFC_3339)
.long(OPT_RFC_3339) .long(OPT_RFC_3339)
.value_name("FMT") .value_name("FMT")
.value_parser([DATE, SECOND, SECONDS, NS]) .value_parser(ShortcutValueParser::new([DATE, SECONDS, NS]))
.help(RFC_3339_HELP_STRING), .help(RFC_3339_HELP_STRING),
) )
.arg( .arg(

View file

@ -13,7 +13,7 @@ Copy, and optionally convert, a file system resource
### Operands ### Operands
- `Bs=BYTES` : read and write up to BYTES bytes at a time (default: 512); - `bs=BYTES` : read and write up to BYTES bytes at a time (default: 512);
overwrites `ibs` and `obs`. overwrites `ibs` and `obs`.
- `cbs=BYTES` : the 'conversion block size' in bytes. Applies to the - `cbs=BYTES` : the 'conversion block size' in bytes. Applies to the
`conv=block`, and `conv=unblock` operations. `conv=block`, and `conv=unblock` operations.
@ -114,7 +114,7 @@ Copy, and optionally convert, a file system resource
### General Flags ### General Flags
- `Direct` : use direct I/O for data. - `direct` : use direct I/O for data.
- `directory` : fail unless the given input (if used as an iflag) or - `directory` : fail unless the given input (if used as an iflag) or
output (if used as an oflag) is a directory. output (if used as an oflag) is a directory.
- `dsync` : use synchronized I/O for data. - `dsync` : use synchronized I/O for data.

View file

@ -36,9 +36,12 @@ use std::os::unix::{
io::{AsRawFd, FromRawFd}, io::{AsRawFd, FromRawFd},
}; };
use std::path::Path; use std::path::Path;
use std::sync::mpsc; use std::sync::{
atomic::{AtomicBool, Ordering::Relaxed},
mpsc, Arc,
};
use std::thread; use std::thread;
use std::time; use std::time::{Duration, Instant};
use clap::{crate_version, Arg, Command}; use clap::{crate_version, Arg, Command};
use gcd::Gcd; use gcd::Gcd;
@ -75,6 +78,45 @@ struct Settings {
status: Option<StatusLevel>, status: Option<StatusLevel>,
} }
/// A timer which triggers on a given interval
///
/// After being constructed with [`Alarm::with_interval`], [`Alarm::is_triggered`]
/// will return true once per the given [`Duration`].
///
/// Can be cloned, but the trigger status is shared across all instances so only
/// the first caller each interval will yield true.
///
/// When all instances are dropped the background thread will exit on the next interval.
#[derive(Debug, Clone)]
pub struct Alarm {
interval: Duration,
trigger: Arc<AtomicBool>,
}
impl Alarm {
pub fn with_interval(interval: Duration) -> Self {
let trigger = Arc::new(AtomicBool::default());
let weak_trigger = Arc::downgrade(&trigger);
thread::spawn(move || {
while let Some(trigger) = weak_trigger.upgrade() {
thread::sleep(interval);
trigger.store(true, Relaxed);
}
});
Self { interval, trigger }
}
pub fn is_triggered(&self) -> bool {
self.trigger.swap(false, Relaxed)
}
pub fn get_interval(&self) -> Duration {
self.interval
}
}
/// A number in blocks or bytes /// A number in blocks or bytes
/// ///
/// Some values (seek, skip, iseek, oseek) can have values either in blocks or in bytes. /// Some values (seek, skip, iseek, oseek) can have values either in blocks or in bytes.
@ -760,7 +802,7 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
// of its report includes the throughput in bytes per second, // of its report includes the throughput in bytes per second,
// which requires knowing how long the process has been // which requires knowing how long the process has been
// running. // running.
let start = time::Instant::now(); let start = Instant::now();
// A good buffer size for reading. // A good buffer size for reading.
// //
@ -780,7 +822,6 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
// information. // information.
let (prog_tx, rx) = mpsc::channel(); let (prog_tx, rx) = mpsc::channel();
let output_thread = thread::spawn(gen_prog_updater(rx, i.settings.status)); let output_thread = thread::spawn(gen_prog_updater(rx, i.settings.status));
let mut progress_as_secs = 0;
// Optimization: if no blocks are to be written, then don't // Optimization: if no blocks are to be written, then don't
// bother allocating any buffers. // bother allocating any buffers.
@ -813,6 +854,12 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
// This is the max size needed. // This is the max size needed.
let mut buf = vec![BUF_INIT_BYTE; bsize]; let mut buf = vec![BUF_INIT_BYTE; bsize];
// Spawn a timer thread to provide a scheduled signal indicating when we
// should send an update of our progress to the reporting thread.
//
// This avoids the need to query the OS monotonic clock for every block.
let alarm = Alarm::with_interval(Duration::from_secs(1));
// Index in the input file where we are reading bytes and in // Index in the input file where we are reading bytes and in
// the output file where we are writing bytes. // the output file where we are writing bytes.
// //
@ -871,9 +918,8 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
// error. // error.
rstat += rstat_update; rstat += rstat_update;
wstat += wstat_update; wstat += wstat_update;
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), false); if alarm.is_triggered() {
if prog_update.duration.as_secs() >= progress_as_secs { let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), false);
progress_as_secs = prog_update.duration.as_secs() + 1;
prog_tx.send(prog_update).unwrap_or(()); prog_tx.send(prog_update).unwrap_or(());
} }
} }
@ -885,7 +931,7 @@ fn finalize<T>(
output: &mut Output, output: &mut Output,
rstat: ReadStat, rstat: ReadStat,
wstat: WriteStat, wstat: WriteStat,
start: time::Instant, start: Instant,
prog_tx: &mpsc::Sender<ProgUpdate>, prog_tx: &mpsc::Sender<ProgUpdate>,
output_thread: thread::JoinHandle<T>, output_thread: thread::JoinHandle<T>,
) -> std::io::Result<()> { ) -> std::io::Result<()> {

View file

@ -145,7 +145,7 @@ impl Stat {
return Ok(Self { return Ok(Self {
path: path.to_path_buf(), path: path.to_path_buf(),
is_dir: metadata.is_dir(), is_dir: metadata.is_dir(),
size: metadata.len(), size: if path.is_dir() { 0 } else { metadata.len() },
blocks: metadata.blocks(), blocks: metadata.blocks(),
inodes: 1, inodes: 1,
inode: Some(file_info), inode: Some(file_info),
@ -162,7 +162,7 @@ impl Stat {
Ok(Self { Ok(Self {
path: path.to_path_buf(), path: path.to_path_buf(),
is_dir: metadata.is_dir(), is_dir: metadata.is_dir(),
size: metadata.len(), size: if path.is_dir() { 0 } else { metadata.len() },
blocks: size_on_disk / 1024 * 2, blocks: size_on_disk / 1024 * 2,
inode: file_info, inode: file_info,
inodes: 1, inodes: 1,

View file

@ -306,7 +306,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
// if there is no program name for some reason, default to "hashsum" // if there is no program name for some reason, default to "hashsum"
let program = args.next().unwrap_or_else(|| OsString::from(NAME)); let program = args.next().unwrap_or_else(|| OsString::from(NAME));
let binary_name = Path::new(&program) let binary_name = Path::new(&program)
.file_name() .file_stem()
.unwrap_or_else(|| OsStr::new(NAME)) .unwrap_or_else(|| OsStr::new(NAME))
.to_string_lossy(); .to_string_lossy();

View file

@ -3010,6 +3010,20 @@ fn display_inode(metadata: &Metadata) -> String {
#[allow(unused_variables)] #[allow(unused_variables)]
fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -> String { fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -> String {
let substitute_string = "?".to_string(); let substitute_string = "?".to_string();
// If we must dereference, ensure that the symlink is actually valid even if the system
// does not support SELinux.
// Conforms to the GNU coreutils where a dangling symlink results in exit code 1.
if must_dereference {
match get_metadata(p_buf, must_dereference) {
Err(err) => {
// The Path couldn't be dereferenced, so return early and set exit code 1
// to indicate a minor error
show!(LsError::IOErrorContext(err, p_buf.to_path_buf(), false));
return substitute_string;
}
Ok(md) => (),
}
}
if config.selinux_supported { if config.selinux_supported {
#[cfg(feature = "selinux")] #[cfg(feature = "selinux")]
{ {

View file

@ -50,10 +50,11 @@ pub mod options {
pub const FILES: &str = "files"; pub const FILES: &str = "files";
} }
const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n"; const MULTI_FILE_TOP_PROMPT: &str = "\r::::::::::::::\n\r{}\n\r::::::::::::::\n";
struct Options { struct Options {
clean_print: bool, clean_print: bool,
from_line: usize,
lines: Option<u16>, lines: Option<u16>,
print_over: bool, print_over: bool,
silent: bool, silent: bool,
@ -72,8 +73,13 @@ impl Options {
(None, Some(number)) if number > 0 => Some(number + 1), (None, Some(number)) if number > 0 => Some(number + 1),
(_, _) => None, (_, _) => None,
}; };
let from_line = match matches.get_one::<usize>(options::FROM_LINE).copied() {
Some(number) if number > 1 => number - 1,
_ => 0,
};
Self { Self {
clean_print: matches.get_flag(options::CLEAN_PRINT), clean_print: matches.get_flag(options::CLEAN_PRINT),
from_line,
lines, lines,
print_over: matches.get_flag(options::PRINT_OVER), print_over: matches.get_flag(options::PRINT_OVER),
silent: matches.get_flag(options::SILENT), silent: matches.get_flag(options::SILENT),
@ -90,7 +96,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
let options = Options::from(&matches); let mut options = Options::from(&matches);
let mut buff = String::new(); let mut buff = String::new();
@ -115,9 +121,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
format!("cannot open {}: No such file or directory", file.quote()), format!("cannot open {}: No such file or directory", file.quote()),
)); ));
} }
if length > 1 {
buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", file.to_str().unwrap()));
}
let opened_file = match File::open(file) { let opened_file = match File::open(file) {
Err(why) => { Err(why) => {
terminal::disable_raw_mode().unwrap(); terminal::disable_raw_mode().unwrap();
@ -130,14 +133,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}; };
let mut reader = BufReader::new(opened_file); let mut reader = BufReader::new(opened_file);
reader.read_to_string(&mut buff).unwrap(); reader.read_to_string(&mut buff).unwrap();
more(&buff, &mut stdout, next_file.copied(), &options)?; more(
&buff,
&mut stdout,
length > 1,
file.to_str(),
next_file.copied(),
&mut options,
)?;
buff.clear(); buff.clear();
} }
reset_term(&mut stdout); reset_term(&mut stdout);
} else if !std::io::stdin().is_terminal() { } else if !std::io::stdin().is_terminal() {
stdin().read_to_string(&mut buff).unwrap(); stdin().read_to_string(&mut buff).unwrap();
let mut stdout = setup_term(); let mut stdout = setup_term();
more(&buff, &mut stdout, None, &options)?; more(&buff, &mut stdout, false, None, None, &mut options)?;
reset_term(&mut stdout); reset_term(&mut stdout);
} else { } else {
return Err(UUsageError::new(1, "bad usage")); return Err(UUsageError::new(1, "bad usage"));
@ -179,6 +189,22 @@ pub fn uu_app() -> Command {
.help("Squeeze multiple blank lines into one") .help("Squeeze multiple blank lines into one")
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg(
Arg::new(options::PLAIN)
.short('u')
.long(options::PLAIN)
.action(ArgAction::SetTrue)
.hide(true),
)
.arg(
Arg::new(options::FROM_LINE)
.short('F')
.long(options::FROM_LINE)
.num_args(1)
.value_name("number")
.value_parser(value_parser!(usize))
.help("Display file beginning from line number"),
)
.arg( .arg(
Arg::new(options::LINES) Arg::new(options::LINES)
.short('n') .short('n')
@ -191,7 +217,6 @@ pub fn uu_app() -> Command {
.arg( .arg(
Arg::new(options::NUMBER) Arg::new(options::NUMBER)
.long(options::NUMBER) .long(options::NUMBER)
.required(false)
.num_args(1) .num_args(1)
.value_parser(value_parser!(u16).range(0..)) .value_parser(value_parser!(u16).range(0..))
.help("Same as --lines"), .help("Same as --lines"),
@ -210,21 +235,6 @@ pub fn uu_app() -> Command {
.long(options::NO_PAUSE) .long(options::NO_PAUSE)
.help("Suppress pause after form feed"), .help("Suppress pause after form feed"),
) )
.arg(
Arg::new(options::PLAIN)
.short('u')
.long(options::PLAIN)
.help("Suppress underlining and bold"),
)
.arg(
Arg::new(options::FROM_LINE)
.short('F')
.allow_hyphen_values(true)
.required(false)
.takes_value(true)
.value_name("number")
.help("Display file beginning from line number"),
)
.arg( .arg(
Arg::new(options::PATTERN) Arg::new(options::PATTERN)
.short('P') .short('P')
@ -273,8 +283,10 @@ fn reset_term(_: &mut usize) {}
fn more( fn more(
buff: &str, buff: &str,
stdout: &mut Stdout, stdout: &mut Stdout,
multiple_file: bool,
file: Option<&str>,
next_file: Option<&str>, next_file: Option<&str>,
options: &Options, options: &mut Options,
) -> UResult<()> { ) -> UResult<()> {
let (cols, mut rows) = terminal::size().unwrap(); let (cols, mut rows) = terminal::size().unwrap();
if let Some(number) = options.lines { if let Some(number) = options.lines {
@ -284,8 +296,23 @@ fn more(
let lines = break_buff(buff, usize::from(cols)); let lines = break_buff(buff, usize::from(cols));
let mut pager = Pager::new(rows, lines, next_file, options); let mut pager = Pager::new(rows, lines, next_file, options);
if multiple_file {
execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
stdout.write_all(
MULTI_FILE_TOP_PROMPT
.replace("{}", file.unwrap_or_default())
.as_bytes(),
)?;
pager.content_rows -= 3;
}
pager.draw(stdout, None); pager.draw(stdout, None);
if pager.should_close() { if multiple_file {
options.from_line = 0;
pager.content_rows += 3;
}
if pager.should_close() && next_file.is_none() {
return Ok(()); return Ok(());
} }
@ -406,7 +433,7 @@ impl<'a> Pager<'a> {
fn new(rows: u16, lines: Vec<String>, next_file: Option<&'a str>, options: &Options) -> Self { fn new(rows: u16, lines: Vec<String>, next_file: Option<&'a str>, options: &Options) -> Self {
let line_count = lines.len(); let line_count = lines.len();
Self { Self {
upper_mark: 0, upper_mark: options.from_line,
content_rows: rows.saturating_sub(1), content_rows: rows.saturating_sub(1),
lines, lines,
next_file, next_file,
@ -472,10 +499,10 @@ impl<'a> Pager<'a> {
} }
fn draw(&mut self, stdout: &mut std::io::Stdout, wrong_key: Option<char>) { fn draw(&mut self, stdout: &mut std::io::Stdout, wrong_key: Option<char>) {
self.draw_lines(stdout);
let lower_mark = self let lower_mark = self
.line_count .line_count
.min(self.upper_mark.saturating_add(self.content_rows.into())); .min(self.upper_mark.saturating_add(self.content_rows.into()));
self.draw_lines(stdout);
self.draw_prompt(stdout, lower_mark, wrong_key); self.draw_prompt(stdout, lower_mark, wrong_key);
stdout.flush().unwrap(); stdout.flush().unwrap();
} }
@ -535,7 +562,6 @@ impl<'a> Pager<'a> {
}; };
let status = format!("--More--({status_inner})"); let status = format!("--More--({status_inner})");
let banner = match (self.silent, wrong_key) { let banner = match (self.silent, wrong_key) {
(true, Some(key)) => format!( (true, Some(key)) => format!(
"{status} [Unknown key: '{key}'. Press 'h' for instructions. (unimplemented)]" "{status} [Unknown key: '{key}'. Press 'h' for instructions. (unimplemented)]"

View file

@ -22,7 +22,7 @@ use std::os::unix;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows; use std::os::windows;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uucore::backup_control::{self, BackupMode}; use uucore::backup_control::{self, source_is_target_backup, BackupMode};
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError, UUsageError}; use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError, UUsageError};
use uucore::fs::{are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file}; use uucore::fs::{are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file};
@ -251,6 +251,17 @@ fn parse_paths(files: &[OsString], b: &Behavior) -> Vec<PathBuf> {
} }
fn handle_two_paths(source: &Path, target: &Path, b: &Behavior) -> UResult<()> { fn handle_two_paths(source: &Path, target: &Path, b: &Behavior) -> UResult<()> {
if b.backup == BackupMode::SimpleBackup && source_is_target_backup(source, target, &b.suffix) {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!(
"backing up {} might destroy source; {} not moved",
target.quote(),
source.quote()
),
)
.into());
}
if source.symlink_metadata().is_err() { if source.symlink_metadata().is_err() {
return Err(MvError::NoSuchFile(source.quote().to_string()).into()); return Err(MvError::NoSuchFile(source.quote().to_string()).into());
} }

View file

@ -1,4 +1,4 @@
#nl # nl
``` ```
nl [OPTION]... [FILE]... nl [OPTION]... [FILE]...

View file

@ -29,7 +29,7 @@ fn parse_style(chars: &[char]) -> Result<crate::NumberingStyle, String> {
pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> Vec<String> { pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> Vec<String> {
// This vector holds error messages encountered. // This vector holds error messages encountered.
let mut errs: Vec<String> = vec![]; let mut errs: Vec<String> = vec![];
settings.renumber = !opts.contains_id(options::NO_RENUMBER); settings.renumber = opts.get_flag(options::NO_RENUMBER);
match opts.get_one::<String>(options::NUMBER_SEPARATOR) { match opts.get_one::<String>(options::NUMBER_SEPARATOR) {
None => {} None => {}
Some(val) => { Some(val) => {

View file

@ -6,8 +6,6 @@
// * file that was distributed with this source code. // * file that was distributed with this source code.
// * // *
// spell-checker:ignore (ToDO) corasick memchr
use clap::{crate_version, Arg, ArgAction, Command}; use clap::{crate_version, Arg, ArgAction, Command};
use std::fs::File; use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read}; use std::io::{stdin, BufRead, BufReader, Read};
@ -18,8 +16,8 @@ use uucore::{format_usage, help_about, help_usage};
mod helper; mod helper;
static ABOUT: &str = help_about!("nl.md"); const ABOUT: &str = help_about!("nl.md");
static USAGE: &str = help_usage!("nl.md"); const USAGE: &str = help_usage!("nl.md");
// Settings store options used by nl to produce its output. // Settings store options used by nl to produce its output.
pub struct Settings { pub struct Settings {
@ -42,6 +40,24 @@ pub struct Settings {
number_separator: String, number_separator: String,
} }
impl Default for Settings {
fn default() -> Self {
Self {
header_numbering: NumberingStyle::NumberForNone,
body_numbering: NumberingStyle::NumberForAll,
footer_numbering: NumberingStyle::NumberForNone,
section_delimiter: ['\\', ':'],
starting_line_number: 1,
line_increment: 1,
join_blank_lines: 1,
number_width: 6,
number_format: NumberFormat::Right,
renumber: true,
number_separator: String::from("\t"),
}
}
}
// NumberingStyle stores which lines are to be numbered. // NumberingStyle stores which lines are to be numbered.
// The possible options are: // The possible options are:
// 1. Number all lines // 1. Number all lines
@ -87,20 +103,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?; let matches = uu_app().try_get_matches_from(args)?;
// A mutable settings object, initialized with the defaults. let mut settings = Settings::default();
let mut settings = Settings {
header_numbering: NumberingStyle::NumberForNone,
body_numbering: NumberingStyle::NumberForAll,
footer_numbering: NumberingStyle::NumberForNone,
section_delimiter: ['\\', ':'],
starting_line_number: 1,
line_increment: 1,
join_blank_lines: 1,
number_width: 6,
number_format: NumberFormat::Right,
renumber: true,
number_separator: String::from("\t"),
};
// Update the settings from the command line options, and terminate the // Update the settings from the command line options, and terminate the
// program if some options could not successfully be parsed. // program if some options could not successfully be parsed.
@ -210,7 +213,8 @@ pub fn uu_app() -> Command {
Arg::new(options::NO_RENUMBER) Arg::new(options::NO_RENUMBER)
.short('p') .short('p')
.long(options::NO_RENUMBER) .long(options::NO_RENUMBER)
.help("do not reset line numbers at logical pages"), .help("do not reset line numbers at logical pages")
.action(ArgAction::SetFalse),
) )
.arg( .arg(
Arg::new(options::NUMBER_SEPARATOR) Arg::new(options::NUMBER_SEPARATOR)

View file

@ -11,11 +11,13 @@ use crate::options::*;
use crate::units::{Result, Unit}; use crate::units::{Result, Unit};
use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command}; use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
use std::io::{BufRead, Write}; use std::io::{BufRead, Write};
use std::str::FromStr;
use units::{IEC_BASES, SI_BASES}; use units::{IEC_BASES, SI_BASES};
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::UResult; use uucore::error::{UError, UResult};
use uucore::ranges::Range; use uucore::ranges::Range;
use uucore::{format_usage, help_about, help_section, help_usage}; use uucore::{format_usage, help_about, help_section, help_usage, show, show_error};
pub mod errors; pub mod errors;
pub mod format; pub mod format;
@ -28,12 +30,8 @@ const USAGE: &str = help_usage!("numfmt.md");
fn handle_args<'a>(args: impl Iterator<Item = &'a str>, options: &NumfmtOptions) -> UResult<()> { fn handle_args<'a>(args: impl Iterator<Item = &'a str>, options: &NumfmtOptions) -> UResult<()> {
for l in args { for l in args {
match format_and_print(l, options) { format_and_handle_validation(l, options)?;
Ok(_) => Ok(()),
Err(e) => Err(NumfmtError::FormattingError(e.to_string())),
}?;
} }
Ok(()) Ok(())
} }
@ -41,23 +39,41 @@ fn handle_buffer<R>(input: R, options: &NumfmtOptions) -> UResult<()>
where where
R: BufRead, R: BufRead,
{ {
let mut lines = input.lines(); for (idx, line_result) in input.lines().by_ref().enumerate() {
for (idx, line) in lines.by_ref().enumerate() { match line_result {
match line { Ok(line) if idx < options.header => {
Ok(l) if idx < options.header => { println!("{line}");
println!("{l}");
Ok(()) Ok(())
} }
Ok(l) => match format_and_print(&l, options) { Ok(line) => format_and_handle_validation(line.as_ref(), options),
Ok(_) => Ok(()), Err(err) => return Err(Box::new(NumfmtError::IoError(err.to_string()))),
Err(e) => Err(NumfmtError::FormattingError(e.to_string())),
},
Err(e) => Err(NumfmtError::IoError(e.to_string())),
}?; }?;
} }
Ok(()) Ok(())
} }
fn format_and_handle_validation(input_line: &str, options: &NumfmtOptions) -> UResult<()> {
let handled_line = format_and_print(input_line, options);
if let Err(error_message) = handled_line {
match options.invalid {
InvalidModes::Abort => {
return Err(Box::new(NumfmtError::FormattingError(error_message)));
}
InvalidModes::Fail => {
show!(NumfmtError::FormattingError(error_message));
}
InvalidModes::Warn => {
show_error!("{}", error_message);
}
InvalidModes::Ignore => {}
};
println!("{}", input_line);
}
Ok(())
}
fn parse_unit(s: &str) -> Result<Unit> { fn parse_unit(s: &str) -> Result<Unit> {
match s { match s {
"auto" => Ok(Unit::Auto), "auto" => Ok(Unit::Auto),
@ -201,6 +217,9 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
.get_one::<String>(options::SUFFIX) .get_one::<String>(options::SUFFIX)
.map(|s| s.to_owned()); .map(|s| s.to_owned());
let invalid =
InvalidModes::from_str(args.get_one::<String>(options::INVALID).unwrap()).unwrap();
Ok(NumfmtOptions { Ok(NumfmtOptions {
transform, transform,
padding, padding,
@ -210,6 +229,7 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
round, round,
suffix, suffix,
format, format,
invalid,
}) })
} }
@ -340,10 +360,7 @@ pub fn uu_app() -> Command {
.arg( .arg(
Arg::new(options::ROUND) Arg::new(options::ROUND)
.long(options::ROUND) .long(options::ROUND)
.help( .help("use METHOD for rounding when scaling")
"use METHOD for rounding when scaling; METHOD can be: up,\
down, from-zero, towards-zero, nearest",
)
.value_name("METHOD") .value_name("METHOD")
.default_value("from-zero") .default_value("from-zero")
.value_parser(["up", "down", "from-zero", "towards-zero", "nearest"]), .value_parser(["up", "down", "from-zero", "towards-zero", "nearest"]),
@ -357,6 +374,14 @@ pub fn uu_app() -> Command {
) )
.value_name("SUFFIX"), .value_name("SUFFIX"),
) )
.arg(
Arg::new(options::INVALID)
.long(options::INVALID)
.help("set the failure mode for invalid input")
.default_value("abort")
.value_parser(["abort", "fail", "warn", "ignore"])
.value_name("INVALID"),
)
.arg( .arg(
Arg::new(options::NUMBER) Arg::new(options::NUMBER)
.hide(true) .hide(true)
@ -366,9 +391,11 @@ pub fn uu_app() -> Command {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use uucore::error::get_exit_code;
use super::{ use super::{
handle_buffer, parse_unit_size, parse_unit_size_suffix, FormatOptions, NumfmtOptions, handle_args, handle_buffer, parse_unit_size, parse_unit_size_suffix, FormatOptions,
Range, RoundMethod, TransformOptions, Unit, InvalidModes, NumfmtOptions, Range, RoundMethod, TransformOptions, Unit,
}; };
use std::io::{BufReader, Error, ErrorKind, Read}; use std::io::{BufReader, Error, ErrorKind, Read};
struct MockBuffer {} struct MockBuffer {}
@ -394,6 +421,7 @@ mod tests {
round: RoundMethod::Nearest, round: RoundMethod::Nearest,
suffix: None, suffix: None,
format: FormatOptions::default(), format: FormatOptions::default(),
invalid: InvalidModes::Abort,
} }
} }
@ -409,6 +437,20 @@ mod tests {
assert_eq!(result.code(), 1); assert_eq!(result.code(), 1);
} }
#[test]
fn broken_buffer_returns_io_error_after_header() {
let mock_buffer = MockBuffer {};
let mut options = get_valid_options();
options.header = 0;
let result = handle_buffer(BufReader::new(mock_buffer), &options)
.expect_err("returned Ok after receiving IO error");
let result_debug = format!("{:?}", result);
let result_display = format!("{}", result);
assert_eq!(result_debug, "IoError(\"broken pipe\")");
assert_eq!(result_display, "broken pipe");
assert_eq!(result.code(), 1);
}
#[test] #[test]
fn non_numeric_returns_formatting_error() { fn non_numeric_returns_formatting_error() {
let input_value = b"135\nhello"; let input_value = b"135\nhello";
@ -431,6 +473,66 @@ mod tests {
assert!(result.is_ok(), "did not return Ok for valid input"); assert!(result.is_ok(), "did not return Ok for valid input");
} }
#[test]
fn warn_returns_ok_for_invalid_input() {
let input_value = b"5\n4Q\n";
let mut options = get_valid_options();
options.invalid = InvalidModes::Warn;
let result = handle_buffer(BufReader::new(&input_value[..]), &options);
assert!(result.is_ok(), "did not return Ok for invalid input");
}
#[test]
fn ignore_returns_ok_for_invalid_input() {
let input_value = b"5\n4Q\n";
let mut options = get_valid_options();
options.invalid = InvalidModes::Ignore;
let result = handle_buffer(BufReader::new(&input_value[..]), &options);
assert!(result.is_ok(), "did not return Ok for invalid input");
}
#[test]
fn buffer_fail_returns_status_2_for_invalid_input() {
let input_value = b"5\n4Q\n";
let mut options = get_valid_options();
options.invalid = InvalidModes::Fail;
handle_buffer(BufReader::new(&input_value[..]), &options).unwrap();
assert!(
get_exit_code() == 2,
"should set exit code 2 for formatting errors"
);
}
#[test]
fn abort_returns_status_2_for_invalid_input() {
let input_value = b"5\n4Q\n";
let mut options = get_valid_options();
options.invalid = InvalidModes::Abort;
let result = handle_buffer(BufReader::new(&input_value[..]), &options);
assert!(result.is_err(), "did not return err for invalid input");
}
#[test]
fn args_fail_returns_status_2_for_invalid_input() {
let input_value = ["5", "4Q"].into_iter();
let mut options = get_valid_options();
options.invalid = InvalidModes::Fail;
handle_args(input_value, &options).unwrap();
assert!(
get_exit_code() == 2,
"should set exit code 2 for formatting errors"
);
}
#[test]
fn args_warn_returns_status_0_for_invalid_input() {
let input_value = ["5", "4Q"].into_iter();
let mut options = get_valid_options();
options.invalid = InvalidModes::Warn;
let result = handle_args(input_value, &options);
assert!(result.is_ok(), "did not return ok for invalid input");
}
#[test] #[test]
fn test_parse_unit_size() { fn test_parse_unit_size() {
assert_eq!(1, parse_unit_size("1").unwrap()); assert_eq!(1, parse_unit_size("1").unwrap());

View file

@ -13,6 +13,7 @@ pub const FROM_UNIT: &str = "from-unit";
pub const FROM_UNIT_DEFAULT: &str = "1"; pub const FROM_UNIT_DEFAULT: &str = "1";
pub const HEADER: &str = "header"; pub const HEADER: &str = "header";
pub const HEADER_DEFAULT: &str = "1"; pub const HEADER_DEFAULT: &str = "1";
pub const INVALID: &str = "invalid";
pub const NUMBER: &str = "NUMBER"; pub const NUMBER: &str = "NUMBER";
pub const PADDING: &str = "padding"; pub const PADDING: &str = "padding";
pub const ROUND: &str = "round"; pub const ROUND: &str = "round";
@ -29,6 +30,14 @@ pub struct TransformOptions {
pub to_unit: usize, pub to_unit: usize,
} }
#[derive(Debug, PartialEq, Eq)]
pub enum InvalidModes {
Abort,
Fail,
Warn,
Ignore,
}
pub struct NumfmtOptions { pub struct NumfmtOptions {
pub transform: TransformOptions, pub transform: TransformOptions,
pub padding: isize, pub padding: isize,
@ -38,6 +47,7 @@ pub struct NumfmtOptions {
pub round: RoundMethod, pub round: RoundMethod,
pub suffix: Option<String>, pub suffix: Option<String>,
pub format: FormatOptions, pub format: FormatOptions,
pub invalid: InvalidModes,
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -227,6 +237,20 @@ impl FromStr for FormatOptions {
} }
} }
impl FromStr for InvalidModes {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"abort" => Ok(Self::Abort),
"fail" => Ok(Self::Fail),
"warn" => Ok(Self::Warn),
"ignore" => Ok(Self::Ignore),
unknown => Err(format!("Unknown invalid mode: {unknown}")),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -336,4 +360,21 @@ mod tests {
assert_eq!(expected_options, "%0'0'0'f".parse().unwrap()); assert_eq!(expected_options, "%0'0'0'f".parse().unwrap());
assert_eq!(expected_options, "%'0'0'0f".parse().unwrap()); assert_eq!(expected_options, "%'0'0'0f".parse().unwrap());
} }
#[test]
fn test_set_invalid_mode() {
assert_eq!(Ok(InvalidModes::Abort), InvalidModes::from_str("abort"));
assert_eq!(Ok(InvalidModes::Abort), InvalidModes::from_str("ABORT"));
assert_eq!(Ok(InvalidModes::Fail), InvalidModes::from_str("fail"));
assert_eq!(Ok(InvalidModes::Fail), InvalidModes::from_str("FAIL"));
assert_eq!(Ok(InvalidModes::Ignore), InvalidModes::from_str("ignore"));
assert_eq!(Ok(InvalidModes::Ignore), InvalidModes::from_str("IGNORE"));
assert_eq!(Ok(InvalidModes::Warn), InvalidModes::from_str("warn"));
assert_eq!(Ok(InvalidModes::Warn), InvalidModes::from_str("WARN"));
assert!(InvalidModes::from_str("something unknown").is_err());
}
} }

View file

@ -43,7 +43,7 @@ pub fn parse_number_of_bytes(s: &str) -> Result<u64, ParseSizeError> {
len -= 1; len -= 1;
} }
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
Some('E') => { Some('E') if radix != 16 => {
multiply = 1024 * 1024 * 1024 * 1024 * 1024 * 1024; multiply = 1024 * 1024 * 1024 * 1024 * 1024 * 1024;
len -= 1; len -= 1;
} }
@ -84,6 +84,7 @@ fn test_parse_number_of_bytes() {
// hex input // hex input
assert_eq!(15, parse_number_of_bytes("0xf").unwrap()); assert_eq!(15, parse_number_of_bytes("0xf").unwrap());
assert_eq!(14, parse_number_of_bytes("0XE").unwrap());
assert_eq!(15, parse_number_of_bytes("0XF").unwrap()); assert_eq!(15, parse_number_of_bytes("0XF").unwrap());
assert_eq!(27, parse_number_of_bytes("0x1b").unwrap()); assert_eq!(27, parse_number_of_bytes("0x1b").unwrap());
assert_eq!(16 * 1024, parse_number_of_bytes("0x10k").unwrap()); assert_eq!(16 * 1024, parse_number_of_bytes("0x10k").unwrap());

View file

@ -5,5 +5,5 @@ Display numbers from FIRST to LAST, in steps of INCREMENT.
``` ```
seq [OPTION]... LAST seq [OPTION]... LAST
seq [OPTION]... FIRST LAST seq [OPTION]... FIRST LAST
seq [OPTION]... FIRST INCREMENT LAST"; seq [OPTION]... FIRST INCREMENT LAST
``` ```

View file

@ -31,7 +31,7 @@ const USAGE: &str = help_usage!("seq.md");
const OPT_SEPARATOR: &str = "separator"; const OPT_SEPARATOR: &str = "separator";
const OPT_TERMINATOR: &str = "terminator"; const OPT_TERMINATOR: &str = "terminator";
const OPT_WIDTHS: &str = "widths"; const OPT_EQUAL_WIDTH: &str = "equal-width";
const OPT_FORMAT: &str = "format"; const OPT_FORMAT: &str = "format";
const ARG_NUMBERS: &str = "numbers"; const ARG_NUMBERS: &str = "numbers";
@ -40,7 +40,7 @@ const ARG_NUMBERS: &str = "numbers";
struct SeqOptions<'a> { struct SeqOptions<'a> {
separator: String, separator: String,
terminator: String, terminator: String,
widths: bool, equal_width: bool,
format: Option<&'a str>, format: Option<&'a str>,
} }
@ -74,7 +74,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.map(|s| s.as_str()) .map(|s| s.as_str())
.unwrap_or("\n") .unwrap_or("\n")
.to_string(), .to_string(),
widths: matches.get_flag(OPT_WIDTHS), equal_width: matches.get_flag(OPT_EQUAL_WIDTH),
format: matches.get_one::<String>(OPT_FORMAT).map(|s| s.as_str()), format: matches.get_one::<String>(OPT_FORMAT).map(|s| s.as_str()),
}; };
@ -123,7 +123,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
(first, increment, last), (first, increment, last),
&options.separator, &options.separator,
&options.terminator, &options.terminator,
options.widths, options.equal_width,
padding, padding,
options.format, options.format,
) )
@ -137,7 +137,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
largest_dec, largest_dec,
&options.separator, &options.separator,
&options.terminator, &options.terminator,
options.widths, options.equal_width,
padding, padding,
options.format, options.format,
), ),
@ -170,9 +170,9 @@ pub fn uu_app() -> Command {
.help("Terminator character (defaults to \\n)"), .help("Terminator character (defaults to \\n)"),
) )
.arg( .arg(
Arg::new(OPT_WIDTHS) Arg::new(OPT_EQUAL_WIDTH)
.short('w') .short('w')
.long("widths") .long("equal-width")
.help("Equalize widths of all numbers by padding with zeros") .help("Equalize widths of all numbers by padding with zeros")
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )

View file

@ -532,7 +532,9 @@ fn wipe_name(orig_path: &Path, verbose: bool) -> Option<PathBuf> {
} }
// Sync every file rename // Sync every file rename
let new_file = File::open(new_path.clone()) let new_file = OpenOptions::new()
.write(true)
.open(new_path.clone())
.expect("Failed to open renamed file for syncing"); .expect("Failed to open renamed file for syncing");
new_file.sync_all().expect("Failed to sync renamed file"); new_file.sync_all().expect("Failed to sync renamed file");

View file

@ -22,9 +22,9 @@ ctrlc = { workspace = true }
fnv = { workspace = true } fnv = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
memchr = { workspace = true } memchr = { workspace = true }
ouroboros = { workspace = true }
rand = { workspace = true } rand = { workspace = true }
rayon = { workspace = true } rayon = { workspace = true }
self_cell = { workspace = true }
tempfile = { workspace = true } tempfile = { workspace = true }
unicode-width = { workspace = true } unicode-width = { workspace = true }
uucore = { workspace = true, features = ["fs"] } uucore = { workspace = true, features = ["fs"] }

View file

@ -16,21 +16,22 @@ use std::{
}; };
use memchr::memchr_iter; use memchr::memchr_iter;
use ouroboros::self_referencing; use self_cell::self_cell;
use uucore::error::{UResult, USimpleError}; use uucore::error::{UResult, USimpleError};
use crate::{numeric_str_cmp::NumInfo, GeneralF64ParseResult, GlobalSettings, Line, SortError}; use crate::{numeric_str_cmp::NumInfo, GeneralF64ParseResult, GlobalSettings, Line, SortError};
/// The chunk that is passed around between threads. self_cell!(
/// `lines` consist of slices into `buffer`. /// The chunk that is passed around between threads.
#[self_referencing(pub_extras)] pub struct Chunk {
#[derive(Debug)] owner: Vec<u8>,
pub struct Chunk {
pub buffer: Vec<u8>, #[covariant]
#[borrows(buffer)] dependent: ChunkContents,
#[covariant] }
pub contents: ChunkContents<'this>,
} impl {Debug}
);
#[derive(Debug)] #[derive(Debug)]
pub struct ChunkContents<'a> { pub struct ChunkContents<'a> {
@ -48,7 +49,7 @@ pub struct LineData<'a> {
impl Chunk { impl Chunk {
/// Destroy this chunk and return its components to be reused. /// Destroy this chunk and return its components to be reused.
pub fn recycle(mut self) -> RecycledChunk { pub fn recycle(mut self) -> RecycledChunk {
let recycled_contents = self.with_contents_mut(|contents| { let recycled_contents = self.with_dependent_mut(|_, contents| {
contents.lines.clear(); contents.lines.clear();
contents.line_data.selections.clear(); contents.line_data.selections.clear();
contents.line_data.num_infos.clear(); contents.line_data.num_infos.clear();
@ -81,15 +82,15 @@ impl Chunk {
selections: recycled_contents.1, selections: recycled_contents.1,
num_infos: recycled_contents.2, num_infos: recycled_contents.2,
parsed_floats: recycled_contents.3, parsed_floats: recycled_contents.3,
buffer: self.into_heads().buffer, buffer: self.into_owner(),
} }
} }
pub fn lines(&self) -> &Vec<Line> { pub fn lines(&self) -> &Vec<Line> {
&self.borrow_contents().lines &self.borrow_dependent().lines
} }
pub fn line_data(&self) -> &LineData { pub fn line_data(&self) -> &LineData {
&self.borrow_contents().line_data &self.borrow_dependent().line_data
} }
} }

View file

@ -158,7 +158,7 @@ fn reader_writer<
/// The function that is executed on the sorter thread. /// The function that is executed on the sorter thread.
fn sorter(receiver: &Receiver<Chunk>, sender: &SyncSender<Chunk>, settings: &GlobalSettings) { fn sorter(receiver: &Receiver<Chunk>, sender: &SyncSender<Chunk>, settings: &GlobalSettings) {
while let Ok(mut payload) = receiver.recv() { while let Ok(mut payload) = receiver.recv() {
payload.with_contents_mut(|contents| { payload.with_dependent_mut(|_, contents| {
sort_by(&mut contents.lines, settings, &contents.line_data); sort_by(&mut contents.lines, settings, &contents.line_data);
}); });
if sender.send(payload).is_err() { if sender.send(payload).is_err() {

View file

@ -288,7 +288,7 @@ impl<'a> FileMerger<'a> {
file_number: file.file_number, file_number: file.file_number,
}); });
file.current_chunk.with_contents(|contents| { file.current_chunk.with_dependent(|_, contents| {
let current_line = &contents.lines[file.line_idx]; let current_line = &contents.lines[file.line_idx];
if settings.unique { if settings.unique {
if let Some(prev) = &prev { if let Some(prev) = &prev {

View file

@ -267,7 +267,11 @@ impl NumberType {
let num_chunks = n_str let num_chunks = n_str
.parse() .parse()
.map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?;
Ok(Self::Bytes(num_chunks)) if num_chunks > 0 {
Ok(Self::Bytes(num_chunks))
} else {
Err(NumberTypeError::NumberOfChunks(s.to_string()))
}
} }
["l", n_str] => { ["l", n_str] => {
let num_chunks = n_str let num_chunks = n_str
@ -357,6 +361,20 @@ impl fmt::Display for StrategyError {
impl Strategy { impl Strategy {
/// Parse a strategy from the command-line arguments. /// Parse a strategy from the command-line arguments.
fn from(matches: &ArgMatches) -> Result<Self, StrategyError> { fn from(matches: &ArgMatches) -> Result<Self, StrategyError> {
fn get_and_parse(
matches: &ArgMatches,
option: &str,
strategy: fn(u64) -> Strategy,
error: fn(ParseSizeError) -> StrategyError,
) -> Result<Strategy, StrategyError> {
let s = matches.get_one::<String>(option).unwrap();
let n = parse_size(s).map_err(error)?;
if n > 0 {
Ok(strategy(n))
} else {
Err(error(ParseSizeError::ParseFailure(s.to_string())))
}
}
// Check that the user is not specifying more than one strategy. // Check that the user is not specifying more than one strategy.
// //
// Note: right now, this exact behavior cannot be handled by // Note: right now, this exact behavior cannot be handled by
@ -370,20 +388,17 @@ impl Strategy {
) { ) {
(false, false, false, false) => Ok(Self::Lines(1000)), (false, false, false, false) => Ok(Self::Lines(1000)),
(true, false, false, false) => { (true, false, false, false) => {
let s = matches.get_one::<String>(OPT_LINES).unwrap(); get_and_parse(matches, OPT_LINES, Self::Lines, StrategyError::Lines)
let n = parse_size(s).map_err(StrategyError::Lines)?;
Ok(Self::Lines(n))
} }
(false, true, false, false) => { (false, true, false, false) => {
let s = matches.get_one::<String>(OPT_BYTES).unwrap(); get_and_parse(matches, OPT_BYTES, Self::Bytes, StrategyError::Bytes)
let n = parse_size(s).map_err(StrategyError::Bytes)?;
Ok(Self::Bytes(n))
}
(false, false, true, false) => {
let s = matches.get_one::<String>(OPT_LINE_BYTES).unwrap();
let n = parse_size(s).map_err(StrategyError::Bytes)?;
Ok(Self::LineBytes(n))
} }
(false, false, true, false) => get_and_parse(
matches,
OPT_LINE_BYTES,
Self::LineBytes,
StrategyError::Bytes,
),
(false, false, false, true) => { (false, false, false, true) => {
let s = matches.get_one::<String>(OPT_NUMBER).unwrap(); let s = matches.get_one::<String>(OPT_NUMBER).unwrap();
let number_type = NumberType::from(s).map_err(StrategyError::NumberType)?; let number_type = NumberType::from(s).map_err(StrategyError::NumberType)?;

View file

@ -3,14 +3,15 @@
// * For the full copyright and license information, please view the LICENSE file // * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code. // * that was distributed with this source code.
// spell-checker:ignore clocal tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed ushort // spell-checker:ignore clocal tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed cfsetospeed ushort
mod flags; mod flags;
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
use nix::libc::{c_ushort, O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ}; use nix::libc::{c_ushort, O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ};
use nix::sys::termios::{ use nix::sys::termios::{
cfgetospeed, tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios, cfgetospeed, cfsetospeed, tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags,
OutputFlags, Termios,
}; };
use nix::{ioctl_read_bad, ioctl_write_ptr_bad}; use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
use std::io::{self, stdout}; use std::io::{self, stdout};
@ -244,12 +245,30 @@ fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> {
Ok(()) Ok(())
} }
fn print_in_save_format(termios: &Termios) {
print!(
"{:x}:{:x}:{:x}:{:x}",
termios.input_flags.bits(),
termios.output_flags.bits(),
termios.control_flags.bits(),
termios.local_flags.bits()
);
for cc in termios.control_chars {
print!(":{cc:x}");
}
println!();
}
fn print_settings(termios: &Termios, opts: &Options) -> nix::Result<()> { fn print_settings(termios: &Termios, opts: &Options) -> nix::Result<()> {
print_terminal_size(termios, opts)?; if opts.save {
print_flags(termios, opts, CONTROL_FLAGS); print_in_save_format(termios);
print_flags(termios, opts, INPUT_FLAGS); } else {
print_flags(termios, opts, OUTPUT_FLAGS); print_terminal_size(termios, opts)?;
print_flags(termios, opts, LOCAL_FLAGS); print_flags(termios, opts, CONTROL_FLAGS);
print_flags(termios, opts, INPUT_FLAGS);
print_flags(termios, opts, OUTPUT_FLAGS);
print_flags(termios, opts, LOCAL_FLAGS);
}
Ok(()) Ok(())
} }
@ -290,6 +309,8 @@ fn print_flags<T: TermiosFlag>(termios: &Termios, opts: &Options, flags: &[Flag<
/// The value inside the `Break` variant of the `ControlFlow` indicates whether /// The value inside the `Break` variant of the `ControlFlow` indicates whether
/// the setting has been applied. /// the setting has been applied.
fn apply_setting(termios: &mut Termios, s: &str) -> ControlFlow<bool> { fn apply_setting(termios: &mut Termios, s: &str) -> ControlFlow<bool> {
apply_baud_rate_flag(termios, s)?;
let (remove, name) = match s.strip_prefix('-') { let (remove, name) = match s.strip_prefix('-') {
Some(s) => (true, s), Some(s) => (true, s),
None => (false, s), None => (false, s),
@ -332,6 +353,39 @@ fn apply_flag<T: TermiosFlag>(
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
fn apply_baud_rate_flag(termios: &mut Termios, input: &str) -> ControlFlow<bool> {
// BSDs use a u32 for the baud rate, so any decimal number applies.
#[cfg(any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"
))]
if let Ok(n) = input.parse::<u32>() {
cfsetospeed(termios, n).expect("Failed to set baud rate");
return ControlFlow::Break(true);
}
// Other platforms use an enum.
#[cfg(not(any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"
)))]
for (text, baud_rate) in BAUD_RATES {
if *text == input {
cfsetospeed(termios, *baud_rate).expect("Failed to set baud rate");
return ControlFlow::Break(true);
}
}
ControlFlow::Continue(())
}
pub fn uu_app() -> Command { pub fn uu_app() -> Command {
Command::new(uucore::util_name()) Command::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())

View file

@ -173,7 +173,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let path = Path::new(&f); let path = Path::new(&f);
if let Err(e) = open(path, OFlag::O_NONBLOCK, Mode::empty()) { if let Err(e) = open(path, OFlag::O_NONBLOCK, Mode::empty()) {
if e != Errno::EACCES || (e == Errno::EACCES && path.is_dir()) { if e != Errno::EACCES || (e == Errno::EACCES && path.is_dir()) {
return e.map_err_context(|| format!("cannot stat {}", f.quote()))?; return e.map_err_context(|| format!("error opening {}", f.quote()))?;
} }
} }
} }
@ -183,7 +183,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if !Path::new(&f).exists() { if !Path::new(&f).exists() {
return Err(USimpleError::new( return Err(USimpleError::new(
1, 1,
format!("cannot stat {}: No such file or directory", f.quote()), format!("error opening {}: No such file or directory", f.quote()),
)); ));
} }
} }

View file

@ -18,7 +18,7 @@ path = "src/tac.rs"
[dependencies] [dependencies]
memchr = { workspace = true } memchr = { workspace = true }
memmap2 = "0.7" memmap2 = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
clap = { workspace = true } clap = { workspace = true }
uucore = { workspace = true } uucore = { workspace = true }

View file

@ -7,12 +7,11 @@
use crate::paths::Input; use crate::paths::Input;
use crate::{parse, platform, Quotable}; use crate::{parse, platform, Quotable};
use clap::crate_version; use clap::{crate_version, value_parser};
use clap::{Arg, ArgAction, ArgMatches, Command}; use clap::{Arg, ArgAction, ArgMatches, Command};
use fundu::{DurationParser, SaturatingInto}; use fundu::{DurationParser, SaturatingInto};
use is_terminal::IsTerminal; use is_terminal::IsTerminal;
use same_file::Handle; use same_file::Handle;
use std::collections::VecDeque;
use std::ffi::OsString; use std::ffi::OsString;
use std::time::Duration; use std::time::Duration;
use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::error::{UResult, USimpleError, UUsageError};
@ -141,7 +140,8 @@ pub struct Settings {
pub use_polling: bool, pub use_polling: bool,
pub verbose: bool, pub verbose: bool,
pub presume_input_pipe: bool, pub presume_input_pipe: bool,
pub inputs: VecDeque<Input>, /// `FILE(s)` positional arguments
pub inputs: Vec<Input>,
} }
impl Default for Settings { impl Default for Settings {
@ -173,11 +173,11 @@ impl Settings {
} }
settings.mode = FilterMode::from_obsolete_args(args); settings.mode = FilterMode::from_obsolete_args(args);
let input = if let Some(name) = name { let input = if let Some(name) = name {
Input::from(&name) Input::from(name)
} else { } else {
Input::default() Input::default()
}; };
settings.inputs.push_back(input); settings.inputs.push(input);
settings settings
} }
@ -285,19 +285,13 @@ impl Settings {
} }
} }
let mut inputs: VecDeque<Input> = matches settings.inputs = matches
.get_many::<String>(options::ARG_FILES) .get_many::<OsString>(options::ARG_FILES)
.map(|v| v.map(|string| Input::from(&string)).collect()) .map(|v| v.map(Input::from).collect())
.unwrap_or_default(); .unwrap_or_else(|| vec![Input::default()]);
// apply default and add '-' to inputs if none is present settings.verbose =
if inputs.is_empty() { settings.inputs.len() > 1 && !matches.get_flag(options::verbosity::QUIET);
inputs.push_front(Input::default());
}
settings.verbose = inputs.len() > 1 && !matches.get_flag(options::verbosity::QUIET);
settings.inputs = inputs;
Ok(settings) Ok(settings)
} }
@ -593,6 +587,7 @@ pub fn uu_app() -> Command {
Arg::new(options::ARG_FILES) Arg::new(options::ARG_FILES)
.action(ArgAction::Append) .action(ArgAction::Append)
.num_args(1..) .num_args(1..)
.value_parser(value_parser!(OsString))
.value_hint(clap::ValueHint::FilePath), .value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -10,7 +10,6 @@ use crate::follow::files::{FileHandling, PathData};
use crate::paths::{Input, InputKind, MetadataExtTail, PathExtTail}; use crate::paths::{Input, InputKind, MetadataExtTail, PathExtTail};
use crate::{platform, text}; use crate::{platform, text};
use notify::{RecommendedWatcher, RecursiveMode, Watcher, WatcherKind}; use notify::{RecommendedWatcher, RecursiveMode, Watcher, WatcherKind};
use std::collections::VecDeque;
use std::io::BufRead; use std::io::BufRead;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::mpsc::{self, channel, Receiver}; use std::sync::mpsc::{self, channel, Receiver};
@ -270,7 +269,7 @@ impl Observer {
self.follow_name() && self.retry self.follow_name() && self.retry
} }
fn init_files(&mut self, inputs: &VecDeque<Input>) -> UResult<()> { fn init_files(&mut self, inputs: &Vec<Input>) -> UResult<()> {
if let Some(watcher_rx) = &mut self.watcher_rx { if let Some(watcher_rx) = &mut self.watcher_rx {
for input in inputs { for input in inputs {
match input.kind() { match input.kind() {

View file

@ -20,6 +20,28 @@ pub enum InputKind {
Stdin, Stdin,
} }
#[cfg(unix)]
impl From<&OsStr> for InputKind {
fn from(value: &OsStr) -> Self {
if value == OsStr::new("-") {
Self::Stdin
} else {
Self::File(PathBuf::from(value))
}
}
}
#[cfg(not(unix))]
impl From<&OsStr> for InputKind {
fn from(value: &OsStr) -> Self {
if value == OsStr::new(text::DASH) {
Self::Stdin
} else {
Self::File(PathBuf::from(value))
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Input { pub struct Input {
kind: InputKind, kind: InputKind,
@ -27,22 +49,13 @@ pub struct Input {
} }
impl Input { impl Input {
pub fn from<T: AsRef<OsStr>>(string: &T) -> Self { pub fn from<T: AsRef<OsStr>>(string: T) -> Self {
let kind = if string.as_ref() == Path::new(text::DASH) { let string = string.as_ref();
InputKind::Stdin
} else {
InputKind::File(PathBuf::from(string.as_ref()))
};
let kind = string.into();
let display_name = match kind { let display_name = match kind {
InputKind::File(_) => string.as_ref().to_string_lossy().to_string(), InputKind::File(_) => string.to_string_lossy().to_string(),
InputKind::Stdin => { InputKind::Stdin => text::STDIN_HEADER.to_string(),
if cfg!(unix) {
text::STDIN_HEADER.to_string()
} else {
string.as_ref().to_string_lossy().to_string()
}
}
}; };
Self { kind, display_name } Self { kind, display_name }

View file

@ -1,4 +1,4 @@
# spell-checker:ignore humantime # spell-checker:ignore datetime
[package] [package]
name = "uu_touch" name = "uu_touch"
version = "0.0.19" version = "0.0.19"
@ -18,8 +18,7 @@ path = "src/touch.rs"
[dependencies] [dependencies]
filetime = { workspace = true } filetime = { workspace = true }
clap = { workspace = true } clap = { workspace = true }
# TODO: use workspace dependency (0.3) when switching from time to chrono parse_datetime = { workspace = true }
humantime_to_duration = "0.2.1"
time = { workspace = true, features = [ time = { workspace = true, features = [
"parsing", "parsing",
"formatting", "formatting",

View file

@ -6,7 +6,7 @@
// For the full copyright and license information, please view the LICENSE file // For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code. // that was distributed with this source code.
// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult mktime YYYYMMDDHHMM YYMMDDHHMM DATETIME YYYYMMDDHHMMS subsecond humantime // spell-checker:ignore (ToDO) filetime datetime MMDDhhmm lpszfilepath mktime YYYYMMDDHHMM YYMMDDHHMM DATETIME YYYYMMDDHHMMS subsecond humantime
use clap::builder::ValueParser; use clap::builder::ValueParser;
use clap::{crate_version, Arg, ArgAction, ArgGroup, Command}; use clap::{crate_version, Arg, ArgAction, ArgGroup, Command};
@ -29,7 +29,7 @@ pub mod options {
pub mod sources { pub mod sources {
pub static DATE: &str = "date"; pub static DATE: &str = "date";
pub static REFERENCE: &str = "reference"; pub static REFERENCE: &str = "reference";
pub static CURRENT: &str = "current"; pub static TIMESTAMP: &str = "timestamp";
} }
pub static HELP: &str = "help"; pub static HELP: &str = "help";
pub static ACCESS: &str = "access"; pub static ACCESS: &str = "access";
@ -84,13 +84,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
) { ) {
(Some(reference), Some(date)) => { (Some(reference), Some(date)) => {
let (atime, mtime) = stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?; let (atime, mtime) = stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?;
if let Ok(offset) = humantime_to_duration::from_str(date) { if let Ok(offset) = parse_datetime::from_str(date) {
let mut seconds = offset.whole_seconds(); let seconds = offset.num_seconds();
let mut nanos = offset.subsec_nanoseconds(); let nanos = offset.num_nanoseconds().unwrap_or(0) % 1_000_000_000;
if nanos < 0 {
nanos += 1_000_000_000;
seconds -= 1;
}
let ref_atime_secs = atime.unix_seconds(); let ref_atime_secs = atime.unix_seconds();
let ref_atime_nanos = atime.nanoseconds(); let ref_atime_nanos = atime.nanoseconds();
@ -120,12 +116,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
(timestamp, timestamp) (timestamp, timestamp)
} }
(None, None) => { (None, None) => {
let timestamp = let timestamp = if let Some(ts) = matches.get_one::<String>(options::sources::TIMESTAMP)
if let Some(current) = matches.get_one::<String>(options::sources::CURRENT) { {
parse_timestamp(current)? parse_timestamp(ts)?
} else { } else {
local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap()) local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap())
}; };
(timestamp, timestamp) (timestamp, timestamp)
} }
}; };
@ -243,7 +239,7 @@ pub fn uu_app() -> Command {
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::sources::CURRENT) Arg::new(options::sources::TIMESTAMP)
.short('t') .short('t')
.help("use [[CC]YY]MMDDhhmm[.ss] instead of the current time") .help("use [[CC]YY]MMDDhhmm[.ss] instead of the current time")
.value_name("STAMP"), .value_name("STAMP"),
@ -255,7 +251,7 @@ pub fn uu_app() -> Command {
.allow_hyphen_values(true) .allow_hyphen_values(true)
.help("parse argument and use it instead of current time") .help("parse argument and use it instead of current time")
.value_name("STRING") .value_name("STRING")
.conflicts_with(options::sources::CURRENT), .conflicts_with(options::sources::TIMESTAMP),
) )
.arg( .arg(
Arg::new(options::MODIFICATION) Arg::new(options::MODIFICATION)
@ -288,7 +284,7 @@ pub fn uu_app() -> Command {
.value_name("FILE") .value_name("FILE")
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
.value_hint(clap::ValueHint::AnyPath) .value_hint(clap::ValueHint::AnyPath)
.conflicts_with(options::sources::CURRENT), .conflicts_with(options::sources::TIMESTAMP),
) )
.arg( .arg(
Arg::new(options::TIME) Arg::new(options::TIME)
@ -299,7 +295,7 @@ pub fn uu_app() -> Command {
equivalent to -m", equivalent to -m",
) )
.value_name("WORD") .value_name("WORD")
.value_parser(["access", "atime", "use"]), .value_parser(["access", "atime", "use", "modify", "mtime"]),
) )
.arg( .arg(
Arg::new(ARG_FILES) Arg::new(ARG_FILES)
@ -311,7 +307,7 @@ pub fn uu_app() -> Command {
.group( .group(
ArgGroup::new(options::SOURCES) ArgGroup::new(options::SOURCES)
.args([ .args([
options::sources::CURRENT, options::sources::TIMESTAMP,
options::sources::DATE, options::sources::DATE,
options::sources::REFERENCE, options::sources::REFERENCE,
]) ])
@ -445,9 +441,13 @@ fn parse_date(s: &str) -> UResult<FileTime> {
} }
} }
if let Ok(duration) = humantime_to_duration::from_str(s) { if let Ok(duration) = parse_datetime::from_str(s) {
let now_local = time::OffsetDateTime::now_local().unwrap(); let now_local = time::OffsetDateTime::now_local().unwrap();
let diff = now_local.checked_add(duration).unwrap(); let diff = now_local
.checked_add(time::Duration::nanoseconds(
duration.num_nanoseconds().unwrap(),
))
.unwrap();
return Ok(local_dt_to_filetime(diff)); return Ok(local_dt_to_filetime(diff));
} }

View file

@ -9,7 +9,7 @@
// cSpell:ignore strs // cSpell:ignore strs
use clap::{builder::ValueParser, Arg, ArgAction, Command}; use clap::{builder::ValueParser, crate_version, Arg, ArgAction, Command};
use std::error::Error; use std::error::Error;
use std::ffi::OsString; use std::ffi::OsString;
use std::io::{self, Write}; use std::io::{self, Write};
@ -44,6 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
pub fn uu_app() -> Command { pub fn uu_app() -> Command {
Command::new(uucore::util_name()) Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT) .about(ABOUT)
.override_usage(format_usage(USAGE)) .override_usage(format_usage(USAGE))
.arg( .arg(

View file

@ -25,7 +25,7 @@ dunce = "1.0.4"
wild = "2.1" wild = "2.1"
glob = "0.3.1" glob = "0.3.1"
# * optional # * optional
itertools = { version = "0.10.5", optional = true } itertools = { version = "0.11.0", optional = true }
thiserror = { workspace = true, optional = true } thiserror = { workspace = true, optional = true }
time = { workspace = true, optional = true, features = [ time = { workspace = true, optional = true, features = [
"formatting", "formatting",
@ -36,7 +36,7 @@ time = { workspace = true, optional = true, features = [
data-encoding = { version = "2.4", optional = true } data-encoding = { version = "2.4", optional = true }
data-encoding-macro = { version = "0.1.13", optional = true } data-encoding-macro = { version = "0.1.13", optional = true }
z85 = { version = "3.0.5", optional = true } z85 = { version = "3.0.5", optional = true }
libc = { version = "0.2.146", optional = true } libc = { version = "0.2.147", optional = true }
once_cell = { workspace = true } once_cell = { workspace = true }
os_display = "0.1.3" os_display = "0.1.3"

View file

@ -33,6 +33,7 @@ pub use crate::mods::version_cmp;
pub use crate::parser::parse_glob; pub use crate::parser::parse_glob;
pub use crate::parser::parse_size; pub use crate::parser::parse_size;
pub use crate::parser::parse_time; pub use crate::parser::parse_time;
pub use crate::parser::shortcut_value_parser;
// * feature-gated modules // * feature-gated modules
#[cfg(feature = "encoding")] #[cfg(feature = "encoding")]

View file

@ -438,6 +438,32 @@ fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf {
} }
} }
/// Returns true if the source file is likely to be the simple backup file for the target file.
///
/// # Arguments
///
/// * `source` - A Path reference that holds the source (backup) file path.
/// * `target` - A Path reference that holds the target file path.
/// * `suffix` - Str that holds the backup suffix.
///
/// # Examples
///
/// ```
/// use std::path::Path;
/// use uucore::backup_control::source_is_target_backup;
/// let source = Path::new("data.txt~");
/// let target = Path::new("data.txt");
/// let suffix = String::from("~");
///
/// assert_eq!(source_is_target_backup(&source, &target, &suffix), true);
/// ```
///
pub fn source_is_target_backup(source: &Path, target: &Path, suffix: &str) -> bool {
let source_filename = source.to_string_lossy();
let target_backup_filename = format!("{}{suffix}", target.to_string_lossy());
source_filename == target_backup_filename
}
// //
// Tests for this module // Tests for this module
// //
@ -626,4 +652,30 @@ mod tests {
let result = determine_backup_suffix(&matches); let result = determine_backup_suffix(&matches);
assert_eq!(result, "-v"); assert_eq!(result, "-v");
} }
#[test]
fn test_source_is_target_backup() {
let source = Path::new("data.txt.bak");
let target = Path::new("data.txt");
let suffix = String::from(".bak");
assert!(source_is_target_backup(&source, &target, &suffix));
}
#[test]
fn test_source_is_not_target_backup() {
let source = Path::new("data.txt");
let target = Path::new("backup.txt");
let suffix = String::from(".bak");
assert!(!source_is_target_backup(&source, &target, &suffix));
}
#[test]
fn test_source_is_target_backup_with_tilde_suffix() {
let source = Path::new("example~");
let target = Path::new("example");
let suffix = String::from("~");
assert!(source_is_target_backup(&source, &target, &suffix));
}
} }

View file

@ -396,7 +396,7 @@ impl Display for UIoError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
use std::io::ErrorKind::*; use std::io::ErrorKind::*;
let mut message; let message;
let message = if self.inner.raw_os_error().is_some() { let message = if self.inner.raw_os_error().is_some() {
// These are errors that come directly from the OS. // These are errors that come directly from the OS.
// We want to normalize their messages across systems, // We want to normalize their messages across systems,
@ -424,7 +424,6 @@ impl Display for UIoError {
// (https://github.com/rust-lang/rust/issues/86442) // (https://github.com/rust-lang/rust/issues/86442)
// are stabilized, we should add them to the match statement. // are stabilized, we should add them to the match statement.
message = strip_errno(&self.inner); message = strip_errno(&self.inner);
capitalize(&mut message);
&message &message
} }
} }
@ -435,7 +434,6 @@ impl Display for UIoError {
// a file that was not found. // a file that was not found.
// There are also errors with entirely custom messages. // There are also errors with entirely custom messages.
message = self.inner.to_string(); message = self.inner.to_string();
capitalize(&mut message);
&message &message
}; };
if let Some(ctx) = &self.context { if let Some(ctx) = &self.context {
@ -446,13 +444,6 @@ impl Display for UIoError {
} }
} }
/// Capitalize the first character of an ASCII string.
fn capitalize(text: &mut str) {
if let Some(first) = text.get_mut(..1) {
first.make_ascii_uppercase();
}
}
/// Strip the trailing " (os error XX)" from io error strings. /// Strip the trailing " (os error XX)" from io error strings.
pub fn strip_errno(err: &std::io::Error) -> String { pub fn strip_errno(err: &std::io::Error) -> String {
let mut msg = err.to_string(); let mut msg = err.to_string();

View file

@ -1,3 +1,4 @@
pub mod parse_glob; pub mod parse_glob;
pub mod parse_size; pub mod parse_size;
pub mod parse_time; pub mod parse_time;
pub mod shortcut_value_parser;

View file

@ -0,0 +1,141 @@
use clap::{
builder::{PossibleValue, TypedValueParser},
error::{ContextKind, ContextValue, ErrorKind},
};
#[derive(Clone)]
pub struct ShortcutValueParser(Vec<PossibleValue>);
/// `ShortcutValueParser` is similar to clap's `PossibleValuesParser`: it verifies that the value is
/// from an enumerated set of `PossibleValue`.
///
/// Whereas `PossibleValuesParser` only accepts exact matches, `ShortcutValueParser` also accepts
/// shortcuts as long as they are unambiguous.
impl ShortcutValueParser {
pub fn new(values: impl Into<Self>) -> Self {
values.into()
}
}
impl TypedValueParser for ShortcutValueParser {
type Value = String;
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let value = value
.to_str()
.ok_or(clap::Error::new(ErrorKind::InvalidUtf8))?;
let matched_values: Vec<_> = self
.0
.iter()
.filter(|x| x.get_name().starts_with(value))
.collect();
if matched_values.len() == 1 {
Ok(matched_values[0].get_name().to_string())
} else {
let mut err = clap::Error::new(ErrorKind::InvalidValue).with_cmd(cmd);
if let Some(arg) = arg {
err.insert(
ContextKind::InvalidArg,
ContextValue::String(arg.to_string()),
);
}
err.insert(
ContextKind::InvalidValue,
ContextValue::String(value.to_string()),
);
err.insert(
ContextKind::ValidValue,
ContextValue::Strings(self.0.iter().map(|x| x.get_name().to_string()).collect()),
);
Err(err)
}
}
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
Some(Box::new(self.0.iter().cloned()))
}
}
impl<I, T> From<I> for ShortcutValueParser
where
I: IntoIterator<Item = T>,
T: Into<PossibleValue>,
{
fn from(values: I) -> Self {
Self(values.into_iter().map(|t| t.into()).collect())
}
}
#[cfg(test)]
mod tests {
use std::ffi::OsStr;
use clap::{builder::TypedValueParser, error::ErrorKind, Command};
use super::ShortcutValueParser;
#[test]
fn test_parse_ref() {
let cmd = Command::new("cmd");
let parser = ShortcutValueParser::new(["abcd"]);
let values = ["a", "ab", "abc", "abcd"];
for value in values {
let result = parser.parse_ref(&cmd, None, OsStr::new(value));
assert_eq!("abcd", result.unwrap());
}
}
#[test]
fn test_parse_ref_with_invalid_value() {
let cmd = Command::new("cmd");
let parser = ShortcutValueParser::new(["abcd"]);
let invalid_values = ["e", "abe", "abcde"];
for invalid_value in invalid_values {
let result = parser.parse_ref(&cmd, None, OsStr::new(invalid_value));
assert_eq!(ErrorKind::InvalidValue, result.unwrap_err().kind());
}
}
#[test]
fn test_parse_ref_with_ambiguous_value() {
let cmd = Command::new("cmd");
let parser = ShortcutValueParser::new(["abcd", "abef"]);
let ambiguous_values = ["a", "ab"];
for ambiguous_value in ambiguous_values {
let result = parser.parse_ref(&cmd, None, OsStr::new(ambiguous_value));
assert_eq!(ErrorKind::InvalidValue, result.unwrap_err().kind());
}
let result = parser.parse_ref(&cmd, None, OsStr::new("abc"));
assert_eq!("abcd", result.unwrap());
let result = parser.parse_ref(&cmd, None, OsStr::new("abe"));
assert_eq!("abef", result.unwrap());
}
#[test]
#[cfg(unix)]
fn test_parse_ref_with_invalid_utf8() {
use std::os::unix::prelude::OsStrExt;
let parser = ShortcutValueParser::new(["abcd"]);
let cmd = Command::new("cmd");
let result = parser.parse_ref(&cmd, None, OsStr::from_bytes(&[0xc3 as u8, 0x28 as u8]));
assert_eq!(ErrorKind::InvalidUtf8, result.unwrap_err().kind());
}
}

View file

@ -158,6 +158,20 @@ fn test_cp_recurse() {
assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n"); assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n");
} }
#[test]
#[cfg(not(target_os = "macos"))]
fn test_cp_recurse_several() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-r")
.arg("-r")
.arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW)
.succeeds();
// Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n");
}
#[test] #[test]
fn test_cp_with_dirs_t() { fn test_cp_with_dirs_t() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
@ -1110,12 +1124,12 @@ fn test_cp_parents_with_permissions_copy_file() {
at.mkdir_all("p1/p2"); at.mkdir_all("p1/p2");
at.touch(file); at.touch(file);
let p1_mode = 0o0777;
let p2_mode = 0o0711;
let file_mode = 0o0702;
#[cfg(unix)] #[cfg(unix)]
{ {
let p1_mode = 0o0777;
let p2_mode = 0o0711;
let file_mode = 0o0702;
at.set_mode("p1", p1_mode); at.set_mode("p1", p1_mode);
at.set_mode("p1/p2", p2_mode); at.set_mode("p1/p2", p2_mode);
at.set_mode(file, file_mode); at.set_mode(file, file_mode);
@ -1151,12 +1165,12 @@ fn test_cp_parents_with_permissions_copy_dir() {
at.mkdir_all(dir2); at.mkdir_all(dir2);
at.touch(file); at.touch(file);
let p1_mode = 0o0777;
let p2_mode = 0o0711;
let file_mode = 0o0702;
#[cfg(unix)] #[cfg(unix)]
{ {
let p1_mode = 0o0777;
let p2_mode = 0o0711;
let file_mode = 0o0702;
at.set_mode("p1", p1_mode); at.set_mode("p1", p1_mode);
at.set_mode("p1/p2", p2_mode); at.set_mode("p1/p2", p2_mode);
at.set_mode(file, file_mode); at.set_mode(file, file_mode);
@ -2345,12 +2359,8 @@ fn test_dir_recursive_copy() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
at.mkdir("parent1"); at.mkdir_all("parent1/child");
at.mkdir("parent2"); at.mkdir_all("parent2/child1/child2/child3");
at.mkdir("parent1/child");
at.mkdir("parent2/child1");
at.mkdir("parent2/child1/child2");
at.mkdir("parent2/child1/child2/child3");
// case-1: copy parent1 -> parent1: should fail // case-1: copy parent1 -> parent1: should fail
scene scene
@ -2724,7 +2734,7 @@ fn test_copy_dir_preserve_permissions_inaccessible_file() {
ucmd.args(&["-p", "-R", "d1", "d2"]) ucmd.args(&["-p", "-R", "d1", "d2"])
.fails() .fails()
.code_is(1) .code_is(1)
.stderr_only("cp: cannot open 'd1/f' for reading: Permission denied\n"); .stderr_only("cp: cannot open 'd1/f' for reading: permission denied\n");
assert!(at.dir_exists("d2")); assert!(at.dir_exists("d2"));
assert!(!at.file_exists("d2/f")); assert!(!at.file_exists("d2/f"));
@ -3127,25 +3137,39 @@ fn test_cp_debug_sparse_auto() {
let ts = TestScenario::new(util_name!()); let ts = TestScenario::new(util_name!());
let at = &ts.fixtures; let at = &ts.fixtures;
at.touch("a"); at.touch("a");
let result = ts
.ucmd() #[cfg(not(any(target_os = "linux", target_os = "macos")))]
ts.ucmd()
.arg("--debug") .arg("--debug")
.arg("--sparse=auto") .arg("--sparse=auto")
.arg("a") .arg("a")
.arg("b") .arg("b")
.succeeds(); .succeeds();
let stdout_str = result.stdout_str();
#[cfg(target_os = "macos")] #[cfg(any(target_os = "linux", target_os = "macos"))]
if !stdout_str
.contains("copy offload: unknown, reflink: unsupported, sparse detection: unsupported")
{ {
panic!("Failure: stdout was \n{stdout_str}"); let result = ts
} .ucmd()
.arg("--debug")
.arg("--sparse=auto")
.arg("a")
.arg("b")
.succeeds();
#[cfg(target_os = "linux")] let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}"); #[cfg(target_os = "macos")]
if !stdout_str
.contains("copy offload: unknown, reflink: unsupported, sparse detection: unsupported")
{
panic!("Failure: stdout was \n{stdout_str}");
}
#[cfg(target_os = "linux")]
if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no")
{
panic!("Failure: stdout was \n{stdout_str}");
}
} }
} }

View file

@ -258,7 +258,7 @@ fn test_type_option() {
} }
#[test] #[test]
#[cfg(not(target_os = "freebsd"))] // FIXME: fix this test for FreeBSD #[cfg(not(any(target_os = "freebsd", target_os = "windows")))] // FIXME: fix test for FreeBSD & Win
fn test_type_option_with_file() { fn test_type_option_with_file() {
let fs_type = new_ucmd!() let fs_type = new_ucmd!()
.args(&["--output=fstype", "."]) .args(&["--output=fstype", "."])
@ -806,7 +806,7 @@ fn test_output_file_all_filesystems() {
} }
#[test] #[test]
#[cfg(not(target_os = "freebsd"))] // FIXME: fix this test for FreeBSD #[cfg(not(any(target_os = "freebsd", target_os = "windows")))] // FIXME: fix test for FreeBSD & Win
fn test_output_file_specific_files() { fn test_output_file_specific_files() {
// Create three files. // Create three files.
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
@ -825,7 +825,7 @@ fn test_output_file_specific_files() {
} }
#[test] #[test]
#[cfg(not(target_os = "freebsd"))] // FIXME: fix this test for FreeBSD #[cfg(not(any(target_os = "freebsd", target_os = "windows")))] // FIXME: fix test for FreeBSD & Win
fn test_file_column_width_if_filename_contains_unicode_chars() { fn test_file_column_width_if_filename_contains_unicode_chars() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
at.touch("äöü.txt"); at.touch("äöü.txt");
@ -848,7 +848,7 @@ fn test_output_field_no_more_than_once() {
} }
#[test] #[test]
#[cfg(not(target_os = "freebsd"))] // FIXME: fix this test for FreeBSD #[cfg(not(any(target_os = "freebsd", target_os = "windows")))] // FIXME: fix test for FreeBSD & Win
fn test_nonexistent_file() { fn test_nonexistent_file() {
new_ucmd!() new_ucmd!()
.arg("does-not-exist") .arg("does-not-exist")

View file

@ -6,6 +6,7 @@
// spell-checker:ignore (paths) sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink // spell-checker:ignore (paths) sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink
#[cfg(not(windows))] #[cfg(not(windows))]
use regex::Regex; use regex::Regex;
#[cfg(not(windows))]
use std::io::Write; use std::io::Write;
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
@ -580,97 +581,58 @@ fn test_du_invalid_threshold() {
#[test] #[test]
fn test_du_apparent_size() { fn test_du_apparent_size() {
let ts = TestScenario::new(util_name!()); let (at, mut ucmd) = at_and_ucmd!();
let result = ts.ucmd().arg("--apparent-size").succeeds();
#[cfg(any(target_os = "linux", target_os = "android"))] at.mkdir_all("a/b");
at.write("a/b/file1", "foo");
at.write("a/b/file2", "foobar");
let result = ucmd.args(&["--apparent-size", "--all", "a"]).succeeds();
#[cfg(not(target_os = "windows"))]
{ {
let result_reference = unwrap_or_return!(expected_result(&ts, &["--apparent-size"])); result.stdout_contains_line("1\ta/b/file2");
assert_eq!(result.stdout_str(), result_reference.stdout_str()); result.stdout_contains_line("1\ta/b/file1");
result.stdout_contains_line("1\ta/b");
result.stdout_contains_line("1\ta");
} }
#[cfg(not(any(target_os = "linux", target_os = "android")))] #[cfg(target_os = "windows")]
_du_apparent_size(result.stdout_str()); {
} result.stdout_contains_line("1\ta\\b\\file2");
result.stdout_contains_line("1\ta\\b\\file1");
#[cfg(target_os = "windows")] result.stdout_contains_line("1\ta\\b");
fn _du_apparent_size(s: &str) { result.stdout_contains_line("1\ta");
assert_eq!( }
s,
"1\t.\\subdir\\deeper\\deeper_dir
1\t.\\subdir\\deeper
6\t.\\subdir\\links
6\t.\\subdir
6\t.
"
);
}
#[cfg(target_vendor = "apple")]
fn _du_apparent_size(s: &str) {
assert_eq!(
s,
"1\t./subdir/deeper/deeper_dir
1\t./subdir/deeper
6\t./subdir/links
6\t./subdir
6\t.
"
);
}
#[cfg(target_os = "freebsd")]
fn _du_apparent_size(s: &str) {
assert_eq!(
s,
"1\t./subdir/deeper/deeper_dir
2\t./subdir/deeper
6\t./subdir/links
8\t./subdir
8\t.
"
);
}
#[cfg(all(
not(target_vendor = "apple"),
not(target_os = "windows"),
not(target_os = "freebsd")
))]
fn _du_apparent_size(s: &str) {
assert_eq!(
s,
"5\t./subdir/deeper/deeper_dir
9\t./subdir/deeper
10\t./subdir/links
22\t./subdir
26\t.
"
);
} }
#[test] #[test]
fn test_du_bytes() { fn test_du_bytes() {
let ts = TestScenario::new(util_name!()); let (at, mut ucmd) = at_and_ucmd!();
let result = ts.ucmd().arg("--bytes").succeeds();
#[cfg(any(target_os = "linux", target_os = "android"))] at.mkdir_all("a/b");
at.write("a/b/file1", "foo");
at.write("a/b/file2", "foobar");
let result = ucmd.args(&["--bytes", "--all", "a"]).succeeds();
#[cfg(not(target_os = "windows"))]
{ {
let result_reference = unwrap_or_return!(expected_result(&ts, &["--bytes"])); result.stdout_contains_line("6\ta/b/file2");
assert_eq!(result.stdout_str(), result_reference.stdout_str()); result.stdout_contains_line("3\ta/b/file1");
result.stdout_contains_line("9\ta/b");
result.stdout_contains_line("9\ta");
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
result.stdout_contains("5145\t.\\subdir\n"); {
#[cfg(target_vendor = "apple")] result.stdout_contains_line("6\ta\\b\\file2");
result.stdout_contains("5625\t./subdir\n"); result.stdout_contains_line("3\ta\\b\\file1");
#[cfg(target_os = "freebsd")] result.stdout_contains_line("9\ta\\b");
result.stdout_contains("7193\t./subdir\n"); result.stdout_contains_line("9\ta");
#[cfg(all( }
not(target_vendor = "apple"),
not(target_os = "windows"),
not(target_os = "freebsd"),
not(target_os = "linux"),
not(target_os = "android"),
))]
result.stdout_contains("21529\t./subdir\n");
} }
#[test] #[test]

View file

@ -2312,56 +2312,15 @@ fn test_ls_version_sort() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
for filename in [ for filename in [
"a2", "a2", "b1", "b20", "a1.4", "b3", "b11", "b20b", "b20a", "a100", "a1.13", "aa", "a1", "aaa",
"b1", "abab", "ab", "a01.40", "a001.001",
"b20",
"a1.4",
"a1.40",
"b3",
"b11",
"b20b",
"b20a",
"a100",
"a1.13",
"aa",
"a1",
"aaa",
"a1.00000040",
"abab",
"ab",
"a01.40",
"a001.001",
"a01.0000001",
"a01.001",
"a001.01",
] { ] {
at.touch(filename); at.touch(filename);
} }
let mut expected = vec![ let mut expected = vec![
"a1", "a1", "a001.001", "a1.4", "a1.13", "a01.40", "a2", "a100", "aa", "aaa", "ab", "abab", "b1",
"a001.001", "b3", "b11", "b20", "b20a", "b20b", "", // because of '\n' at the end of the output
"a001.01",
"a01.0000001",
"a01.001",
"a1.4",
"a1.13",
"a01.40",
"a1.00000040",
"a1.40",
"a2",
"a100",
"aa",
"aaa",
"ab",
"abab",
"b1",
"b3",
"b11",
"b20",
"b20a",
"b20b",
"", // because of '\n' at the end of the output
]; ];
let result = scene.ucmd().arg("-1v").succeeds(); let result = scene.ucmd().arg("-1v").succeeds();
@ -3137,6 +3096,16 @@ fn test_ls_dangling_symlinks() {
.stderr_contains("No such file or directory") .stderr_contains("No such file or directory")
.stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" }); .stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" });
scene
.ucmd()
.arg("-LZ")
.arg("temp_dir")
.fails()
.code_is(1)
.stderr_contains("cannot access")
.stderr_contains("No such file or directory")
.stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" });
scene scene
.ucmd() .ucmd()
.arg("-Ll") .arg("-Ll")

View file

@ -21,9 +21,15 @@ fn test_valid_arg() {
new_ucmd!().arg("-s").succeeds(); new_ucmd!().arg("-s").succeeds();
new_ucmd!().arg("--squeeze").succeeds(); new_ucmd!().arg("--squeeze").succeeds();
new_ucmd!().arg("-u").succeeds();
new_ucmd!().arg("--plain").succeeds();
new_ucmd!().arg("-n").arg("10").succeeds(); new_ucmd!().arg("-n").arg("10").succeeds();
new_ucmd!().arg("--lines").arg("0").succeeds(); new_ucmd!().arg("--lines").arg("0").succeeds();
new_ucmd!().arg("--number").arg("0").succeeds(); new_ucmd!().arg("--number").arg("0").succeeds();
new_ucmd!().arg("-F").arg("10").succeeds();
new_ucmd!().arg("--from-line").arg("0").succeeds();
} }
} }
@ -34,6 +40,42 @@ fn test_invalid_arg() {
new_ucmd!().arg("--lines").arg("-10").fails(); new_ucmd!().arg("--lines").arg("-10").fails();
new_ucmd!().arg("--number").arg("-10").fails(); new_ucmd!().arg("--number").arg("-10").fails();
new_ucmd!().arg("--from-line").arg("-10").fails();
}
}
#[test]
fn test_argument_from_file() {
if std::io::stdout().is_terminal() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file = "test_file";
at.write(file, "1\n2");
// output all lines
scene
.ucmd()
.arg("-F")
.arg("0")
.arg(file)
.succeeds()
.no_stderr()
.stdout_contains("1")
.stdout_contains("2");
// output only the second line
scene
.ucmd()
.arg("-F")
.arg("2")
.arg(file)
.succeeds()
.no_stderr()
.stdout_contains("2")
.stdout_does_not_contain("1");
} }
} }

View file

@ -510,6 +510,22 @@ fn test_mv_same_hardlink_backup_simple() {
.succeeds(); .succeeds();
} }
#[test]
#[cfg(all(unix, not(target_os = "android")))]
fn test_mv_same_hardlink_backup_simple_destroy() {
let (at, mut ucmd) = at_and_ucmd!();
let file_a = "test_mv_same_file_a~";
let file_b = "test_mv_same_file_a";
at.touch(file_a);
at.touch(file_b);
ucmd.arg(file_a)
.arg(file_b)
.arg("--b=simple")
.fails()
.stderr_contains("backing up 'test_mv_same_file_a' might destroy source");
}
#[test] #[test]
fn test_mv_same_file_not_dot_dir() { fn test_mv_same_file_not_dot_dir() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();

View file

@ -71,3 +71,10 @@ fn test_sections_and_styles() {
} }
// spell-checker:enable // spell-checker:enable
} }
#[test]
fn test_no_renumber() {
for arg in ["-p", "--no-renumber"] {
new_ucmd!().arg(arg).succeeds();
}
}

View file

@ -666,8 +666,79 @@ fn test_invalid_stdin_number_in_middle_of_input() {
} }
#[test] #[test]
fn test_invalid_argument_number_returns_status_2() { fn test_invalid_stdin_number_with_warn_returns_status_0() {
new_ucmd!().args(&["hello"]).fails().code_is(2); new_ucmd!()
.args(&["--invalid=warn"])
.pipe_in("4Q")
.succeeds()
.stdout_is("4Q\n")
.stderr_is("numfmt: invalid suffix in input: '4Q'\n");
}
#[test]
fn test_invalid_stdin_number_with_ignore_returns_status_0() {
new_ucmd!()
.args(&["--invalid=ignore"])
.pipe_in("4Q")
.succeeds()
.stdout_only("4Q\n");
}
#[test]
fn test_invalid_stdin_number_with_abort_returns_status_2() {
new_ucmd!()
.args(&["--invalid=abort"])
.pipe_in("4Q")
.fails()
.code_is(2)
.stderr_only("numfmt: invalid suffix in input: '4Q'\n");
}
#[test]
fn test_invalid_stdin_number_with_fail_returns_status_2() {
new_ucmd!()
.args(&["--invalid=fail"])
.pipe_in("4Q")
.fails()
.code_is(2)
.stdout_is("4Q\n")
.stderr_is("numfmt: invalid suffix in input: '4Q'\n");
}
#[test]
fn test_invalid_arg_number_with_warn_returns_status_0() {
new_ucmd!()
.args(&["--invalid=warn", "4Q"])
.succeeds()
.stdout_is("4Q\n")
.stderr_is("numfmt: invalid suffix in input: '4Q'\n");
}
#[test]
fn test_invalid_arg_number_with_ignore_returns_status_0() {
new_ucmd!()
.args(&["--invalid=ignore", "4Q"])
.succeeds()
.stdout_only("4Q\n");
}
#[test]
fn test_invalid_arg_number_with_abort_returns_status_2() {
new_ucmd!()
.args(&["--invalid=abort", "4Q"])
.fails()
.code_is(2)
.stderr_only("numfmt: invalid suffix in input: '4Q'\n");
}
#[test]
fn test_invalid_arg_number_with_fail_returns_status_2() {
new_ucmd!()
.args(&["--invalid=fail", "4Q"])
.fails()
.code_is(2)
.stdout_is("4Q\n")
.stderr_is("numfmt: invalid suffix in input: '4Q'\n");
} }
#[test] #[test]

View file

@ -627,6 +627,35 @@ fn test_skip_bytes() {
)); ));
} }
#[test]
fn test_skip_bytes_hex() {
let input = "abcdefghijklmnopq"; // spell-checker:disable-line
new_ucmd!()
.arg("-c")
.arg("--skip-bytes=0xB")
.run_piped_stdin(input.as_bytes())
.no_stderr()
.success()
.stdout_is(unindent(
"
0000013 l m n o p q
0000021
",
));
new_ucmd!()
.arg("-c")
.arg("--skip-bytes=0xE")
.run_piped_stdin(input.as_bytes())
.no_stderr()
.success()
.stdout_is(unindent(
"
0000016 o p q
0000021
",
));
}
#[test] #[test]
fn test_skip_bytes_error() { fn test_skip_bytes_error() {
let input = "12345"; let input = "12345";

View file

@ -208,10 +208,13 @@ fn test_separator_and_terminator() {
#[test] #[test]
fn test_equalize_widths() { fn test_equalize_widths() {
new_ucmd!() let args = ["-w", "--equal-width"];
.args(&["-w", "5", "10"]) for arg in args {
.run() new_ucmd!()
.stdout_is("05\n06\n07\n08\n09\n10\n"); .args(&[arg, "5", "10"])
.run()
.stdout_is("05\n06\n07\n08\n09\n10\n");
}
} }
#[test] #[test]

View file

@ -18,7 +18,7 @@ fn test_shred_remove() {
at.touch(file_b); at.touch(file_b);
// Shred file_a. // Shred file_a.
scene.ucmd().arg("-u").arg(file_a).run(); scene.ucmd().arg("-u").arg(file_a).succeeds();
// file_a was deleted, file_b exists. // file_a was deleted, file_b exists.
assert!(!at.file_exists(file_a)); assert!(!at.file_exists(file_a));

View file

@ -758,3 +758,36 @@ fn test_round_robin() {
assert_eq!(file_read("xaa"), "1\n3\n5\n"); assert_eq!(file_read("xaa"), "1\n3\n5\n");
assert_eq!(file_read("xab"), "2\n4\n"); assert_eq!(file_read("xab"), "2\n4\n");
} }
#[test]
fn test_split_invalid_input() {
// Test if stdout/stderr for '--lines' option is correct
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("file");
scene
.ucmd()
.args(&["--lines", "0", "file"])
.fails()
.no_stdout()
.stderr_contains("split: invalid number of lines: 0");
scene
.ucmd()
.args(&["-C", "0", "file"])
.fails()
.no_stdout()
.stderr_contains("split: invalid number of bytes: 0");
scene
.ucmd()
.args(&["-b", "0", "file"])
.fails()
.no_stdout()
.stderr_contains("split: invalid number of bytes: 0");
scene
.ucmd()
.args(&["-n", "0", "file"])
.fails()
.no_stdout()
.stderr_contains("split: invalid number of chunks: 0");
}

View file

@ -41,7 +41,7 @@ fn test_sync_no_existing_files() {
.arg("--data") .arg("--data")
.arg("do-no-exist") .arg("do-no-exist")
.fails() .fails()
.stderr_contains("cannot stat"); .stderr_contains("error opening");
} }
#[test] #[test]
@ -63,9 +63,9 @@ fn test_sync_no_permission_dir() {
ts.ccmd("chmod").arg("0").arg(dir).succeeds(); ts.ccmd("chmod").arg("0").arg(dir).succeeds();
let result = ts.ucmd().arg("--data").arg(dir).fails(); let result = ts.ucmd().arg("--data").arg(dir).fails();
result.stderr_contains("sync: cannot stat 'foo': Permission denied"); result.stderr_contains("sync: error opening 'foo': Permission denied");
let result = ts.ucmd().arg(dir).fails(); let result = ts.ucmd().arg(dir).fails();
result.stderr_contains("sync: cannot stat 'foo': Permission denied"); result.stderr_contains("sync: error opening 'foo': Permission denied");
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]

View file

@ -145,12 +145,8 @@ fn test_stdin_redirect_offset() {
} }
#[test] #[test]
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] // FIXME: for currently not working platforms #[cfg(all(not(target_vendor = "apple")))] // FIXME: for currently not working platforms
fn test_stdin_redirect_offset2() { fn test_stdin_redirect_offset2() {
// FIXME: windows: Failed because of difference in printed header. See below.
// actual : ==> - <==
// expected: ==> standard input <==
// like test_stdin_redirect_offset but with multiple files // like test_stdin_redirect_offset but with multiple files
let ts = TestScenario::new(util_name!()); let ts = TestScenario::new(util_name!());

View file

@ -204,19 +204,23 @@ fn test_touch_set_cymdhms_time() {
#[test] #[test]
fn test_touch_set_only_atime() { fn test_touch_set_only_atime() {
let (at, mut ucmd) = at_and_ucmd!(); let atime_args = ["-a", "--time=access", "--time=atime", "--time=use"];
let file = "test_touch_set_only_atime"; let file = "test_touch_set_only_atime";
ucmd.args(&["-t", "201501011234", "-a", file]) for atime_arg in atime_args {
.succeeds() let (at, mut ucmd) = at_and_ucmd!();
.no_stderr();
assert!(at.file_exists(file)); ucmd.args(&["-t", "201501011234", atime_arg, file])
.succeeds()
.no_stderr();
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); assert!(at.file_exists(file));
let (atime, mtime) = get_file_times(&at, file);
assert!(atime != mtime); let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000");
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45240); let (atime, mtime) = get_file_times(&at, file);
assert!(atime != mtime);
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45240);
}
} }
#[test] #[test]
@ -301,19 +305,23 @@ fn test_touch_set_both_time_and_date() {
#[test] #[test]
fn test_touch_set_only_mtime() { fn test_touch_set_only_mtime() {
let (at, mut ucmd) = at_and_ucmd!(); let mtime_args = ["-m", "--time=modify", "--time=mtime"];
let file = "test_touch_set_only_mtime"; let file = "test_touch_set_only_mtime";
ucmd.args(&["-t", "201501011234", "-m", file]) for mtime_arg in mtime_args {
.succeeds() let (at, mut ucmd) = at_and_ucmd!();
.no_stderr();
assert!(at.file_exists(file)); ucmd.args(&["-t", "201501011234", mtime_arg, file])
.succeeds()
.no_stderr();
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); assert!(at.file_exists(file));
let (atime, mtime) = get_file_times(&at, file);
assert!(atime != mtime); let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000");
assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), 45240); let (atime, mtime) = get_file_times(&at, file);
assert!(atime != mtime);
assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), 45240);
}
} }
#[test] #[test]

View file

@ -35,6 +35,11 @@ fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1); new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
} }
#[test]
fn test_version() {
new_ucmd!().arg("--version").succeeds();
}
#[test] #[test]
fn test_simple() { fn test_simple() {
run(NO_ARGS, b"y\ny\ny\ny\n"); run(NO_ARGS, b"y\ny\ny\ny\n");

View file

@ -659,6 +659,17 @@ impl CmdResult {
self self
} }
#[track_caller]
pub fn stdout_contains_line<T: AsRef<str>>(&self, cmp: T) -> &Self {
assert!(
self.stdout_str().lines().any(|line| line == cmp.as_ref()),
"'{}' does not contain line '{}'",
self.stdout_str(),
cmp.as_ref()
);
self
}
#[track_caller] #[track_caller]
pub fn stderr_contains<T: AsRef<str>>(&self, cmp: T) -> &Self { pub fn stderr_contains<T: AsRef<str>>(&self, cmp: T) -> &Self {
assert!( assert!(

View file

@ -11,28 +11,31 @@ ME="${0}"
ME_dir="$(dirname -- "$(readlink -fm -- "${ME}")")" ME_dir="$(dirname -- "$(readlink -fm -- "${ME}")")"
REPO_main_dir="$(dirname -- "${ME_dir}")" REPO_main_dir="$(dirname -- "${ME_dir}")"
echo "ME='${ME}'"
echo "ME_dir='${ME_dir}'"
echo "REPO_main_dir='${REPO_main_dir}'"
### * config (from environment with fallback defaults); note: GNU is expected to be a sibling repo directory ### * config (from environment with fallback defaults); note: GNU is expected to be a sibling repo directory
path_UUTILS=${path_UUTILS:-${REPO_main_dir}} path_UUTILS=${path_UUTILS:-${REPO_main_dir}}
path_GNU="$(readlink -fm -- "${path_GNU:-${path_UUTILS}/../gnu}")" path_GNU="$(readlink -fm -- "${path_GNU:-${path_UUTILS}/../gnu}")"
echo "path_UUTILS='${path_UUTILS}'"
echo "path_GNU='${path_GNU}'"
### ###
if test ! -d "${path_GNU}"; then if test ! -d "${path_GNU}"; then
echo "Could not find GNU (expected at '${path_GNU}')" echo "Could not find GNU coreutils (expected at '${path_GNU}')"
echo "Run the following to download into the expected path:"
echo "git clone --recurse-submodules https://github.com/coreutils/coreutils.git \"${path_GNU}\"" echo "git clone --recurse-submodules https://github.com/coreutils/coreutils.git \"${path_GNU}\""
exit 1 exit 1
fi fi
### ###
echo "ME='${ME}'"
echo "ME_dir='${ME_dir}'"
echo "REPO_main_dir='${REPO_main_dir}'"
echo "path_UUTILS='${path_UUTILS}'"
echo "path_GNU='${path_GNU}'"
###
UU_MAKE_PROFILE=${UU_MAKE_PROFILE:-release} UU_MAKE_PROFILE=${UU_MAKE_PROFILE:-release}
echo "UU_MAKE_PROFILE='${UU_MAKE_PROFILE}'" echo "UU_MAKE_PROFILE='${UU_MAKE_PROFILE}'"
@ -232,3 +235,8 @@ sed -i -e "s/Try 'mv --help' for more information/For more information, try '--h
sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/misc/printf-cov.pl sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/misc/printf-cov.pl
sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold <SIZE>' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold <SIZE>' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh
# disable two kind of tests:
# "hostid BEFORE --help" doesn't fail for GNU. we fail. we are probably doing better
# "hostid BEFORE --help AFTER " same for this
sed -i -e "s/env \$prog \$BEFORE \$opt > out2/env \$prog \$BEFORE \$opt > out2 #/" -e "s/env \$prog \$BEFORE \$opt AFTER > out3/env \$prog \$BEFORE \$opt AFTER > out3 #/" -e "s/compare exp out2/compare exp out2 #/" -e "s/compare exp out3/compare exp out3 #/" tests/misc/help-version-getopt.sh