1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 03:57:44 +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
shell: bash
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
RUST_MIN_SRV=$(grep -P "^\s+RUST_MIN_SRV:" .github/workflows/CICD.yml | grep -Po "(?<=\x22)\d+[.]\d+(?:[.]\d+)?(?=\x22)" )
outputs RUST_MIN_SRV
- uses: dtolnay/rust-toolchain@${{ steps.vars.outputs.RUST_MIN_SRV }}
echo "RUST_MIN_SRV=${RUST_MIN_SRV}" >> $GITHUB_OUTPUT
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ steps.vars.outputs.RUST_MIN_SRV }}
- uses: Swatinem/rust-cache@v2
- name: Ensure updated 'Cargo.lock'
shell: bash
@ -67,7 +67,7 @@ jobs:
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
uses: EndBug/add-and-commit@v9
with:
branch: ${{ env.BRANCH_TARGET }}
new_branch: ${{ env.BRANCH_TARGET }}
default_author: github_actions
message: "maint ~ refresh 'Cargo.lock'"
add: Cargo.lock
@ -90,13 +90,11 @@ jobs:
id: vars
shell: bash
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
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
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
with:
toolchain: stable
@ -114,7 +112,7 @@ jobs:
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
uses: EndBug/add-and-commit@v9
with:
branch: ${{ env.BRANCH_TARGET }}
new_branch: ${{ env.BRANCH_TARGET }}
default_author: github_actions
message: "maint ~ rustfmt (`cargo fmt`)"
env:

View file

@ -205,7 +205,7 @@ jobs:
path_UUTILS='${{ steps.vars.outputs.path_UUTILS }}'
# https://github.com/uutils/coreutils/issues/4294
# 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 }}

View file

@ -35,7 +35,7 @@ jobs:
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- name: Prepare, build and test
uses: vmactions/freebsd-vm@v0.3.0
uses: vmactions/freebsd-vm@v0.3.1
with:
usesh: true
# 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
uses: mozilla-actions/sccache-action@v0.0.3
- name: Prepare, build and test
uses: vmactions/freebsd-vm@v0.3.0
uses: vmactions/freebsd-vm@v0.3.1
with:
usesh: true
# sync: sshfs

1
.gitignore vendored
View file

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

1
.vscode/cSpell.json vendored
View file

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

370
Cargo.lock generated
View file

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

View file

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

View file

@ -1,6 +1,7 @@
<!-- markdownlint-disable MD033 MD041 MD002 -->
<!-- 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">
![uutils logo](docs/src/logo.svg)
@ -19,11 +20,14 @@
---
</div>
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
options might be missing or different behavior might be experienced.
<div class="oranda-hide">
To install it:
```shell
@ -31,6 +35,8 @@ cargo install coreutils
~/.cargo/bin/coreutils
```
</div>
<!-- markdownlint-disable-next-line MD026 -->
## 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,
that scripts can be easily transferred between platforms.
<div class="oranda-hide">
## Documentation
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
the [coreutils docs](https://github.com/uutils/uutils.github.io) repository.
<!-- ANCHOR: build (this mark is needed for mdbook) -->
## 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)
</div> <!-- close oranda-hide div -->
## Contributing
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).

View file

@ -59,8 +59,6 @@ highlight = "all"
# introduces it.
# spell-checker: disable
skip = [
# is-terminal
{ name = "hermit-abi", version = "0.3.1" },
# procfs
{ name = "rustix", version = "0.36.14" },
# rustix
@ -87,6 +85,10 @@ skip = [
{ name = "redox_syscall", version = "0.3.5" },
# cpp_macros
{ 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

View file

@ -61,7 +61,20 @@ feature is adopted from [FreeBSD](https://www.freebsd.org/cgi/man.cgi?cut).
## `fmt`
`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
mail headers in the input. `-q/--quick` breaks lines more quickly. And `-T/--tab-width` defines the
`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
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.
## `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
))
.infer_long_args(true)
.args_override_self(true)
.arg(
Arg::new(options::TARGET_DIRECTORY)
.short('t')

View file

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

View file

@ -13,7 +13,7 @@ Copy, and optionally convert, a file system resource
### 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`.
- `cbs=BYTES` : the 'conversion block size' in bytes. Applies to the
`conv=block`, and `conv=unblock` operations.
@ -114,7 +114,7 @@ Copy, and optionally convert, a file system resource
### 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
output (if used as an oflag) is a directory.
- `dsync` : use synchronized I/O for data.

View file

@ -36,9 +36,12 @@ use std::os::unix::{
io::{AsRawFd, FromRawFd},
};
use std::path::Path;
use std::sync::mpsc;
use std::sync::{
atomic::{AtomicBool, Ordering::Relaxed},
mpsc, Arc,
};
use std::thread;
use std::time;
use std::time::{Duration, Instant};
use clap::{crate_version, Arg, Command};
use gcd::Gcd;
@ -75,6 +78,45 @@ struct Settings {
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
///
/// 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,
// which requires knowing how long the process has been
// running.
let start = time::Instant::now();
let start = Instant::now();
// A good buffer size for reading.
//
@ -780,7 +822,6 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
// information.
let (prog_tx, rx) = mpsc::channel();
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
// 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.
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
// 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.
rstat += rstat_update;
wstat += wstat_update;
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), false);
if prog_update.duration.as_secs() >= progress_as_secs {
progress_as_secs = prog_update.duration.as_secs() + 1;
if alarm.is_triggered() {
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), false);
prog_tx.send(prog_update).unwrap_or(());
}
}
@ -885,7 +931,7 @@ fn finalize<T>(
output: &mut Output,
rstat: ReadStat,
wstat: WriteStat,
start: time::Instant,
start: Instant,
prog_tx: &mpsc::Sender<ProgUpdate>,
output_thread: thread::JoinHandle<T>,
) -> std::io::Result<()> {

View file

@ -145,7 +145,7 @@ impl Stat {
return Ok(Self {
path: path.to_path_buf(),
is_dir: metadata.is_dir(),
size: metadata.len(),
size: if path.is_dir() { 0 } else { metadata.len() },
blocks: metadata.blocks(),
inodes: 1,
inode: Some(file_info),
@ -162,7 +162,7 @@ impl Stat {
Ok(Self {
path: path.to_path_buf(),
is_dir: metadata.is_dir(),
size: metadata.len(),
size: if path.is_dir() { 0 } else { metadata.len() },
blocks: size_on_disk / 1024 * 2,
inode: file_info,
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"
let program = args.next().unwrap_or_else(|| OsString::from(NAME));
let binary_name = Path::new(&program)
.file_name()
.file_stem()
.unwrap_or_else(|| OsStr::new(NAME))
.to_string_lossy();

View file

@ -3010,6 +3010,20 @@ fn display_inode(metadata: &Metadata) -> String {
#[allow(unused_variables)]
fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -> 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 {
#[cfg(feature = "selinux")]
{

View file

@ -50,10 +50,11 @@ pub mod options {
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 {
clean_print: bool,
from_line: usize,
lines: Option<u16>,
print_over: bool,
silent: bool,
@ -72,8 +73,13 @@ impl Options {
(None, Some(number)) if number > 0 => Some(number + 1),
(_, _) => None,
};
let from_line = match matches.get_one::<usize>(options::FROM_LINE).copied() {
Some(number) if number > 1 => number - 1,
_ => 0,
};
Self {
clean_print: matches.get_flag(options::CLEAN_PRINT),
from_line,
lines,
print_over: matches.get_flag(options::PRINT_OVER),
silent: matches.get_flag(options::SILENT),
@ -90,7 +96,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Err(e) => return Err(e.into()),
};
let options = Options::from(&matches);
let mut options = Options::from(&matches);
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()),
));
}
if length > 1 {
buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", file.to_str().unwrap()));
}
let opened_file = match File::open(file) {
Err(why) => {
terminal::disable_raw_mode().unwrap();
@ -130,14 +133,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
};
let mut reader = BufReader::new(opened_file);
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();
}
reset_term(&mut stdout);
} else if !std::io::stdin().is_terminal() {
stdin().read_to_string(&mut buff).unwrap();
let mut stdout = setup_term();
more(&buff, &mut stdout, None, &options)?;
more(&buff, &mut stdout, false, None, None, &mut options)?;
reset_term(&mut stdout);
} else {
return Err(UUsageError::new(1, "bad usage"));
@ -179,6 +189,22 @@ pub fn uu_app() -> Command {
.help("Squeeze multiple blank lines into one")
.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::new(options::LINES)
.short('n')
@ -191,7 +217,6 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::NUMBER)
.long(options::NUMBER)
.required(false)
.num_args(1)
.value_parser(value_parser!(u16).range(0..))
.help("Same as --lines"),
@ -210,21 +235,6 @@ pub fn uu_app() -> Command {
.long(options::NO_PAUSE)
.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::new(options::PATTERN)
.short('P')
@ -273,8 +283,10 @@ fn reset_term(_: &mut usize) {}
fn more(
buff: &str,
stdout: &mut Stdout,
multiple_file: bool,
file: Option<&str>,
next_file: Option<&str>,
options: &Options,
options: &mut Options,
) -> UResult<()> {
let (cols, mut rows) = terminal::size().unwrap();
if let Some(number) = options.lines {
@ -284,8 +296,23 @@ fn more(
let lines = break_buff(buff, usize::from(cols));
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);
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(());
}
@ -406,7 +433,7 @@ impl<'a> Pager<'a> {
fn new(rows: u16, lines: Vec<String>, next_file: Option<&'a str>, options: &Options) -> Self {
let line_count = lines.len();
Self {
upper_mark: 0,
upper_mark: options.from_line,
content_rows: rows.saturating_sub(1),
lines,
next_file,
@ -472,10 +499,10 @@ impl<'a> Pager<'a> {
}
fn draw(&mut self, stdout: &mut std::io::Stdout, wrong_key: Option<char>) {
self.draw_lines(stdout);
let lower_mark = self
.line_count
.min(self.upper_mark.saturating_add(self.content_rows.into()));
self.draw_lines(stdout);
self.draw_prompt(stdout, lower_mark, wrong_key);
stdout.flush().unwrap();
}
@ -535,7 +562,6 @@ impl<'a> Pager<'a> {
};
let status = format!("--More--({status_inner})");
let banner = match (self.silent, wrong_key) {
(true, Some(key)) => format!(
"{status} [Unknown key: '{key}'. Press 'h' for instructions. (unimplemented)]"

View file

@ -22,7 +22,7 @@ use std::os::unix;
#[cfg(windows)]
use std::os::windows;
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::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};
@ -251,6 +251,17 @@ fn parse_paths(files: &[OsString], b: &Behavior) -> Vec<PathBuf> {
}
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() {
return Err(MvError::NoSuchFile(source.quote().to_string()).into());
}

View file

@ -1,4 +1,4 @@
#nl
# nl
```
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> {
// This vector holds error messages encountered.
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) {
None => {}
Some(val) => {

View file

@ -6,8 +6,6 @@
// * file that was distributed with this source code.
// *
// spell-checker:ignore (ToDO) corasick memchr
use clap::{crate_version, Arg, ArgAction, Command};
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
@ -18,8 +16,8 @@ use uucore::{format_usage, help_about, help_usage};
mod helper;
static ABOUT: &str = help_about!("nl.md");
static USAGE: &str = help_usage!("nl.md");
const ABOUT: &str = help_about!("nl.md");
const USAGE: &str = help_usage!("nl.md");
// Settings store options used by nl to produce its output.
pub struct Settings {
@ -42,6 +40,24 @@ pub struct Settings {
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.
// The possible options are:
// 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)?;
// A mutable settings object, initialized with the defaults.
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"),
};
let mut settings = Settings::default();
// Update the settings from the command line options, and terminate the
// program if some options could not successfully be parsed.
@ -210,7 +213,8 @@ pub fn uu_app() -> Command {
Arg::new(options::NO_RENUMBER)
.short('p')
.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::new(options::NUMBER_SEPARATOR)

View file

@ -11,11 +11,13 @@ use crate::options::*;
use crate::units::{Result, Unit};
use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
use std::io::{BufRead, Write};
use std::str::FromStr;
use units::{IEC_BASES, SI_BASES};
use uucore::display::Quotable;
use uucore::error::UResult;
use uucore::error::{UError, UResult};
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 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<()> {
for l in args {
match format_and_print(l, options) {
Ok(_) => Ok(()),
Err(e) => Err(NumfmtError::FormattingError(e.to_string())),
}?;
format_and_handle_validation(l, options)?;
}
Ok(())
}
@ -41,23 +39,41 @@ fn handle_buffer<R>(input: R, options: &NumfmtOptions) -> UResult<()>
where
R: BufRead,
{
let mut lines = input.lines();
for (idx, line) in lines.by_ref().enumerate() {
match line {
Ok(l) if idx < options.header => {
println!("{l}");
for (idx, line_result) in input.lines().by_ref().enumerate() {
match line_result {
Ok(line) if idx < options.header => {
println!("{line}");
Ok(())
}
Ok(l) => match format_and_print(&l, options) {
Ok(_) => Ok(()),
Err(e) => Err(NumfmtError::FormattingError(e.to_string())),
},
Err(e) => Err(NumfmtError::IoError(e.to_string())),
Ok(line) => format_and_handle_validation(line.as_ref(), options),
Err(err) => return Err(Box::new(NumfmtError::IoError(err.to_string()))),
}?;
}
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> {
match s {
"auto" => Ok(Unit::Auto),
@ -201,6 +217,9 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
.get_one::<String>(options::SUFFIX)
.map(|s| s.to_owned());
let invalid =
InvalidModes::from_str(args.get_one::<String>(options::INVALID).unwrap()).unwrap();
Ok(NumfmtOptions {
transform,
padding,
@ -210,6 +229,7 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
round,
suffix,
format,
invalid,
})
}
@ -340,10 +360,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::ROUND)
.long(options::ROUND)
.help(
"use METHOD for rounding when scaling; METHOD can be: up,\
down, from-zero, towards-zero, nearest",
)
.help("use METHOD for rounding when scaling")
.value_name("METHOD")
.default_value("from-zero")
.value_parser(["up", "down", "from-zero", "towards-zero", "nearest"]),
@ -357,6 +374,14 @@ pub fn uu_app() -> Command {
)
.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::new(options::NUMBER)
.hide(true)
@ -366,9 +391,11 @@ pub fn uu_app() -> Command {
#[cfg(test)]
mod tests {
use uucore::error::get_exit_code;
use super::{
handle_buffer, parse_unit_size, parse_unit_size_suffix, FormatOptions, NumfmtOptions,
Range, RoundMethod, TransformOptions, Unit,
handle_args, handle_buffer, parse_unit_size, parse_unit_size_suffix, FormatOptions,
InvalidModes, NumfmtOptions, Range, RoundMethod, TransformOptions, Unit,
};
use std::io::{BufReader, Error, ErrorKind, Read};
struct MockBuffer {}
@ -394,6 +421,7 @@ mod tests {
round: RoundMethod::Nearest,
suffix: None,
format: FormatOptions::default(),
invalid: InvalidModes::Abort,
}
}
@ -409,6 +437,20 @@ mod tests {
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]
fn non_numeric_returns_formatting_error() {
let input_value = b"135\nhello";
@ -431,6 +473,66 @@ mod tests {
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]
fn test_parse_unit_size() {
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 HEADER: &str = "header";
pub const HEADER_DEFAULT: &str = "1";
pub const INVALID: &str = "invalid";
pub const NUMBER: &str = "NUMBER";
pub const PADDING: &str = "padding";
pub const ROUND: &str = "round";
@ -29,6 +30,14 @@ pub struct TransformOptions {
pub to_unit: usize,
}
#[derive(Debug, PartialEq, Eq)]
pub enum InvalidModes {
Abort,
Fail,
Warn,
Ignore,
}
pub struct NumfmtOptions {
pub transform: TransformOptions,
pub padding: isize,
@ -38,6 +47,7 @@ pub struct NumfmtOptions {
pub round: RoundMethod,
pub suffix: Option<String>,
pub format: FormatOptions,
pub invalid: InvalidModes,
}
#[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)]
mod tests {
use super::*;
@ -336,4 +360,21 @@ mod tests {
assert_eq!(expected_options, "%0'0'0'f".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;
}
#[cfg(target_pointer_width = "64")]
Some('E') => {
Some('E') if radix != 16 => {
multiply = 1024 * 1024 * 1024 * 1024 * 1024 * 1024;
len -= 1;
}
@ -84,6 +84,7 @@ fn test_parse_number_of_bytes() {
// hex input
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!(27, parse_number_of_bytes("0x1b").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]... 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_TERMINATOR: &str = "terminator";
const OPT_WIDTHS: &str = "widths";
const OPT_EQUAL_WIDTH: &str = "equal-width";
const OPT_FORMAT: &str = "format";
const ARG_NUMBERS: &str = "numbers";
@ -40,7 +40,7 @@ const ARG_NUMBERS: &str = "numbers";
struct SeqOptions<'a> {
separator: String,
terminator: String,
widths: bool,
equal_width: bool,
format: Option<&'a str>,
}
@ -74,7 +74,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.map(|s| s.as_str())
.unwrap_or("\n")
.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()),
};
@ -123,7 +123,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
(first, increment, last),
&options.separator,
&options.terminator,
options.widths,
options.equal_width,
padding,
options.format,
)
@ -137,7 +137,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
largest_dec,
&options.separator,
&options.terminator,
options.widths,
options.equal_width,
padding,
options.format,
),
@ -170,9 +170,9 @@ pub fn uu_app() -> Command {
.help("Terminator character (defaults to \\n)"),
)
.arg(
Arg::new(OPT_WIDTHS)
Arg::new(OPT_EQUAL_WIDTH)
.short('w')
.long("widths")
.long("equal-width")
.help("Equalize widths of all numbers by padding with zeros")
.action(ArgAction::SetTrue),
)

View file

@ -532,7 +532,9 @@ fn wipe_name(orig_path: &Path, verbose: bool) -> Option<PathBuf> {
}
// 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");
new_file.sync_all().expect("Failed to sync renamed file");

View file

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

View file

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

View file

@ -288,7 +288,7 @@ impl<'a> FileMerger<'a> {
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];
if settings.unique {
if let Some(prev) = &prev {

View file

@ -267,7 +267,11 @@ impl NumberType {
let num_chunks = n_str
.parse()
.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] => {
let num_chunks = n_str
@ -357,6 +361,20 @@ impl fmt::Display for StrategyError {
impl Strategy {
/// Parse a strategy from the command-line arguments.
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.
//
// Note: right now, this exact behavior cannot be handled by
@ -370,20 +388,17 @@ impl Strategy {
) {
(false, false, false, false) => Ok(Self::Lines(1000)),
(true, false, false, false) => {
let s = matches.get_one::<String>(OPT_LINES).unwrap();
let n = parse_size(s).map_err(StrategyError::Lines)?;
Ok(Self::Lines(n))
get_and_parse(matches, OPT_LINES, Self::Lines, StrategyError::Lines)
}
(false, true, false, false) => {
let s = matches.get_one::<String>(OPT_BYTES).unwrap();
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))
get_and_parse(matches, OPT_BYTES, Self::Bytes, StrategyError::Bytes)
}
(false, false, true, false) => get_and_parse(
matches,
OPT_LINE_BYTES,
Self::LineBytes,
StrategyError::Bytes,
),
(false, false, false, true) => {
let s = matches.get_one::<String>(OPT_NUMBER).unwrap();
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
// * 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;
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
use nix::libc::{c_ushort, O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ};
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 std::io::{self, stdout};
@ -244,12 +245,30 @@ fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> {
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<()> {
print_terminal_size(termios, opts)?;
print_flags(termios, opts, CONTROL_FLAGS);
print_flags(termios, opts, INPUT_FLAGS);
print_flags(termios, opts, OUTPUT_FLAGS);
print_flags(termios, opts, LOCAL_FLAGS);
if opts.save {
print_in_save_format(termios);
} else {
print_terminal_size(termios, opts)?;
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(())
}
@ -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 setting has been applied.
fn apply_setting(termios: &mut Termios, s: &str) -> ControlFlow<bool> {
apply_baud_rate_flag(termios, s)?;
let (remove, name) = match s.strip_prefix('-') {
Some(s) => (true, s),
None => (false, s),
@ -332,6 +353,39 @@ fn apply_flag<T: TermiosFlag>(
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 {
Command::new(uucore::util_name())
.version(crate_version!())

View file

@ -173,7 +173,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let path = Path::new(&f);
if let Err(e) = open(path, OFlag::O_NONBLOCK, Mode::empty()) {
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() {
return Err(USimpleError::new(
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]
memchr = { workspace = true }
memmap2 = "0.7"
memmap2 = { workspace = true }
regex = { workspace = true }
clap = { workspace = true }
uucore = { workspace = true }

View file

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

View file

@ -20,6 +20,28 @@ pub enum InputKind {
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)]
pub struct Input {
kind: InputKind,
@ -27,22 +49,13 @@ pub struct Input {
}
impl Input {
pub fn from<T: AsRef<OsStr>>(string: &T) -> Self {
let kind = if string.as_ref() == Path::new(text::DASH) {
InputKind::Stdin
} else {
InputKind::File(PathBuf::from(string.as_ref()))
};
pub fn from<T: AsRef<OsStr>>(string: T) -> Self {
let string = string.as_ref();
let kind = string.into();
let display_name = match kind {
InputKind::File(_) => string.as_ref().to_string_lossy().to_string(),
InputKind::Stdin => {
if cfg!(unix) {
text::STDIN_HEADER.to_string()
} else {
string.as_ref().to_string_lossy().to_string()
}
}
InputKind::File(_) => string.to_string_lossy().to_string(),
InputKind::Stdin => text::STDIN_HEADER.to_string(),
};
Self { kind, display_name }

View file

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

View file

@ -6,7 +6,7 @@
// For the full copyright and license information, please view the LICENSE file
// 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::{crate_version, Arg, ArgAction, ArgGroup, Command};
@ -29,7 +29,7 @@ pub mod options {
pub mod sources {
pub static DATE: &str = "date";
pub static REFERENCE: &str = "reference";
pub static CURRENT: &str = "current";
pub static TIMESTAMP: &str = "timestamp";
}
pub static HELP: &str = "help";
pub static ACCESS: &str = "access";
@ -84,13 +84,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
) {
(Some(reference), Some(date)) => {
let (atime, mtime) = stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?;
if let Ok(offset) = humantime_to_duration::from_str(date) {
let mut seconds = offset.whole_seconds();
let mut nanos = offset.subsec_nanoseconds();
if nanos < 0 {
nanos += 1_000_000_000;
seconds -= 1;
}
if let Ok(offset) = parse_datetime::from_str(date) {
let seconds = offset.num_seconds();
let nanos = offset.num_nanoseconds().unwrap_or(0) % 1_000_000_000;
let ref_atime_secs = atime.unix_seconds();
let ref_atime_nanos = atime.nanoseconds();
@ -120,12 +116,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
(timestamp, timestamp)
}
(None, None) => {
let timestamp =
if let Some(current) = matches.get_one::<String>(options::sources::CURRENT) {
parse_timestamp(current)?
} else {
local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap())
};
let timestamp = if let Some(ts) = matches.get_one::<String>(options::sources::TIMESTAMP)
{
parse_timestamp(ts)?
} else {
local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap())
};
(timestamp, timestamp)
}
};
@ -243,7 +239,7 @@ pub fn uu_app() -> Command {
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::sources::CURRENT)
Arg::new(options::sources::TIMESTAMP)
.short('t')
.help("use [[CC]YY]MMDDhhmm[.ss] instead of the current time")
.value_name("STAMP"),
@ -255,7 +251,7 @@ pub fn uu_app() -> Command {
.allow_hyphen_values(true)
.help("parse argument and use it instead of current time")
.value_name("STRING")
.conflicts_with(options::sources::CURRENT),
.conflicts_with(options::sources::TIMESTAMP),
)
.arg(
Arg::new(options::MODIFICATION)
@ -288,7 +284,7 @@ pub fn uu_app() -> Command {
.value_name("FILE")
.value_parser(ValueParser::os_string())
.value_hint(clap::ValueHint::AnyPath)
.conflicts_with(options::sources::CURRENT),
.conflicts_with(options::sources::TIMESTAMP),
)
.arg(
Arg::new(options::TIME)
@ -299,7 +295,7 @@ pub fn uu_app() -> Command {
equivalent to -m",
)
.value_name("WORD")
.value_parser(["access", "atime", "use"]),
.value_parser(["access", "atime", "use", "modify", "mtime"]),
)
.arg(
Arg::new(ARG_FILES)
@ -311,7 +307,7 @@ pub fn uu_app() -> Command {
.group(
ArgGroup::new(options::SOURCES)
.args([
options::sources::CURRENT,
options::sources::TIMESTAMP,
options::sources::DATE,
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 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));
}

View file

@ -9,7 +9,7 @@
// 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::ffi::OsString;
use std::io::{self, Write};
@ -44,6 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.arg(

View file

@ -25,7 +25,7 @@ dunce = "1.0.4"
wild = "2.1"
glob = "0.3.1"
# * optional
itertools = { version = "0.10.5", optional = true }
itertools = { version = "0.11.0", optional = true }
thiserror = { workspace = true, optional = true }
time = { workspace = true, optional = true, features = [
"formatting",
@ -36,7 +36,7 @@ time = { workspace = true, optional = true, features = [
data-encoding = { version = "2.4", optional = true }
data-encoding-macro = { version = "0.1.13", 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 }
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_size;
pub use crate::parser::parse_time;
pub use crate::parser::shortcut_value_parser;
// * feature-gated modules
#[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
//
@ -626,4 +652,30 @@ mod tests {
let result = determine_backup_suffix(&matches);
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> {
use std::io::ErrorKind::*;
let mut message;
let message;
let message = if self.inner.raw_os_error().is_some() {
// These are errors that come directly from the OS.
// We want to normalize their messages across systems,
@ -424,7 +424,6 @@ impl Display for UIoError {
// (https://github.com/rust-lang/rust/issues/86442)
// are stabilized, we should add them to the match statement.
message = strip_errno(&self.inner);
capitalize(&mut message);
&message
}
}
@ -435,7 +434,6 @@ impl Display for UIoError {
// a file that was not found.
// There are also errors with entirely custom messages.
message = self.inner.to_string();
capitalize(&mut message);
&message
};
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.
pub fn strip_errno(err: &std::io::Error) -> String {
let mut msg = err.to_string();

View file

@ -1,3 +1,4 @@
pub mod parse_glob;
pub mod parse_size;
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");
}
#[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]
fn test_cp_with_dirs_t() {
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.touch(file);
let p1_mode = 0o0777;
let p2_mode = 0o0711;
let file_mode = 0o0702;
#[cfg(unix)]
{
let p1_mode = 0o0777;
let p2_mode = 0o0711;
let file_mode = 0o0702;
at.set_mode("p1", p1_mode);
at.set_mode("p1/p2", p2_mode);
at.set_mode(file, file_mode);
@ -1151,12 +1165,12 @@ fn test_cp_parents_with_permissions_copy_dir() {
at.mkdir_all(dir2);
at.touch(file);
let p1_mode = 0o0777;
let p2_mode = 0o0711;
let file_mode = 0o0702;
#[cfg(unix)]
{
let p1_mode = 0o0777;
let p2_mode = 0o0711;
let file_mode = 0o0702;
at.set_mode("p1", p1_mode);
at.set_mode("p1/p2", p2_mode);
at.set_mode(file, file_mode);
@ -2345,12 +2359,8 @@ fn test_dir_recursive_copy() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("parent1");
at.mkdir("parent2");
at.mkdir("parent1/child");
at.mkdir("parent2/child1");
at.mkdir("parent2/child1/child2");
at.mkdir("parent2/child1/child2/child3");
at.mkdir_all("parent1/child");
at.mkdir_all("parent2/child1/child2/child3");
// case-1: copy parent1 -> parent1: should fail
scene
@ -2724,7 +2734,7 @@ fn test_copy_dir_preserve_permissions_inaccessible_file() {
ucmd.args(&["-p", "-R", "d1", "d2"])
.fails()
.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.file_exists("d2/f"));
@ -3127,25 +3137,39 @@ fn test_cp_debug_sparse_auto() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let result = ts
.ucmd()
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
ts.ucmd()
.arg("--debug")
.arg("--sparse=auto")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
#[cfg(target_os = "macos")]
if !stdout_str
.contains("copy offload: unknown, reflink: unsupported, sparse detection: unsupported")
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
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")]
if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}");
let stdout_str = result.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]
#[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() {
let fs_type = new_ucmd!()
.args(&["--output=fstype", "."])
@ -806,7 +806,7 @@ fn test_output_file_all_filesystems() {
}
#[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() {
// Create three files.
let (at, mut ucmd) = at_and_ucmd!();
@ -825,7 +825,7 @@ fn test_output_file_specific_files() {
}
#[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() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("äöü.txt");
@ -848,7 +848,7 @@ fn test_output_field_no_more_than_once() {
}
#[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() {
new_ucmd!()
.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
#[cfg(not(windows))]
use regex::Regex;
#[cfg(not(windows))]
use std::io::Write;
#[cfg(any(target_os = "linux", target_os = "android"))]
@ -580,97 +581,58 @@ fn test_du_invalid_threshold() {
#[test]
fn test_du_apparent_size() {
let ts = TestScenario::new(util_name!());
let result = ts.ucmd().arg("--apparent-size").succeeds();
let (at, mut ucmd) = at_and_ucmd!();
#[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"]));
assert_eq!(result.stdout_str(), result_reference.stdout_str());
result.stdout_contains_line("1\ta/b/file2");
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")))]
_du_apparent_size(result.stdout_str());
}
#[cfg(target_os = "windows")]
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_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.
"
);
#[cfg(target_os = "windows")]
{
result.stdout_contains_line("1\ta\\b\\file2");
result.stdout_contains_line("1\ta\\b\\file1");
result.stdout_contains_line("1\ta\\b");
result.stdout_contains_line("1\ta");
}
}
#[test]
fn test_du_bytes() {
let ts = TestScenario::new(util_name!());
let result = ts.ucmd().arg("--bytes").succeeds();
let (at, mut ucmd) = at_and_ucmd!();
#[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"]));
assert_eq!(result.stdout_str(), result_reference.stdout_str());
result.stdout_contains_line("6\ta/b/file2");
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")]
result.stdout_contains("5145\t.\\subdir\n");
#[cfg(target_vendor = "apple")]
result.stdout_contains("5625\t./subdir\n");
#[cfg(target_os = "freebsd")]
result.stdout_contains("7193\t./subdir\n");
#[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");
{
result.stdout_contains_line("6\ta\\b\\file2");
result.stdout_contains_line("3\ta\\b\\file1");
result.stdout_contains_line("9\ta\\b");
result.stdout_contains_line("9\ta");
}
}
#[test]

View file

@ -2312,56 +2312,15 @@ fn test_ls_version_sort() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
for filename in [
"a2",
"b1",
"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",
"a2", "b1", "b20", "a1.4", "b3", "b11", "b20b", "b20a", "a100", "a1.13", "aa", "a1", "aaa",
"abab", "ab", "a01.40", "a001.001",
] {
at.touch(filename);
}
let mut expected = vec![
"a1",
"a001.001",
"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
"a1", "a001.001", "a1.4", "a1.13", "a01.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();
@ -3137,6 +3096,16 @@ fn test_ls_dangling_symlinks() {
.stderr_contains("No such file or directory")
.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
.ucmd()
.arg("-Ll")

View file

@ -21,9 +21,15 @@ fn test_valid_arg() {
new_ucmd!().arg("-s").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("--lines").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("--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();
}
#[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]
fn test_mv_same_file_not_dot_dir() {
let (at, mut ucmd) = at_and_ucmd!();

View file

@ -71,3 +71,10 @@ fn test_sections_and_styles() {
}
// 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]
fn test_invalid_argument_number_returns_status_2() {
new_ucmd!().args(&["hello"]).fails().code_is(2);
fn test_invalid_stdin_number_with_warn_returns_status_0() {
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]

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]
fn test_skip_bytes_error() {
let input = "12345";

View file

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

View file

@ -18,7 +18,7 @@ fn test_shred_remove() {
at.touch(file_b);
// 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.
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("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("do-no-exist")
.fails()
.stderr_contains("cannot stat");
.stderr_contains("error opening");
}
#[test]
@ -63,9 +63,9 @@ fn test_sync_no_permission_dir() {
ts.ccmd("chmod").arg("0").arg(dir).succeeds();
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();
result.stderr_contains("sync: cannot stat 'foo': Permission denied");
result.stderr_contains("sync: error opening 'foo': Permission denied");
}
#[cfg(not(target_os = "windows"))]

View file

@ -145,12 +145,8 @@ fn test_stdin_redirect_offset() {
}
#[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() {
// FIXME: windows: Failed because of difference in printed header. See below.
// actual : ==> - <==
// expected: ==> standard input <==
// like test_stdin_redirect_offset but with multiple files
let ts = TestScenario::new(util_name!());

View file

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

View file

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

View file

@ -659,6 +659,17 @@ impl CmdResult {
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]
pub fn stderr_contains<T: AsRef<str>>(&self, cmp: T) -> &Self {
assert!(

View file

@ -11,28 +11,31 @@ ME="${0}"
ME_dir="$(dirname -- "$(readlink -fm -- "${ME}")")"
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
path_UUTILS=${path_UUTILS:-${REPO_main_dir}}
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
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}\""
exit 1
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}
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/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