diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..ccae1f831 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: uutils diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index fb2300ba9..589126b6f 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -220,7 +220,7 @@ jobs: fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') # * convert any warnings to GHA UI annotations; ref: - S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- -W clippy::manual_string_new -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } + S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- -W clippy::default_trait_access -W clippy::manual_string_new -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi style_spellcheck: @@ -319,7 +319,7 @@ jobs: shell: bash run: | RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items - - uses: DavidAnson/markdownlint-cli2-action@v10 + - uses: DavidAnson/markdownlint-cli2-action@v11 with: command: fix globs: | @@ -452,8 +452,20 @@ jobs: test -f /tmp/usr/local/share/man/man1/whoami.1 # Check that the completion is present test -f /tmp/usr/local/share/zsh/site-functions/_install + test -f /tmp/usr/local/share/bash-completion/completions/head + test -f /tmp/usr/local/share/fish/vendor_completions.d/cat.fish env: RUST_BACKTRACE: "1" + - name: "`make uninstall`" + shell: bash + run: | + DESTDIR=/tmp/ make uninstall + # Check that the manpage is not present + ! test -f /tmp/usr/local/share/man/man1/whoami.1 + # Check that the completion is not present + ! test -f /tmp/usr/local/share/zsh/site-functions/_install + ! test -f /tmp/usr/local/share/bash-completion/completions/head + ! test -f /tmp/usr/local/share/fish/vendor_completions.d/cat.fish build_rust_stable: name: Build/stable @@ -1048,6 +1060,15 @@ jobs: name: toybox-result.json path: ${{ steps.vars.outputs.TEST_SUMMARY_FILE }} + toml_format: + runs-on: ubuntu-latest + steps: + - name: Clone repository + uses: actions/checkout@v3 + + - name: Check + run: npx --yes @taplo/cli fmt --check + coverage: name: Code Coverage runs-on: ${{ matrix.job.os }} diff --git a/.github/workflows/CheckScripts.yml b/.github/workflows/CheckScripts.yml new file mode 100644 index 000000000..e1b3b24d2 --- /dev/null +++ b/.github/workflows/CheckScripts.yml @@ -0,0 +1,74 @@ +name: CheckScripts + +# spell-checker:ignore ludeeus mfinelli + +env: + SCRIPT_DIR: 'util' + +on: + push: + branches: + - main + paths: + - 'util/**/*.sh' + pull_request: + branches: + - main + paths: + - 'util/**/*.sh' + +# End the current execution if there is a new changeset in the PR. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + shell_check: + name: ShellScript/Check + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v3 + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + env: + SHELLCHECK_OPTS: -s bash + with: + severity: warning + scandir: ${{ env.SCRIPT_DIR }} + format: tty + + shell_fmt: + name: ShellScript/Format + # no need to run in pr events + # shfmt will be performed on main branch when the PR is merged + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + needs: [ shell_check ] + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v3 + - name: Setup shfmt + uses: mfinelli/setup-shfmt@v2 + - name: Run shfmt + shell: bash + run: | + # show differs first for every files that need to be formatted + # fmt options: bash syntax, 4 spaces indent, indent for switch-case + echo "## show the differences between formatted and original scripts..." + find ${{ env.SCRIPT_DIR }} -name "*.sh" -print0 | xargs -0 shfmt -ln=bash -i 4 -ci -d || true + # perform a shell format + echo "## perform a shell format..." + # ignore the error code because `-d` will always return false when the file has difference + find ${{ env.SCRIPT_DIR }} -name "*.sh" -print0 | xargs -0 shfmt -ln=bash -i 4 -ci -w + - name: Commit any changes + uses: EndBug/add-and-commit@v9 + with: + default_author: github_actions + message: "style: auto format by CI (shfmt)" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml index e1729b173..97b0be34a 100644 --- a/.github/workflows/FixPR.yml +++ b/.github/workflows/FixPR.yml @@ -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: diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 372bf0717..eb3e22a80 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -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 }} diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 9507b3a56..095ec3230 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -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 diff --git a/.gitignore b/.gitignore index 77e8f717e..ed4e54ec5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ target/ /busybox/ /.vscode/ /.vs/ +/public/ *~ .*.swp .*.swo diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json index edac1c21e..6ceb038c2 100644 --- a/.vscode/cSpell.json +++ b/.vscode/cSpell.json @@ -19,6 +19,7 @@ // files to ignore (globs supported) "ignorePaths": [ "Cargo.lock", + "oranda.json", "target/**", "tests/**/fixtures/**", "src/uu/dd/test-resources/**", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7dc2565c1..bd87e2d05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -356,6 +356,7 @@ The Coreutils have different implementations, with different levels of completio * [V lang](https://github.com/vlang/coreutils) * [SerenityOS](https://github.com/SerenityOS/serenity/tree/master/Userland/Utilities) * [Initial Unix](https://github.com/dspinellis/unix-history-repo) +* [Perl Power Tools](https://metacpan.org/pod/PerlPowerTools) However, when reimplementing the tools/options in Rust, don't read their source codes when they are using reciprocal licenses (ex: GNU GPL, GNU LGPL, etc). diff --git a/Cargo.lock b/Cargo.lock index d7a1f5b9b..161949e8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,53 +2,30 @@ # 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 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 +110,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", @@ -157,7 +135,7 @@ version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", @@ -169,7 +147,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 1.0.109", "which", ] @@ -179,6 +157,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + [[package]] name = "blake2b_simd" version = "1.0.1" @@ -192,9 +176,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" +checksum = "729b71f35bd3fa1a4c86b85d32c8b9069ea7fe14f7a53cfabb65f62d4265b888" dependencies = [ "arrayref", "arrayvec", @@ -215,12 +199,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 +228,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" @@ -289,22 +272,21 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.0" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.3.0" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", "once_cell", "strsim", @@ -336,16 +318,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 +343,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" @@ -394,7 +388,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "coreutils" -version = "0.0.18" +version = "0.0.20" dependencies = [ "chrono", "clap", @@ -403,7 +397,6 @@ dependencies = [ "conv", "filetime", "glob", - "help_parser", "hex-literal", "is-terminal", "libc", @@ -424,7 +417,6 @@ dependencies = [ "textwrap", "time", "unindent", - "users", "uu_arch", "uu_base32", "uu_base64", @@ -529,6 +521,7 @@ dependencies = [ "uu_whoami", "uu_yes", "uucore", + "uuhelp_parser", "walkdir", "zip", ] @@ -545,52 +538,52 @@ dependencies = [ [[package]] name = "cpp" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec5e86d4f6547f0218ad923d9508244a71ef83b763196e6698b4f70f3595185" +checksum = "43b6a1d47f376a62bbea281408fe331879b9822c1edb8f67320c7cb8d96a02eb" dependencies = [ "cpp_macros", ] [[package]] name = "cpp_build" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f4d303b8ec35fb3afd7e963e2c898117f1e49930becb703e4a7ac528ad2dd0" +checksum = "5b8f839b67a3deba30b58b281b5fca61477a90830beb084c58bdb9bebd227a1a" dependencies = [ "cc", "cpp_common", "lazy_static", "proc-macro2", "regex", - "syn", + "syn 2.0.23", "unicode-xid", ] [[package]] name = "cpp_common" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76071bb9c8c4dd2b5eb209907deab7b031323cf1be3dfdc6ec5d37f4f187d8a1" +checksum = "42d2b968b7b2ac412836e8ce1dfc3dfd5648ae7e8a42fcbbf5bc1d33bb795b0d" dependencies = [ "lazy_static", "proc-macro2", - "syn", + "syn 2.0.23", ] [[package]] name = "cpp_macros" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fdaa01904c12a8989dbfa110b41ef27efc432ac9934f691b9732f01cb64dc01" +checksum = "5d36d9acb58020380e756d56a8dfc84d0f6ace423bbd4fedf83992b257b7f147" dependencies = [ - "aho-corasick 0.7.19", + "aho-corasick 0.7.20", "byteorder", "cpp_common", "lazy_static", "proc-macro2", "quote", - "syn", + "syn 2.0.23", ] [[package]] @@ -656,11 +649,11 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.26.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags", + "bitflags 2.3.3", "crossterm_winapi", "libc", "mio", @@ -672,9 +665,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] @@ -695,16 +688,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "ctrlc" version = "3.4.0" @@ -721,50 +704,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" @@ -788,7 +727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" dependencies = [ "data-encoding", - "syn", + "syn 1.0.109", ] [[package]] @@ -810,9 +749,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" @@ -856,13 +798,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -881,7 +823,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfeb22a59deb24c3262c43ffcafd1eb807180f371f9fcc99098d181b5d639be" dependencies = [ - "bitflags", + "bitflags 1.3.2", "log", "scopeguard", "uuid", @@ -889,12 +831,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "file_diff" @@ -904,14 +843,14 @@ checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5" [[package]] name = "filetime" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", - "windows-sys 0.45.0", + "redox_syscall", + "windows-sys 0.48.0", ] [[package]] @@ -957,15 +896,24 @@ dependencies = [ [[package]] name = "fundu" -version = "0.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a37cfff04a32112c22c5497b20b0b09100fca406e76afd47b2ba5ab33d7a851" +checksum = "6c04cb831a8dccadfe3774b07cba4574a1ec24974d761510e65d8a543c2d7cb4" +dependencies = [ + "fundu-core", +] + +[[package]] +name = "fundu-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a889e633afd839fb5b04fe53adfd588cefe518e71ec8d3c929698c6daf2acd" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -978,9 +926,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -988,15 +936,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -1005,32 +953,32 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.23", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-timer" @@ -1040,9 +988,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -1074,9 +1022,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -1100,31 +1048,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] - -[[package]] -name = "help_parser" -version = "0.0.18" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -1149,16 +1081,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" @@ -1175,12 +1097,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]] @@ -1201,7 +1122,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ - "bitflags", + "bitflags 1.3.2", "inotify-sys", "libc", ] @@ -1215,42 +1136,33 @@ dependencies = [ "libc", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "io-lifetimes" -version = "1.0.5" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix 0.37.7", + "hermit-abi", + "rustix 0.38.8", "windows-sys 0.48.0", ] [[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", ] @@ -1295,7 +1207,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", ] @@ -1313,9 +1225,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libloading" @@ -1328,13 +1240,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" @@ -1344,9 +1253,15 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "linux-raw-sys" -version = "0.3.0" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lock_api" @@ -1369,9 +1284,9 @@ dependencies = [ [[package]] name = "lscolors" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a9df1d1fb6d9e92fa043e9eb9a3ecf6892c7b542bae5137cd1e419e40aa8bf" +checksum = "bf7015a04103ad78abb77e4b79ed151e767922d1cfde5f62640471c629a2320d" dependencies = [ "nu-ansi-term", ] @@ -1399,9 +1314,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9ff02d2efdc645fca1ee55f45545b996e7da776b5b60c4e170334457551693" +checksum = "180d4b35be83d33392d1d1bfbd2ae1eca7ff5de1a94d3fc87faaa99a069e7cbd" dependencies = [ "libc", ] @@ -1448,7 +1363,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", @@ -1466,11 +1381,11 @@ dependencies = [ [[package]] name = "notify" -version = "6.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d9ba6c734de18ca27c8cef5cd7058aa4ac9f63596131e4c7e41e579319032a2" +checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossbeam-channel", "filetime", "fsevent-sys", @@ -1484,11 +1399,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.47.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df031e117bca634c262e9bd3173776844b6c17a90b3741c9163663b4385af76" +checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" dependencies = [ - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1514,20 +1429,20 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] [[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", ] @@ -1548,9 +1463,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.17.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "onig" @@ -1558,7 +1473,7 @@ version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "once_cell", "onig_sys", @@ -1576,9 +1491,9 @@ 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", @@ -1593,38 +1508,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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -1637,15 +1520,25 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.0", +] + +[[package]] +name = "parse_datetime" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecceaede7767a9a98058687a321bc91742eff7670167a34104afb30fc8757df" +dependencies = [ + "chrono", + "regex", ] [[package]] @@ -1656,18 +1549,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", @@ -1685,9 +1578,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", ] @@ -1712,9 +1605,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "platform-info" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "827dc4f7a81331d48c8abf11b5ac18673b390d33e9632327e286d940289aefab" +checksum = "d6259c4860e53bf665016f1b2f46a8859cadfa717581dc9d597ae4069de6300f" dependencies = [ "libc", "winapi", @@ -1734,45 +1627,25 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pretty_assertions" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ - "ctor", "diff", - "output_vt100", "yansi", ] [[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", ] @@ -1783,11 +1656,11 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "byteorder", "hex", "lazy_static", - "rustix 0.36.14", + "rustix 0.36.15", ] [[package]] @@ -1809,9 +1682,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", ] @@ -1877,22 +1750,13 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1903,32 +1767,44 @@ checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f" [[package]] name = "regex" -version = "1.8.3" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ - "aho-corasick 1.0.1", + "aho-corasick 1.0.2", "memchr", + "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.1.10" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick 1.0.2", + "memchr", + "regex-syntax", +] [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "relative-path" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf2521270932c3c7bed1a59151222bd7643c79310f2916f01925e1e16255698" [[package]] name = "rlimit" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a29d87a652dc4d43c586328706bb5cdff211f3f39a530f240b53f7221dab8e" +checksum = "3560f70f30a0f16d11d01ed078a07740fe6b489667abc7c7b029155d9f21c3d8" dependencies = [ "libc", ] @@ -1941,9 +1817,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", @@ -1953,23 +1829,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", @@ -1992,11 +1871,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.14" +version = "0.36.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e4d67015953998ad0eb82887a0eb0129e18a7e2f3b7b0f6c422fddcd503d62" +checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -2006,16 +1885,29 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.7" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys 0.3.0", - "windows-sys 0.45.0", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys 0.4.5", + "windows-sys 0.48.0", ] [[package]] @@ -2034,10 +1926,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" @@ -2045,7 +1937,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00576725d21b588213fbd4af84cd7e4cc4304e8e9bd6c0f5a1498a3e2ca6a51" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "once_cell", "reference-counted-singleton", @@ -2090,9 +1982,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -2117,9 +2009,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", @@ -2171,9 +2063,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" @@ -2211,9 +2103,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", @@ -2222,15 +2125,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", - "rustix 0.37.7", - "windows-sys 0.45.0", + "redox_syscall", + "rustix 0.38.8", + "windows-sys 0.48.0", ] [[package]] @@ -2242,22 +2145,13 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ - "rustix 0.37.7", + "rustix 0.37.23", "windows-sys 0.48.0", ] @@ -2290,7 +2184,7 @@ checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2322,6 +2216,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" @@ -2336,13 +2239,9 @@ checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-linebreak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" -dependencies = [ - "hashbrown", - "regex", -] +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-segmentation" @@ -2368,16 +2267,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa30f5ea51ff7edfc797c6d3f9ec8cbd8cfedef5371766b7181d33977f4814f" -[[package]] -name = "users" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" -dependencies = [ - "libc", - "log", -] - [[package]] name = "utf8parse" version = "0.2.1" @@ -2386,7 +2275,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uu_arch" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "platform-info", @@ -2395,7 +2284,7 @@ dependencies = [ [[package]] name = "uu_base32" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2403,7 +2292,7 @@ dependencies = [ [[package]] name = "uu_base64" -version = "0.0.18" +version = "0.0.20" dependencies = [ "uu_base32", "uucore", @@ -2411,7 +2300,7 @@ dependencies = [ [[package]] name = "uu_basename" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2419,7 +2308,7 @@ dependencies = [ [[package]] name = "uu_basenc" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uu_base32", @@ -2428,7 +2317,7 @@ dependencies = [ [[package]] name = "uu_cat" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "is-terminal", @@ -2439,7 +2328,7 @@ dependencies = [ [[package]] name = "uu_chcon" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "fts-sys", @@ -2451,7 +2340,7 @@ dependencies = [ [[package]] name = "uu_chgrp" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2459,7 +2348,7 @@ dependencies = [ [[package]] name = "uu_chmod" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -2468,7 +2357,7 @@ dependencies = [ [[package]] name = "uu_chown" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2476,7 +2365,7 @@ dependencies = [ [[package]] name = "uu_chroot" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2484,7 +2373,7 @@ dependencies = [ [[package]] name = "uu_cksum" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "hex", @@ -2493,7 +2382,7 @@ dependencies = [ [[package]] name = "uu_comm" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2501,7 +2390,7 @@ dependencies = [ [[package]] name = "uu_cp" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "exacl", @@ -2517,7 +2406,7 @@ dependencies = [ [[package]] name = "uu_csplit" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "regex", @@ -2527,7 +2416,7 @@ dependencies = [ [[package]] name = "uu_cut" -version = "0.0.18" +version = "0.0.20" dependencies = [ "bstr", "clap", @@ -2538,20 +2427,19 @@ dependencies = [ [[package]] name = "uu_date" -version = "0.0.18" +version = "0.0.20" dependencies = [ "chrono", "clap", - "humantime_to_duration", "libc", - "time", + "parse_datetime", "uucore", "windows-sys 0.48.0", ] [[package]] name = "uu_dd" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "gcd", @@ -2563,16 +2451,17 @@ dependencies = [ [[package]] name = "uu_df" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", + "tempfile", "unicode-width", "uucore", ] [[package]] name = "uu_dir" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uu_ls", @@ -2581,7 +2470,7 @@ dependencies = [ [[package]] name = "uu_dircolors" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2589,7 +2478,7 @@ dependencies = [ [[package]] name = "uu_dirname" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2597,7 +2486,7 @@ dependencies = [ [[package]] name = "uu_du" -version = "0.0.18" +version = "0.0.20" dependencies = [ "chrono", "clap", @@ -2608,7 +2497,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2616,7 +2505,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "nix", @@ -2626,7 +2515,7 @@ dependencies = [ [[package]] name = "uu_expand" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "unicode-width", @@ -2635,7 +2524,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "num-bigint", @@ -2646,7 +2535,7 @@ dependencies = [ [[package]] name = "uu_factor" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "coz", @@ -2659,7 +2548,7 @@ dependencies = [ [[package]] name = "uu_false" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2667,7 +2556,7 @@ dependencies = [ [[package]] name = "uu_fmt" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "unicode-width", @@ -2676,7 +2565,7 @@ dependencies = [ [[package]] name = "uu_fold" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2684,7 +2573,7 @@ dependencies = [ [[package]] name = "uu_groups" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2692,7 +2581,7 @@ dependencies = [ [[package]] name = "uu_hashsum" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "hex", @@ -2703,7 +2592,7 @@ dependencies = [ [[package]] name = "uu_head" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "memchr", @@ -2712,7 +2601,7 @@ dependencies = [ [[package]] name = "uu_hostid" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -2721,7 +2610,7 @@ dependencies = [ [[package]] name = "uu_hostname" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "hostname", @@ -2731,7 +2620,7 @@ dependencies = [ [[package]] name = "uu_id" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "selinux", @@ -2740,19 +2629,18 @@ dependencies = [ [[package]] name = "uu_install" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "file_diff", "filetime", "libc", - "time", "uucore", ] [[package]] name = "uu_join" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "memchr", @@ -2761,7 +2649,7 @@ dependencies = [ [[package]] name = "uu_kill" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "nix", @@ -2770,7 +2658,7 @@ dependencies = [ [[package]] name = "uu_link" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2778,7 +2666,7 @@ dependencies = [ [[package]] name = "uu_ln" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2786,7 +2674,7 @@ dependencies = [ [[package]] name = "uu_logname" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -2795,7 +2683,7 @@ dependencies = [ [[package]] name = "uu_ls" -version = "0.0.18" +version = "0.0.20" dependencies = [ "chrono", "clap", @@ -2813,7 +2701,7 @@ dependencies = [ [[package]] name = "uu_mkdir" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2821,7 +2709,7 @@ dependencies = [ [[package]] name = "uu_mkfifo" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -2830,7 +2718,7 @@ dependencies = [ [[package]] name = "uu_mknod" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -2839,7 +2727,7 @@ dependencies = [ [[package]] name = "uu_mktemp" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "rand", @@ -2849,7 +2737,7 @@ dependencies = [ [[package]] name = "uu_more" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "crossterm", @@ -2862,7 +2750,7 @@ dependencies = [ [[package]] name = "uu_mv" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "fs_extra", @@ -2872,7 +2760,7 @@ dependencies = [ [[package]] name = "uu_nice" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -2882,7 +2770,7 @@ dependencies = [ [[package]] name = "uu_nl" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "regex", @@ -2891,7 +2779,7 @@ dependencies = [ [[package]] name = "uu_nohup" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "is-terminal", @@ -2901,7 +2789,7 @@ dependencies = [ [[package]] name = "uu_nproc" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -2910,7 +2798,7 @@ dependencies = [ [[package]] name = "uu_numfmt" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2918,7 +2806,7 @@ dependencies = [ [[package]] name = "uu_od" -version = "0.0.18" +version = "0.0.20" dependencies = [ "byteorder", "clap", @@ -2928,7 +2816,7 @@ dependencies = [ [[package]] name = "uu_paste" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2936,7 +2824,7 @@ dependencies = [ [[package]] name = "uu_pathchk" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -2945,7 +2833,7 @@ dependencies = [ [[package]] name = "uu_pinky" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2953,19 +2841,19 @@ dependencies = [ [[package]] name = "uu_pr" -version = "0.0.18" +version = "0.0.20" dependencies = [ + "chrono", "clap", "itertools", "quick-error", "regex", - "time", "uucore", ] [[package]] name = "uu_printenv" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2973,7 +2861,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2981,7 +2869,7 @@ dependencies = [ [[package]] name = "uu_ptx" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "regex", @@ -2990,7 +2878,7 @@ dependencies = [ [[package]] name = "uu_pwd" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -2998,7 +2886,7 @@ dependencies = [ [[package]] name = "uu_readlink" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3006,7 +2894,7 @@ dependencies = [ [[package]] name = "uu_realpath" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3014,7 +2902,7 @@ dependencies = [ [[package]] name = "uu_relpath" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3022,7 +2910,7 @@ dependencies = [ [[package]] name = "uu_rm" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -3033,7 +2921,7 @@ dependencies = [ [[package]] name = "uu_rmdir" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -3042,7 +2930,7 @@ dependencies = [ [[package]] name = "uu_runcon" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -3053,7 +2941,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.18" +version = "0.0.20" dependencies = [ "bigdecimal", "clap", @@ -3064,7 +2952,7 @@ dependencies = [ [[package]] name = "uu_shred" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -3074,7 +2962,7 @@ dependencies = [ [[package]] name = "uu_shuf" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "memchr", @@ -3085,7 +2973,7 @@ dependencies = [ [[package]] name = "uu_sleep" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "fundu", @@ -3094,7 +2982,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.18" +version = "0.0.20" dependencies = [ "binary-heap-plus", "clap", @@ -3103,9 +2991,9 @@ dependencies = [ "fnv", "itertools", "memchr", - "ouroboros", "rand", "rayon", + "self_cell", "tempfile", "unicode-width", "uucore", @@ -3113,7 +3001,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "memchr", @@ -3122,7 +3010,7 @@ dependencies = [ [[package]] name = "uu_stat" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3130,7 +3018,7 @@ dependencies = [ [[package]] name = "uu_stdbuf" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "tempfile", @@ -3140,7 +3028,7 @@ dependencies = [ [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.18" +version = "0.0.20" dependencies = [ "cpp", "cpp_build", @@ -3150,7 +3038,7 @@ dependencies = [ [[package]] name = "uu_stty" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "nix", @@ -3159,7 +3047,7 @@ dependencies = [ [[package]] name = "uu_sum" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3167,7 +3055,7 @@ dependencies = [ [[package]] name = "uu_sync" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -3178,7 +3066,7 @@ dependencies = [ [[package]] name = "uu_tac" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "memchr", @@ -3189,7 +3077,7 @@ dependencies = [ [[package]] name = "uu_tail" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "fundu", @@ -3206,7 +3094,7 @@ dependencies = [ [[package]] name = "uu_tee" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -3215,17 +3103,17 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "uucore", ] [[package]] name = "uu_timeout" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -3235,19 +3123,19 @@ dependencies = [ [[package]] name = "uu_touch" -version = "0.0.18" +version = "0.0.20" dependencies = [ + "chrono", "clap", "filetime", - "humantime_to_duration", - "time", + "parse_datetime", "uucore", "windows-sys 0.48.0", ] [[package]] name = "uu_tr" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "nom", @@ -3256,7 +3144,7 @@ dependencies = [ [[package]] name = "uu_true" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3264,7 +3152,7 @@ dependencies = [ [[package]] name = "uu_truncate" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3272,7 +3160,7 @@ dependencies = [ [[package]] name = "uu_tsort" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3280,7 +3168,7 @@ dependencies = [ [[package]] name = "uu_tty" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "is-terminal", @@ -3290,7 +3178,7 @@ dependencies = [ [[package]] name = "uu_uname" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "platform-info", @@ -3299,7 +3187,7 @@ dependencies = [ [[package]] name = "uu_unexpand" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "unicode-width", @@ -3308,7 +3196,7 @@ dependencies = [ [[package]] name = "uu_uniq" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3316,7 +3204,7 @@ dependencies = [ [[package]] name = "uu_unlink" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3324,7 +3212,7 @@ dependencies = [ [[package]] name = "uu_uptime" -version = "0.0.18" +version = "0.0.20" dependencies = [ "chrono", "clap", @@ -3333,7 +3221,7 @@ dependencies = [ [[package]] name = "uu_users" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3341,7 +3229,7 @@ dependencies = [ [[package]] name = "uu_vdir" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uu_ls", @@ -3350,7 +3238,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.18" +version = "0.0.20" dependencies = [ "bytecount", "clap", @@ -3363,7 +3251,7 @@ dependencies = [ [[package]] name = "uu_who" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "uucore", @@ -3371,7 +3259,7 @@ dependencies = [ [[package]] name = "uu_whoami" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "libc", @@ -3381,7 +3269,7 @@ dependencies = [ [[package]] name = "uu_yes" -version = "0.0.18" +version = "0.0.20" dependencies = [ "clap", "itertools", @@ -3391,7 +3279,7 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.18" +version = "0.0.20" dependencies = [ "blake2b_simd", "blake3", @@ -3427,13 +3315,17 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.18" +version = "0.0.20" dependencies = [ - "help_parser", "proc-macro2", "quote", + "uuhelp_parser", ] +[[package]] +name = "uuhelp_parser" +version = "0.0.20" + [[package]] name = "uuid" version = "1.2.2" @@ -3484,7 +3376,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -3506,7 +3398,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3702,9 +3594,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "xattr" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea263437ca03c1522846a4ddafbca2542d0ad5ed9b784909d4b27b76f62bc34a" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index ebd9a4bf4..365b160dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ # coreutils (uutils) # * see the repository LICENSE, README, and CONTRIBUTING files for more information -# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu mangen humantime +# spell-checker:ignore (libs) bigdecimal datetime fundu gethostid kqueue libselinux mangen memmap procfs uuhelp [package] name = "coreutils" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" @@ -22,16 +22,16 @@ edition = "2021" build = "build.rs" [features] -default = [ "feat_common_core" ] +default = ["feat_common_core"] ## OS feature shortcodes -macos = [ "feat_os_macos" ] -unix = [ "feat_os_unix" ] -windows = [ "feat_os_windows" ] +macos = ["feat_os_macos"] +unix = ["feat_os_unix"] +windows = ["feat_os_windows"] ## project-specific feature shortcodes nightly = [] test_unimplemented = [] # * only build `uudoc` when `--feature uudoc` is activated -uudoc = ["zip", "dep:help_parser"] +uudoc = ["zip", "dep:uuhelp_parser"] ## features # "feat_acl" == enable support for ACLs (access control lists; by using`--features feat_acl`) # NOTE: @@ -42,239 +42,237 @@ feat_acl = ["cp/feat_acl"] # NOTE: # * The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time. # * Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time. -feat_selinux = ["cp/selinux", "id/selinux", "ls/selinux", "selinux", "feat_require_selinux"] +feat_selinux = [ + "cp/selinux", + "id/selinux", + "ls/selinux", + "selinux", + "feat_require_selinux", +] ## ## feature sets ## (common/core and Tier1) feature sets # "feat_common_core" == baseline core set of utilities which can be built/run on most targets feat_common_core = [ - "base32", - "base64", - "basename", - "basenc", - "cat", - "cksum", - "comm", - "cp", - "csplit", - "cut", - "date", - "df", - "dir", - "dircolors", - "dirname", - "dd", - "du", - "echo", - "env", - "expand", - "expr", - "factor", - "false", - "fmt", - "fold", - "hashsum", - "head", - "join", - "link", - "ln", - "ls", - "mkdir", - "mktemp", - "more", - "mv", - "nl", - "numfmt", - "od", - "paste", - "pr", - "printenv", - "printf", - "ptx", - "pwd", - "readlink", - "realpath", - "relpath", - "rm", - "rmdir", - "seq", - "shred", - "shuf", - "sleep", - "sort", - "split", - "sum", - "tac", - "tail", - "tee", - "test", - "tr", - "true", - "truncate", - "tsort", - "touch", - "unexpand", - "uniq", - "unlink", - "vdir", - "wc", - "yes", + "base32", + "base64", + "basename", + "basenc", + "cat", + "cksum", + "comm", + "cp", + "csplit", + "cut", + "date", + "df", + "dir", + "dircolors", + "dirname", + "dd", + "du", + "echo", + "env", + "expand", + "expr", + "factor", + "false", + "fmt", + "fold", + "hashsum", + "head", + "join", + "link", + "ln", + "ls", + "mkdir", + "mktemp", + "more", + "mv", + "nl", + "numfmt", + "od", + "paste", + "pr", + "printenv", + "printf", + "ptx", + "pwd", + "readlink", + "realpath", + "relpath", + "rm", + "rmdir", + "seq", + "shred", + "shuf", + "sleep", + "sort", + "split", + "sum", + "tac", + "tail", + "tee", + "test", + "tr", + "true", + "truncate", + "tsort", + "touch", + "unexpand", + "uniq", + "unlink", + "vdir", + "wc", + "yes", ] # "feat_Tier1" == expanded set of utilities which can be built/run on the usual rust "Tier 1" target platforms (ref: ) feat_Tier1 = [ - "feat_common_core", - # - "arch", - "hostname", - "nproc", - "sync", - "touch", - "uname", - "whoami", + "feat_common_core", + # + "arch", + "hostname", + "nproc", + "sync", + "touch", + "uname", + "whoami", ] ## (primary platforms) feature sets # "feat_os_macos" == set of utilities which can be built/run on the MacOS platform feat_os_macos = [ - "feat_os_unix", ## == a modern/usual *nix platform - # - "feat_require_unix_hostid", + "feat_os_unix", ## == a modern/usual *nix platform + # + "feat_require_unix_hostid", ] # "feat_os_unix" == set of utilities which can be built/run on modern/usual *nix platforms feat_os_unix = [ - "feat_Tier1", - # - "feat_require_crate_cpp", - "feat_require_unix", - "feat_require_unix_utmpx", + "feat_Tier1", + # + "feat_require_crate_cpp", + "feat_require_unix", + "feat_require_unix_utmpx", ] # "feat_os_windows" == set of utilities which can be built/run on modern/usual windows platforms feat_os_windows = [ - "feat_Tier1", ## == "feat_os_windows_legacy" + "hostname" + "feat_Tier1", ## == "feat_os_windows_legacy" + "hostname" ] ## (secondary platforms) feature sets # "feat_os_unix_gnueabihf" == set of utilities which can be built/run on the "arm-unknown-linux-gnueabihf" target (ARMv6 Linux [hardfloat]) feat_os_unix_gnueabihf = [ - "feat_Tier1", - # - "feat_require_unix", - "feat_require_unix_hostid", - "feat_require_unix_utmpx", + "feat_Tier1", + # + "feat_require_unix", + "feat_require_unix_hostid", + "feat_require_unix_utmpx", ] # "feat_os_unix_musl" == set of utilities which can be built/run on targets binding to the "musl" library (ref: ) feat_os_unix_musl = [ - "feat_Tier1", - # - "feat_require_unix", - "feat_require_unix_hostid", + "feat_Tier1", + # + "feat_require_unix", + "feat_require_unix_hostid", ] feat_os_unix_android = [ - "feat_Tier1", - # - "feat_require_unix", + "feat_Tier1", + # + "feat_require_unix", ] ## feature sets with requirements (restricting cross-platform availability) # # ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities # # "feat_require_crate_cpp" == set of utilities requiring the `cpp` crate (which fail to compile on several platforms; as of 2020-04-23) -feat_require_crate_cpp = [ - "stdbuf", -] +feat_require_crate_cpp = ["stdbuf"] # "feat_require_unix" == set of utilities requiring support which is only available on unix platforms (as of 2020-04-23) feat_require_unix = [ - "chgrp", - "chmod", - "chown", - "chroot", - "groups", - "id", - "install", - "kill", - "logname", - "mkfifo", - "mknod", - "nice", - "nohup", - "pathchk", - "stat", - "stty", - "timeout", - "tty", + "chgrp", + "chmod", + "chown", + "chroot", + "groups", + "id", + "install", + "kill", + "logname", + "mkfifo", + "mknod", + "nice", + "nohup", + "pathchk", + "stat", + "stty", + "timeout", + "tty", ] # "feat_require_unix_utmpx" == set of utilities requiring unix utmp/utmpx support # * ref: -feat_require_unix_utmpx = [ - "pinky", - "uptime", - "users", - "who", -] +feat_require_unix_utmpx = ["pinky", "uptime", "users", "who"] # "feat_require_unix_hostid" == set of utilities requiring gethostid in libc (only some unixes provide) -feat_require_unix_hostid = [ - "hostid", -] +feat_require_unix_hostid = ["hostid"] # "feat_require_selinux" == set of utilities depending on SELinux. -feat_require_selinux = [ - "chcon", - "runcon", -] +feat_require_selinux = ["chcon", "runcon"] ## (alternate/newer/smaller platforms) feature sets # "feat_os_unix_fuchsia" == set of utilities which can be built/run on the "Fuchsia" OS (refs: ; ) feat_os_unix_fuchsia = [ - "feat_common_core", - # - "feat_require_crate_cpp", - # - "chgrp", - "chmod", - "chown", - "du", - "groups", - "hostid", - "install", - "logname", - "mkfifo", - "mknod", - "nice", - "pathchk", - "tty", - "uname", - "unlink", + "feat_common_core", + # + "feat_require_crate_cpp", + # + "chgrp", + "chmod", + "chown", + "du", + "groups", + "hostid", + "install", + "logname", + "mkfifo", + "mknod", + "nice", + "pathchk", + "tty", + "uname", + "unlink", ] # "feat_os_unix_redox" == set of utilities which can be built/run on "Redox OS" (refs: ; ) feat_os_unix_redox = [ - "feat_common_core", - # - "chmod", - "uname", + "feat_common_core", + # + "chmod", + "uname", ] # "feat_os_windows_legacy" == slightly restricted set of utilities which can be built/run on early windows platforms (eg, "WinXP") feat_os_windows_legacy = [ - "feat_common_core", - # - "arch", - "nproc", - "sync", - "touch", - "whoami", + "feat_common_core", + # + "arch", + "nproc", + "sync", + "touch", + "whoami", ] ## # * bypass/override ~ translate 'test' feature name to avoid dependency collision with rust core 'test' crate (o/w surfaces as compiler errors during testing) -test = [ "uu_test" ] +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=["std", "alloc", "clock"]} +chrono = { version = "^0.4.26", default-features = false, features = [ + "std", + "alloc", + "clock", +] } clap = { version = "4.3", features = ["wrap_help", "cargo"] } clap_complete = "4.3" clap_mangen = "0.2" compare = "0.1.0" coz = { version = "0.1.3" } -crossterm = ">=0.26.1" +crossterm = ">=0.27.0" ctrlc = { version = "3.4", features = ["termination"] } exacl = "0.10.0" file_diff = "1.0.0" @@ -282,189 +280,192 @@ filetime = "0.2" fnv = "1.0.7" fs_extra = "1.3.0" fts-sys = "0.2" -fundu = "0.5.1" +fundu = "2.0.0" gcd = "2.3" glob = "0.3.1" half = "2.2" -humantime_to_duration = "0.2.1" indicatif = "0.17" is-terminal = "0.4.7" -itertools = "0.10.5" -libc = "0.2.144" -lscolors = { version = "0.14.0", default-features=false, features = ["nu-ansi-term"] } +itertools = "0.11.0" +libc = "0.2.147" +lscolors = { version = "0.15.0", default-features = false, features = [ + "nu-ansi-term", +] } memchr = "2" -nix = { version="0.26", default-features=false } +memmap2 = "0.7" +nix = { version = "0.26", default-features = false } nom = "7.1.3" -notify = { version = "=6.0.0", features=["macos_kqueue"]} +notify = { version = "=6.0.1", features = ["macos_kqueue"] } num-bigint = "0.4.3" -num-traits = "0.2.15" +num-traits = "0.2.16" number_prefix = "0.4" -once_cell = "1.17.2" +once_cell = "1.18.0" onig = { version = "~6.4", default-features = false } -ouroboros = "0.15.6" -phf = "0.11.1" -phf_codegen = "0.11.1" -platform-info = "2.0.1" +parse_datetime = "0.4.0" +phf = "0.11.2" +phf_codegen = "0.11.2" +platform-info = "2.0.2" 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.3" -rstest = "0.17.0" -rust-ini = "0.18.0" +regex = "1.9.3" +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"] } -tempfile = "3.5.0" +signal-hook = "0.3.17" +smallvec = { version = "1.11", features = ["union"] } +tempfile = "3.6.0" term_grid = "0.1.5" terminal_size = "0.2.6" -textwrap = { version="0.16.0", features=["terminal_size"] } +textwrap = { version = "0.16.0", features = ["terminal_size"] } thiserror = "1.0" -time = { version="0.3" } +time = { version = "0.3" } unicode-segmentation = "1.10.1" unicode-width = "0.1.10" utf-8 = "0.7.6" walkdir = "2.3" winapi-util = "0.1.5" -windows-sys = { version="0.48.0", default-features=false } -xattr = "1.0.0" -zip = { version = "0.6.6", default_features=false, features=["deflate"] } +windows-sys = { version = "0.48.0", default-features = false } +xattr = "1.0.1" +zip = { version = "0.6.6", default_features = false, features = ["deflate"] } hex = "0.4.3" md-5 = "0.10.5" sha1 = "0.10.5" -sha2 = "0.10.6" +sha2 = "0.10.7" sha3 = "0.10.8" blake2b_simd = "1.0.1" -blake3 = "1.3.3" +blake3 = "1.4.0" sm3 = "0.4.2" digest = "0.10.7" -uucore = { version=">=0.0.18", package="uucore", path="src/uucore" } -uucore_procs = { version=">=0.0.18", package="uucore_procs", path="src/uucore_procs" } -uu_ls = { version=">=0.0.18", path="src/uu/ls" } -uu_base32 = { version=">=0.0.18", path="src/uu/base32"} +uucore = { version = ">=0.0.19", package = "uucore", path = "src/uucore" } +uucore_procs = { version = ">=0.0.19", package = "uucore_procs", path = "src/uucore_procs" } +uu_ls = { version = ">=0.0.18", path = "src/uu/ls" } +uu_base32 = { version = ">=0.0.18", path = "src/uu/base32" } [dependencies] -clap = { workspace=true } -once_cell = { workspace=true } -uucore = { workspace=true } -clap_complete = { workspace=true } -clap_mangen = { workspace=true } -phf = { workspace=true } -selinux = { workspace=true, optional = true } -textwrap = { workspace=true } -zip = { workspace=true, optional = true } +clap = { workspace = true } +once_cell = { workspace = true } +uucore = { workspace = true } +clap_complete = { workspace = true } +clap_mangen = { workspace = true } +phf = { workspace = true } +selinux = { workspace = true, optional = true } +textwrap = { workspace = true } +zip = { workspace = true, optional = true } -help_parser = { path="src/help_parser", optional = true } +uuhelp_parser = { optional = true, version = ">=0.0.19", path = "src/uuhelp_parser" } # * uutils -uu_test = { optional=true, version="0.0.18", package="uu_test", path="src/uu/test" } +uu_test = { optional = true, version = "0.0.20", package = "uu_test", path = "src/uu/test" } # -arch = { optional=true, version="0.0.18", package="uu_arch", path="src/uu/arch" } -base32 = { optional=true, version="0.0.18", package="uu_base32", path="src/uu/base32" } -base64 = { optional=true, version="0.0.18", package="uu_base64", path="src/uu/base64" } -basename = { optional=true, version="0.0.18", package="uu_basename", path="src/uu/basename" } -basenc = { optional=true, version="0.0.18", package="uu_basenc", path="src/uu/basenc" } -cat = { optional=true, version="0.0.18", package="uu_cat", path="src/uu/cat" } -chcon = { optional=true, version="0.0.18", package="uu_chcon", path="src/uu/chcon" } -chgrp = { optional=true, version="0.0.18", package="uu_chgrp", path="src/uu/chgrp" } -chmod = { optional=true, version="0.0.18", package="uu_chmod", path="src/uu/chmod" } -chown = { optional=true, version="0.0.18", package="uu_chown", path="src/uu/chown" } -chroot = { optional=true, version="0.0.18", package="uu_chroot", path="src/uu/chroot" } -cksum = { optional=true, version="0.0.18", package="uu_cksum", path="src/uu/cksum" } -comm = { optional=true, version="0.0.18", package="uu_comm", path="src/uu/comm" } -cp = { optional=true, version="0.0.18", package="uu_cp", path="src/uu/cp" } -csplit = { optional=true, version="0.0.18", package="uu_csplit", path="src/uu/csplit" } -cut = { optional=true, version="0.0.18", package="uu_cut", path="src/uu/cut" } -date = { optional=true, version="0.0.18", package="uu_date", path="src/uu/date" } -dd = { optional=true, version="0.0.18", package="uu_dd", path="src/uu/dd" } -df = { optional=true, version="0.0.18", package="uu_df", path="src/uu/df" } -dir = { optional=true, version="0.0.18", package="uu_dir", path="src/uu/dir" } -dircolors= { optional=true, version="0.0.18", package="uu_dircolors", path="src/uu/dircolors" } -dirname = { optional=true, version="0.0.18", package="uu_dirname", path="src/uu/dirname" } -du = { optional=true, version="0.0.18", package="uu_du", path="src/uu/du" } -echo = { optional=true, version="0.0.18", package="uu_echo", path="src/uu/echo" } -env = { optional=true, version="0.0.18", package="uu_env", path="src/uu/env" } -expand = { optional=true, version="0.0.18", package="uu_expand", path="src/uu/expand" } -expr = { optional=true, version="0.0.18", package="uu_expr", path="src/uu/expr" } -factor = { optional=true, version="0.0.18", package="uu_factor", path="src/uu/factor" } -false = { optional=true, version="0.0.18", package="uu_false", path="src/uu/false" } -fmt = { optional=true, version="0.0.18", package="uu_fmt", path="src/uu/fmt" } -fold = { optional=true, version="0.0.18", package="uu_fold", path="src/uu/fold" } -groups = { optional=true, version="0.0.18", package="uu_groups", path="src/uu/groups" } -hashsum = { optional=true, version="0.0.18", package="uu_hashsum", path="src/uu/hashsum" } -head = { optional=true, version="0.0.18", package="uu_head", path="src/uu/head" } -hostid = { optional=true, version="0.0.18", package="uu_hostid", path="src/uu/hostid" } -hostname = { optional=true, version="0.0.18", package="uu_hostname", path="src/uu/hostname" } -id = { optional=true, version="0.0.18", package="uu_id", path="src/uu/id" } -install = { optional=true, version="0.0.18", package="uu_install", path="src/uu/install" } -join = { optional=true, version="0.0.18", package="uu_join", path="src/uu/join" } -kill = { optional=true, version="0.0.18", package="uu_kill", path="src/uu/kill" } -link = { optional=true, version="0.0.18", package="uu_link", path="src/uu/link" } -ln = { optional=true, version="0.0.18", package="uu_ln", path="src/uu/ln" } -ls = { optional=true, version="0.0.18", package="uu_ls", path="src/uu/ls" } -logname = { optional=true, version="0.0.18", package="uu_logname", path="src/uu/logname" } -mkdir = { optional=true, version="0.0.18", package="uu_mkdir", path="src/uu/mkdir" } -mkfifo = { optional=true, version="0.0.18", package="uu_mkfifo", path="src/uu/mkfifo" } -mknod = { optional=true, version="0.0.18", package="uu_mknod", path="src/uu/mknod" } -mktemp = { optional=true, version="0.0.18", package="uu_mktemp", path="src/uu/mktemp" } -more = { optional=true, version="0.0.18", package="uu_more", path="src/uu/more" } -mv = { optional=true, version="0.0.18", package="uu_mv", path="src/uu/mv" } -nice = { optional=true, version="0.0.18", package="uu_nice", path="src/uu/nice" } -nl = { optional=true, version="0.0.18", package="uu_nl", path="src/uu/nl" } -nohup = { optional=true, version="0.0.18", package="uu_nohup", path="src/uu/nohup" } -nproc = { optional=true, version="0.0.18", package="uu_nproc", path="src/uu/nproc" } -numfmt = { optional=true, version="0.0.18", package="uu_numfmt", path="src/uu/numfmt" } -od = { optional=true, version="0.0.18", package="uu_od", path="src/uu/od" } -paste = { optional=true, version="0.0.18", package="uu_paste", path="src/uu/paste" } -pathchk = { optional=true, version="0.0.18", package="uu_pathchk", path="src/uu/pathchk" } -pinky = { optional=true, version="0.0.18", package="uu_pinky", path="src/uu/pinky" } -pr = { optional=true, version="0.0.18", package="uu_pr", path="src/uu/pr" } -printenv = { optional=true, version="0.0.18", package="uu_printenv", path="src/uu/printenv" } -printf = { optional=true, version="0.0.18", package="uu_printf", path="src/uu/printf" } -ptx = { optional=true, version="0.0.18", package="uu_ptx", path="src/uu/ptx" } -pwd = { optional=true, version="0.0.18", package="uu_pwd", path="src/uu/pwd" } -readlink = { optional=true, version="0.0.18", package="uu_readlink", path="src/uu/readlink" } -realpath = { optional=true, version="0.0.18", package="uu_realpath", path="src/uu/realpath" } -relpath = { optional=true, version="0.0.18", package="uu_relpath", path="src/uu/relpath" } -rm = { optional=true, version="0.0.18", package="uu_rm", path="src/uu/rm" } -rmdir = { optional=true, version="0.0.18", package="uu_rmdir", path="src/uu/rmdir" } -runcon = { optional=true, version="0.0.18", package="uu_runcon", path="src/uu/runcon" } -seq = { optional=true, version="0.0.18", package="uu_seq", path="src/uu/seq" } -shred = { optional=true, version="0.0.18", package="uu_shred", path="src/uu/shred" } -shuf = { optional=true, version="0.0.18", package="uu_shuf", path="src/uu/shuf" } -sleep = { optional=true, version="0.0.18", package="uu_sleep", path="src/uu/sleep" } -sort = { optional=true, version="0.0.18", package="uu_sort", path="src/uu/sort" } -split = { optional=true, version="0.0.18", package="uu_split", path="src/uu/split" } -stat = { optional=true, version="0.0.18", package="uu_stat", path="src/uu/stat" } -stdbuf = { optional=true, version="0.0.18", package="uu_stdbuf", path="src/uu/stdbuf" } -stty = { optional=true, version="0.0.18", package="uu_stty", path="src/uu/stty" } -sum = { optional=true, version="0.0.18", package="uu_sum", path="src/uu/sum" } -sync = { optional=true, version="0.0.18", package="uu_sync", path="src/uu/sync" } -tac = { optional=true, version="0.0.18", package="uu_tac", path="src/uu/tac" } -tail = { optional=true, version="0.0.18", package="uu_tail", path="src/uu/tail" } -tee = { optional=true, version="0.0.18", package="uu_tee", path="src/uu/tee" } -timeout = { optional=true, version="0.0.18", package="uu_timeout", path="src/uu/timeout" } -touch = { optional=true, version="0.0.18", package="uu_touch", path="src/uu/touch" } -tr = { optional=true, version="0.0.18", package="uu_tr", path="src/uu/tr" } -true = { optional=true, version="0.0.18", package="uu_true", path="src/uu/true" } -truncate = { optional=true, version="0.0.18", package="uu_truncate", path="src/uu/truncate" } -tsort = { optional=true, version="0.0.18", package="uu_tsort", path="src/uu/tsort" } -tty = { optional=true, version="0.0.18", package="uu_tty", path="src/uu/tty" } -uname = { optional=true, version="0.0.18", package="uu_uname", path="src/uu/uname" } -unexpand = { optional=true, version="0.0.18", package="uu_unexpand", path="src/uu/unexpand" } -uniq = { optional=true, version="0.0.18", package="uu_uniq", path="src/uu/uniq" } -unlink = { optional=true, version="0.0.18", package="uu_unlink", path="src/uu/unlink" } -uptime = { optional=true, version="0.0.18", package="uu_uptime", path="src/uu/uptime" } -users = { optional=true, version="0.0.18", package="uu_users", path="src/uu/users" } -vdir = { optional=true, version="0.0.18", package="uu_vdir", path="src/uu/vdir" } -wc = { optional=true, version="0.0.18", package="uu_wc", path="src/uu/wc" } -who = { optional=true, version="0.0.18", package="uu_who", path="src/uu/who" } -whoami = { optional=true, version="0.0.18", package="uu_whoami", path="src/uu/whoami" } -yes = { optional=true, version="0.0.18", package="uu_yes", path="src/uu/yes" } +arch = { optional = true, version = "0.0.20", package = "uu_arch", path = "src/uu/arch" } +base32 = { optional = true, version = "0.0.20", package = "uu_base32", path = "src/uu/base32" } +base64 = { optional = true, version = "0.0.20", package = "uu_base64", path = "src/uu/base64" } +basename = { optional = true, version = "0.0.20", package = "uu_basename", path = "src/uu/basename" } +basenc = { optional = true, version = "0.0.20", package = "uu_basenc", path = "src/uu/basenc" } +cat = { optional = true, version = "0.0.20", package = "uu_cat", path = "src/uu/cat" } +chcon = { optional = true, version = "0.0.20", package = "uu_chcon", path = "src/uu/chcon" } +chgrp = { optional = true, version = "0.0.20", package = "uu_chgrp", path = "src/uu/chgrp" } +chmod = { optional = true, version = "0.0.20", package = "uu_chmod", path = "src/uu/chmod" } +chown = { optional = true, version = "0.0.20", package = "uu_chown", path = "src/uu/chown" } +chroot = { optional = true, version = "0.0.20", package = "uu_chroot", path = "src/uu/chroot" } +cksum = { optional = true, version = "0.0.20", package = "uu_cksum", path = "src/uu/cksum" } +comm = { optional = true, version = "0.0.20", package = "uu_comm", path = "src/uu/comm" } +cp = { optional = true, version = "0.0.20", package = "uu_cp", path = "src/uu/cp" } +csplit = { optional = true, version = "0.0.20", package = "uu_csplit", path = "src/uu/csplit" } +cut = { optional = true, version = "0.0.20", package = "uu_cut", path = "src/uu/cut" } +date = { optional = true, version = "0.0.20", package = "uu_date", path = "src/uu/date" } +dd = { optional = true, version = "0.0.20", package = "uu_dd", path = "src/uu/dd" } +df = { optional = true, version = "0.0.20", package = "uu_df", path = "src/uu/df" } +dir = { optional = true, version = "0.0.20", package = "uu_dir", path = "src/uu/dir" } +dircolors = { optional = true, version = "0.0.20", package = "uu_dircolors", path = "src/uu/dircolors" } +dirname = { optional = true, version = "0.0.20", package = "uu_dirname", path = "src/uu/dirname" } +du = { optional = true, version = "0.0.20", package = "uu_du", path = "src/uu/du" } +echo = { optional = true, version = "0.0.20", package = "uu_echo", path = "src/uu/echo" } +env = { optional = true, version = "0.0.20", package = "uu_env", path = "src/uu/env" } +expand = { optional = true, version = "0.0.20", package = "uu_expand", path = "src/uu/expand" } +expr = { optional = true, version = "0.0.20", package = "uu_expr", path = "src/uu/expr" } +factor = { optional = true, version = "0.0.20", package = "uu_factor", path = "src/uu/factor" } +false = { optional = true, version = "0.0.20", package = "uu_false", path = "src/uu/false" } +fmt = { optional = true, version = "0.0.20", package = "uu_fmt", path = "src/uu/fmt" } +fold = { optional = true, version = "0.0.20", package = "uu_fold", path = "src/uu/fold" } +groups = { optional = true, version = "0.0.20", package = "uu_groups", path = "src/uu/groups" } +hashsum = { optional = true, version = "0.0.20", package = "uu_hashsum", path = "src/uu/hashsum" } +head = { optional = true, version = "0.0.20", package = "uu_head", path = "src/uu/head" } +hostid = { optional = true, version = "0.0.20", package = "uu_hostid", path = "src/uu/hostid" } +hostname = { optional = true, version = "0.0.20", package = "uu_hostname", path = "src/uu/hostname" } +id = { optional = true, version = "0.0.20", package = "uu_id", path = "src/uu/id" } +install = { optional = true, version = "0.0.20", package = "uu_install", path = "src/uu/install" } +join = { optional = true, version = "0.0.20", package = "uu_join", path = "src/uu/join" } +kill = { optional = true, version = "0.0.20", package = "uu_kill", path = "src/uu/kill" } +link = { optional = true, version = "0.0.20", package = "uu_link", path = "src/uu/link" } +ln = { optional = true, version = "0.0.20", package = "uu_ln", path = "src/uu/ln" } +ls = { optional = true, version = "0.0.20", package = "uu_ls", path = "src/uu/ls" } +logname = { optional = true, version = "0.0.20", package = "uu_logname", path = "src/uu/logname" } +mkdir = { optional = true, version = "0.0.20", package = "uu_mkdir", path = "src/uu/mkdir" } +mkfifo = { optional = true, version = "0.0.20", package = "uu_mkfifo", path = "src/uu/mkfifo" } +mknod = { optional = true, version = "0.0.20", package = "uu_mknod", path = "src/uu/mknod" } +mktemp = { optional = true, version = "0.0.20", package = "uu_mktemp", path = "src/uu/mktemp" } +more = { optional = true, version = "0.0.20", package = "uu_more", path = "src/uu/more" } +mv = { optional = true, version = "0.0.20", package = "uu_mv", path = "src/uu/mv" } +nice = { optional = true, version = "0.0.20", package = "uu_nice", path = "src/uu/nice" } +nl = { optional = true, version = "0.0.20", package = "uu_nl", path = "src/uu/nl" } +nohup = { optional = true, version = "0.0.20", package = "uu_nohup", path = "src/uu/nohup" } +nproc = { optional = true, version = "0.0.20", package = "uu_nproc", path = "src/uu/nproc" } +numfmt = { optional = true, version = "0.0.20", package = "uu_numfmt", path = "src/uu/numfmt" } +od = { optional = true, version = "0.0.20", package = "uu_od", path = "src/uu/od" } +paste = { optional = true, version = "0.0.20", package = "uu_paste", path = "src/uu/paste" } +pathchk = { optional = true, version = "0.0.20", package = "uu_pathchk", path = "src/uu/pathchk" } +pinky = { optional = true, version = "0.0.20", package = "uu_pinky", path = "src/uu/pinky" } +pr = { optional = true, version = "0.0.20", package = "uu_pr", path = "src/uu/pr" } +printenv = { optional = true, version = "0.0.20", package = "uu_printenv", path = "src/uu/printenv" } +printf = { optional = true, version = "0.0.20", package = "uu_printf", path = "src/uu/printf" } +ptx = { optional = true, version = "0.0.20", package = "uu_ptx", path = "src/uu/ptx" } +pwd = { optional = true, version = "0.0.20", package = "uu_pwd", path = "src/uu/pwd" } +readlink = { optional = true, version = "0.0.20", package = "uu_readlink", path = "src/uu/readlink" } +realpath = { optional = true, version = "0.0.20", package = "uu_realpath", path = "src/uu/realpath" } +relpath = { optional = true, version = "0.0.20", package = "uu_relpath", path = "src/uu/relpath" } +rm = { optional = true, version = "0.0.20", package = "uu_rm", path = "src/uu/rm" } +rmdir = { optional = true, version = "0.0.20", package = "uu_rmdir", path = "src/uu/rmdir" } +runcon = { optional = true, version = "0.0.20", package = "uu_runcon", path = "src/uu/runcon" } +seq = { optional = true, version = "0.0.20", package = "uu_seq", path = "src/uu/seq" } +shred = { optional = true, version = "0.0.20", package = "uu_shred", path = "src/uu/shred" } +shuf = { optional = true, version = "0.0.20", package = "uu_shuf", path = "src/uu/shuf" } +sleep = { optional = true, version = "0.0.20", package = "uu_sleep", path = "src/uu/sleep" } +sort = { optional = true, version = "0.0.20", package = "uu_sort", path = "src/uu/sort" } +split = { optional = true, version = "0.0.20", package = "uu_split", path = "src/uu/split" } +stat = { optional = true, version = "0.0.20", package = "uu_stat", path = "src/uu/stat" } +stdbuf = { optional = true, version = "0.0.20", package = "uu_stdbuf", path = "src/uu/stdbuf" } +stty = { optional = true, version = "0.0.20", package = "uu_stty", path = "src/uu/stty" } +sum = { optional = true, version = "0.0.20", package = "uu_sum", path = "src/uu/sum" } +sync = { optional = true, version = "0.0.20", package = "uu_sync", path = "src/uu/sync" } +tac = { optional = true, version = "0.0.20", package = "uu_tac", path = "src/uu/tac" } +tail = { optional = true, version = "0.0.20", package = "uu_tail", path = "src/uu/tail" } +tee = { optional = true, version = "0.0.20", package = "uu_tee", path = "src/uu/tee" } +timeout = { optional = true, version = "0.0.20", package = "uu_timeout", path = "src/uu/timeout" } +touch = { optional = true, version = "0.0.20", package = "uu_touch", path = "src/uu/touch" } +tr = { optional = true, version = "0.0.20", package = "uu_tr", path = "src/uu/tr" } +true = { optional = true, version = "0.0.20", package = "uu_true", path = "src/uu/true" } +truncate = { optional = true, version = "0.0.20", package = "uu_truncate", path = "src/uu/truncate" } +tsort = { optional = true, version = "0.0.20", package = "uu_tsort", path = "src/uu/tsort" } +tty = { optional = true, version = "0.0.20", package = "uu_tty", path = "src/uu/tty" } +uname = { optional = true, version = "0.0.20", package = "uu_uname", path = "src/uu/uname" } +unexpand = { optional = true, version = "0.0.20", package = "uu_unexpand", path = "src/uu/unexpand" } +uniq = { optional = true, version = "0.0.20", package = "uu_uniq", path = "src/uu/uniq" } +unlink = { optional = true, version = "0.0.20", package = "uu_unlink", path = "src/uu/unlink" } +uptime = { optional = true, version = "0.0.20", package = "uu_uptime", path = "src/uu/uptime" } +users = { optional = true, version = "0.0.20", package = "uu_users", path = "src/uu/users" } +vdir = { optional = true, version = "0.0.20", package = "uu_vdir", path = "src/uu/vdir" } +wc = { optional = true, version = "0.0.20", package = "uu_wc", path = "src/uu/wc" } +who = { optional = true, version = "0.0.20", package = "uu_who", path = "src/uu/who" } +whoami = { optional = true, version = "0.0.20", package = "uu_whoami", path = "src/uu/whoami" } +yes = { optional = true, version = "0.0.20", package = "uu_yes", path = "src/uu/yes" } # this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)" # factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" } @@ -475,35 +476,34 @@ yes = { optional=true, version="0.0.18", package="uu_yes", path="src/uu/yes #pin_cc = { version="1.0.61, < 1.0.62", package="cc" } ## cc v1.0.62 has compiler errors for MinRustV v1.32.0, requires 1.34 (for `std::str::split_ascii_whitespace()`) [dev-dependencies] -chrono = { workspace=true } +chrono = { workspace = true } conv = "0.3" -filetime = { workspace=true } -glob = { workspace=true } -libc = { workspace=true } +filetime = { workspace = true } +glob = { workspace = true } +libc = { workspace = true } pretty_assertions = "1" -rand = { workspace=true } -regex = { workspace=true } -sha1 = { version="0.10", features=["std"] } -tempfile = { workspace=true } -time = { workspace=true, features=["local-offset"] } +rand = { workspace = true } +regex = { workspace = true } +sha1 = { version = "0.10", features = ["std"] } +tempfile = { workspace = true } +time = { workspace = true, features = ["local-offset"] } unindent = "0.2" -uucore = { workspace=true, features=["entries", "process", "signals"] } -walkdir = { workspace=true } -is-terminal = { workspace=true } +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.1" [target.'cfg(unix)'.dev-dependencies] -nix = { workspace=true, features=["process", "signal", "user"] } -rust-users = { version="0.11", package="users" } +nix = { workspace = true, features = ["process", "signal", "user"] } rand_pcg = "0.3" [build-dependencies] -phf_codegen = { workspace=true } +phf_codegen = { workspace = true } [[bin]] name = "coreutils" diff --git a/GNUmakefile b/GNUmakefile index 26d6de5ba..c672458a1 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,4 +1,4 @@ -# spell-checker:ignore (misc) testsuite runtest findstring (targets) busytest toybox distclean pkgs nextest ; (vars/env) BINDIR BUILDDIR CARGOFLAGS DESTDIR DOCSDIR INSTALLDIR INSTALLEES MULTICALL DATAROOTDIR TESTDIR +# spell-checker:ignore (misc) testsuite runtest findstring (targets) busytest toybox distclean pkgs nextest ; (vars/env) BINDIR BUILDDIR CARGOFLAGS DESTDIR DOCSDIR INSTALLDIR INSTALLEES MULTICALL DATAROOTDIR TESTDIR manpages # Config options PROFILE ?= debug @@ -337,7 +337,21 @@ clean: distclean: clean $(CARGO) clean $(CARGOFLAGS) && $(CARGO) update $(CARGOFLAGS) -install: build +manpages: build-coreutils + mkdir -p $(BUILDDIR)/man/ + $(foreach prog, $(INSTALLEES), \ + $(BUILDDIR)/coreutils manpage $(prog) > $(BUILDDIR)/man/$(PROG_PREFIX)$(prog).1; \ + ) + +completions: build-coreutils + mkdir -p $(BUILDDIR)/completions/zsh $(BUILDDIR)/completions/bash $(BUILDDIR)/completions/fish + $(foreach prog, $(INSTALLEES), \ + $(BUILDDIR)/coreutils completion $(prog) zsh > $(BUILDDIR)/completions/zsh/_$(PROG_PREFIX)$(prog); \ + $(BUILDDIR)/coreutils completion $(prog) bash > $(BUILDDIR)/completions/bash/$(PROG_PREFIX)$(prog); \ + $(BUILDDIR)/coreutils completion $(prog) fish > $(BUILDDIR)/completions/fish/$(PROG_PREFIX)$(prog).fish; \ + ) + +install: build manpages completions mkdir -p $(INSTALLDIR_BIN) ifeq (${MULTICALL}, y) $(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils @@ -349,15 +363,18 @@ else $(INSTALL) $(BUILDDIR)/$(prog) $(INSTALLDIR_BIN)/$(PROG_PREFIX)$(prog);) $(if $(findstring test,$(INSTALLEES)), $(INSTALL) $(BUILDDIR)/test $(INSTALLDIR_BIN)/$(PROG_PREFIX)[) endif + mkdir -p $(DESTDIR)$(DATAROOTDIR)/man/man1 + $(foreach prog, $(INSTALLEES), \ + $(INSTALL) $(BUILDDIR)/man/$(PROG_PREFIX)$(prog).1 $(DESTDIR)$(DATAROOTDIR)/man/man1/; \ + ) + mkdir -p $(DESTDIR)$(DATAROOTDIR)/zsh/site-functions mkdir -p $(DESTDIR)$(DATAROOTDIR)/bash-completion/completions mkdir -p $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d - mkdir -p $(DESTDIR)$(DATAROOTDIR)/man/man1 $(foreach prog, $(INSTALLEES), \ - $(BUILDDIR)/coreutils completion $(prog) zsh > $(DESTDIR)$(DATAROOTDIR)/zsh/site-functions/_$(PROG_PREFIX)$(prog); \ - $(BUILDDIR)/coreutils completion $(prog) bash > $(DESTDIR)$(DATAROOTDIR)/bash-completion/completions/$(PROG_PREFIX)$(prog); \ - $(BUILDDIR)/coreutils completion $(prog) fish > $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d/$(PROG_PREFIX)$(prog).fish; \ - $(BUILDDIR)/coreutils manpage $(prog) > $(DESTDIR)$(DATAROOTDIR)/man/man1/$(PROG_PREFIX)$(prog).1; \ + $(INSTALL) $(BUILDDIR)/completions/zsh/_$(PROG_PREFIX)$(prog) $(DESTDIR)$(DATAROOTDIR)/zsh/site-functions/; \ + $(INSTALL) $(BUILDDIR)/completions/bash/$(PROG_PREFIX)$(prog) $(DESTDIR)$(DATAROOTDIR)/bash-completion/completions/; \ + $(INSTALL) $(BUILDDIR)/completions/fish/$(PROG_PREFIX)$(prog).fish $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d/; \ ) uninstall: @@ -369,5 +386,6 @@ endif rm -f $(addprefix $(DESTDIR)$(DATAROOTDIR)/zsh/site-functions/_$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(DESTDIR)$(DATAROOTDIR)/bash-completion/completions/$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d/$(PROG_PREFIX),$(addsuffix .fish,$(PROGS))) + rm -f $(addprefix $(DESTDIR)$(DATAROOTDIR)/man/man1/$(PROG_PREFIX),$(addsuffix .1,$(PROGS))) .PHONY: all build build-coreutils build-pkgs test distclean clean busytest install uninstall diff --git a/Makefile.toml b/Makefile.toml index ded2cd55b..84698df5f 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -20,15 +20,12 @@ run_task = "_init" [tasks._init] private = true -dependencies = [ - "_init-vars", -] +dependencies = ["_init-vars"] [tasks._init-vars] private = true script_runner = "@duckscript" -script = [ -''' +script = [''' # reset build/test flags set_env CARGO_MAKE_CARGO_BUILD_TEST_FLAGS "" # determine features @@ -90,54 +87,36 @@ for arg in "${args_utils_list}" end args_utils = trim "${args_utils}" set_env CARGO_MAKE_TASK_BUILD_UTILS_ARGS "${args_utils}" -''' -] +'''] ### tasks [tasks.default] description = "## *DEFAULT* Build (debug-mode) and test project" category = "[project]" -dependencies = [ - "action-build-debug", - "test-terse", -] +dependencies = ["action-build-debug", "test-terse"] ## [tasks.build] description = "## Build (release-mode) project" category = "[project]" -dependencies = [ - "core::pre-build", - "action-build-release", - "core::post-build", -] +dependencies = ["core::pre-build", "action-build-release", "core::post-build"] [tasks.build-debug] description = "## Build (debug-mode) project" category = "[project]" -dependencies = [ - "action-build-debug", -] +dependencies = ["action-build-debug"] [tasks.build-examples] description = "## Build (release-mode) project example(s); usage: `cargo make (build-examples | examples) [EXAMPLE]...`" category = "[project]" -dependencies = [ - "core::pre-build", - "action-build-examples", - "core::post-build", -] +dependencies = ["core::pre-build", "action-build-examples", "core::post-build"] [tasks.build-features] description = "## Build (with features; release-mode) project; usage: `cargo make (build-features | features) FEATURE...`" category = "[project]" -dependencies = [ - "core::pre-build", - "action-build-features", - "core::post-build", -] +dependencies = ["core::pre-build", "action-build-features", "core::post-build"] [tasks.build-release] alias = "build" @@ -148,9 +127,7 @@ alias = "build-debug" [tasks.example] description = "hidden singular-form alias for 'examples'" category = "[project]" -dependencies = [ - "examples", -] +dependencies = ["examples"] [tasks.examples] alias = "build-examples" @@ -161,17 +138,12 @@ alias = "build-features" [tasks.format] description = "## Format code files (with `cargo fmt`; includes tests)" category = "[project]" -dependencies = [ - "action-format", - "action-format-tests", -] +dependencies = ["action-format", "action-format-tests"] [tasks.help] description = "## Display help" category = "[project]" -dependencies = [ - "action-display-help", -] +dependencies = ["action-display-help"] [tasks.install] description = "## Install project binary (to $HOME/.cargo/bin)" @@ -182,10 +154,7 @@ args = ["install", "--path", "."] [tasks.lint] description = "## Display lint report" category = "[project]" -dependencies = [ - "action-clippy", - "action-fmt_report", -] +dependencies = ["action-clippy", "action-fmt_report"] [tasks.release] alias = "build" @@ -193,48 +162,32 @@ alias = "build" [tasks.test] description = "## Run project tests" category = "[project]" -dependencies = [ - "core::pre-test", - "core::test", - "core::post-test", -] +dependencies = ["core::pre-test", "core::test", "core::post-test"] [tasks.test-terse] description = "## Run project tests (with terse/summary output)" category = "[project]" -dependencies = [ - "core::pre-test", - "action-test_quiet", - "core::post-test", -] +dependencies = ["core::pre-test", "action-test_quiet", "core::post-test"] [tasks.test-util] description = "## Test (individual) utilities; usage: `cargo make (test-util | test-uutil) [UTIL_NAME...]`" category = "[project]" -dependencies = [ - "action-test-utils", -] +dependencies = ["action-test-utils"] [tasks.test-utils] description = "hidden plural-form alias for 'test-util'" category = "[project]" -dependencies = [ - "test-util", -] +dependencies = ["test-util"] [tasks.test-uutil] description = "hidden alias for 'test-util'" category = "[project]" -dependencies = [ - "test-util", -] +dependencies = ["test-util"] [tasks.test-uutils] description = "hidden alias for 'test-util'" category = "[project]" -dependencies = [ - "test-util", -] +dependencies = ["test-util"] [tasks.uninstall] description = "## Remove project binary (from $HOME/.cargo/bin)" @@ -246,63 +199,66 @@ args = ["uninstall"] description = "## Build (individual; release-mode) utilities; usage: `cargo make (util | uutil) [UTIL_NAME...]`" category = "[project]" dependencies = [ - "core::pre-build", - "action-determine-utils", - "action-build-utils", - "core::post-build", + "core::pre-build", + "action-determine-utils", + "action-build-utils", + "core::post-build", ] [tasks.utils] description = "hidden plural-form alias for 'util'" category = "[project]" -dependencies = [ - "util", -] +dependencies = ["util"] [tasks.uutil] description = "hidden alias for 'util'" category = "[project]" -dependencies = [ - "util", -] +dependencies = ["util"] [tasks.uutils] description = "hidden plural-form alias for 'util'" category = "[project]" -dependencies = [ - "util", -] +dependencies = ["util"] ### actions [tasks.action-build-release] description = "`cargo build --release`" command = "cargo" -args = ["build", "--release", "@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )" ] +args = ["build", "--release", "@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )"] [tasks.action-build-debug] description = "`cargo build`" command = "cargo" -args = ["build", "@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )" ] +args = ["build", "@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )"] [tasks.action-build-examples] description = "`cargo build (--examples|(--example EXAMPLE)...)`" command = "cargo" -args = ["build", "--release", "@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )", "${CARGO_MAKE_TASK_BUILD_EXAMPLES_ARGS}" ] +args = [ + "build", + "--release", + "@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )", + "${CARGO_MAKE_TASK_BUILD_EXAMPLES_ARGS}", +] [tasks.action-build-features] description = "`cargo build --release --features FEATURES`" command = "cargo" -args = ["build", "--release", "--no-default-features", "--features", "${CARGO_MAKE_TASK_BUILD_FEATURES_ARGS}" ] +args = [ + "build", + "--release", + "--no-default-features", + "--features", + "${CARGO_MAKE_TASK_BUILD_FEATURES_ARGS}", +] [tasks.action-build-utils] description = "Build individual utilities" -dependencies = [ - "action-determine-utils", -] +dependencies = ["action-determine-utils"] command = "cargo" # args = ["build", "@@remove-empty(CARGO_MAKE_TASK_BUILD_UTILS_ARGS)" ] -args = ["build", "--release", "@@split(CARGO_MAKE_TASK_BUILD_UTILS_ARGS, )" ] +args = ["build", "--release", "@@split(CARGO_MAKE_TASK_BUILD_UTILS_ARGS, )"] [tasks.action-clippy] description = "`cargo clippy` lint report" @@ -311,8 +267,7 @@ args = ["clippy", "@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )"] [tasks.action-determine-utils] script_runner = "@duckscript" -script = [ -''' +script = [''' package_options = get_env CARGO_MAKE_TASK_BUILD_UTILS_ARGS if is_empty "${package_options}" show_utils = get_env CARGO_MAKE_VAR_SHOW_UTILS @@ -335,13 +290,11 @@ if is_empty "${package_options}" package_options = trim "${package_options}" end_if set_env CARGO_MAKE_TASK_BUILD_UTILS_ARGS "${package_options}" -''' -] +'''] [tasks.action-determine-tests] script_runner = "@duckscript" -script = [ -''' +script = [''' test_files = glob_array tests/**/*.rs for file in ${test_files} file = replace "${file}" "\\" "/" @@ -354,8 +307,7 @@ for file in ${test_files} end_if end set_env CARGO_MAKE_VAR_TESTS "${tests}" -''' -] +'''] [tasks.action-format] description = "`cargo fmt`" @@ -364,9 +316,7 @@ args = ["fmt"] [tasks.action-format-tests] description = "`cargo fmt` tests" -dependencies = [ - "action-determine-tests", -] +dependencies = ["action-determine-tests"] command = "cargo" args = ["fmt", "--", "@@split(CARGO_MAKE_VAR_TESTS, )"] @@ -381,16 +331,18 @@ args = ["fmt", "--", "--check"] [tasks.action-spellcheck-codespell] description = "`codespell` spellcheck repository" command = "codespell" # (from `pip install codespell`) -args = [".", "--skip=*/.git,./target,./tests/fixtures", "--ignore-words-list=mut,od"] +args = [ + ".", + "--skip=*/.git,./target,./tests/fixtures", + "--ignore-words-list=mut,od", +] [tasks.action-test-utils] description = "Build individual utilities" -dependencies = [ - "action-determine-utils", -] +dependencies = ["action-determine-utils"] command = "cargo" # args = ["build", "@@remove-empty(CARGO_MAKE_TASK_BUILD_UTILS_ARGS)" ] -args = ["test", "@@split(CARGO_MAKE_TASK_BUILD_UTILS_ARGS, )" ] +args = ["test", "@@split(CARGO_MAKE_TASK_BUILD_UTILS_ARGS, )"] [tasks.action-test_quiet] description = "Test (in `--quiet` mode)" @@ -399,8 +351,7 @@ args = ["test", "--quiet", "@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )"] [tasks.action-display-help] script_runner = "@duckscript" -script = [ -''' +script = [''' echo "" echo "usage: `cargo make TARGET [ARGS...]`" echo "" @@ -432,5 +383,4 @@ script = [ end_if end echo "" -''' -] +'''] diff --git a/README.md b/README.md index 5a9f968ae..4929224d1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ - + +
![uutils logo](docs/src/logo.svg) @@ -19,11 +20,14 @@ --- +
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. +
+ To install it: ```shell @@ -31,6 +35,8 @@ cargo install coreutils ~/.cargo/bin/coreutils ``` +
+ ## 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. +
+ ## 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. + ## Requirements @@ -301,6 +310,8 @@ See for the main meta bugs ![Evolution over time](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.png?raw=true) +
+ ## Contributing To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). diff --git a/build.rs b/build.rs index 04948c0d3..09b33fa91 100644 --- a/build.rs +++ b/build.rs @@ -38,14 +38,14 @@ pub fn main() { let mut mf = File::create(Path::new(&out_dir).join("uutils_map.rs")).unwrap(); mf.write_all( - "type UtilityMap = phf::Map<&'static str, (fn(T) -> i32, fn() -> Command)>;\n\ + "type UtilityMap = phf::OrderedMap<&'static str, (fn(T) -> i32, fn() -> Command)>;\n\ \n\ fn util_map() -> UtilityMap {\n" .as_bytes(), ) .unwrap(); - let mut phf_map = phf_codegen::Map::<&str>::new(); + let mut phf_map = phf_codegen::OrderedMap::<&str>::new(); for krate in &crates { let map_value = format!("({krate}::uumain, {krate}::uu_app)"); match krate.as_ref() { diff --git a/deny.toml b/deny.toml index c4d83c513..f587f0720 100644 --- a/deny.toml +++ b/deny.toml @@ -11,7 +11,7 @@ unmaintained = "warn" yanked = "warn" notice = "warn" ignore = [ - #"RUSTSEC-0000-0000", + #"RUSTSEC-0000-0000", ] # This section is considered when running `cargo deny check licenses` @@ -20,15 +20,14 @@ ignore = [ [licenses] unlicensed = "deny" allow = [ - "MIT", - "Apache-2.0", - "ISC", - "BSD-2-Clause", - "BSD-2-Clause-FreeBSD", - "BSD-3-Clause", - "CC0-1.0", - "MPL-2.0", # XXX considered copyleft? - "Unicode-DFS-2016", + "MIT", + "Apache-2.0", + "ISC", + "BSD-2-Clause", + "BSD-2-Clause-FreeBSD", + "BSD-3-Clause", + "CC0-1.0", + "Unicode-DFS-2016", ] copyleft = "deny" allow-osi-fsf-free = "neither" @@ -59,29 +58,37 @@ highlight = "all" # introduces it. # spell-checker: disable skip = [ - # is-terminal - { name = "hermit-abi", version = "0.3.1" }, - # is-terminal - { name = "rustix", version = "0.36.14" }, - # is-terminal (via rustix) - { name = "io-lifetimes", version = "1.0.5" }, - # is-terminal - { name = "linux-raw-sys", version = "0.1.4" }, - # is-terminal - { name = "windows-sys", version = "0.45.0" }, - { name = "windows-targets", version = "0.42.2" }, - { name = "windows_aarch64_gnullvm", version = "0.42.2" }, - { name = "windows_aarch64_msvc", version = "0.42.2" }, - { name = "windows_i686_gnu", version = "0.42.2" }, - { name = "windows_i686_msvc", version = "0.42.2" }, - { name = "windows_x86_64_gnu", version = "0.42.2" }, - { name = "windows_x86_64_gnullvm", version = "0.42.2" }, - { name = "windows_x86_64_msvc", version = "0.42.2" }, - - # tempfile - { name = "redox_syscall", version = "0.3.5" }, - # cpp_macros - { name = "aho-corasick", version = "0.7.19" }, + # procfs + { name = "rustix", version = "0.36.15" }, + # rustix + { name = "linux-raw-sys", version = "0.1.4" }, + { name = "linux-raw-sys", version = "0.3.8" }, + # tempfile + { name = "rustix", version = "0.37.23" }, + # various crates + { name = "windows-sys", version = "0.45.0" }, + # windows-sys + { name = "windows-targets", version = "0.42.2" }, + # windows-targets + { name = "windows_aarch64_gnullvm", version = "0.42.2" }, + # windows-targets + { name = "windows_aarch64_msvc", version = "0.42.2" }, + # windows-targets + { name = "windows_i686_gnu", version = "0.42.2" }, + # windows-targets + { name = "windows_i686_msvc", version = "0.42.2" }, + # windows-targets + { name = "windows_x86_64_gnu", version = "0.42.2" }, + # windows-targets + { name = "windows_x86_64_gnullvm", version = "0.42.2" }, + # windows-targets + { name = "windows_x86_64_msvc", version = "0.42.2" }, + # cpp_macros + { name = "aho-corasick", version = "0.7.20" }, + # various crates + { name = "syn", version = "1.0.109" }, + # various crates + { name = "bitflags", version = "1.3.2" }, ] # spell-checker: enable diff --git a/docs/book.toml b/docs/book.toml index b9b31cfaf..ff91c4652 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -6,8 +6,8 @@ src = "src" title = "uutils Documentation" [output.html] -git-repository-url = "https://github.com/rust-lang/cargo/tree/master/src/doc/src" +git-repository-url = "https://github.com/uutils/coreutils/tree/main/docs/src" [preprocessor.toc] command = "mdbook-toc" -renderer = ["html"] \ No newline at end of file +renderer = ["html"] diff --git a/docs/src/extensions.md b/docs/src/extensions.md index 281d8ef2e..eeb00ff35 100644 --- a/docs/src/extensions.md +++ b/docs/src/extensions.md @@ -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. diff --git a/docs/src/installation.md b/docs/src/installation.md index e3e5ccf16..e5fcb6220 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -37,7 +37,7 @@ apk update uutils-coreutils ### Arch -[![Arch package](https://repology.org/badge/version-for-repo/arch/uutils-coreutils.svg)](https://archlinux.org/packages/community/x86_64/uutils-coreutils/) +[![Arch package](https://repology.org/badge/version-for-repo/arch/uutils-coreutils.svg)](https://archlinux.org/packages/extra/x86_64/uutils-coreutils/) ```shell pacman -S uutils-coreutils diff --git a/docs/src/oranda.css b/docs/src/oranda.css new file mode 100644 index 000000000..5581cc0c5 --- /dev/null +++ b/docs/src/oranda.css @@ -0,0 +1,4 @@ +.logo { + display: block; + height: 170px; +} diff --git a/oranda.json b/oranda.json new file mode 100644 index 000000000..b0a93c19a --- /dev/null +++ b/oranda.json @@ -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"] + } +} diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 8440689c6..d6487dc49 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -212,8 +212,15 @@ fn gen_manpage( fn gen_coreutils_app(util_map: &UtilityMap) -> Command { let mut command = Command::new("coreutils"); - for (_, (_, sub_app)) in util_map { - command = command.subcommand(sub_app()); + for (name, (_, sub_app)) in util_map { + // Recreate a small subcommand with only the relevant info + // (name & short description) + let about = sub_app() + .get_about() + .expect("Could not get the 'about'") + .to_string(); + let sub_app = Command::new(name).about(about); + command = command.subcommand(sub_app); } command } diff --git a/src/bin/uudoc.rs b/src/bin/uudoc.rs index b7a2ce581..5ac858226 100644 --- a/src/bin/uudoc.rs +++ b/src/bin/uudoc.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore tldr +// spell-checker:ignore tldr uuhelp use clap::Command; use std::collections::HashMap; @@ -178,7 +178,7 @@ impl<'a, 'b> MDWriter<'a, 'b> { fn usage(&mut self) -> io::Result<()> { if let Some(markdown) = &self.markdown { - let usage = help_parser::parse_usage(markdown); + let usage = uuhelp_parser::parse_usage(markdown); let usage = usage.replace("{}", self.name); writeln!(self.w, "\n```")?; @@ -191,7 +191,7 @@ impl<'a, 'b> MDWriter<'a, 'b> { fn about(&mut self) -> io::Result<()> { if let Some(markdown) = &self.markdown { - writeln!(self.w, "{}", help_parser::parse_about(markdown)) + writeln!(self.w, "{}", uuhelp_parser::parse_about(markdown)) } else { Ok(()) } @@ -199,7 +199,7 @@ impl<'a, 'b> MDWriter<'a, 'b> { fn after_help(&mut self) -> io::Result<()> { if let Some(markdown) = &self.markdown { - if let Some(after_help) = help_parser::parse_section("after help", markdown) { + if let Some(after_help) = uuhelp_parser::parse_section("after help", markdown) { return writeln!(self.w, "\n\n{after_help}"); } } diff --git a/src/help_parser/Cargo.toml b/src/help_parser/Cargo.toml deleted file mode 100644 index d3a8a0f54..000000000 --- a/src/help_parser/Cargo.toml +++ /dev/null @@ -1,5 +0,0 @@ -[package] -name = "help_parser" -version = "0.0.18" -edition = "2021" -license = "MIT" diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index b2cdfdc17..35e7e3892 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_arch" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "arch ~ (uutils) display machine architecture" @@ -15,9 +15,9 @@ edition = "2021" path = "src/arch.rs" [dependencies] -platform-info = { workspace=true } -clap = { workspace=true } -uucore = { workspace=true } +platform-info = { workspace = true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "arch" diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index e4a8464d9..97c853eb6 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base32" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" @@ -15,8 +15,8 @@ edition = "2021" path = "src/base32.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features = ["encoding"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["encoding"] } [[bin]] name = "base32" diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index e5ea4cf14..ef4a063fe 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base64" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" @@ -15,8 +15,8 @@ edition = "2021" path = "src/base64.rs" [dependencies] -uucore = { workspace=true, features = ["encoding"] } -uu_base32 = { workspace=true } +uucore = { workspace = true, features = ["encoding"] } +uu_base32 = { workspace = true } [[bin]] name = "base64" diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index f234d1021..4fd2b6f8c 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basename" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" @@ -15,8 +15,8 @@ edition = "2021" path = "src/basename.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "basename" diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index cc4264b51..4676a6d8f 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basenc" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "basenc ~ (uutils) decode/encode input" @@ -15,9 +15,9 @@ edition = "2021" path = "src/basenc.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features = ["encoding"] } -uu_base32 = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true, features = ["encoding"] } +uu_base32 = { workspace = true } [[bin]] name = "basenc" diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index 941637514..636e3bfef 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cat" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "cat ~ (uutils) concatenate and display input" @@ -15,13 +15,13 @@ edition = "2021" path = "src/cat.rs" [dependencies] -clap = { workspace=true } +clap = { workspace = true } thiserror = { workspace = true } is-terminal = { workspace = true } -uucore = { workspace=true, features=["fs", "pipes"] } +uucore = { workspace = true, features = ["fs", "pipes"] } [target.'cfg(unix)'.dependencies] -nix = { workspace=true } +nix = { workspace = true } [[bin]] name = "cat" diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 4a5e7e0ad..2c4117a32 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -290,7 +290,6 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::SHOW_NONPRINTING_TABS) .short('t') - .long(options::SHOW_NONPRINTING_TABS) .help("equivalent to -vT") .action(ArgAction::SetTrue), ) diff --git a/src/uu/chcon/Cargo.toml b/src/uu/chcon/Cargo.toml index e5dc6b93c..f621aa012 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chcon" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "chcon ~ (uutils) change file security context" @@ -14,12 +14,12 @@ edition = "2021" path = "src/chcon.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["entries", "fs", "perms"] } -selinux = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true, features = ["entries", "fs", "perms"] } +selinux = { workspace = true } thiserror = { workspace = true } -libc = { workspace=true } -fts-sys = { workspace=true } +libc = { workspace = true } +fts-sys = { workspace = true } [[bin]] name = "chcon" diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 13b8c7e4d..9ca3dc26a 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chgrp" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" @@ -15,8 +15,8 @@ edition = "2021" path = "src/chgrp.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["entries", "fs", "perms"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["entries", "fs", "perms"] } [[bin]] name = "chgrp" diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index 8b7d09e92..a788e83ca 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chmod" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "chmod ~ (uutils) change mode of FILE" @@ -15,9 +15,9 @@ edition = "2021" path = "src/chmod.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -uucore = { workspace=true, features=["fs", "mode"] } +clap = { workspace = true } +libc = { workspace = true } +uucore = { workspace = true, features = ["fs", "mode"] } [[bin]] name = "chmod" diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index bc28d8640..3b92e288e 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chown" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" @@ -15,8 +15,8 @@ edition = "2021" path = "src/chown.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["entries", "fs", "perms"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["entries", "fs", "perms"] } [[bin]] name = "chown" diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index ca083753d..d2f58320d 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chroot" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" @@ -15,8 +15,8 @@ edition = "2021" path = "src/chroot.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["entries", "fs"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["entries", "fs"] } [[bin]] name = "chroot" diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index 61c66e704..70886df05 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cksum" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" @@ -15,9 +15,9 @@ edition = "2021" path = "src/cksum.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["sum"] } -hex = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true, features = ["sum"] } +hex = { workspace = true } [[bin]] name = "cksum" diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index 0371d46c1..b86aa4902 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_comm" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "comm ~ (uutils) compare sorted inputs" @@ -15,8 +15,8 @@ edition = "2021" path = "src/comm.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "comm" diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 32d34112b..c768bde0c 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "uu_cp" -version = "0.0.18" +version = "0.0.20" authors = [ - "Jordy Dickinson ", - "Joshua S. Miller ", - "uutils developers", + "Jordy Dickinson ", + "Joshua S. Miller ", + "uutils developers", ] license = "MIT" description = "cp ~ (uutils) copy SOURCE to DESTINATION" @@ -19,18 +19,18 @@ edition = "2021" path = "src/cp.rs" [dependencies] -clap = { workspace=true } -filetime = { workspace=true } -libc = { workspace=true } -quick-error = { workspace=true } -selinux = { workspace=true, optional=true } -uucore = { workspace=true, features=["entries", "fs", "perms", "mode"] } -walkdir = { workspace=true } -indicatif = { workspace=true } +clap = { workspace = true } +filetime = { workspace = true } +libc = { workspace = true } +quick-error = { workspace = true } +selinux = { workspace = true, optional = true } +uucore = { workspace = true, features = ["entries", "fs", "perms", "mode"] } +walkdir = { workspace = true } +indicatif = { workspace = true } [target.'cfg(unix)'.dependencies] -xattr = { workspace=true } -exacl = { workspace=true, optional=true } +xattr = { workspace = true } +exacl = { workspace = true, optional = true } [[bin]] name = "cp" diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index f8087c7f0..de9dd1c91 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -43,7 +43,8 @@ use uucore::fs::{ }; use uucore::update_control::{self, UpdateMode}; use uucore::{ - crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error, show_warning, + crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error, + show_warning, util_name, }; use crate::copydir::copy_directory; @@ -229,10 +230,79 @@ pub struct Options { backup_suffix: String, target_dir: Option, update: UpdateMode, + debug: bool, verbose: bool, progress_bar: bool, } +/// Enum representing various debug states of the offload and reflink actions. +#[derive(Debug)] +#[allow(dead_code)] // All of them are used on Linux +enum OffloadReflinkDebug { + Unknown, + No, + Yes, + Avoided, + Unsupported, +} + +/// Enum representing various debug states of the sparse detection. +#[derive(Debug)] +#[allow(dead_code)] // silent for now until we use them +enum SparseDebug { + Unknown, + No, + Zeros, + SeekHole, + SeekHoleZeros, + Unsupported, +} + +/// Struct that contains the debug state for each action in a file copy operation. +#[derive(Debug)] +struct CopyDebug { + offload: OffloadReflinkDebug, + reflink: OffloadReflinkDebug, + sparse_detection: SparseDebug, +} + +impl OffloadReflinkDebug { + fn to_string(&self) -> &'static str { + match self { + Self::No => "no", + Self::Yes => "yes", + Self::Avoided => "avoided", + Self::Unsupported => "unsupported", + Self::Unknown => "unknown", + } + } +} + +impl SparseDebug { + fn to_string(&self) -> &'static str { + match self { + Self::No => "no", + Self::Zeros => "zeros", + Self::SeekHole => "SEEK_HOLE", + Self::SeekHoleZeros => "SEEK_HOLE + zeros", + Self::Unsupported => "unsupported", + Self::Unknown => "unknown", + } + } +} + +/// This function prints the debug information of a file copy operation if +/// no hard link or symbolic link is required, and data copy is required. +/// It prints the debug information of the offload, reflink, and sparse detection actions. +fn show_debug(copy_debug: &CopyDebug) { + println!( + "copy offload: {}, reflink: {}, sparse detection: {}", + copy_debug.offload.to_string(), + copy_debug.reflink.to_string(), + copy_debug.sparse_detection.to_string(), + ); +} + const ABOUT: &str = help_about!("cp.md"); const USAGE: &str = help_usage!("cp.md"); const AFTER_HELP: &str = help_section!("after help", "cp.md"); @@ -269,6 +339,7 @@ mod options { pub const STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; pub const SYMBOLIC_LINK: &str = "symbolic-link"; pub const TARGET_DIRECTORY: &str = "target-directory"; + pub const DEBUG: &str = "debug"; pub const VERBOSE: &str = "verbose"; } @@ -312,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') @@ -369,6 +441,12 @@ pub fn uu_app() -> Command { .help("remove any trailing slashes from each SOURCE argument") .action(ArgAction::SetTrue), ) + .arg( + Arg::new(options::DEBUG) + .long(options::DEBUG) + .help("explain how a file is copied. Implies -v") + .action(ArgAction::SetTrue), + ) .arg( Arg::new(options::VERBOSE) .short('v') @@ -438,6 +516,7 @@ pub fn uu_app() -> Command { PRESERVABLE_ATTRIBUTES, )) .num_args(0..) + .require_equals(true) .value_name("ATTR_LIST") .overrides_with_all([ options::ARCHIVE, @@ -839,7 +918,8 @@ impl Options { one_file_system: matches.get_flag(options::ONE_FILE_SYSTEM), parents: matches.get_flag(options::PARENTS), update: update_mode, - verbose: matches.get_flag(options::VERBOSE), + debug: matches.get_flag(options::DEBUG), + verbose: matches.get_flag(options::VERBOSE) || matches.get_flag(options::DEBUG), strip_trailing_slashes: matches.get_flag(options::STRIP_TRAILING_SLASHES), reflink_mode: { if let Some(reflink) = matches.get_one::(options::REFLINK) { @@ -1025,23 +1105,21 @@ fn preserve_hardlinks( } /// When handling errors, we don't always want to show them to the user. This function handles that. -/// If the error is printed, returns true, false otherwise. -fn show_error_if_needed(error: &Error) -> bool { +fn show_error_if_needed(error: &Error) { match error { // When using --no-clobber, we don't want to show // an error message - Error::NotAllFilesCopied => (), + Error::NotAllFilesCopied => { + // Need to return an error code + } Error::Skipped => { // touch a b && echo "n"|cp -i a b && echo $? // should return an error from GNU 9.2 - return true; } _ => { show_error!("{}", error); - return true; } } - false } /// Copy all `sources` to `target`. Returns an @@ -1098,9 +1176,8 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu options, &mut symlinked_files, ) { - if show_error_if_needed(&error) { - non_fatal_errors = true; - } + show_error_if_needed(&error); + non_fatal_errors = true; } } seen_sources.insert(source); @@ -1177,13 +1254,23 @@ fn copy_source( } impl OverwriteMode { - fn verify(&self, path: &Path) -> CopyResult<()> { + fn verify(&self, path: &Path, verbose: bool) -> CopyResult<()> { match *self { - Self::NoClobber => Err(Error::NotAllFilesCopied), + Self::NoClobber => { + if verbose { + println!("skipped {}", path.quote()); + } else { + eprintln!("{}: not replacing {}", util_name(), path.quote()); + } + Err(Error::NotAllFilesCopied) + } Self::Interactive(_) => { if prompt_yes!("overwrite {}?", path.quote()) { Ok(()) } else { + if verbose { + println!("skipped {}", path.quote()); + } Err(Error::Skipped) } } @@ -1391,7 +1478,7 @@ fn handle_existing_dest( return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into()); } - options.overwrite.verify(dest)?; + options.overwrite.verify(dest, options.verbose)?; let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); if let Some(backup_path) = backup_path { @@ -1749,11 +1836,11 @@ fn copy_helper( File::create(dest).context(dest.display().to_string())?; } else if source_is_fifo && options.recursive && !options.copy_contents { #[cfg(unix)] - copy_fifo(dest, options.overwrite)?; + copy_fifo(dest, options.overwrite, options.verbose)?; } else if source_is_symlink { copy_link(source, dest, symlinked_files)?; } else { - copy_on_write( + let copy_debug = copy_on_write( source, dest, options.reflink_mode, @@ -1762,6 +1849,10 @@ fn copy_helper( #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] source_is_fifo, )?; + + if !options.attributes_only && options.debug { + show_debug(©_debug); + } } Ok(()) @@ -1770,9 +1861,9 @@ fn copy_helper( // "Copies" a FIFO by creating a new one. This workaround is because Rust's // built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390). #[cfg(unix)] -fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> { +fn copy_fifo(dest: &Path, overwrite: OverwriteMode, verbose: bool) -> CopyResult<()> { if dest.exists() { - overwrite.verify(dest)?; + overwrite.verify(dest, verbose)?; fs::remove_file(dest)?; } diff --git a/src/uu/cp/src/platform/linux.rs b/src/uu/cp/src/platform/linux.rs index 7d97813dd..18f2520a2 100644 --- a/src/uu/cp/src/platform/linux.rs +++ b/src/uu/cp/src/platform/linux.rs @@ -13,7 +13,7 @@ use quick_error::ResultExt; use uucore::mode::get_umask; -use crate::{CopyResult, ReflinkMode, SparseMode}; +use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; // From /usr/include/linux/fs.h: // #define FICLONE _IOW(0x94, 9, int) @@ -145,24 +145,51 @@ pub(crate) fn copy_on_write( sparse_mode: SparseMode, context: &str, source_is_fifo: bool, -) -> CopyResult<()> { +) -> CopyResult { + let mut copy_debug = CopyDebug { + offload: OffloadReflinkDebug::Unknown, + reflink: OffloadReflinkDebug::Unsupported, + sparse_detection: SparseDebug::No, + }; + let result = match (reflink_mode, sparse_mode) { - (ReflinkMode::Never, SparseMode::Always) => sparse_copy(source, dest), - (ReflinkMode::Never, _) => std::fs::copy(source, dest).map(|_| ()), - (ReflinkMode::Auto, SparseMode::Always) => sparse_copy(source, dest), + (ReflinkMode::Never, SparseMode::Always) => { + copy_debug.sparse_detection = SparseDebug::Zeros; + copy_debug.offload = OffloadReflinkDebug::Avoided; + copy_debug.reflink = OffloadReflinkDebug::No; + sparse_copy(source, dest) + } + (ReflinkMode::Never, _) => { + copy_debug.sparse_detection = SparseDebug::No; + copy_debug.reflink = OffloadReflinkDebug::No; + std::fs::copy(source, dest).map(|_| ()) + } + (ReflinkMode::Auto, SparseMode::Always) => { + copy_debug.offload = OffloadReflinkDebug::Avoided; + copy_debug.sparse_detection = SparseDebug::Zeros; + copy_debug.reflink = OffloadReflinkDebug::Unsupported; + sparse_copy(source, dest) + } (ReflinkMode::Auto, _) => { + copy_debug.sparse_detection = SparseDebug::No; + copy_debug.reflink = OffloadReflinkDebug::Unsupported; if source_is_fifo { copy_fifo_contents(source, dest).map(|_| ()) } else { clone(source, dest, CloneFallback::FSCopy) } } - (ReflinkMode::Always, SparseMode::Auto) => clone(source, dest, CloneFallback::Error), + (ReflinkMode::Always, SparseMode::Auto) => { + copy_debug.sparse_detection = SparseDebug::No; + copy_debug.reflink = OffloadReflinkDebug::Yes; + + clone(source, dest, CloneFallback::Error) + } (ReflinkMode::Always, _) => { return Err("`--reflink=always` can be used only with --sparse=auto".into()) } }; result.context(context)?; - Ok(()) + Ok(copy_debug) } diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs index 4407e0edf..b173aa959 100644 --- a/src/uu/cp/src/platform/macos.rs +++ b/src/uu/cp/src/platform/macos.rs @@ -11,7 +11,7 @@ use std::path::Path; use quick_error::ResultExt; -use crate::{CopyResult, ReflinkMode, SparseMode}; +use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; /// Copies `source` to `dest` using copy-on-write if possible. /// @@ -24,10 +24,15 @@ pub(crate) fn copy_on_write( sparse_mode: SparseMode, context: &str, source_is_fifo: bool, -) -> CopyResult<()> { +) -> CopyResult { if sparse_mode != SparseMode::Auto { return Err("--sparse is only supported on linux".to_string().into()); } + let mut copy_debug = CopyDebug { + offload: OffloadReflinkDebug::Unknown, + reflink: OffloadReflinkDebug::Unsupported, + sparse_detection: SparseDebug::Unsupported, + }; // Extract paths in a form suitable to be passed to a syscall. // The unwrap() is safe because they come from the command-line and so contain non nul @@ -72,6 +77,7 @@ pub(crate) fn copy_on_write( return Err(format!("failed to clone {source:?} from {dest:?}: {error}").into()) } _ => { + copy_debug.reflink = OffloadReflinkDebug::Yes; if source_is_fifo { let mut src_file = File::open(source)?; let mut dst_file = File::create(dest)?; @@ -83,5 +89,5 @@ pub(crate) fn copy_on_write( }; } - Ok(()) + Ok(copy_debug) } diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index b70da2f23..f5882f75e 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -8,7 +8,7 @@ use std::path::Path; use quick_error::ResultExt; -use crate::{CopyResult, ReflinkMode, SparseMode}; +use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; /// Copies `source` to `dest` for systems without copy-on-write pub(crate) fn copy_on_write( @@ -17,7 +17,7 @@ pub(crate) fn copy_on_write( reflink_mode: ReflinkMode, sparse_mode: SparseMode, context: &str, -) -> CopyResult<()> { +) -> CopyResult { if reflink_mode != ReflinkMode::Never { return Err("--reflink is only supported on linux and macOS" .to_string() @@ -26,8 +26,12 @@ pub(crate) fn copy_on_write( if sparse_mode != SparseMode::Auto { return Err("--sparse is only supported on linux".to_string().into()); } - + let copy_debug = CopyDebug { + offload: OffloadReflinkDebug::Unsupported, + reflink: OffloadReflinkDebug::Unsupported, + sparse_detection: SparseDebug::Unsupported, + }; fs::copy(source, dest).context(context)?; - Ok(()) + Ok(copy_debug) } diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index 560ddf6e8..051f567b4 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_csplit" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output" @@ -15,10 +15,10 @@ edition = "2021" path = "src/csplit.rs" [dependencies] -clap = { workspace=true } +clap = { workspace = true } thiserror = { workspace = true } -regex = { workspace=true } -uucore = { workspace=true, features=["entries", "fs"] } +regex = { workspace = true } +uucore = { workspace = true, features = ["entries", "fs"] } [[bin]] name = "csplit" diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 1a94b4156..d1925308e 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -127,7 +127,7 @@ where I: Iterator)>, { // split the file based on patterns - for pattern in patterns.into_iter() { + for pattern in patterns { let pattern_as_str = pattern.to_string(); let is_skip = matches!(pattern, patterns::Pattern::SkipToMatch(_, _, _)); match pattern { @@ -552,6 +552,109 @@ where } } +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let args = args.collect_ignore(); + + let matches = uu_app().try_get_matches_from(args)?; + + // get the file to split + let file_name = matches.get_one::(options::FILE).unwrap(); + + // get the patterns to split on + let patterns: Vec = matches + .get_many::(options::PATTERN) + .unwrap() + .map(|s| s.to_string()) + .collect(); + let patterns = patterns::get_patterns(&patterns[..])?; + let options = CsplitOptions::new(&matches); + if file_name == "-" { + let stdin = io::stdin(); + Ok(csplit(&options, patterns, stdin.lock())?) + } else { + let file = File::open(file_name) + .map_err_context(|| format!("cannot access {}", file_name.quote()))?; + let file_metadata = file + .metadata() + .map_err_context(|| format!("cannot access {}", file_name.quote()))?; + if !file_metadata.is_file() { + return Err(CsplitError::NotRegularFile(file_name.to_string()).into()); + } + Ok(csplit(&options, patterns, BufReader::new(file))?) + } +} + +pub fn uu_app() -> Command { + Command::new(uucore::util_name()) + .version(crate_version!()) + .about(ABOUT) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) + .arg( + Arg::new(options::SUFFIX_FORMAT) + .short('b') + .long(options::SUFFIX_FORMAT) + .value_name("FORMAT") + .help("use sprintf FORMAT instead of %02d"), + ) + .arg( + Arg::new(options::PREFIX) + .short('f') + .long(options::PREFIX) + .value_name("PREFIX") + .help("use PREFIX instead of 'xx'"), + ) + .arg( + Arg::new(options::KEEP_FILES) + .short('k') + .long(options::KEEP_FILES) + .help("do not remove output files on errors") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::SUPPRESS_MATCHED) + .long(options::SUPPRESS_MATCHED) + .help("suppress the lines matching PATTERN") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::DIGITS) + .short('n') + .long(options::DIGITS) + .value_name("DIGITS") + .help("use specified number of digits instead of 2"), + ) + .arg( + Arg::new(options::QUIET) + .short('s') + .long(options::QUIET) + .visible_alias("silent") + .help("do not print counts of output file sizes") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::ELIDE_EMPTY_FILES) + .short('z') + .long(options::ELIDE_EMPTY_FILES) + .help("remove empty output files") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::FILE) + .hide(true) + .required(true) + .value_hint(clap::ValueHint::FilePath), + ) + .arg( + Arg::new(options::PATTERN) + .hide(true) + .action(clap::ArgAction::Append) + .required(true), + ) + .after_help(AFTER_HELP) +} + #[cfg(test)] mod tests { use super::*; @@ -714,106 +817,3 @@ mod tests { assert!(input_splitter.next().is_none()); } } - -#[uucore::main] -pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_ignore(); - - let matches = uu_app().try_get_matches_from(args)?; - - // get the file to split - let file_name = matches.get_one::(options::FILE).unwrap(); - - // get the patterns to split on - let patterns: Vec = matches - .get_many::(options::PATTERN) - .unwrap() - .map(|s| s.to_string()) - .collect(); - let patterns = patterns::get_patterns(&patterns[..])?; - let options = CsplitOptions::new(&matches); - if file_name == "-" { - let stdin = io::stdin(); - Ok(csplit(&options, patterns, stdin.lock())?) - } else { - let file = File::open(file_name) - .map_err_context(|| format!("cannot access {}", file_name.quote()))?; - let file_metadata = file - .metadata() - .map_err_context(|| format!("cannot access {}", file_name.quote()))?; - if !file_metadata.is_file() { - return Err(CsplitError::NotRegularFile(file_name.to_string()).into()); - } - Ok(csplit(&options, patterns, BufReader::new(file))?) - } -} - -pub fn uu_app() -> Command { - Command::new(uucore::util_name()) - .version(crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .infer_long_args(true) - .arg( - Arg::new(options::SUFFIX_FORMAT) - .short('b') - .long(options::SUFFIX_FORMAT) - .value_name("FORMAT") - .help("use sprintf FORMAT instead of %02d"), - ) - .arg( - Arg::new(options::PREFIX) - .short('f') - .long(options::PREFIX) - .value_name("PREFIX") - .help("use PREFIX instead of 'xx'"), - ) - .arg( - Arg::new(options::KEEP_FILES) - .short('k') - .long(options::KEEP_FILES) - .help("do not remove output files on errors") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::SUPPRESS_MATCHED) - .long(options::SUPPRESS_MATCHED) - .help("suppress the lines matching PATTERN") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::DIGITS) - .short('n') - .long(options::DIGITS) - .value_name("DIGITS") - .help("use specified number of digits instead of 2"), - ) - .arg( - Arg::new(options::QUIET) - .short('s') - .long(options::QUIET) - .visible_alias("silent") - .help("do not print counts of output file sizes") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::ELIDE_EMPTY_FILES) - .short('z') - .long(options::ELIDE_EMPTY_FILES) - .help("remove empty output files") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::FILE) - .hide(true) - .required(true) - .value_hint(clap::ValueHint::FilePath), - ) - .arg( - Arg::new(options::PATTERN) - .hide(true) - .action(clap::ArgAction::Append) - .required(true), - ) - .after_help(AFTER_HELP) -} diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 6e49ce4d4..6f37aa336 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cut" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" @@ -15,11 +15,11 @@ edition = "2021" path = "src/cut.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } -memchr = { workspace=true } -bstr = { workspace=true } -is-terminal = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } +memchr = { workspace = true } +bstr = { workspace = true } +is-terminal = { workspace = true } [[bin]] name = "cut" diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index 923ca4aa7..de5b5f2a2 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,7 +1,7 @@ -# spell-checker:ignore humantime +# spell-checker:ignore datetime [package] name = "uu_date" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "date ~ (uutils) display or set the current time" @@ -16,18 +16,19 @@ edition = "2021" path = "src/date.rs" [dependencies] -chrono = { workspace=true } -#/ TODO: check if we can avoid chrono+time -time = { workspace=true } -clap = { workspace=true } -uucore = { workspace=true } -humantime_to_duration = { workspace=true } +chrono = { workspace = true } +clap = { workspace = true } +uucore = { workspace = true } +parse_datetime = { workspace = true } [target.'cfg(unix)'.dependencies] -libc = { workspace=true } +libc = { workspace = true } [target.'cfg(windows)'.dependencies] -windows-sys = { workspace=true, features = ["Win32_Foundation", "Win32_System_SystemInformation"] } +windows-sys = { workspace = true, features = [ + "Win32_Foundation", + "Win32_System_SystemInformation", +] } [[bin]] name = "date" diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 381619f06..adfb74128 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -6,10 +6,10 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes humantime +// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes use chrono::format::{Item, StrftimeItems}; -use chrono::{DateTime, Duration as ChronoDuration, FixedOffset, Local, Offset, Utc}; +use chrono::{DateTime, Duration, FixedOffset, Local, Offset, Utc}; #[cfg(windows)] use chrono::{Datelike, Timelike}; use clap::{crate_version, Arg, ArgAction, Command}; @@ -18,7 +18,6 @@ use libc::{clock_settime, timespec, CLOCK_REALTIME}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; -use time::Duration; use uucore::display::Quotable; #[cfg(not(any(target_os = "redox")))] use uucore::error::FromIo; @@ -27,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"); @@ -111,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` @@ -132,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}"), @@ -171,7 +169,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let date_source = if let Some(date) = matches.get_one::(OPT_DATE) { - if let Ok(duration) = humantime_to_duration::from_str(date.as_str()) { + if let Ok(duration) = parse_datetime::from_str(date.as_str()) { DateSource::Human(duration) } else { DateSource::Custom(date.into()) @@ -226,13 +224,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let iter = std::iter::once(date); Box::new(iter) } - DateSource::Human(ref input) => { - // Get the current DateTime and convert the input time::Duration to chrono::Duration - // for things like "1 year ago" + DateSource::Human(relative_time) => { + // Get the current DateTime for things like "1 year ago" let current_time = DateTime::::from(Local::now()); - let input_chrono = ChronoDuration::seconds(input.as_seconds_f32() as i64) - + ChronoDuration::nanoseconds(input.subsec_nanoseconds() as i64); - let iter = std::iter::once(Ok(current_time + input_chrono)); + let iter = std::iter::once(Ok(current_time + relative_time)); Box::new(iter) } DateSource::File(ref path) => { @@ -321,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), @@ -337,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( diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 04b60dc31..0a69ae374 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dd" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "dd ~ (uutils) copy and convert files" @@ -15,16 +15,16 @@ edition = "2021" path = "src/dd.rs" [dependencies] -clap = { workspace=true } -gcd = { workspace=true } -libc = { workspace=true } -uucore = { workspace=true, features=["memo"] } +clap = { workspace = true } +gcd = { workspace = true } +libc = { workspace = true } +uucore = { workspace = true, features = ["memo"] } [target.'cfg(any(target_os = "linux"))'.dependencies] -nix = { workspace=true, features = ["fs"] } +nix = { workspace = true, features = ["fs"] } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] -signal-hook = { workspace=true } +signal-hook = { workspace = true } [[bin]] name = "dd" diff --git a/src/uu/dd/dd.md b/src/uu/dd/dd.md index b60fc14fa..504910884 100644 --- a/src/uu/dd/dd.md +++ b/src/uu/dd/dd.md @@ -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. diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 6727f4bb0..4e1287939 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -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, } +/// 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, +} + +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( output: &mut Output, rstat: ReadStat, wstat: WriteStat, - start: time::Instant, + start: Instant, prog_tx: &mpsc::Sender, output_thread: thread::JoinHandle, ) -> std::io::Result<()> { diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index 65af053b8..a9d29ff63 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -498,7 +498,7 @@ mod tests { fn prog_update_write(n: u128) -> ProgUpdate { ProgUpdate { - read_stat: Default::default(), + read_stat: ReadStat::default(), write_stat: WriteStat { bytes_total: n, ..Default::default() @@ -510,8 +510,8 @@ mod tests { fn prog_update_duration(duration: Duration) -> ProgUpdate { ProgUpdate { - read_stat: Default::default(), - write_stat: Default::default(), + read_stat: ReadStat::default(), + write_stat: WriteStat::default(), duration, complete: false, } @@ -557,8 +557,8 @@ mod tests { #[test] fn test_prog_update_write_prog_line() { let prog_update = ProgUpdate { - read_stat: Default::default(), - write_stat: Default::default(), + read_stat: ReadStat::default(), + write_stat: WriteStat::default(), duration: Duration::new(1, 0), // one second complete: false, }; @@ -613,8 +613,8 @@ mod tests { #[test] fn write_transfer_stats() { let prog_update = ProgUpdate { - read_stat: Default::default(), - write_stat: Default::default(), + read_stat: ReadStat::default(), + write_stat: WriteStat::default(), duration: Duration::new(1, 0), // one second complete: false, }; @@ -634,8 +634,8 @@ mod tests { fn write_final_transfer_stats() { // Tests the formatting of the final statistics written after a progress line. let prog_update = ProgUpdate { - read_stat: Default::default(), - write_stat: Default::default(), + read_stat: ReadStat::default(), + write_stat: WriteStat::default(), duration: Duration::new(1, 0), // one second complete: false, }; diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index 9cca407bb..047fe9be0 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_df" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "df ~ (uutils) display file system information" @@ -15,9 +15,12 @@ edition = "2021" path = "src/df.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["libc", "fsext"] } -unicode-width = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true, features = ["libc", "fsext"] } +unicode-width = { workspace = true } + +[dev-dependencies] +tempfile = "3" [[bin]] name = "df" diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 685ebe58a..ef1584323 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -746,7 +746,7 @@ mod tests { #[test] fn test_remote_included() { - let opt = Default::default(); + let opt = Options::default(); let m = mount_info("ext4", "/mnt/foo", true, false); assert!(is_included(&m, &opt)); } @@ -773,7 +773,7 @@ mod tests { #[test] fn test_dummy_excluded() { - let opt = Default::default(); + let opt = Options::default(); let m = mount_info("ext4", "/mnt/foo", false, true); assert!(!is_included(&m, &opt)); } @@ -864,11 +864,11 @@ mod tests { mod filter_mount_list { - use crate::filter_mount_list; + use crate::{filter_mount_list, Options}; #[test] fn test_empty() { - let opt = Default::default(); + let opt = Options::default(); let mount_infos = vec![]; assert!(filter_mount_list(mount_infos, &opt).is_empty()); } diff --git a/src/uu/df/src/filesystem.rs b/src/uu/df/src/filesystem.rs index 461815e67..d50822e7f 100644 --- a/src/uu/df/src/filesystem.rs +++ b/src/uu/df/src/filesystem.rs @@ -42,7 +42,7 @@ pub(crate) struct Filesystem { /// This function returns the element of `mounts` on which `path` is /// mounted. If there are no matches, this function returns /// [`None`]. If there are two or more matches, then the single -/// [`MountInfo`] with the longest mount directory is returned. +/// [`MountInfo`] with the device name corresponding to the entered path. /// /// If `canonicalize` is `true`, then the `path` is canonicalized /// before checking whether it matches any mount directories. @@ -68,9 +68,19 @@ where path.as_ref().to_path_buf() }; + // Find the potential mount point that matches entered path let maybe_mount_point = mounts .iter() - .find(|mi| mi.dev_name.eq(&path.to_string_lossy())); + // Create pair MountInfo, canonicalized device name + // TODO Abstract from accessing real filesystem to + // make code more testable + .map(|m| (m, std::fs::canonicalize(&m.dev_name))) + // Ignore non existing paths + .filter(|m| m.1.is_ok()) + .map(|m| (m.0, m.1.ok().unwrap())) + // Try to find canonicalized device name corresponding to entered path + .find(|m| m.1.eq(&path)) + .map(|m| m.0); maybe_mount_point.or_else(|| { mounts @@ -148,12 +158,12 @@ mod tests { // Create a fake `MountInfo` with the given directory name. fn mount_info(mount_dir: &str) -> MountInfo { MountInfo { - dev_id: Default::default(), - dev_name: Default::default(), - fs_type: Default::default(), + dev_id: String::default(), + dev_name: String::default(), + fs_type: String::default(), mount_dir: String::from(mount_dir), - mount_option: Default::default(), - mount_root: Default::default(), + mount_option: String::default(), + mount_root: String::default(), remote: Default::default(), dummy: Default::default(), } @@ -211,10 +221,16 @@ mod tests { #[test] fn test_dev_name_match() { + let tmp = tempfile::TempDir::new().expect("Failed to create temp dir"); + let dev_name = std::fs::canonicalize(tmp.path()) + .expect("Failed to canonicalize tmp path") + .to_string_lossy() + .to_string(); + let mut mount_info = mount_info("/foo"); - mount_info.dev_name = "/dev/sda2".to_string(); + mount_info.dev_name = dev_name.clone(); let mounts = [mount_info]; - let actual = mount_info_from_path(&mounts, "/dev/sda2", false).unwrap(); + let actual = mount_info_from_path(&mounts, dev_name, false).unwrap(); assert!(mount_info_eq(actual, &mounts[0])); } } diff --git a/src/uu/df/src/table.rs b/src/uu/df/src/table.rs index a1a58792d..06bfc3383 100644 --- a/src/uu/df/src/table.rs +++ b/src/uu/df/src/table.rs @@ -513,7 +513,7 @@ mod tests { #[test] fn test_default_header() { - let options = Default::default(); + let options = Options::default(); assert_eq!( Header::get_headers(&options), vec!( diff --git a/src/uu/dir/Cargo.toml b/src/uu/dir/Cargo.toml index c8cb861f9..7bd144f02 100644 --- a/src/uu/dir/Cargo.toml +++ b/src/uu/dir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dir" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -C -b" @@ -15,9 +15,9 @@ edition = "2021" path = "src/dir.rs" [dependencies] -clap = { workspace=true, features = ["env"] } -uucore = { workspace=true, features=["entries", "fs"] } -uu_ls = { workspace=true } +clap = { workspace = true, features = ["env"] } +uucore = { workspace = true, features = ["entries", "fs"] } +uu_ls = { workspace = true } [[bin]] name = "dir" diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index 7d729f396..f490a4728 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dircolors" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "dircolors ~ (uutils) display commands to set LS_COLORS" @@ -15,8 +15,8 @@ edition = "2021" path = "src/dircolors.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "dircolors" diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index f002bf15a..4ac789937 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dirname" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "dirname ~ (uutils) display parent directory of PATHNAME" @@ -15,8 +15,8 @@ edition = "2021" path = "src/dirname.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "dirname" diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 22659bc0e..041efeaad 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_du" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "du ~ (uutils) display disk usage" @@ -15,14 +15,17 @@ edition = "2021" path = "src/du.rs" [dependencies] -chrono = { workspace=true } +chrono = { workspace = true } # For the --exclude & --exclude-from options -glob = { workspace=true } -clap = { workspace=true } -uucore = { workspace=true } +glob = { workspace = true } +clap = { workspace = true } +uucore = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] -windows-sys = { workspace=true, features = ["Win32_Storage_FileSystem", "Win32_Foundation"] } +windows-sys = { workspace = true, features = [ + "Win32_Storage_FileSystem", + "Win32_Foundation", +] } [[bin]] name = "du" diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 3325ca1f5..db385c720 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -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, diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index 276fdaf85..a95bcbe82 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_echo" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "echo ~ (uutils) display TEXT" @@ -15,8 +15,8 @@ edition = "2021" path = "src/echo.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "echo" diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index 689bb12a4..551249b77 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_env" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND" @@ -15,12 +15,12 @@ edition = "2021" path = "src/env.rs" [dependencies] -clap = { workspace=true } -rust-ini = { workspace=true } -uucore = { workspace=true, features=["signals"]} +clap = { workspace = true } +rust-ini = { workspace = true } +uucore = { workspace = true, features = ["signals"] } [target.'cfg(unix)'.dependencies] -nix = { workspace=true, features = ["signal"] } +nix = { workspace = true, features = ["signal"] } [[bin]] diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index f30fcbce5..894a21be0 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expand" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "expand ~ (uutils) convert input tabs to spaces" @@ -15,9 +15,9 @@ edition = "2021" path = "src/expand.rs" [dependencies] -clap = { workspace=true } -unicode-width = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +unicode-width = { workspace = true } +uucore = { workspace = true } [[bin]] name = "expand" diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 26c9d6724..3ea796ee0 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expr" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "expr ~ (uutils) display the value of EXPRESSION" @@ -15,11 +15,11 @@ edition = "2021" path = "src/expr.rs" [dependencies] -clap = { workspace=true } -num-bigint = { workspace=true } -num-traits = { workspace=true } -onig = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +num-bigint = { workspace = true } +num-traits = { workspace = true } +onig = { workspace = true } +uucore = { workspace = true } [[bin]] name = "expr" diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 8de973503..c97dbcba4 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_factor" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "factor ~ (uutils) display the prime factors of each NUMBER" @@ -12,15 +12,15 @@ categories = ["command-line-utilities"] edition = "2021" [build-dependencies] -num-traits = { workspace=true } # used in src/numerics.rs, which is included by build.rs +num-traits = { workspace = true } # used in src/numerics.rs, which is included by build.rs [dependencies] -clap = { workspace=true } -coz = { workspace=true, optional = true } -num-traits = { workspace=true } -rand = { workspace=true } -smallvec = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +coz = { workspace = true, optional = true } +num-traits = { workspace = true } +rand = { workspace = true } +smallvec = { workspace = true } +uucore = { workspace = true } [dev-dependencies] quickcheck = "1.0.3" diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index 44d983788..9eb0ccbb1 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_false" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "false ~ (uutils) do nothing and fail" @@ -15,8 +15,8 @@ edition = "2021" path = "src/false.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "false" diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index 1446e8ba5..cabb144cf 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fmt" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "fmt ~ (uutils) reformat each paragraph of input" @@ -15,9 +15,9 @@ edition = "2021" path = "src/fmt.rs" [dependencies] -clap = { workspace=true } -unicode-width = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +unicode-width = { workspace = true } +uucore = { workspace = true } [[bin]] name = "fmt" diff --git a/src/uu/fmt/src/parasplit.rs b/src/uu/fmt/src/parasplit.rs index a2d70b088..c94c81974 100644 --- a/src/uu/fmt/src/parasplit.rs +++ b/src/uu/fmt/src/parasplit.rs @@ -598,7 +598,7 @@ impl<'a> Iterator for WordSplit<'a> { self.prev_punct && (before_tab.is_some() || word_start_relative > 1); // now record whether this word ends in punctuation - self.prev_punct = match self.string[..self.position].chars().rev().next() { + self.prev_punct = match self.string[..self.position].chars().next_back() { Some(ch) => WordSplit::is_punctuation(ch), _ => panic!("fatal: expected word not to be empty"), }; diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index 3358fb7cc..a476e0f13 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fold" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "fold ~ (uutils) wrap each line of input" @@ -15,8 +15,8 @@ edition = "2021" path = "src/fold.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "fold" diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 940661bc4..989dc9c11 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_groups" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "groups ~ (uutils) display group memberships for USERNAME" @@ -15,8 +15,8 @@ edition = "2021" path = "src/groups.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["entries", "process"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["entries", "process"] } [[bin]] name = "groups" diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 638b3b60f..cca2dbfe1 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hashsum" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "hashsum ~ (uutils) display or check input digests" @@ -15,11 +15,11 @@ edition = "2021" path = "src/hashsum.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["sum"] } -memchr = { workspace=true } -regex = { workspace=true } -hex = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true, features = ["sum"] } +memchr = { workspace = true } +regex = { workspace = true } +hex = { workspace = true } [[bin]] name = "hashsum" diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 7b571efcd..cc1b050fd 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -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(); diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index a82678285..6326fcd8b 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_head" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "head ~ (uutils) display the first lines of input" @@ -15,9 +15,9 @@ edition = "2021" path = "src/head.rs" [dependencies] -clap = { workspace=true } -memchr = { workspace=true } -uucore = { workspace=true, features=["ringbuffer", "lines"] } +clap = { workspace = true } +memchr = { workspace = true } +uucore = { workspace = true, features = ["ringbuffer", "lines"] } [[bin]] name = "head" diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 2ccf20904..2517a98ea 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -575,7 +575,7 @@ mod tests { } #[test] fn test_options_correct_defaults() { - let opts: HeadOptions = Default::default(); + let opts = HeadOptions::default(); assert!(!opts.verbose); assert!(!opts.quiet); @@ -622,12 +622,10 @@ mod tests { #[test] #[cfg(target_os = "linux")] fn test_arg_iterate_bad_encoding() { - #[allow(clippy::invalid_utf8_in_unchecked)] - let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") }; + use std::os::unix::ffi::OsStringExt; + let invalid = OsString::from_vec(vec![b'\x80', b'\x81']); // this arises from a conversion from OsString to &str - assert!( - arg_iterate(vec![OsString::from("head"), OsString::from(invalid)].into_iter()).is_err() - ); + assert!(arg_iterate(vec![OsString::from("head"), invalid].into_iter()).is_err()); } #[test] fn read_early_exit() { diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index b24b6591e..56c359a0c 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -114,7 +114,13 @@ pub fn parse_num(src: &str) -> Result<(u64, bool), ParseSizeError> { return Err(ParseSizeError::ParseFailure(src.to_string())); } - parse_size(size_string).map(|n| (n, all_but_last)) + // remove leading zeros so that size is interpreted as decimal, not octal + let trimmed_string = size_string.trim_start_matches('0'); + if trimmed_string.is_empty() { + Ok((0, all_but_last)) + } else { + parse_size(trimmed_string).map(|n| (n, all_but_last)) + } } #[cfg(test)] diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index a31286bbd..298a9da69 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostid" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "hostid ~ (uutils) display the numeric identifier of the current host" @@ -15,9 +15,9 @@ edition = "2021" path = "src/hostid.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +libc = { workspace = true } +uucore = { workspace = true } [[bin]] name = "hostid" diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index c39f8fdae..d28f2511f 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostname" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "hostname ~ (uutils) display or set the host name of the current host" @@ -15,12 +15,15 @@ edition = "2021" path = "src/hostname.rs" [dependencies] -clap = { workspace=true } +clap = { workspace = true } hostname = { version = "0.3", features = ["set"] } -uucore = { workspace=true, features=["wide"] } +uucore = { workspace = true, features = ["wide"] } [target.'cfg(target_os = "windows")'.dependencies] -windows-sys = { workspace=true, features = ["Win32_Networking_WinSock", "Win32_Foundation"] } +windows-sys = { workspace = true, features = [ + "Win32_Networking_WinSock", + "Win32_Foundation", +] } [[bin]] name = "hostname" diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index dd363340b..bd80a447c 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_id" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "id ~ (uutils) display user and group information for USER" @@ -15,9 +15,9 @@ edition = "2021" path = "src/id.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["entries", "process"] } -selinux = { workspace=true, optional=true } +clap = { workspace = true } +uucore = { workspace = true, features = ["entries", "process"] } +selinux = { workspace = true, optional = true } [[bin]] name = "id" diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 789f84de3..8228e0d20 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "uu_install" -version = "0.0.18" -authors = [ - "Ben Eills ", - "uutils developers", -] +version = "0.0.20" +authors = ["Ben Eills ", "uutils developers"] license = "MIT" description = "install ~ (uutils) copy files from SOURCE to DESTINATION (with specified attributes)" @@ -18,14 +15,11 @@ edition = "2021" path = "src/install.rs" [dependencies] -clap = { workspace=true } -filetime = { workspace=true } -file_diff = { workspace=true } -libc = { workspace=true } -uucore = { workspace=true, features=["fs", "mode", "perms", "entries"] } - -[dev-dependencies] -time = { workspace=true } +clap = { workspace = true } +filetime = { workspace = true } +file_diff = { workspace = true } +libc = { workspace = true } +uucore = { workspace = true, features = ["fs", "mode", "perms", "entries"] } [[bin]] name = "install" diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index d92c032aa..a06303c24 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_join" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "join ~ (uutils) merge lines from inputs with matching join fields" @@ -15,9 +15,9 @@ edition = "2021" path = "src/join.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } -memchr = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } +memchr = { workspace = true } [[bin]] name = "join" diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index 21010160c..8db333e4a 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_kill" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "kill ~ (uutils) send a signal to a process" @@ -15,9 +15,9 @@ edition = "2021" path = "src/kill.rs" [dependencies] -clap = { workspace=true } -nix = { workspace=true, features = ["signal"] } -uucore = { workspace=true, features=["signals"] } +clap = { workspace = true } +nix = { workspace = true, features = ["signal"] } +uucore = { workspace = true, features = ["signals"] } [[bin]] name = "kill" diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index 2d81883a6..abac74b3f 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_link" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "link ~ (uutils) create a hard (file system) link to FILE" @@ -15,8 +15,8 @@ edition = "2021" path = "src/link.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "link" diff --git a/src/uu/ln/Cargo.toml b/src/uu/ln/Cargo.toml index c06218fb7..7674129f3 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ln" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "ln ~ (uutils) create a (file system) link to TARGET" @@ -15,8 +15,8 @@ edition = "2021" path = "src/ln.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["fs"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "ln" diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index e75571e0f..85cfb766a 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_logname" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "logname ~ (uutils) display the login name of the current user" @@ -15,9 +15,9 @@ edition = "2021" path = "src/logname.rs" [dependencies] -libc = { workspace=true } -clap = { workspace=true } -uucore = { workspace=true } +libc = { workspace = true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "logname" diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 251268c2b..6122460fc 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ls" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "ls ~ (uutils) display directory contents" @@ -15,18 +15,18 @@ edition = "2021" path = "src/ls.rs" [dependencies] -clap = { workspace=true, features = ["env"] } -chrono = { workspace=true } -unicode-width = { workspace=true } -number_prefix = { workspace=true } -term_grid = { workspace=true } -terminal_size = { workspace=true } -glob = { workspace=true } -lscolors = { workspace=true } -uucore = { workspace=true, features = ["entries", "fs"] } -once_cell = { workspace=true } -is-terminal = { workspace=true } -selinux = { workspace=true, optional = true } +clap = { workspace = true, features = ["env"] } +chrono = { workspace = true } +unicode-width = { workspace = true } +number_prefix = { workspace = true } +term_grid = { workspace = true } +terminal_size = { workspace = true } +glob = { workspace = true } +lscolors = { workspace = true } +uucore = { workspace = true, features = ["entries", "fs"] } +once_cell = { workspace = true } +is-terminal = { workspace = true } +selinux = { workspace = true, optional = true } [[bin]] name = "ls" diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index e5ad4bcd4..cead745cd 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -17,6 +17,8 @@ use lscolors::LsColors; use number_prefix::NumberPrefix; use once_cell::unsync::OnceCell; use std::collections::HashSet; +use std::num::IntErrorKind; + #[cfg(windows)] use std::os::windows::fs::MetadataExt; use std::{ @@ -294,6 +296,7 @@ enum Sort { Time, Version, Extension, + Width, } #[derive(PartialEq)] @@ -496,6 +499,7 @@ fn extract_sort(options: &clap::ArgMatches) -> Sort { "size" => Sort::Size, "version" => Sort::Version, "extension" => Sort::Extension, + "width" => Sort::Width, // below should never happen as clap already restricts the values. _ => unreachable!("Invalid field for --sort"), } @@ -655,6 +659,19 @@ fn extract_indicator_style(options: &clap::ArgMatches) -> IndicatorStyle { } } +fn parse_width(s: &str) -> Result { + let radix = match s.starts_with('0') && s.len() > 1 { + true => 8, + false => 10, + }; + match u16::from_str_radix(s, radix) { + Ok(x) => Ok(x), + Err(e) => match e.kind() { + IntErrorKind::PosOverflow => Ok(u16::MAX), + _ => Err(LsError::InvalidLineWidth(s.into())), + }, + } +} impl Config { #[allow(clippy::cognitive_complexity)] pub fn from(options: &clap::ArgMatches) -> UResult { @@ -793,20 +810,7 @@ impl Config { }; let width = match options.get_one::(options::WIDTH) { - Some(x) => { - if x.starts_with('0') && x.len() > 1 { - // Read number as octal - match u16::from_str_radix(x, 8) { - Ok(v) => v, - Err(_) => return Err(LsError::InvalidLineWidth(x.into()).into()), - } - } else { - match x.parse::() { - Ok(u) => u, - Err(_) => return Err(LsError::InvalidLineWidth(x.into()).into()), - } - } - } + Some(x) => parse_width(x)?, None => match terminal_size::terminal_size() { Some((width, _)) => width.0, None => match std::env::var_os("COLUMNS") { @@ -1200,6 +1204,7 @@ pub fn uu_app() -> Command { Arg::new(options::quoting::LITERAL) .short('N') .long(options::quoting::LITERAL) + .alias("l") .help("Use literal quoting style. Equivalent to `--quoting-style=literal`") .overrides_with_all([ options::QUOTING_STYLE, @@ -1322,9 +1327,9 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::SORT) .long(options::SORT) - .help("Sort by : name, none (-U), time (-t), size (-S) or extension (-X)") + .help("Sort by : name, none (-U), time (-t), size (-S), extension (-X) or width") .value_name("field") - .value_parser(["name", "none", "time", "size", "version", "extension"]) + .value_parser(["name", "none", "time", "size", "version", "extension", "width"]) .require_equals(true) .overrides_with_all([ options::SORT, @@ -1929,14 +1934,22 @@ fn sort_entries(entries: &mut [PathData], config: &Config, out: &mut BufWriter entries.sort_by_key(|k| Reverse(k.md(out).map(|md| md.len()).unwrap_or(0))), // The default sort in GNU ls is case insensitive Sort::Name => entries.sort_by(|a, b| a.display_name.cmp(&b.display_name)), - Sort::Version => entries - .sort_by(|a, b| version_cmp(&a.p_buf.to_string_lossy(), &b.p_buf.to_string_lossy())), + Sort::Version => entries.sort_by(|a, b| { + version_cmp(&a.p_buf.to_string_lossy(), &b.p_buf.to_string_lossy()) + .then(a.p_buf.to_string_lossy().cmp(&b.p_buf.to_string_lossy())) + }), Sort::Extension => entries.sort_by(|a, b| { a.p_buf .extension() .cmp(&b.p_buf.extension()) .then(a.p_buf.file_stem().cmp(&b.p_buf.file_stem())) }), + Sort::Width => entries.sort_by(|a, b| { + a.display_name + .len() + .cmp(&b.display_name.len()) + .then(a.display_name.cmp(&b.display_name)) + }), Sort::None => {} } @@ -3000,6 +3013,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")] { diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index e2b65c030..d971d4eaf 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkdir" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "mkdir ~ (uutils) create DIRECTORY" @@ -15,8 +15,8 @@ edition = "2021" path = "src/mkdir.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["fs", "mode"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["fs", "mode"] } [[bin]] name = "mkdir" diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 8fd154a3f..21b00ffe7 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkfifo" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "mkfifo ~ (uutils) create FIFOs (named pipes)" @@ -15,9 +15,9 @@ edition = "2021" path = "src/mkfifo.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +libc = { workspace = true } +uucore = { workspace = true } [[bin]] name = "mkfifo" diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index ee272dbf7..266c23629 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mknod" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "mknod ~ (uutils) create special file NAME of TYPE" @@ -16,9 +16,9 @@ name = "uu_mknod" path = "src/mknod.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -uucore = { workspace=true, features=["mode"] } +clap = { workspace = true } +libc = { workspace = true } +uucore = { workspace = true, features = ["mode"] } [[bin]] name = "mknod" diff --git a/src/uu/mktemp/Cargo.toml b/src/uu/mktemp/Cargo.toml index 7f1ac62ee..9d4825559 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mktemp" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "mktemp ~ (uutils) create and display a temporary file or directory from TEMPLATE" @@ -15,10 +15,10 @@ edition = "2021" path = "src/mktemp.rs" [dependencies] -clap = { workspace=true } -rand = { workspace=true } -tempfile = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +rand = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true } [[bin]] name = "mktemp" diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index fc360d0f5..9c0860d92 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -8,7 +8,7 @@ // spell-checker:ignore (paths) GPGHome findxs -use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; +use clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgMatches, Command}; use uucore::display::{println_verbatim, Quotable}; use uucore::error::{FromIo, UError, UResult, UUsageError}; use uucore::{format_usage, help_about, help_usage}; @@ -38,6 +38,7 @@ static OPT_DRY_RUN: &str = "dry-run"; static OPT_QUIET: &str = "quiet"; static OPT_SUFFIX: &str = "suffix"; static OPT_TMPDIR: &str = "tmpdir"; +static OPT_P: &str = "p"; static OPT_T: &str = "t"; static ARG_TEMPLATE: &str = "template"; @@ -130,7 +131,7 @@ struct Options { /// The directory in which to create the temporary file. /// /// If `None`, the file will be created in the current directory. - tmpdir: Option, + tmpdir: Option, /// The suffix to append to the temporary file, if any. suffix: Option, @@ -142,72 +143,32 @@ struct Options { template: String, } -/// Decide whether the argument to `--tmpdir` should actually be the template. -/// -/// This function is required to work around a limitation of `clap`, -/// the command-line argument parsing library. In case the command -/// line is -/// -/// ```sh -/// mktemp --tmpdir XXX -/// ``` -/// -/// the program should behave like -/// -/// ```sh -/// mktemp --tmpdir=${TMPDIR:-/tmp} XXX -/// ``` -/// -/// However, `clap` thinks that `XXX` is the value of the `--tmpdir` -/// option. This function returns `true` in this case and `false` -/// in all other cases. -fn is_tmpdir_argument_actually_the_template(matches: &ArgMatches) -> bool { - if !matches.contains_id(ARG_TEMPLATE) { - if let Some(tmpdir) = matches.get_one::(OPT_TMPDIR) { - if !Path::new(tmpdir).is_dir() && tmpdir.contains("XXX") { - return true; - } - } - } - false -} - impl Options { fn from(matches: &ArgMatches) -> Self { - // Special case to work around a limitation of `clap`; see - // `is_tmpdir_argument_actually_the_template()` for more - // information. - // - // Fixed in clap 3 - // See https://github.com/clap-rs/clap/pull/1587 - let (tmpdir, template) = if is_tmpdir_argument_actually_the_template(matches) { - let tmpdir = Some(env::temp_dir().display().to_string()); - let template = matches.get_one::(OPT_TMPDIR).unwrap().to_string(); - (tmpdir, template) - } else { + let tmpdir = matches + .get_one::(OPT_TMPDIR) + .or_else(|| matches.get_one::(OPT_P)) + .cloned(); + let (tmpdir, template) = match matches.get_one::(ARG_TEMPLATE) { // If no template argument is given, `--tmpdir` is implied. - match matches.get_one::(ARG_TEMPLATE) { - None => { - let tmpdir = match matches.get_one::(OPT_TMPDIR) { - None => Some(env::temp_dir().display().to_string()), - Some(tmpdir) => Some(tmpdir.to_string()), - }; - let template = DEFAULT_TEMPLATE; - (tmpdir, template.to_string()) - } - Some(template) => { - let tmpdir = if env::var(TMPDIR_ENV_VAR).is_ok() && matches.get_flag(OPT_T) { - env::var(TMPDIR_ENV_VAR).ok() - } else if matches.contains_id(OPT_TMPDIR) { - matches.get_one::(OPT_TMPDIR).map(String::from) - } else if matches.get_flag(OPT_T) { - // mktemp -t foo.xxx should export in TMPDIR - Some(env::temp_dir().display().to_string()) - } else { - matches.get_one::(OPT_TMPDIR).map(String::from) - }; - (tmpdir, template.to_string()) - } + None => { + let tmpdir = Some(tmpdir.unwrap_or_else(env::temp_dir)); + let template = DEFAULT_TEMPLATE; + (tmpdir, template.to_string()) + } + Some(template) => { + let tmpdir = if env::var(TMPDIR_ENV_VAR).is_ok() && matches.get_flag(OPT_T) { + env::var_os(TMPDIR_ENV_VAR).map(|t| t.into()) + } else if tmpdir.is_some() { + tmpdir + } else if matches.get_flag(OPT_T) || matches.contains_id(OPT_TMPDIR) { + // If --tmpdir is given without an argument, or -t is given + // export in TMPDIR + Some(env::temp_dir()) + } else { + None + }; + (tmpdir, template.to_string()) } }; Self { @@ -372,7 +333,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if env::var("POSIXLY_CORRECT").is_ok() { // If POSIXLY_CORRECT was set, template MUST be the last argument. - if is_tmpdir_argument_actually_the_template(&matches) || matches.contains_id(ARG_TEMPLATE) { + if matches.contains_id(ARG_TEMPLATE) { // Template argument was provided, check if was the last one. if args.last().unwrap() != &options.template { return Err(Box::new(MkTempError::TooManyTemplates)); @@ -444,8 +405,16 @@ pub fn uu_app() -> Command { .value_name("SUFFIX"), ) .arg( - Arg::new(OPT_TMPDIR) + Arg::new(OPT_P) .short('p') + .help("short form of --tmpdir") + .value_name("DIR") + .num_args(1) + .value_parser(ValueParser::path_buf()) + .value_hint(clap::ValueHint::DirPath), + ) + .arg( + Arg::new(OPT_TMPDIR) .long(OPT_TMPDIR) .help( "interpret TEMPLATE relative to DIR; if DIR is not specified, use \ @@ -457,6 +426,10 @@ pub fn uu_app() -> Command { // Allows use of default argument just by setting --tmpdir. Else, // use provided input to generate tmpdir .num_args(0..=1) + // Require an equals to avoid ambiguity if no tmpdir is supplied + .require_equals(true) + .overrides_with(OPT_P) + .value_parser(ValueParser::path_buf()) .value_hint(clap::ValueHint::DirPath), ) .arg( diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 573eafc43..0b7eb0763 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_more" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "more ~ (uutils) input perusal filter" @@ -15,15 +15,15 @@ edition = "2021" path = "src/more.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } -crossterm = { workspace=true } -is-terminal = { workspace=true } -unicode-width = { workspace=true } -unicode-segmentation = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } +crossterm = { workspace = true } +is-terminal = { workspace = true } +unicode-width = { workspace = true } +unicode-segmentation = { workspace = true } [target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies] -nix = { workspace=true } +nix = { workspace = true } [[bin]] name = "more" diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 996a1dc61..c488ba8af 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -14,10 +14,10 @@ use std::{ time::Duration, }; -use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; +use clap::{crate_version, value_parser, Arg, ArgAction, ArgMatches, Command}; use crossterm::event::KeyEventKind; use crossterm::{ - cursor::MoveTo, + cursor::{MoveTo, MoveUp}, event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, execute, queue, style::Attribute, @@ -50,19 +50,37 @@ 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 { - silent: bool, clean_print: bool, + from_line: usize, + lines: Option, print_over: bool, + silent: bool, squeeze: bool, } impl Options { fn from(matches: &ArgMatches) -> Self { + let lines = match ( + matches.get_one::(options::LINES).copied(), + matches.get_one::(options::NUMBER).copied(), + ) { + // We add 1 to the number of lines to display because the last line + // is used for the banner + (Some(number), _) if number > 0 => Some(number + 1), + (None, Some(number)) if number > 0 => Some(number + 1), + (_, _) => None, + }; + let from_line = match matches.get_one::(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), squeeze: matches.get_flag(options::SQUEEZE), @@ -78,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(); @@ -103,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(); @@ -118,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")); @@ -167,6 +189,38 @@ 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') + .long(options::LINES) + .value_name("number") + .num_args(1) + .value_parser(value_parser!(u16).range(0..)) + .help("The number of lines per screen full"), + ) + .arg( + Arg::new(options::NUMBER) + .long(options::NUMBER) + .num_args(1) + .value_parser(value_parser!(u16).range(0..)) + .help("Same as --lines"), + ) // The commented arguments below are unimplemented: /* .arg( @@ -181,37 +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::LINES) - .short('n') - .long(options::LINES) - .value_name("number") - .takes_value(true) - .help("The number of lines per screen full"), - ) - .arg( - Arg::new(options::NUMBER) - .allow_hyphen_values(true) - .long(options::NUMBER) - .required(false) - .takes_value(true) - .help("Same as --lines"), - ) - .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') @@ -260,15 +283,36 @@ 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, rows) = terminal::size().unwrap(); + let (cols, mut rows) = terminal::size().unwrap(); + if let Some(number) = options.lines { + rows = number; + } + 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(()); } @@ -327,6 +371,7 @@ fn more( .. }) => { pager.page_up(); + paging_add_back_message(options, stdout)?; } Event::Key(KeyEvent { code: KeyCode::Char('j'), @@ -347,7 +392,7 @@ fn more( pager.prev_line(); } Event::Resize(col, row) => { - pager.page_resize(col, row); + pager.page_resize(col, row, options.lines); } Event::Key(KeyEvent { code: KeyCode::Char(k), @@ -388,7 +433,7 @@ impl<'a> Pager<'a> { fn new(rows: u16, lines: Vec, 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, @@ -447,15 +492,17 @@ impl<'a> Pager<'a> { } // TODO: Deal with column size changes. - fn page_resize(&mut self, _: u16, row: u16) { - self.content_rows = row.saturating_sub(1); + fn page_resize(&mut self, _: u16, row: u16, option_line: Option) { + if option_line.is_none() { + self.content_rows = row.saturating_sub(1); + }; } fn draw(&mut self, stdout: &mut std::io::Stdout, wrong_key: Option) { + 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(); } @@ -515,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)]" @@ -536,6 +582,14 @@ impl<'a> Pager<'a> { } } +fn paging_add_back_message(options: &Options, stdout: &mut std::io::Stdout) -> UResult<()> { + if options.lines.is_some() { + execute!(stdout, MoveUp(1))?; + stdout.write_all("\n\r...back 1 page\n".as_bytes())?; + } + Ok(()) +} + // Break the lines on the cols of the terminal fn break_buff(buff: &str, cols: usize) -> Vec { let mut lines = Vec::with_capacity(buff.lines().count()); diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index 5bc660d08..ab90e4509 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mv" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "mv ~ (uutils) move (rename) SOURCE to DESTINATION" @@ -15,10 +15,10 @@ edition = "2021" path = "src/mv.rs" [dependencies] -clap = { workspace=true } -fs_extra = { workspace=true } -indicatif = { workspace=true } -uucore = { workspace=true, features=["fs"] } +clap = { workspace = true } +fs_extra = { workspace = true } +indicatif = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "mv" diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 5ca677c69..03d3881b1 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -22,10 +22,10 @@ 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_to_same_file; +use uucore::fs::{are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file}; use uucore::update_control::{self, UpdateMode}; use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show}; @@ -251,12 +251,25 @@ fn parse_paths(files: &[OsString], b: &Behavior) -> Vec { } 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()); } - if (source.eq(target) || are_hardlinks_to_same_file(source, target)) - && b.backup != BackupMode::SimpleBackup + if (source.eq(target) + || are_hardlinks_to_same_file(source, target) + || are_hardlinks_or_one_way_symlink_to_same_file(source, target)) + && b.backup == BackupMode::NoBackup { if source.eq(Path::new(".")) || source.ends_with("/.") || source.is_file() { return Err( @@ -387,7 +400,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR } match rename(sourcepath, &targetpath, b, multi_progress.as_ref()) { - Err(e) if e.to_string() == "" => set_exit_code(1), + Err(e) if e.to_string().is_empty() => set_exit_code(1), Err(e) => { let e = e.map_err_context(|| { format!( @@ -420,9 +433,7 @@ fn rename( let mut backup_path = None; if to.exists() { - if (b.update == UpdateMode::ReplaceIfOlder || b.update == UpdateMode::ReplaceNone) - && b.overwrite == OverwriteMode::Interactive - { + if b.update == UpdateMode::ReplaceIfOlder && b.overwrite == OverwriteMode::Interactive { // `mv -i --update old new` when `new` exists doesn't move anything // and exit with 0 return Ok(()); diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index 3c5d763da..1f6a560ff 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nice" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "nice ~ (uutils) run PROGRAM with modified scheduling priority" @@ -15,10 +15,10 @@ edition = "2021" path = "src/nice.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -nix = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +libc = { workspace = true } +nix = { workspace = true } +uucore = { workspace = true } [[bin]] name = "nice" diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index efa3f3e0b..376600703 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nl" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "nl ~ (uutils) display input with added line numbers" @@ -15,9 +15,9 @@ edition = "2021" path = "src/nl.rs" [dependencies] -clap = { workspace=true } -regex = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +regex = { workspace = true } +uucore = { workspace = true } [[bin]] name = "nl" diff --git a/src/uu/nl/nl.md b/src/uu/nl/nl.md index b64b9f627..bf3952cb2 100644 --- a/src/uu/nl/nl.md +++ b/src/uu/nl/nl.md @@ -1,7 +1,23 @@ -#nl +# nl ``` nl [OPTION]... [FILE]... ``` Number lines of files + +## After Help + +`STYLE` is one of: + +* `a` number all lines +* `t` number only nonempty lines +* `n` number no lines +* `pBRE` number only lines that contain a match for the basic regular + expression, `BRE` + +`FORMAT` is one of: + +* `ln` left justified, no leading zeros +* `rn` right justified, no leading zeros +* `rz` right justified, leading zeros diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index a62936d75..a9dbbad79 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -5,17 +5,15 @@ use crate::options; // parse_style parses a style string into a NumberingStyle. fn parse_style(chars: &[char]) -> Result { if chars.len() == 1 && chars[0] == 'a' { - Ok(crate::NumberingStyle::NumberForAll) + Ok(crate::NumberingStyle::All) } else if chars.len() == 1 && chars[0] == 't' { - Ok(crate::NumberingStyle::NumberForNonEmpty) + Ok(crate::NumberingStyle::NonEmpty) } else if chars.len() == 1 && chars[0] == 'n' { - Ok(crate::NumberingStyle::NumberForNone) + Ok(crate::NumberingStyle::None) } else if chars.len() > 1 && chars[0] == 'p' { let s: String = chars[1..].iter().cloned().collect(); match regex::Regex::new(&s) { - Ok(re) => Ok(crate::NumberingStyle::NumberForRegularExpression(Box::new( - re, - ))), + Ok(re) => Ok(crate::NumberingStyle::Regex(Box::new(re))), Err(_) => Err(String::from("Illegal regular expression")), } } else { @@ -29,30 +27,14 @@ fn parse_style(chars: &[char]) -> Result { pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> Vec { // This vector holds error messages encountered. let mut errs: Vec = vec![]; - settings.renumber = !opts.contains_id(options::NO_RENUMBER); - match opts.get_one::(options::NUMBER_SEPARATOR) { - None => {} - Some(val) => { - settings.number_separator = val.to_owned(); - } - } - match opts.get_one::(options::NUMBER_FORMAT) { - None => {} - Some(val) => match val.as_str() { - "ln" => { - settings.number_format = crate::NumberFormat::Left; - } - "rn" => { - settings.number_format = crate::NumberFormat::Right; - } - "rz" => { - settings.number_format = crate::NumberFormat::RightZero; - } - _ => { - errs.push(String::from("Illegal value for -n")); - } - }, + settings.renumber = opts.get_flag(options::NO_RENUMBER); + if let Some(val) = opts.get_one::(options::NUMBER_SEPARATOR) { + settings.number_separator = val.to_owned(); } + settings.number_format = opts + .get_one::(options::NUMBER_FORMAT) + .map(Into::into) + .unwrap_or_default(); match opts.get_one::(options::BODY_NUMBERING) { None => {} Some(val) => { @@ -95,53 +77,25 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> } } } - match opts.get_one::(options::LINE_INCREMENT) { + match opts.get_one::(options::NUMBER_WIDTH) { None => {} - Some(val) => { - let conv: Option = val.parse().ok(); - match conv { - None => { - errs.push(String::from("Illegal value for -i")); - } - Some(num) => settings.line_increment = num, - } - } + Some(num) if *num > 0 => settings.number_width = *num, + Some(_) => errs.push(String::from( + "Invalid line number field width: ‘0’: Numerical result out of range", + )), } - match opts.get_one::(options::NUMBER_WIDTH) { + match opts.get_one::(options::JOIN_BLANK_LINES) { None => {} - Some(val) => { - let conv: Option = val.parse().ok(); - match conv { - None => { - errs.push(String::from("Illegal value for -w")); - } - Some(num) => settings.number_width = num, - } - } + Some(num) if *num > 0 => settings.join_blank_lines = *num, + Some(_) => errs.push(String::from( + "Invalid line number of blank lines: ‘0’: Numerical result out of range", + )), } - match opts.get_one::(options::STARTING_LINE_NUMBER) { - None => {} - Some(val) => { - let conv: Option = val.parse().ok(); - match conv { - None => { - errs.push(String::from("Illegal value for -v")); - } - Some(num) => settings.starting_line_number = num, - } - } + if let Some(num) = opts.get_one::(options::LINE_INCREMENT) { + settings.line_increment = *num; } - match opts.get_one::(options::JOIN_BLANK_LINES) { - None => {} - Some(val) => { - let conv: Option = val.parse().ok(); - match conv { - None => { - errs.push(String::from("Illegal value for -l")); - } - Some(num) => settings.join_blank_lines = num, - } - } + if let Some(num) = opts.get_one::(options::STARTING_LINE_NUMBER) { + settings.starting_line_number = *num; } errs } diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 44fdec8c7..85ddb1b41 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -6,20 +6,18 @@ // * 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}; -use std::iter::repeat; use std::path::Path; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::{format_usage, help_about, help_section, 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 AFTER_HELP: &str = help_section!("after help", "nl.md"); +const USAGE: &str = help_usage!("nl.md"); // Settings store options used by nl to produce its output. pub struct Settings { @@ -30,8 +28,8 @@ pub struct Settings { // The variable corresponding to -d section_delimiter: [char; 2], // The variables corresponding to the options -v, -i, -l, -w. - starting_line_number: u64, - line_increment: u64, + starting_line_number: i64, + line_increment: i64, join_blank_lines: u64, number_width: usize, // Used with String::from_char, hence usize. // The format of the number and the (default value for) @@ -42,29 +40,72 @@ pub struct Settings { number_separator: String, } +impl Default for Settings { + fn default() -> Self { + Self { + header_numbering: NumberingStyle::None, + body_numbering: NumberingStyle::NonEmpty, + footer_numbering: NumberingStyle::None, + 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 // 2. Number only nonempty lines // 3. Don't number any lines at all // 4. Number all lines that match a basic regular expression. -#[allow(clippy::enum_variant_names)] enum NumberingStyle { - NumberForAll, - NumberForNonEmpty, - NumberForNone, - NumberForRegularExpression(Box), + All, + NonEmpty, + None, + Regex(Box), } // NumberFormat specifies how line numbers are output within their allocated // space. They are justified to the left or right, in the latter case with // the option of having all unused space to its left turned into leading zeroes. +#[derive(Default)] enum NumberFormat { Left, + #[default] Right, RightZero, } +impl> From for NumberFormat { + fn from(s: T) -> Self { + match s.as_ref() { + "ln" => Self::Left, + "rn" => Self::Right, + "rz" => Self::RightZero, + _ => unreachable!("Should have been caught by clap"), + } + } +} + +impl NumberFormat { + // Turns a line number into a `String` with at least `min_width` chars, + // formatted according to the `NumberFormat`s variant. + fn format(&self, number: i64, min_width: usize) -> String { + match self { + Self::Left => format!("{number: format!("{number:>min_width$}"), + Self::RightZero if number < 0 => format!("-{0:0>1$}", number.abs(), min_width - 1), + Self::RightZero => format!("{number:0>min_width$}"), + } + } +} + pub mod options { pub const HELP: &str = "help"; pub const FILE: &str = "file"; @@ -87,20 +128,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. @@ -143,6 +171,7 @@ pub fn uu_app() -> Command { .about(ABOUT) .version(crate_version!()) .override_usage(format_usage(USAGE)) + .after_help(AFTER_HELP) .infer_long_args(true) .disable_help_flag(true) .arg( @@ -162,7 +191,7 @@ pub fn uu_app() -> Command { .short('b') .long(options::BODY_NUMBERING) .help("use STYLE for numbering body lines") - .value_name("SYNTAX"), + .value_name("STYLE"), ) .arg( Arg::new(options::SECTION_DELIMITER) @@ -190,27 +219,31 @@ pub fn uu_app() -> Command { .short('i') .long(options::LINE_INCREMENT) .help("line number increment at each line") - .value_name("NUMBER"), + .value_name("NUMBER") + .value_parser(clap::value_parser!(i64)), ) .arg( Arg::new(options::JOIN_BLANK_LINES) .short('l') .long(options::JOIN_BLANK_LINES) .help("group of NUMBER empty lines counted as one") - .value_name("NUMBER"), + .value_name("NUMBER") + .value_parser(clap::value_parser!(u64)), ) .arg( Arg::new(options::NUMBER_FORMAT) .short('n') .long(options::NUMBER_FORMAT) .help("insert line numbers according to FORMAT") - .value_name("FORMAT"), + .value_name("FORMAT") + .value_parser(["ln", "rn", "rz"]), ) .arg( 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) @@ -224,180 +257,103 @@ pub fn uu_app() -> Command { .short('v') .long(options::STARTING_LINE_NUMBER) .help("first line number on each logical page") - .value_name("NUMBER"), + .value_name("NUMBER") + .value_parser(clap::value_parser!(i64)), ) .arg( Arg::new(options::NUMBER_WIDTH) .short('w') .long(options::NUMBER_WIDTH) .help("use NUMBER columns for line numbers") - .value_name("NUMBER"), + .value_name("NUMBER") + .value_parser(clap::value_parser!(usize)), ) } // nl implements the main functionality for an individual buffer. -#[allow(clippy::cognitive_complexity)] fn nl(reader: &mut BufReader, settings: &Settings) -> UResult<()> { - let regexp: regex::Regex = regex::Regex::new(r".?").unwrap(); + let mut current_numbering_style = &settings.body_numbering; let mut line_no = settings.starting_line_number; - // The current line number's width as a string. Using to_string is inefficient - // but since we only do it once, it should not hurt. - let mut line_no_width = line_no.to_string().len(); - let line_no_width_initial = line_no_width; - // Stores the smallest integer with one more digit than line_no, so that - // when line_no >= line_no_threshold, we need to use one more digit. - let mut line_no_threshold = 10u64.pow(line_no_width as u32); - let mut empty_line_count: u64 = 0; - let fill_char = match settings.number_format { - NumberFormat::RightZero => '0', - _ => ' ', - }; - // Initially, we use the body's line counting settings - let mut regex_filter = match settings.body_numbering { - NumberingStyle::NumberForRegularExpression(ref re) => re, - _ => ®exp, - }; - let mut line_filter: fn(&str, ®ex::Regex) -> bool = pass_regex; - for l in reader.lines() { - let mut l = l.map_err_context(|| "could not read line".to_string())?; - // Sanitize the string. We want to print the newline ourselves. - if l.ends_with('\n') { - l.pop(); - } - // Next we iterate through the individual chars to see if this - // is one of the special lines starting a new "section" in the - // document. - let line = l; - let mut odd = false; - // matched_group counts how many copies of section_delimiter - // this string consists of (0 if there's anything else) - let mut matched_groups = 0u8; - for c in line.chars() { - // If this is a newline character, the loop should end. - if c == '\n' { - break; - } - // If we have already seen three groups (corresponding to - // a header) or the current char does not form part of - // a new group, then this line is not a segment indicator. - if matched_groups >= 3 || settings.section_delimiter[usize::from(odd)] != c { - matched_groups = 0; - break; - } - if odd { - // We have seen a new group and count it. - matched_groups += 1; - } - odd = !odd; - } + let mut consecutive_empty_lines = 0; + + for line in reader.lines() { + let line = line.map_err_context(|| "could not read line".to_string())?; - // See how many groups we matched. That will tell us if this is - // a line starting a new segment, and the number of groups - // indicates what type of segment. - if matched_groups > 0 { - // The current line is a section delimiter, so we output - // a blank line. - println!(); - // However the line does not count as a blank line, so we - // reset the counter used for --join-blank-lines. - empty_line_count = 0; - match *match matched_groups { - 3 => { - // This is a header, so we may need to reset the - // line number and the line width - if settings.renumber { - line_no = settings.starting_line_number; - line_no_width = line_no_width_initial; - line_no_threshold = 10u64.pow(line_no_width as u32); - } - &settings.header_numbering - } - 1 => &settings.footer_numbering, - // The only option left is 2, but rust wants - // a catch-all here. - _ => &settings.body_numbering, - } { - NumberingStyle::NumberForAll => { - line_filter = pass_all; - } - NumberingStyle::NumberForNonEmpty => { - line_filter = pass_nonempty; - } - NumberingStyle::NumberForNone => { - line_filter = pass_none; - } - NumberingStyle::NumberForRegularExpression(ref re) => { - line_filter = pass_regex; - regex_filter = re; - } - } - continue; - } - // From this point on we format and print a "regular" line. if line.is_empty() { - // The line is empty, which means that we have to care - // about the --join-blank-lines parameter. - empty_line_count += 1; + consecutive_empty_lines += 1; } else { - // This saves us from having to check for an empty string - // in the next selector. - empty_line_count = 0; - } - if !line_filter(&line, regex_filter) - || (empty_line_count > 0 && empty_line_count < settings.join_blank_lines) - { - // No number is printed for this line. Either we did not - // want to print one in the first place, or it is a blank - // line but we are still collecting more blank lines via - // the option --join-blank-lines. - println!("{line}"); - continue; - } - // If we make it here, then either we are printing a non-empty - // line or assigning a line number to an empty line. Either - // way, start counting empties from zero once more. - empty_line_count = 0; - // A line number is to be printed. - let w = if settings.number_width > line_no_width { - settings.number_width - line_no_width - } else { - 0 + consecutive_empty_lines = 0; }; - let fill: String = repeat(fill_char).take(w).collect(); - match settings.number_format { - NumberFormat::Left => println!( - "{1}{0}{2}{3}", - fill, line_no, settings.number_separator, line - ), - _ => println!( - "{0}{1}{2}{3}", - fill, line_no, settings.number_separator, line - ), - } - // Now update the variables for the (potential) next - // line. - line_no += settings.line_increment; - while line_no >= line_no_threshold { - // The line number just got longer. - line_no_threshold *= 10; - line_no_width += 1; + + // FIXME section delimiters are hardcoded and settings.section_delimiter is ignored + // because --section-delimiter is not correctly implemented yet + let _ = settings.section_delimiter; // XXX suppress "field never read" warning + let new_numbering_style = match line.as_str() { + "\\:\\:\\:" => Some(&settings.header_numbering), + "\\:\\:" => Some(&settings.body_numbering), + "\\:" => Some(&settings.footer_numbering), + _ => None, + }; + + if let Some(new_style) = new_numbering_style { + current_numbering_style = new_style; + line_no = settings.starting_line_number; + println!(); + } else { + let is_line_numbered = match current_numbering_style { + // consider $join_blank_lines consecutive empty lines to be one logical line + // for numbering, and only number the last one + NumberingStyle::All + if line.is_empty() + && consecutive_empty_lines % settings.join_blank_lines != 0 => + { + false + } + NumberingStyle::All => true, + NumberingStyle::NonEmpty => !line.is_empty(), + NumberingStyle::None => false, + NumberingStyle::Regex(re) => re.is_match(&line), + }; + + if is_line_numbered { + println!( + "{}{}{}", + settings + .number_format + .format(line_no, settings.number_width), + settings.number_separator, + line + ); + // update line number for the potential next line + line_no += settings.line_increment; + } else { + let spaces = " ".repeat(settings.number_width + 1); + println!("{spaces}{line}"); + } } } Ok(()) } -fn pass_regex(line: &str, re: ®ex::Regex) -> bool { - re.is_match(line) -} +#[cfg(test)] +mod test { + use super::*; -fn pass_nonempty(line: &str, _: ®ex::Regex) -> bool { - !line.is_empty() -} + #[test] + fn test_format() { + assert_eq!(NumberFormat::Left.format(12, 1), "12"); + assert_eq!(NumberFormat::Left.format(-12, 1), "-12"); + assert_eq!(NumberFormat::Left.format(12, 4), "12 "); + assert_eq!(NumberFormat::Left.format(-12, 4), "-12 "); -fn pass_none(_: &str, _: ®ex::Regex) -> bool { - false -} + assert_eq!(NumberFormat::Right.format(12, 1), "12"); + assert_eq!(NumberFormat::Right.format(-12, 1), "-12"); + assert_eq!(NumberFormat::Right.format(12, 4), " 12"); + assert_eq!(NumberFormat::Right.format(-12, 4), " -12"); -fn pass_all(_: &str, _: ®ex::Regex) -> bool { - true + assert_eq!(NumberFormat::RightZero.format(12, 1), "12"); + assert_eq!(NumberFormat::RightZero.format(-12, 1), "-12"); + assert_eq!(NumberFormat::RightZero.format(12, 4), "0012"); + assert_eq!(NumberFormat::RightZero.format(-12, 4), "-012"); + } } diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 7067e51e8..b7e4c395a 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nohup" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "nohup ~ (uutils) run COMMAND, ignoring hangup signals" @@ -15,10 +15,10 @@ edition = "2021" path = "src/nohup.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -is-terminal = { workspace=true } -uucore = { workspace=true, features=["fs"] } +clap = { workspace = true } +libc = { workspace = true } +is-terminal = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "nohup" diff --git a/src/uu/nproc/Cargo.toml b/src/uu/nproc/Cargo.toml index 13202b739..7fad14274 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nproc" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "nproc ~ (uutils) display the number of processing units available" @@ -15,9 +15,9 @@ edition = "2021" path = "src/nproc.rs" [dependencies] -libc = { workspace=true } -clap = { workspace=true } -uucore = { workspace=true, features=["fs"] } +libc = { workspace = true } +clap = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "nproc" diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index 2f3a2e69a..6cf0f4320 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_numfmt" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "numfmt ~ (uutils) reformat NUMBER" @@ -15,8 +15,8 @@ edition = "2021" path = "src/numfmt.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "numfmt" diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index f1fd4b115..b0a5670d4 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -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, 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(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 { match s { "auto" => Ok(Unit::Auto), @@ -201,6 +217,9 @@ fn parse_options(args: &ArgMatches) -> Result { .get_one::(options::SUFFIX) .map(|s| s.to_owned()); + let invalid = + InvalidModes::from_str(args.get_one::(options::INVALID).unwrap()).unwrap(); + Ok(NumfmtOptions { transform, padding, @@ -210,6 +229,7 @@ fn parse_options(args: &ArgMatches) -> Result { 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()); diff --git a/src/uu/numfmt/src/options.rs b/src/uu/numfmt/src/options.rs index b70cf87e4..bef4a8ce3 100644 --- a/src/uu/numfmt/src/options.rs +++ b/src/uu/numfmt/src/options.rs @@ -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, 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 { + 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()); + } } diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index 28abbfce2..7bb597ab3 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_od" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "od ~ (uutils) display formatted representation of input" @@ -15,10 +15,10 @@ edition = "2021" path = "src/od.rs" [dependencies] -byteorder = { workspace=true } -clap = { workspace=true } -half = { workspace=true } -uucore = { workspace=true } +byteorder = { workspace = true } +clap = { workspace = true } +half = { workspace = true } +uucore = { workspace = true } [[bin]] name = "od" diff --git a/src/uu/od/src/parse_nrofbytes.rs b/src/uu/od/src/parse_nrofbytes.rs index 4c310755b..7d3bca03d 100644 --- a/src/uu/od/src/parse_nrofbytes.rs +++ b/src/uu/od/src/parse_nrofbytes.rs @@ -43,7 +43,7 @@ pub fn parse_number_of_bytes(s: &str) -> Result { 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()); diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index 36765594e..7d6ed229e 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_paste" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "paste ~ (uutils) merge lines from inputs" @@ -15,8 +15,8 @@ edition = "2021" path = "src/paste.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "paste" diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index 01a626042..b8f4f0310 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pathchk" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "pathchk ~ (uutils) diagnose invalid or non-portable PATHNAME" @@ -15,9 +15,9 @@ edition = "2021" path = "src/pathchk.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +libc = { workspace = true } +uucore = { workspace = true } [[bin]] name = "pathchk" diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index f2c930831..8566faf68 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pinky" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "pinky ~ (uutils) display user information" @@ -15,8 +15,8 @@ edition = "2021" path = "src/pinky.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["utmpx", "entries"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["utmpx", "entries"] } [[bin]] name = "pinky" diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index 414168c64..71e843a84 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pr" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "pr ~ (uutils) convert text files for printing" @@ -15,12 +15,12 @@ edition = "2021" path = "src/pr.rs" [dependencies] -clap = { workspace=true } -time = { workspace=true, features = ["local-offset", "macros", "formatting"] } -uucore = { workspace=true, features=["entries"] } -quick-error = { workspace=true } -itertools = { workspace=true } -regex = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true, features = ["entries"] } +quick-error = { workspace = true } +itertools = { workspace = true } +regex = { workspace = true } +chrono = { workspace = true } [[bin]] name = "pr" diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index b3be8a755..37674bad7 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -6,6 +6,7 @@ // spell-checker:ignore (ToDO) adFfmprt, kmerge +use chrono::{DateTime, Local}; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use itertools::Itertools; use quick_error::ResultExt; @@ -15,8 +16,6 @@ use std::fs::{metadata, File}; use std::io::{stdin, stdout, BufRead, BufReader, Lines, Read, Write}; #[cfg(unix)] use std::os::unix::fs::FileTypeExt; -use time::macros::format_description; -use time::OffsetDateTime; use quick_error::quick_error; use uucore::display::Quotable; @@ -37,8 +36,7 @@ const DEFAULT_COLUMN_WIDTH: usize = 72; const DEFAULT_COLUMN_WIDTH_WITH_S_OPTION: usize = 512; const DEFAULT_COLUMN_SEPARATOR: &char = &TAB; const FF: u8 = 0x0C_u8; -const DATE_TIME_FORMAT: &[time::format_description::FormatItem] = - format_description!("[month repr:short] [day] [hour]:[minute] [year]"); +const DATE_TIME_FORMAT: &str = "%b %d %H:%M %Y"; mod options { pub const HEADER: &str = "header"; @@ -571,10 +569,8 @@ fn build_options( let line_separator = "\n".to_string(); let last_modified_time = if is_merge_mode || paths[0].eq(FILE_STDIN) { - // let date_time = Local::now(); - // date_time.format("%b %d %H:%M %Y").to_string() - let date_time = OffsetDateTime::now_local().unwrap(); - date_time.format(&DATE_TIME_FORMAT).unwrap() + let date_time = Local::now(); + date_time.format(DATE_TIME_FORMAT).to_string() } else { file_last_modified_time(paths.first().unwrap()) }; @@ -1215,12 +1211,8 @@ fn file_last_modified_time(path: &str) -> String { .map(|i| { i.modified() .map(|x| { - let date_time: OffsetDateTime = x.into(); - let offset = OffsetDateTime::now_local().unwrap().offset(); - date_time - .to_offset(offset) - .format(&DATE_TIME_FORMAT) - .unwrap() + let date_time: DateTime = x.into(); + date_time.format(DATE_TIME_FORMAT).to_string() }) .unwrap_or_default() }) diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index 007348a95..11666bd61 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printenv" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "printenv ~ (uutils) display value of environment VAR" @@ -15,8 +15,8 @@ edition = "2021" path = "src/printenv.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "printenv" diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index aa64a24ef..eefcf33c0 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "uu_printf" -version = "0.0.18" -authors = [ - "Nathan Ross", - "uutils developers", -] +version = "0.0.20" +authors = ["Nathan Ross", "uutils developers"] license = "MIT" description = "printf ~ (uutils) FORMAT and display ARGUMENTS" @@ -18,8 +15,8 @@ edition = "2021" path = "src/printf.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["memo"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["memo"] } [[bin]] name = "printf" diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index 8f2cda938..e9b11223c 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ptx" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "ptx ~ (uutils) display a permuted index of input" @@ -15,9 +15,9 @@ edition = "2021" path = "src/ptx.rs" [dependencies] -clap = { workspace=true } -regex = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +regex = { workspace = true } +uucore = { workspace = true } [[bin]] name = "ptx" diff --git a/src/uu/pwd/Cargo.toml b/src/uu/pwd/Cargo.toml index af1451564..09989d079 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pwd" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "pwd ~ (uutils) display current working directory" @@ -15,8 +15,8 @@ edition = "2021" path = "src/pwd.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "pwd" diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 7af194872..9e04dd38b 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -115,7 +115,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // if POSIXLY_CORRECT is set, we want to a logical resolution. // This produces a different output when doing mkdir -p a/b && ln -s a/b c && cd c && pwd // We should get c in this case instead of a/b at the end of the path - let cwd = if matches.get_flag(OPT_LOGICAL) || env::var("POSIXLY_CORRECT").is_ok() { + let cwd = if matches.get_flag(OPT_PHYSICAL) { + physical_path() + } else if matches.get_flag(OPT_LOGICAL) || env::var("POSIXLY_CORRECT").is_ok() { logical_path() } else { physical_path() diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index aed065207..ba407d9cc 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_readlink" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "readlink ~ (uutils) display resolved path of PATHNAME" @@ -15,8 +15,8 @@ edition = "2021" path = "src/readlink.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["fs"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "readlink" diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index 17af009c6..802941cd1 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_realpath" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "realpath ~ (uutils) display resolved absolute path of PATHNAME" @@ -15,8 +15,8 @@ edition = "2021" path = "src/realpath.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["fs"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "realpath" diff --git a/src/uu/relpath/Cargo.toml b/src/uu/relpath/Cargo.toml index 7046aafee..62d47e83a 100644 --- a/src/uu/relpath/Cargo.toml +++ b/src/uu/relpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_relpath" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "relpath ~ (uutils) display relative path of PATHNAME_TO from PATHNAME_FROM" @@ -15,8 +15,8 @@ edition = "2021" path = "src/relpath.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["fs"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "relpath" diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 5151beb93..2807d2d5b 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rm" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "rm ~ (uutils) remove PATHNAME" @@ -15,15 +15,15 @@ edition = "2021" path = "src/rm.rs" [dependencies] -clap = { workspace=true } -walkdir = { workspace=true } -uucore = { workspace=true, features=["fs"] } +clap = { workspace = true } +walkdir = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [target.'cfg(unix)'.dependencies] -libc = { workspace=true } +libc = { workspace = true } [target.'cfg(windows)'.dependencies] -windows-sys = { workspace=true, features = ["Win32_Storage_FileSystem"] } +windows-sys = { workspace = true, features = ["Win32_Storage_FileSystem"] } [[bin]] name = "rm" diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index f2e2050d9..fb4a2149e 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -370,7 +370,7 @@ fn handle_dir(path: &Path, options: &Options) -> bool { } fn remove_dir(path: &Path, options: &Options) -> bool { - if prompt_file(path, options, true) { + if prompt_dir(path, options) { if let Ok(mut read_dir) = fs::read_dir(path) { if options.dir || options.recursive { if read_dir.next().is_none() { @@ -415,7 +415,7 @@ fn remove_dir(path: &Path, options: &Options) -> bool { } fn remove_file(path: &Path, options: &Options) -> bool { - if prompt_file(path, options, false) { + if prompt_file(path, options) { match fs::remove_file(path) { Ok(_) => { if options.verbose { @@ -437,8 +437,23 @@ fn remove_file(path: &Path, options: &Options) -> bool { false } +fn prompt_dir(path: &Path, options: &Options) -> bool { + // If interactive is Never we never want to send prompts + if options.interactive == InteractiveMode::Never { + return true; + } + + // We can't use metadata.permissions.readonly for directories because it only works on files + // So we have to handle whether a directory is writable manually + if let Ok(metadata) = fs::metadata(path) { + handle_writable_directory(path, options, &metadata) + } else { + true + } +} + #[allow(clippy::cognitive_complexity)] -fn prompt_file(path: &Path, options: &Options, is_dir: bool) -> bool { +fn prompt_file(path: &Path, options: &Options) -> bool { // If interactive is Never we never want to send prompts if options.interactive == InteractiveMode::Never { return true; @@ -451,58 +466,45 @@ fn prompt_file(path: &Path, options: &Options, is_dir: bool) -> bool { } } } - if is_dir { - // We can't use metadata.permissions.readonly for directories because it only works on files - // So we have to handle whether a directory is writable on not manually - if let Ok(metadata) = fs::metadata(path) { - handle_writable_directory(path, options, &metadata) - } else { - true - } - } else { - // File::open(path) doesn't open the file in write mode so we need to use file options to open it in also write mode to check if it can written too - match File::options().read(true).write(true).open(path) { - Ok(file) => { - if let Ok(metadata) = file.metadata() { - if metadata.permissions().readonly() { - if metadata.len() == 0 { - prompt_yes!( - "remove write-protected regular empty file {}?", - path.quote() - ) - } else { - prompt_yes!("remove write-protected regular file {}?", path.quote()) - } - } else if options.interactive == InteractiveMode::Always { - if metadata.len() == 0 { - prompt_yes!("remove regular empty file {}?", path.quote()) - } else { - prompt_yes!("remove file {}?", path.quote()) - } - } else { - true - } - } else { - true - } - } - Err(err) => { - if err.kind() == ErrorKind::PermissionDenied { - if let Ok(metadata) = fs::metadata(path) { - if metadata.len() == 0 { - prompt_yes!( - "remove write-protected regular empty file {}?", - path.quote() - ) - } else { - prompt_yes!("remove write-protected regular file {}?", path.quote()) - } + // File::open(path) doesn't open the file in write mode so we need to use file options to open it in also write mode to check if it can written too + match File::options().read(true).write(true).open(path) { + Ok(file) => { + if let Ok(metadata) = file.metadata() { + if metadata.permissions().readonly() { + if metadata.len() == 0 { + prompt_yes!( + "remove write-protected regular empty file {}?", + path.quote() + ) } else { prompt_yes!("remove write-protected regular file {}?", path.quote()) } + } else if options.interactive == InteractiveMode::Always { + if metadata.len() == 0 { + prompt_yes!("remove regular empty file {}?", path.quote()) + } else { + prompt_yes!("remove file {}?", path.quote()) + } } else { true } + } else { + true + } + } + Err(err) => { + if err.kind() == ErrorKind::PermissionDenied { + match fs::metadata(path) { + Ok(metadata) if metadata.len() == 0 => { + prompt_yes!( + "remove write-protected regular empty file {}?", + path.quote() + ) + } + _ => prompt_yes!("remove write-protected regular file {}?", path.quote()), + } + } else { + true } } } diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index f35817c6c..4579cce0a 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rmdir" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "rmdir ~ (uutils) remove empty DIRECTORY" @@ -15,9 +15,9 @@ edition = "2021" path = "src/rmdir.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["fs"] } -libc = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true, features = ["fs"] } +libc = { workspace = true } [[bin]] name = "rmdir" diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 9a864266a..d0123186f 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -100,7 +100,7 @@ fn remove(mut path: &Path, opts: Opts) -> Result<(), Error<'_>> { if opts.parents { while let Some(new) = path.parent() { path = new; - if path.as_os_str() == "" { + if path.as_os_str().is_empty() { break; } remove_single(path, opts)?; diff --git a/src/uu/runcon/Cargo.toml b/src/uu/runcon/Cargo.toml index 5373658e4..9ea9dc769 100644 --- a/src/uu/runcon/Cargo.toml +++ b/src/uu/runcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_runcon" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "runcon ~ (uutils) run command with specified security context" @@ -14,11 +14,11 @@ edition = "2021" path = "src/runcon.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["entries", "fs", "perms"] } -selinux = { workspace=true } -thiserror = { workspace=true } -libc = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true, features = ["entries", "fs", "perms"] } +selinux = { workspace = true } +thiserror = { workspace = true } +libc = { workspace = true } [[bin]] name = "runcon" diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index c217d067f..2646d3609 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore bigdecimal [package] name = "uu_seq" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "seq ~ (uutils) display a sequence of numbers" @@ -16,11 +16,11 @@ edition = "2021" path = "src/seq.rs" [dependencies] -bigdecimal = { workspace=true } -clap = { workspace=true } -num-bigint = { workspace=true } -num-traits = { workspace=true } -uucore = { workspace=true, features=["memo"] } +bigdecimal = { workspace = true } +clap = { workspace = true } +num-bigint = { workspace = true } +num-traits = { workspace = true } +uucore = { workspace = true, features = ["memo"] } [[bin]] name = "seq" diff --git a/src/uu/seq/seq.md b/src/uu/seq/seq.md index 065404e20..8e67391e4 100644 --- a/src/uu/seq/seq.md +++ b/src/uu/seq/seq.md @@ -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 ``` diff --git a/src/uu/seq/src/error.rs b/src/uu/seq/src/error.rs index ae641a978..fc8452e13 100644 --- a/src/uu/seq/src/error.rs +++ b/src/uu/seq/src/error.rs @@ -2,7 +2,7 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore numberparse argtype +// spell-checker:ignore numberparse //! Errors returned by seq. use std::error::Error; use std::fmt::Display; @@ -25,29 +25,11 @@ pub enum SeqError { /// The parameter is the increment argument as a [`String`] as read /// from the command line. ZeroIncrement(String), + + /// No arguments were passed to this function, 1 or more is required + NoArguments, } -impl SeqError { - /// The [`String`] argument as read from the command-line. - fn arg(&self) -> &str { - match self { - Self::ParseError(s, _) => s, - Self::ZeroIncrement(s) => s, - } - } - - /// The type of argument that is causing the error. - fn argtype(&self) -> &str { - match self { - Self::ParseError(_, e) => match e { - ParseNumberError::Float => "floating point argument", - ParseNumberError::Nan => "'not-a-number' argument", - ParseNumberError::Hex => "hexadecimal argument", - }, - Self::ZeroIncrement(_) => "Zero increment value", - } - } -} impl UError for SeqError { /// Always return 1. fn code(&self) -> i32 { @@ -63,6 +45,17 @@ impl Error for SeqError {} impl Display for SeqError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "invalid {}: {}", self.argtype(), self.arg().quote()) + match self { + Self::ParseError(s, e) => { + let error_type = match e { + ParseNumberError::Float => "floating point", + ParseNumberError::Nan => "'not-a-number'", + ParseNumberError::Hex => "hexadecimal", + }; + write!(f, "invalid {error_type} argument: {}", s.quote()) + } + Self::ZeroIncrement(s) => write!(f, "invalid Zero increment value: {}", s.quote()), + Self::NoArguments => write!(f, "missing operand"), + } } } diff --git a/src/uu/seq/src/extendedbigint.rs b/src/uu/seq/src/extendedbigint.rs index bbe64300a..46153bb7d 100644 --- a/src/uu/seq/src/extendedbigint.rs +++ b/src/uu/seq/src/extendedbigint.rs @@ -65,11 +65,7 @@ impl Display for ExtendedBigInt { Self::BigInt(n) => n.fmt(f), Self::Infinity => f32::INFINITY.fmt(f), Self::MinusInfinity => f32::NEG_INFINITY.fmt(f), - Self::MinusZero => { - // FIXME Come up with a way of formatting this with a - // "-" prefix. - 0.fmt(f) - } + Self::MinusZero => "-0".fmt(f), Self::Nan => "nan".fmt(f), } } @@ -206,13 +202,9 @@ mod tests { #[test] fn test_display() { assert_eq!(format!("{}", ExtendedBigInt::BigInt(BigInt::zero())), "0"); + assert_eq!(format!("{}", ExtendedBigInt::MinusZero), "-0"); assert_eq!(format!("{}", ExtendedBigInt::Infinity), "inf"); assert_eq!(format!("{}", ExtendedBigInt::MinusInfinity), "-inf"); assert_eq!(format!("{}", ExtendedBigInt::Nan), "nan"); - // FIXME Come up with a way of displaying negative zero as - // "-0". Currently it displays as just "0". - // - // assert_eq!(format!("{}", ExtendedBigInt::MinusZero), "-0"); - // } } diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 97382ed1b..dec66a7b1 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -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>, } @@ -58,10 +58,13 @@ type RangeFloat = (ExtendedBigDecimal, ExtendedBigDecimal, ExtendedBigDecimal); pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; - let numbers = matches - .get_many::(ARG_NUMBERS) - .unwrap() - .collect::>(); + let numbers_option = matches.get_many::(ARG_NUMBERS); + + if numbers_option.is_none() { + return Err(SeqError::NoArguments.into()); + } + + let numbers = numbers_option.unwrap().collect::>(); let options = SeqOptions { separator: matches @@ -74,7 +77,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::(OPT_FORMAT).map(|s| s.as_str()), }; @@ -123,7 +126,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 +140,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 +173,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), ) @@ -203,7 +206,6 @@ fn write_value_float( value: &ExtendedBigDecimal, width: usize, precision: usize, - _is_first_iteration: bool, ) -> std::io::Result<()> { let value_as_str = if *value == ExtendedBigDecimal::Infinity || *value == ExtendedBigDecimal::MinusInfinity { @@ -220,16 +222,13 @@ fn write_value_int( value: &ExtendedBigInt, width: usize, pad: bool, - is_first_iteration: bool, ) -> std::io::Result<()> { let value_as_str = if pad { - if *value == ExtendedBigInt::MinusZero && is_first_iteration { - format!("-{value:>0width$}", width = width - 1) + if *value == ExtendedBigInt::MinusZero { + format!("{value:00width$}") } - } else if *value == ExtendedBigInt::MinusZero && is_first_iteration { - format!("-{value}") } else { format!("{value}") }; @@ -279,13 +278,7 @@ fn print_seq( exit(1); } } - None => write_value_float( - &mut stdout, - &value, - padding, - largest_dec, - is_first_iteration, - )?, + None => write_value_float(&mut stdout, &value, padding, largest_dec)?, } // TODO Implement augmenting addition. value = value + increment.clone(); @@ -344,7 +337,7 @@ fn print_seq_integers( exit(1); } } - None => write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?, + None => write_value_int(&mut stdout, &value, padding, pad)?, } // TODO Implement augmenting addition. value = value + increment.clone(); diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index e085041c3..81e49dc00 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shred" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "shred ~ (uutils) hide former FILE contents with repeated overwrites" @@ -15,10 +15,10 @@ edition = "2021" path = "src/shred.rs" [dependencies] -clap = { workspace=true } -rand = { workspace=true } -uucore = { workspace=true } -libc = { workspace=true } +clap = { workspace = true } +rand = { workspace = true } +uucore = { workspace = true } +libc = { workspace = true } [[bin]] name = "shred" diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 89f857b19..fd14a3245 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -19,6 +19,7 @@ use std::os::unix::prelude::PermissionsExt; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; +use uucore::parse_size::parse_size; use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_if_err}; const ABOUT: &str = help_about!("shred.md"); @@ -238,7 +239,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .get_one::(options::SIZE) .map(|s| s.to_string()); let size = get_size(size_arg); - let exact = matches.get_flag(options::EXACT) && size.is_none(); // if -s is given, ignore -x + let exact = matches.get_flag(options::EXACT) || size.is_some(); let zero = matches.get_flag(options::ZERO); let verbose = matches.get_flag(options::VERBOSE); @@ -318,38 +319,17 @@ pub fn uu_app() -> Command { ) } -// TODO: Add support for all postfixes here up to and including EiB -// http://www.gnu.org/software/coreutils/manual/coreutils.html#Block-size fn get_size(size_str_opt: Option) -> Option { - size_str_opt.as_ref()?; - - let mut size_str = size_str_opt.as_ref().unwrap().clone(); - // Immutably look at last character of size string - let unit = match size_str.chars().last().unwrap() { - 'K' => { - size_str.pop(); - 1024u64 - } - 'M' => { - size_str.pop(); - (1024 * 1024) as u64 - } - 'G' => { - size_str.pop(); - (1024 * 1024 * 1024) as u64 - } - _ => 1u64, - }; - - let coefficient = match size_str.parse::() { - Ok(u) => u, - Err(_) => { - show_error!("{}: Invalid file size", size_str_opt.unwrap().maybe_quote()); - std::process::exit(1); - } - }; - - Some(coefficient * unit) + size_str_opt + .as_ref() + .and_then(|size| parse_size(size.as_str()).ok()) + .or_else(|| { + if let Some(size) = size_str_opt { + show_error!("invalid file size: {}", size.quote()); + std::process::exit(1); + } + None + }) } fn pass_name(pass_type: &PassType) -> String { @@ -552,7 +532,9 @@ fn wipe_name(orig_path: &Path, verbose: bool) -> Option { } // 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"); diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index 8c7dd7278..281e51d9e 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shuf" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "shuf ~ (uutils) display random permutations of input lines" @@ -15,11 +15,11 @@ edition = "2021" path = "src/shuf.rs" [dependencies] -clap = { workspace=true } -memchr = { workspace=true } -rand = { workspace=true } -rand_core = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +memchr = { workspace = true } +rand = { workspace = true } +rand_core = { workspace = true } +uucore = { workspace = true } [[bin]] name = "shuf" diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index 6091ac1da..f69678770 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sleep" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "sleep ~ (uutils) pause for DURATION" @@ -15,9 +15,9 @@ edition = "2021" path = "src/sleep.rs" [dependencies] -clap = { workspace=true } -fundu = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +fundu = { workspace = true } +uucore = { workspace = true } [[bin]] name = "sleep" diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 009986095..8acb7724f 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -14,7 +14,7 @@ use uucore::{ }; use clap::{crate_version, Arg, ArgAction, Command}; -use fundu::{self, DurationParser, ParseError}; +use fundu::{self, DurationParser, ParseError, SaturatingInto}; static ABOUT: &str = help_about!("sleep.md"); const USAGE: &str = help_usage!("sleep.md"); @@ -63,7 +63,7 @@ pub fn uu_app() -> Command { fn sleep(args: &[&str]) -> UResult<()> { let mut arg_error = false; - use fundu::TimeUnit::*; + use fundu::TimeUnit::{Day, Hour, Minute, Second}; let parser = DurationParser::with_time_units(&[Second, Minute, Hour, Day]); let sleep_dur = args @@ -91,7 +91,9 @@ fn sleep(args: &[&str]) -> UResult<()> { None } }) - .fold(Duration::ZERO, |acc, n| acc.saturating_add(n)); + .fold(Duration::ZERO, |acc, n| { + acc.saturating_add(SaturatingInto::::saturating_into(n)) + }); if arg_error { return Err(UUsageError::new(1, "")); diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 41d6bd66f..eb9cb9b59 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sort" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "sort ~ (uutils) sort input lines" @@ -15,19 +15,19 @@ edition = "2021" path = "src/sort.rs" [dependencies] -binary-heap-plus = { workspace=true } -clap = { workspace=true } -compare = { workspace=true } -ctrlc = { workspace=true } -fnv = { workspace=true } -itertools = { workspace=true } -memchr = { workspace=true } -ouroboros = { workspace=true } -rand = { workspace=true } -rayon = { workspace=true } -tempfile = { workspace=true } -unicode-width = { workspace=true } -uucore = { workspace=true, features=["fs"] } +binary-heap-plus = { workspace = true } +clap = { workspace = true } +compare = { workspace = true } +ctrlc = { workspace = true } +fnv = { workspace = true } +itertools = { workspace = true } +memchr = { workspace = true } +rand = { workspace = true } +rayon = { workspace = true } +self_cell = { workspace = true } +tempfile = { workspace = true } +unicode-width = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "sort" diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index 9b60d5f5b..ffee7e453 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -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, - #[borrows(buffer)] - #[covariant] - pub contents: ChunkContents<'this>, -} +self_cell!( + /// The chunk that is passed around between threads. + pub struct Chunk { + owner: Vec, + + #[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 { - &self.borrow_contents().lines + &self.borrow_dependent().lines } pub fn line_data(&self) -> &LineData { - &self.borrow_contents().line_data + &self.borrow_dependent().line_data } } diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 45ddc7304..27cb12d0b 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -158,7 +158,7 @@ fn reader_writer< /// The function that is executed on the sorter thread. fn sorter(receiver: &Receiver, sender: &SyncSender, 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() { diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index f6da0ee32..7c682d88f 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -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 { diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index 05f81c897..1a986ea76 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -269,7 +269,7 @@ mod tests { fn parses_exp() { let n = "1"; assert_eq!( - NumInfo::parse(n, &Default::default()), + NumInfo::parse(n, &NumInfoParseSettings::default()), ( NumInfo { exponent: 0, @@ -280,7 +280,7 @@ mod tests { ); let n = "100"; assert_eq!( - NumInfo::parse(n, &Default::default()), + NumInfo::parse(n, &NumInfoParseSettings::default()), ( NumInfo { exponent: 2, @@ -308,7 +308,7 @@ mod tests { ); let n = "1,000"; assert_eq!( - NumInfo::parse(n, &Default::default()), + NumInfo::parse(n, &NumInfoParseSettings::default()), ( NumInfo { exponent: 0, @@ -319,7 +319,7 @@ mod tests { ); let n = "1000.00"; assert_eq!( - NumInfo::parse(n, &Default::default()), + NumInfo::parse(n, &NumInfoParseSettings::default()), ( NumInfo { exponent: 3, @@ -333,7 +333,7 @@ mod tests { fn parses_negative_exp() { let n = "0.00005"; assert_eq!( - NumInfo::parse(n, &Default::default()), + NumInfo::parse(n, &NumInfoParseSettings::default()), ( NumInfo { exponent: -5, @@ -344,7 +344,7 @@ mod tests { ); let n = "00000.00005"; assert_eq!( - NumInfo::parse(n, &Default::default()), + NumInfo::parse(n, &NumInfoParseSettings::default()), ( NumInfo { exponent: -5, @@ -359,7 +359,7 @@ mod tests { fn parses_sign() { let n = "5"; assert_eq!( - NumInfo::parse(n, &Default::default()), + NumInfo::parse(n, &NumInfoParseSettings::default()), ( NumInfo { exponent: 0, @@ -370,7 +370,7 @@ mod tests { ); let n = "-5"; assert_eq!( - NumInfo::parse(n, &Default::default()), + NumInfo::parse(n, &NumInfoParseSettings::default()), ( NumInfo { exponent: 0, @@ -381,7 +381,7 @@ mod tests { ); let n = " -5"; assert_eq!( - NumInfo::parse(n, &Default::default()), + NumInfo::parse(n, &NumInfoParseSettings::default()), ( NumInfo { exponent: 0, @@ -393,8 +393,8 @@ mod tests { } fn test_helper(a: &str, b: &str, expected: Ordering) { - let (a_info, a_range) = NumInfo::parse(a, &Default::default()); - let (b_info, b_range) = NumInfo::parse(b, &Default::default()); + let (a_info, a_range) = NumInfo::parse(a, &NumInfoParseSettings::default()); + let (b_info, b_range) = NumInfo::parse(b, &NumInfoParseSettings::default()); let ordering = numeric_str_cmp( (&a[a_range.to_owned()], &a_info), (&b[b_range.to_owned()], &b_info), @@ -469,7 +469,7 @@ mod tests { } #[test] fn single_minus() { - let info = NumInfo::parse("-", &Default::default()); + let info = NumInfo::parse("-", &NumInfoParseSettings::default()); assert_eq!( info, ( diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index cae21b163..881e53e26 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_split" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "split ~ (uutils) split input into output files" @@ -15,9 +15,9 @@ edition = "2021" path = "src/split.rs" [dependencies] -clap = { workspace=true } -memchr = { workspace=true } -uucore = { workspace=true, features=["fs"] } +clap = { workspace = true } +memchr = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "split" diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 6e29e6f4b..f1be0c47d 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -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 { + fn get_and_parse( + matches: &ArgMatches, + option: &str, + strategy: fn(u64) -> Strategy, + error: fn(ParseSizeError) -> StrategyError, + ) -> Result { + let s = matches.get_one::(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::(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::(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::(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::(OPT_NUMBER).unwrap(); let number_type = NumberType::from(s).map_err(StrategyError::NumberType)?; diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index bd873404e..91b479118 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stat" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "stat ~ (uutils) display FILE status" @@ -15,8 +15,8 @@ edition = "2021" path = "src/stat.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["entries", "libc", "fs", "fsext"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["entries", "libc", "fs", "fsext"] } [[bin]] name = "stat" diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 69f3c2760..9ffa064b8 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -614,6 +614,10 @@ impl Stater { fn do_stat(&self, file: &OsStr, stdin_is_fifo: bool) -> i32 { let display_name = file.to_string_lossy(); let file = if cfg!(unix) && display_name == "-" { + if self.show_fs { + show_error!("using '-' to denote standard input does not work in file system mode"); + return 1; + } if let Ok(p) = Path::new("/dev/stdin").canonicalize() { p.into_os_string() } else { @@ -622,7 +626,6 @@ impl Stater { } else { OsString::from(file) }; - if self.show_fs { #[cfg(unix)] let p = file.as_bytes(); diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 224af033e..66b74f9c5 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "stdbuf ~ (uutils) run COMMAND with modified standard stream buffering" @@ -15,12 +15,12 @@ edition = "2021" path = "src/stdbuf.rs" [dependencies] -clap = { workspace=true } -tempfile = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true } [build-dependencies] -libstdbuf = { version="0.0.18", package="uu_stdbuf_libstdbuf", path="src/libstdbuf" } +libstdbuf = { version = "0.0.20", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } [[bin]] name = "stdbuf" diff --git a/src/uu/stdbuf/build.rs b/src/uu/stdbuf/build.rs index 9ed9a6207..a8472243a 100644 --- a/src/uu/stdbuf/build.rs +++ b/src/uu/stdbuf/build.rs @@ -9,7 +9,7 @@ mod platform { pub const DYLIB_EXT: &str = ".so"; } -#[cfg(any(target_vendor = "apple"))] +#[cfg(target_vendor = "apple")] mod platform { pub const DYLIB_EXT: &str = ".dylib"; } diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index d9d94cd57..25104db2b 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf_libstdbuf" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "stdbuf/libstdbuf ~ (uutils); dynamic library required for stdbuf" @@ -14,12 +14,15 @@ edition = "2021" [lib] name = "libstdbuf" path = "src/libstdbuf.rs" -crate-type = ["cdylib", "rlib"] # XXX: note: the rlib is just to prevent Cargo from spitting out a warning +crate-type = [ + "cdylib", + "rlib", +] # XXX: note: the rlib is just to prevent Cargo from spitting out a warning [dependencies] cpp = "0.5" -libc = { workspace=true } -uucore = { version=">=0.0.18", package="uucore", path="../../../../uucore" } +libc = { workspace = true } +uucore = { version = ">=0.0.19", package = "uucore", path = "../../../../uucore" } [build-dependencies] cpp_build = "0.5" diff --git a/src/uu/stty/Cargo.toml b/src/uu/stty/Cargo.toml index f6ea290d9..aadae6c92 100644 --- a/src/uu/stty/Cargo.toml +++ b/src/uu/stty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stty" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "stty ~ (uutils) print or change terminal characteristics" @@ -15,9 +15,9 @@ edition = "2021" path = "src/stty.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } -nix = { workspace=true, features = ["term", "ioctl"] } +clap = { workspace = true } +uucore = { workspace = true } +nix = { workspace = true, features = ["term", "ioctl"] } [[bin]] name = "stty" diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index 2c3f00eec..536c08d80 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -7,6 +7,8 @@ // spell-checker:ignore ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl ixoff ixon iuclc ixany imaxbel iutf // spell-checker:ignore opost olcuc ocrnl onlcr onocr onlret ofill ofdel nldly crdly tabdly bsdly vtdly ffdly // spell-checker:ignore isig icanon iexten echoe crterase echok echonl noflsh xcase tostop echoprt prterase echoctl ctlecho echoke crtkill flusho extproc +// spell-checker:ignore lnext rprnt susp swtch vdiscard veof veol verase vintr vkill vlnext vquit vreprint vstart vstop vsusp vswtc vwerase werase +// spell-checker:ignore sigquit sigtstp use crate::Flag; @@ -19,7 +21,10 @@ use crate::Flag; target_os = "openbsd" )))] use nix::sys::termios::BaudRate; -use nix::sys::termios::{ControlFlags as C, InputFlags as I, LocalFlags as L, OutputFlags as O}; +use nix::sys::termios::{ + ControlFlags as C, InputFlags as I, LocalFlags as L, OutputFlags as O, + SpecialCharacterIndices as S, +}; pub const CONTROL_FLAGS: &[Flag] = &[ Flag::new("parenb", C::PARENB), @@ -313,3 +318,40 @@ pub const BAUD_RATES: &[(&str, BaudRate)] = &[ ))] ("4000000", BaudRate::B4000000), ]; +/// Control characters for the stty command. +/// +/// This constant provides a mapping between the names of control characters +/// and their corresponding values in the `S` enum. +pub const CONTROL_CHARS: &[(&str, S)] = &[ + // Sends an interrupt signal (SIGINT). + ("intr", S::VINTR), + // Sends a quit signal (SIGQUIT). + ("quit", S::VQUIT), + // Deletes the last typed character. + ("erase", S::VERASE), + // Deletes the current line. + ("kill", S::VKILL), + // Signals the end of input. + ("eof", S::VEOF), + // Signals the end of line. + ("eol", S::VEOL), + // Alternate end-of-line character. + ("eol2", S::VEOL2), + // Switch character (only on Linux). + #[cfg(target_os = "linux")] + ("swtch", S::VSWTC), + // Starts output after it has been stopped. + ("start", S::VSTART), + // Stops output. + ("stop", S::VSTOP), + // Sends a suspend signal (SIGTSTP). + ("susp", S::VSUSP), + // Reprints the current line. + ("rprnt", S::VREPRINT), + // Deletes the last word typed. + ("werase", S::VWERASE), + // Enters literal mode (next character is taken literally). + ("lnext", S::VLNEXT), + // Discards the current line. + ("discard", S::VDISCARD), +]; diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index e09662471..c933e48ae 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -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 erange tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed cfsetospeed ushort vmin vtime 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, SpecialCharacterIndices, Termios, }; use nix::{ioctl_read_bad, ioctl_write_ptr_bad}; use std::io::{self, stdout}; @@ -29,7 +30,7 @@ use uucore::{format_usage, help_about, help_usage}; target_os = "openbsd" )))] use flags::BAUD_RATES; -use flags::{CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS}; +use flags::{CONTROL_CHARS, CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS}; const USAGE: &str = help_usage!("stty.md"); const SUMMARY: &str = help_about!("stty.md"); @@ -244,12 +245,77 @@ fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> { Ok(()) } +fn control_char_to_string(cc: nix::libc::cc_t) -> nix::Result { + if cc == 0 { + return Ok("".to_string()); + } + + let (meta_prefix, code) = if cc >= 0x80 { + ("M-", cc - 0x80) + } else { + ("", cc) + }; + + // Determine the '^'-prefix if applicable and character based on the code + let (ctrl_prefix, character) = match code { + // Control characters in ASCII range + 0..=0x1f => Ok(("^", (b'@' + code) as char)), + // Printable ASCII characters + 0x20..=0x7e => Ok(("", code as char)), + // DEL character + 0x7f => Ok(("^", '?')), + // Out of range (above 8 bits) + _ => Err(nix::errno::Errno::ERANGE), + }?; + + Ok(format!("{meta_prefix}{ctrl_prefix}{character}")) +} + +fn print_control_chars(termios: &Termios, opts: &Options) -> nix::Result<()> { + if !opts.all { + // TODO: this branch should print values that differ from defaults + return Ok(()); + } + + for (text, cc_index) in CONTROL_CHARS { + print!( + "{text} = {}; ", + control_char_to_string(termios.control_chars[*cc_index as usize])? + ); + } + println!( + "min = {}; time = {};", + termios.control_chars[SpecialCharacterIndices::VMIN as usize], + termios.control_chars[SpecialCharacterIndices::VTIME as usize] + ); + 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_control_chars(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 +356,8 @@ fn print_flags(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 { + apply_baud_rate_flag(termios, s)?; + let (remove, name) = match s.strip_prefix('-') { Some(s) => (true, s), None => (false, s), @@ -332,6 +400,39 @@ fn apply_flag( ControlFlow::Continue(()) } +fn apply_baud_rate_flag(termios: &mut Termios, input: &str) -> ControlFlow { + // 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::() { + 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!()) diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index 37a997224..0879dd68f 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sum" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "sum ~ (uutils) display checksum and block counts for input" @@ -15,8 +15,8 @@ edition = "2021" path = "src/sum.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "sum" diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 0ea415b13..e9b5a8e07 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -34,7 +34,7 @@ fn bsd_sum(mut reader: Box) -> (usize, u16) { Ok(n) if n != 0 => { bytes_read += n; for &byte in buf[..n].iter() { - checksum = (checksum >> 1) + ((checksum & 1) << 15); + checksum = checksum.rotate_right(1); checksum = checksum.wrapping_add(u16::from(byte)); } } diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index 49cfbf1b5..0a0695ed5 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sync" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "sync ~ (uutils) synchronize cache writes to storage" @@ -15,15 +15,19 @@ edition = "2021" path = "src/sync.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -uucore = { workspace=true, features=["wide"] } +clap = { workspace = true } +libc = { workspace = true } +uucore = { workspace = true, features = ["wide"] } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] -nix = { workspace=true } +nix = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] -windows-sys = { workspace=true, features = ["Win32_Storage_FileSystem", "Win32_System_WindowsProgramming", "Win32_Foundation"] } +windows-sys = { workspace = true, features = [ + "Win32_Storage_FileSystem", + "Win32_System_WindowsProgramming", + "Win32_Foundation", +] } [[bin]] name = "sync" diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index e12703bca..821ad639b 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -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()), )); } } diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 0d9056915..0decf6194 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uu_tac" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "tac ~ (uutils) concatenate and display input lines in reverse order" @@ -17,11 +17,11 @@ edition = "2021" path = "src/tac.rs" [dependencies] -memchr = { workspace=true } -memmap2 = "0.6" -regex = { workspace=true } -clap = { workspace=true } -uucore = { workspace=true } +memchr = { workspace = true } +memmap2 = { workspace = true } +regex = { workspace = true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "tac" diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index a82670f9e..a22b148fe 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore (libs) kqueue fundu [package] name = "uu_tail" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "tail ~ (uutils) display the last lines of input" @@ -16,21 +16,24 @@ edition = "2021" path = "src/tail.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -memchr = { workspace=true } -notify = { workspace=true } -uucore = { workspace=true } -same-file = { workspace=true } -is-terminal = { workspace=true } -fundu = { workspace=true } +clap = { workspace = true } +libc = { workspace = true } +memchr = { workspace = true } +notify = { workspace = true } +uucore = { workspace = true } +same-file = { workspace = true } +is-terminal = { workspace = true } +fundu = { workspace = true } [target.'cfg(windows)'.dependencies] -windows-sys = { workspace=true, features = ["Win32_System_Threading", "Win32_Foundation"] } -winapi-util = { workspace=true } +windows-sys = { workspace = true, features = [ + "Win32_System_Threading", + "Win32_Foundation", +] } +winapi-util = { workspace = true } [dev-dependencies] -rstest = { workspace=true } +rstest = { workspace = true } [[bin]] name = "tail" diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 5fb5ef2b3..3b4984819 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -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; +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}; @@ -80,7 +79,7 @@ impl FilterMode { Err(e) => { return Err(USimpleError::new( 1, - format!("invalid number of bytes: {e}"), + format!("invalid number of bytes: '{e}'"), )) } } @@ -141,7 +140,8 @@ pub struct Settings { pub use_polling: bool, pub verbose: bool, pub presume_input_pipe: bool, - pub inputs: VecDeque, + /// `FILE(s)` positional arguments + pub inputs: Vec, } impl Default for Settings { @@ -149,21 +149,21 @@ impl Default for Settings { Self { max_unchanged_stats: 5, sleep_sec: Duration::from_secs_f32(1.0), - follow: Default::default(), - mode: Default::default(), + follow: Option::default(), + mode: FilterMode::default(), pid: Default::default(), retry: Default::default(), use_polling: Default::default(), verbose: Default::default(), presume_input_pipe: Default::default(), - inputs: Default::default(), + inputs: Vec::default(), } } } impl Settings { pub fn from_obsolete_args(args: &parse::ObsoleteArgs, name: Option<&OsString>) -> Self { - let mut settings: Self = Default::default(); + let mut settings = Self::default(); if args.follow { settings.follow = if name.is_some() { Some(FollowMode::Name) @@ -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 } @@ -235,12 +235,15 @@ impl Settings { // `DURATION::MAX` or `infinity` was given // * not applied here but it supports customizable time units and provides better error // messages - settings.sleep_sec = - DurationParser::without_time_units() - .parse(source) - .map_err(|_| { - UUsageError::new(1, format!("invalid number of seconds: '{source}'")) - })?; + settings.sleep_sec = match DurationParser::without_time_units().parse(source) { + Ok(duration) => SaturatingInto::::saturating_into(duration), + Err(_) => { + return Err(UUsageError::new( + 1, + format!("invalid number of seconds: '{source}'"), + )) + } + } } if let Some(s) = matches.get_one::(options::MAX_UNCHANGED_STATS) { @@ -282,19 +285,13 @@ impl Settings { } } - let mut inputs: VecDeque = matches - .get_many::(options::ARG_FILES) - .map(|v| v.map(|string| Input::from(&string)).collect()) - .unwrap_or_default(); + settings.inputs = matches + .get_many::(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) } @@ -415,16 +412,17 @@ fn parse_num(src: &str) -> Result { starting_with = true; } } - } else { - return Err(ParseSizeError::ParseFailure(src.to_string())); } - parse_size(size_string).map(|n| match (n, starting_with) { - (0, true) => Signum::PlusZero, - (0, false) => Signum::MinusZero, - (n, true) => Signum::Positive(n), - (n, false) => Signum::Negative(n), - }) + match parse_size(size_string) { + Ok(n) => match (n, starting_with) { + (0, true) => Ok(Signum::PlusZero), + (0, false) => Ok(Signum::MinusZero), + (n, true) => Ok(Signum::Positive(n)), + (n, false) => Ok(Signum::Negative(n)), + }, + Err(_) => Err(ParseSizeError::ParseFailure(size_string.to_string())), + } } pub fn parse_args(args: impl uucore::Args) -> UResult { @@ -589,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), ) } diff --git a/src/uu/tail/src/follow/watch.rs b/src/uu/tail/src/follow/watch.rs index 67ad8f0c2..9569e6e21 100644 --- a/src/uu/tail/src/follow/watch.rs +++ b/src/uu/tail/src/follow/watch.rs @@ -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) -> UResult<()> { + fn init_files(&mut self, inputs: &Vec) -> UResult<()> { if let Some(watcher_rx) = &mut self.watcher_rx { for input in inputs { match input.kind() { diff --git a/src/uu/tail/src/paths.rs b/src/uu/tail/src/paths.rs index 4badd6866..5ed654037 100644 --- a/src/uu/tail/src/paths.rs +++ b/src/uu/tail/src/paths.rs @@ -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>(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>(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 } diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index b0cf0e916..10fa63768 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tee" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "tee ~ (uutils) display input and copy to FILE" @@ -15,9 +15,9 @@ edition = "2021" path = "src/tee.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -uucore = { workspace=true, features=["libc", "signals"] } +clap = { workspace = true } +libc = { workspace = true } +uucore = { workspace = true, features = ["libc", "signals"] } [[bin]] name = "tee" diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index b3229f506..4926bff32 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_test" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "test ~ (uutils) evaluate comparison and file type expressions" @@ -15,12 +15,12 @@ edition = "2021" path = "src/test.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +libc = { workspace = true } +uucore = { workspace = true } [target.'cfg(target_os = "redox")'.dependencies] -redox_syscall = { workspace=true } +redox_syscall = { workspace = true } [[bin]] name = "test" diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index 8b92f9d8b..452a5b04a 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_timeout" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "timeout ~ (uutils) run COMMAND with a DURATION time limit" @@ -15,10 +15,10 @@ edition = "2021" path = "src/timeout.rs" [dependencies] -clap = { workspace=true } -libc = { workspace=true } -nix = { workspace=true, features = ["signal"] } -uucore = { workspace=true, features=["process", "signals"] } +clap = { workspace = true } +libc = { workspace = true } +nix = { workspace = true, features = ["signal"] } +uucore = { workspace = true, features = ["process", "signals"] } [[bin]] name = "timeout" diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index 0bb89ec84..ce0752093 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -1,7 +1,7 @@ -# spell-checker:ignore humantime +# spell-checker:ignore datetime [package] name = "uu_touch" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "touch ~ (uutils) change FILE timestamps" @@ -16,14 +16,17 @@ edition = "2021" path = "src/touch.rs" [dependencies] -filetime = { workspace=true } -clap = { workspace=true } -humantime_to_duration = { workspace=true } -time = { workspace=true, features = ["parsing", "formatting", "local-offset", "macros"] } -uucore = { workspace=true, features=["libc"] } +filetime = { workspace = true } +clap = { workspace = true } +chrono = { workspace = true } +parse_datetime = { workspace = true } +uucore = { workspace = true, features = ["libc"] } [target.'cfg(target_os = "windows")'.dependencies] -windows-sys = { workspace=true, features = ["Win32_Storage_FileSystem", "Win32_Foundation"] } +windows-sys = { workspace = true, features = [ + "Win32_Storage_FileSystem", + "Win32_Foundation", +] } [[bin]] name = "touch" diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 55663fdab..baa28890c 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -6,16 +6,16 @@ // 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 lpszfilepath mktime DATETIME subsecond datelike timelike +// spell-checker:ignore (FORMATS) MMDDhhmm YYYYMMDDHHMM YYMMDDHHMM YYYYMMDDHHMMS +use chrono::{DateTime, Datelike, Duration, Local, NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; use clap::builder::ValueParser; use clap::{crate_version, Arg, ArgAction, ArgGroup, Command}; -use filetime::{set_symlink_file_times, FileTime}; +use filetime::{set_file_times, set_symlink_file_times, FileTime}; use std::ffi::OsString; use std::fs::{self, File}; use std::path::{Path, PathBuf}; -use time::macros::{format_description, offset, time}; -use time::Duration; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage, show}; @@ -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"; @@ -41,27 +41,31 @@ pub mod options { static ARG_FILES: &str = "files"; -// Convert a date/time to a date with a TZ offset -fn to_local(tm: time::PrimitiveDateTime) -> time::OffsetDateTime { - let offset = match time::OffsetDateTime::now_local() { - Ok(lo) => lo.offset(), - Err(e) => { - panic!("error: {e}"); - } - }; - tm.assume_offset(offset) +mod format { + pub(crate) const POSIX_LOCALE: &str = "%a %b %e %H:%M:%S %Y"; + pub(crate) const ISO_8601: &str = "%Y-%m-%d"; + // "%Y%m%d%H%M.%S" 15 chars + pub(crate) const YYYYMMDDHHMM_DOT_SS: &str = "%Y%m%d%H%M.%S"; + // "%Y-%m-%d %H:%M:%S.%SS" 12 chars + pub(crate) const YYYYMMDDHHMMSS: &str = "%Y-%m-%d %H:%M:%S.%f"; + // "%Y-%m-%d %H:%M:%S" 12 chars + pub(crate) const YYYYMMDDHHMMS: &str = "%Y-%m-%d %H:%M:%S"; + // "%Y-%m-%d %H:%M" 12 chars + // Used for example in tests/touch/no-rights.sh + pub(crate) const YYYY_MM_DD_HH_MM: &str = "%Y-%m-%d %H:%M"; + // "%Y%m%d%H%M" 12 chars + pub(crate) const YYYYMMDDHHMM: &str = "%Y%m%d%H%M"; + // "%Y-%m-%d %H:%M +offset" + // Used for example in tests/touch/relative.sh + pub(crate) const YYYYMMDDHHMM_OFFSET: &str = "%Y-%m-%d %H:%M %z"; } -// Convert a date/time with a TZ offset into a FileTime -fn local_dt_to_filetime(dt: time::OffsetDateTime) -> FileTime { - FileTime::from_unix_time(dt.unix_timestamp(), dt.nanosecond()) -} - -// Convert a date/time, considering that the input is in UTC time -// Used for touch -d 1970-01-01 18:43:33.023456789 for example -fn dt_to_filename(tm: time::PrimitiveDateTime) -> FileTime { - let dt = tm.assume_offset(offset!(UTC)); - local_dt_to_filetime(dt) +/// Convert a DateTime with a TZ offset into a FileTime +/// +/// The DateTime is converted into a unix timestamp from which the FileTime is +/// constructed. +fn datetime_to_filetime(dt: &DateTime) -> FileTime { + FileTime::from_unix_time(dt.timestamp(), dt.timestamp_subsec_nanos()) } #[uucore::main] @@ -84,13 +88,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 +120,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { (timestamp, timestamp) } (None, None) => { - let timestamp = - if let Some(current) = matches.get_one::(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::(options::sources::TIMESTAMP) + { + parse_timestamp(ts)? + } else { + datetime_to_filetime(&Local::now()) + }; (timestamp, timestamp) } }; @@ -216,7 +216,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else if matches.get_flag(options::NO_DEREF) { set_symlink_file_times(path, atime, mtime) } else { - filetime::set_file_times(path, atime, mtime) + set_file_times(path, atime, mtime) } .map_err_context(|| format!("setting times of {}", path.quote()))?; } @@ -243,7 +243,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 +255,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 +288,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 +299,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 +311,7 @@ pub fn uu_app() -> Command { .group( ArgGroup::new(options::SOURCES) .args([ - options::sources::CURRENT, + options::sources::TIMESTAMP, options::sources::DATE, options::sources::REFERENCE, ]) @@ -336,64 +336,6 @@ fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> { )) } -const POSIX_LOCALE_FORMAT: &[time::format_description::FormatItem] = format_description!( - "[weekday repr:short] [month repr:short] [day padding:space] \ - [hour]:[minute]:[second] [year]" -); - -const ISO_8601_FORMAT: &[time::format_description::FormatItem] = - format_description!("[year]-[month]-[day]"); - -// "%Y%m%d%H%M.%S" 15 chars -const YYYYMMDDHHMM_DOT_SS_FORMAT: &[time::format_description::FormatItem] = format_description!( - "[year repr:full][month repr:numerical padding:zero]\ - [day][hour][minute].[second]" -); - -// "%Y-%m-%d %H:%M:%S.%SS" 12 chars -const YYYYMMDDHHMMSS_FORMAT: &[time::format_description::FormatItem] = format_description!( - "[year repr:full]-[month repr:numerical padding:zero]-\ - [day] [hour]:[minute]:[second].[subsecond]" -); - -// "%Y-%m-%d %H:%M:%S" 12 chars -const YYYYMMDDHHMMS_FORMAT: &[time::format_description::FormatItem] = format_description!( - "[year repr:full]-[month repr:numerical padding:zero]-\ - [day] [hour]:[minute]:[second]" -); - -// "%Y-%m-%d %H:%M" 12 chars -// Used for example in tests/touch/no-rights.sh -const YYYY_MM_DD_HH_MM_FORMAT: &[time::format_description::FormatItem] = format_description!( - "[year repr:full]-[month repr:numerical padding:zero]-\ - [day] [hour]:[minute]" -); - -// "%Y%m%d%H%M" 12 chars -const YYYYMMDDHHMM_FORMAT: &[time::format_description::FormatItem] = format_description!( - "[year repr:full][month repr:numerical padding:zero]\ - [day][hour][minute]" -); - -// "%y%m%d%H%M.%S" 13 chars -const YYMMDDHHMM_DOT_SS_FORMAT: &[time::format_description::FormatItem] = format_description!( - "[year repr:last_two padding:none][month][day]\ - [hour][minute].[second]" -); - -// "%y%m%d%H%M" 10 chars -const YYMMDDHHMM_FORMAT: &[time::format_description::FormatItem] = format_description!( - "[year repr:last_two padding:none][month padding:zero][day padding:zero]\ - [hour repr:24 padding:zero][minute padding:zero]" -); - -// "%Y-%m-%d %H:%M +offset" -// Used for example in tests/touch/relative.sh -const YYYYMMDDHHMM_OFFSET_FORMAT: &[time::format_description::FormatItem] = format_description!( - "[year]-[month]-[day] [hour repr:24]:[minute] \ - [offset_hour sign:mandatory][offset_minute]" -); - fn parse_date(s: &str) -> UResult { // This isn't actually compatible with GNU touch, but there doesn't seem to // be any simple specification for what format this parameter allows and I'm @@ -409,62 +351,61 @@ fn parse_date(s: &str) -> UResult { // Tue Dec 3 ... // ("%c", POSIX_LOCALE_FORMAT), // - if let Ok(parsed) = time::PrimitiveDateTime::parse(s, &POSIX_LOCALE_FORMAT) { - return Ok(local_dt_to_filetime(to_local(parsed))); + if let Ok(parsed) = Local.datetime_from_str(s, format::POSIX_LOCALE) { + return Ok(datetime_to_filetime(&parsed)); } // Also support other formats found in the GNU tests like // in tests/misc/stat-nanoseconds.sh // or tests/touch/no-rights.sh for fmt in [ - YYYYMMDDHHMMS_FORMAT, - YYYYMMDDHHMMSS_FORMAT, - YYYY_MM_DD_HH_MM_FORMAT, - YYYYMMDDHHMM_OFFSET_FORMAT, + format::YYYYMMDDHHMMS, + format::YYYYMMDDHHMMSS, + format::YYYY_MM_DD_HH_MM, + format::YYYYMMDDHHMM_OFFSET, ] { - if let Ok(parsed) = time::PrimitiveDateTime::parse(s, &fmt) { - return Ok(dt_to_filename(parsed)); + if let Ok(parsed) = Utc.datetime_from_str(s, fmt) { + return Ok(datetime_to_filetime(&parsed)); } } // "Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)" // ("%F", ISO_8601_FORMAT), - if let Ok(parsed) = time::Date::parse(s, &ISO_8601_FORMAT) { - return Ok(local_dt_to_filetime(to_local( - time::PrimitiveDateTime::new(parsed, time!(00:00)), - ))); + if let Ok(parsed_date) = NaiveDate::parse_from_str(s, format::ISO_8601) { + let parsed = Local + .from_local_datetime(&parsed_date.and_time(NaiveTime::MIN)) + .unwrap(); + return Ok(datetime_to_filetime(&parsed)); } // "@%s" is "The number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). (TZ) (Calculated from mktime(tm).)" if s.bytes().next() == Some(b'@') { if let Ok(ts) = &s[1..].parse::() { - // Don't convert to local time in this case - seconds since epoch are not time-zone dependent - return Ok(local_dt_to_filetime( - time::OffsetDateTime::from_unix_timestamp(*ts).unwrap(), - )); + return Ok(FileTime::from_unix_time(*ts, 0)); } } - if let Ok(duration) = humantime_to_duration::from_str(s) { - let now_local = time::OffsetDateTime::now_local().unwrap(); - let diff = now_local.checked_add(duration).unwrap(); - return Ok(local_dt_to_filetime(diff)); + if let Ok(duration) = parse_datetime::from_str(s) { + let dt = Local::now() + duration; + return Ok(datetime_to_filetime(&dt)); } Err(USimpleError::new(1, format!("Unable to parse date: {s}"))) } fn parse_timestamp(s: &str) -> UResult { - // TODO: handle error - let now = time::OffsetDateTime::now_utc(); + use format::*; - let (mut format, mut ts) = match s.chars().count() { - 15 => (YYYYMMDDHHMM_DOT_SS_FORMAT, s.to_owned()), - 12 => (YYYYMMDDHHMM_FORMAT, s.to_owned()), - 13 => (YYMMDDHHMM_DOT_SS_FORMAT, s.to_owned()), - 10 => (YYMMDDHHMM_FORMAT, s.to_owned()), - 11 => (YYYYMMDDHHMM_DOT_SS_FORMAT, format!("{}{}", now.year(), s)), - 8 => (YYYYMMDDHHMM_FORMAT, format!("{}{}", now.year(), s)), + let current_year = || Local::now().year(); + + let (format, ts) = match s.chars().count() { + 15 => (YYYYMMDDHHMM_DOT_SS, s.to_owned()), + 12 => (YYYYMMDDHHMM, s.to_owned()), + // If we don't add "20", we have insufficient information to parse + 13 => (YYYYMMDDHHMM_DOT_SS, format!("20{}", s)), + 10 => (YYYYMMDDHHMM, format!("20{}", s)), + 11 => (YYYYMMDDHHMM_DOT_SS, format!("{}{}", current_year(), s)), + 8 => (YYYYMMDDHHMM, format!("{}{}", current_year(), s)), _ => { return Err(USimpleError::new( 1, @@ -472,54 +413,33 @@ fn parse_timestamp(s: &str) -> UResult { )) } }; - // workaround time returning Err(TryFromParsed(InsufficientInformation)) for year w/ - // repr:last_two - // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1ccfac7c07c5d1c7887a11decf0e1996 - if s.chars().count() == 10 { - format = YYYYMMDDHHMM_FORMAT; - ts = "20".to_owned() + &ts; - } else if s.chars().count() == 13 { - format = YYYYMMDDHHMM_DOT_SS_FORMAT; - ts = "20".to_owned() + &ts; - } - let leap_sec = if (format == YYYYMMDDHHMM_DOT_SS_FORMAT || format == YYMMDDHHMM_DOT_SS_FORMAT) - && ts.ends_with(".60") - { - // Work around to disable leap seconds - // Used in gnu/tests/touch/60-seconds - ts = ts.replace(".60", ".59"); - true - } else { - false - }; - - let tm = time::PrimitiveDateTime::parse(&ts, &format) + let mut local = chrono::Local + .datetime_from_str(&ts, format) .map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", ts.quote())))?; - let mut local = to_local(tm); - if leap_sec { - // We are dealing with a leap second, add it - local = local.saturating_add(Duration::SECOND); + + // Chrono caps seconds at 59, but 60 is valid. It might be a leap second + // or wrap to the next minute. But that doesn't really matter, because we + // only care about the timestamp anyway. + // Tested in gnu/tests/touch/60-seconds + if local.second() == 59 && ts.ends_with(".60") { + local += Duration::seconds(1); } - let ft = local_dt_to_filetime(local); - // // We have to check that ft is valid time. Due to daylight saving - // // time switch, local time can jump from 1:59 AM to 3:00 AM, - // // in which case any time between 2:00 AM and 2:59 AM is not valid. - // // Convert back to local time and see if we got the same value back. - // let ts = time::Timespec { - // sec: ft.unix_seconds(), - // nsec: 0, - // }; - // let tm2 = time::at(ts); - // if tm.tm_hour != tm2.tm_hour { - // return Err(USimpleError::new( - // 1, - // format!("invalid date format {}", s.quote()), - // )); - // } + // Due to daylight saving time switch, local time can jump from 1:59 AM to + // 3:00 AM, in which case any time between 2:00 AM and 2:59 AM is not + // valid. If we are within this jump, chrono takes the offset from before + // the jump. If we then jump forward an hour, we get the new corrected + // offset. Jumping back will then now correctly take the jump into account. + let local2 = local + Duration::hours(1) - Duration::hours(1); + if local.hour() != local2.hour() { + return Err(USimpleError::new( + 1, + format!("invalid date format {}", s.quote()), + )); + } - Ok(ft) + Ok(datetime_to_filetime(&local)) } // TODO: this may be a good candidate to put in fsext.rs diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index 559546efd..7bc942485 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tr" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "tr ~ (uutils) translate characters within input and display" @@ -15,9 +15,9 @@ edition = "2021" path = "src/tr.rs" [dependencies] -nom = { workspace=true } -clap = { workspace=true } -uucore = { workspace=true } +nom = { workspace = true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "tr" diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index bab8161d8..d70cc0d7a 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_true" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "true ~ (uutils) do nothing and succeed" @@ -15,8 +15,8 @@ edition = "2021" path = "src/true.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "true" diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index 0d5615eea..4d756f46c 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_truncate" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "truncate ~ (uutils) truncate (or extend) FILE to SIZE" @@ -15,8 +15,8 @@ edition = "2021" path = "src/truncate.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "truncate" diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index 08a035eac..1e2b72d2f 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tsort" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "tsort ~ (uutils) topologically sort input (partially ordered) pairs" @@ -15,8 +15,8 @@ edition = "2021" path = "src/tsort.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "tsort" diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index a70f32dfd..68f51b213 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -6,7 +6,7 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. use clap::{crate_version, Arg, Command}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; @@ -103,8 +103,8 @@ pub fn uu_app() -> Command { // but using integer may improve performance. #[derive(Default)] struct Graph { - in_edges: HashMap>, - out_edges: HashMap>, + in_edges: BTreeMap>, + out_edges: BTreeMap>, result: Vec, } @@ -122,7 +122,7 @@ impl Graph { } fn init_node(&mut self, n: &str) { - self.in_edges.insert(n.to_string(), HashSet::new()); + self.in_edges.insert(n.to_string(), BTreeSet::new()); self.out_edges.insert(n.to_string(), vec![]); } @@ -171,11 +171,6 @@ impl Graph { } fn is_acyclic(&self) -> bool { - for edges in self.out_edges.values() { - if !edges.is_empty() { - return false; - } - } - true + self.out_edges.values().all(|edge| edge.is_empty()) } } diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index ed4f2ba53..f94026831 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tty" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "tty ~ (uutils) display the name of the terminal connected to standard input" @@ -15,10 +15,10 @@ edition = "2021" path = "src/tty.rs" [dependencies] -clap = { workspace=true } -nix = { workspace=true, features=["term"] } -is-terminal = { workspace=true } -uucore = { workspace=true, features=["fs"] } +clap = { workspace = true } +nix = { workspace = true, features = ["term"] } +is-terminal = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "tty" diff --git a/src/uu/uname/Cargo.toml b/src/uu/uname/Cargo.toml index a24113cb7..1c0421c65 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uname" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "uname ~ (uutils) display system information" @@ -15,9 +15,9 @@ edition = "2021" path = "src/uname.rs" [dependencies] -platform-info = { workspace=true } -clap = { workspace=true } -uucore = { workspace=true } +platform-info = { workspace = true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "uname" diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index 5e4d486cb..483881368 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unexpand" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "unexpand ~ (uutils) convert input spaces to tabs" @@ -15,9 +15,9 @@ edition = "2021" path = "src/unexpand.rs" [dependencies] -clap = { workspace=true } -unicode-width = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +unicode-width = { workspace = true } +uucore = { workspace = true } [[bin]] name = "unexpand" diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index 1491f81f2..b70cc993a 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uniq" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "uniq ~ (uutils) filter identical adjacent lines from input" @@ -15,8 +15,8 @@ edition = "2021" path = "src/uniq.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "uniq" diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index 99efdbaee..401e0d591 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unlink" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "unlink ~ (uutils) remove a (file system) link to FILE" @@ -15,8 +15,8 @@ edition = "2021" path = "src/unlink.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true } [[bin]] name = "unlink" diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index fdd49cb84..1835340ad 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uptime" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "uptime ~ (uutils) display dynamic system information" @@ -15,9 +15,9 @@ edition = "2021" path = "src/uptime.rs" [dependencies] -chrono = { workspace=true } -clap = { workspace=true } -uucore = { workspace=true, features=["libc", "utmpx"] } +chrono = { workspace = true } +clap = { workspace = true } +uucore = { workspace = true, features = ["libc", "utmpx"] } [[bin]] name = "uptime" diff --git a/src/uu/users/Cargo.toml b/src/uu/users/Cargo.toml index 023b34a3e..40341cf77 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_users" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "users ~ (uutils) display names of currently logged-in users" @@ -15,8 +15,8 @@ edition = "2021" path = "src/users.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["utmpx"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["utmpx"] } [[bin]] name = "users" diff --git a/src/uu/vdir/Cargo.toml b/src/uu/vdir/Cargo.toml index 281e7158e..55624dad8 100644 --- a/src/uu/vdir/Cargo.toml +++ b/src/uu/vdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_vdir" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -l -b" @@ -15,9 +15,9 @@ edition = "2021" path = "src/vdir.rs" [dependencies] -clap = { workspace=true, features = ["env"] } -uucore = { workspace=true, features=["entries", "fs"] } -uu_ls = { workspace=true } +clap = { workspace = true, features = ["env"] } +uucore = { workspace = true, features = ["entries", "fs"] } +uu_ls = { workspace = true } [[bin]] name = "vdir" diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 2b42affb5..396a0f8dc 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_wc" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "wc ~ (uutils) display newline, word, and byte counts for input" @@ -15,15 +15,15 @@ edition = "2021" path = "src/wc.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["pipes"] } -bytecount = { workspace=true } -thiserror = { workspace=true } -unicode-width = { workspace=true } +clap = { workspace = true } +uucore = { workspace = true, features = ["pipes"] } +bytecount = { workspace = true } +thiserror = { workspace = true } +unicode-width = { workspace = true } [target.'cfg(unix)'.dependencies] -nix = { workspace=true } -libc = { workspace=true } +nix = { workspace = true } +libc = { workspace = true } [[bin]] name = "wc" diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index 5b5b57bf1..c727a3fc2 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_who" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "who ~ (uutils) display information about currently logged-in users" @@ -15,8 +15,8 @@ edition = "2021" path = "src/who.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["utmpx"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["utmpx"] } [[bin]] name = "who" diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index 7e13c14f3..774831a66 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_whoami" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "whoami ~ (uutils) display user name of current effective user ID" @@ -15,14 +15,18 @@ edition = "2021" path = "src/whoami.rs" [dependencies] -clap = { workspace=true } -uucore = { workspace=true, features=["entries"] } +clap = { workspace = true } +uucore = { workspace = true, features = ["entries"] } [target.'cfg(target_os = "windows")'.dependencies] -windows-sys = { workspace=true, features = ["Win32_NetworkManagement_NetManagement", "Win32_System_WindowsProgramming", "Win32_Foundation"] } +windows-sys = { workspace = true, features = [ + "Win32_NetworkManagement_NetManagement", + "Win32_System_WindowsProgramming", + "Win32_Foundation", +] } [target.'cfg(unix)'.dependencies] -libc = { workspace=true } +libc = { workspace = true } [[bin]] name = "whoami" diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index 9d661fb0d..bd44c34f9 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_yes" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "yes ~ (uutils) repeatedly display a line with STRING (or 'y')" @@ -15,15 +15,15 @@ edition = "2021" path = "src/yes.rs" [dependencies] -clap = { workspace=true } -itertools = { workspace=true } +clap = { workspace = true } +itertools = { workspace = true } [target.'cfg(unix)'.dependencies] -uucore = { workspace=true, features=["pipes", "signals"] } -nix = { workspace=true } +uucore = { workspace = true, features = ["pipes", "signals"] } +nix = { workspace = true } [target.'cfg(not(unix))'.dependencies] -uucore = { workspace=true, features=["pipes"] } +uucore = { workspace = true, features = ["pipes"] } [[bin]] name = "yes" diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index fd5124064..72c19b872 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -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( diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index ea6bb2424..d376e807a 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uucore" -version = "0.0.18" +version = "0.0.20" authors = ["uutils developers"] license = "MIT" description = "uutils ~ 'core' uutils code library (cross-platform)" @@ -15,50 +15,58 @@ categories = ["command-line-utilities"] edition = "2021" [lib] -path="src/lib/lib.rs" +path = "src/lib/lib.rs" [dependencies] -clap = { workspace=true } -uucore_procs = { workspace=true } -dns-lookup = { version="2.0.2", optional=true } +clap = { workspace = true } +uucore_procs = { workspace = true } +dns-lookup = { version = "2.0.2", optional = true } dunce = "1.0.4" wild = "2.1" -glob = "0.3.1" +glob = { workspace = true } # * optional -itertools = { version="0.10.5", optional=true } -thiserror = { workspace=true, optional=true } -time = { workspace=true, optional=true, features = ["formatting", "local-offset", "macros"] } +itertools = { workspace = true, optional = true } +thiserror = { workspace = true, optional = true } +time = { workspace = true, optional = true, features = [ + "formatting", + "local-offset", + "macros", +] } # * "problem" dependencies (pinned) -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.144", optional=true } -once_cell = { workspace=true } +data-encoding = { version = "2.4", optional = true } +data-encoding-macro = { version = "0.1.13", optional = true } +z85 = { version = "3.0.5", optional = true } +libc = { workspace = true, optional = true } +once_cell = { workspace = true } os_display = "0.1.3" -digest = { workspace=true } -hex = { workspace=true } -memchr = { workspace=true } -md-5 = { workspace=true } -sha1 = { workspace=true } -sha2 = { workspace=true } -sha3 = { workspace=true } -blake2b_simd = { workspace=true } -blake3 = { workspace=true } -sm3 = { workspace=true } +digest = { workspace = true } +hex = { workspace = true } +memchr = { workspace = true } +md-5 = { workspace = true } +sha1 = { workspace = true } +sha2 = { workspace = true } +sha3 = { workspace = true } +blake2b_simd = { workspace = true } +blake3 = { workspace = true } +sm3 = { workspace = true } [target.'cfg(unix)'.dependencies] -walkdir = { workspace=true, optional=true } -nix = { workspace=true, features = ["fs", "uio", "zerocopy", "signal"] } +walkdir = { workspace = true, optional = true } +nix = { workspace = true, features = ["fs", "uio", "zerocopy", "signal"] } [dev-dependencies] -clap = { workspace=true } -once_cell = { workspace=true } -tempfile = { workspace=true } +clap = { workspace = true } +once_cell = { workspace = true } +tempfile = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] -winapi-util = { version= "0.1.5", optional=true } -windows-sys = { version = "0.48.0", optional = true, default-features = false, features = ["Win32_Storage_FileSystem", "Win32_Foundation", "Win32_System_WindowsProgramming"] } +winapi-util = { workspace = true, optional = true } +windows-sys = { workspace = true, optional = true, default-features = false, features = [ + "Win32_Storage_FileSystem", + "Win32_Foundation", + "Win32_System_WindowsProgramming", +] } [features] default = [] diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 797be9c2c..5804a2235 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -31,6 +31,9 @@ use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR}; #[cfg(target_os = "windows")] use winapi_util::AsHandleRef; +/// Used to check if the `mode` has its `perm` bit set. +/// +/// This macro expands to `mode & perm != 0`. #[cfg(unix)] #[macro_export] macro_rules! has { @@ -635,12 +638,42 @@ pub fn are_hardlinks_to_same_file(_source: &Path, _target: &Path) -> bool { /// * `bool` - Returns `true` if the paths are hard links to the same file, and `false` otherwise. #[cfg(unix)] pub fn are_hardlinks_to_same_file(source: &Path, target: &Path) -> bool { + let source_metadata = match fs::symlink_metadata(source) { + Ok(metadata) => metadata, + Err(_) => return false, + }; + + let target_metadata = match fs::symlink_metadata(target) { + Ok(metadata) => metadata, + Err(_) => return false, + }; + + source_metadata.ino() == target_metadata.ino() && source_metadata.dev() == target_metadata.dev() +} + +#[cfg(not(unix))] +pub fn are_hardlinks_or_one_way_symlink_to_same_file(_source: &Path, _target: &Path) -> bool { + false +} + +/// Checks if either two paths are hard links to the same file or if the source path is a symbolic link which when fully resolved points to target path +/// +/// # Arguments +/// +/// * `source` - A reference to a `Path` representing the source path. +/// * `target` - A reference to a `Path` representing the target path. +/// +/// # Returns +/// +/// * `bool` - Returns `true` if either of above conditions are true, and `false` otherwise. +#[cfg(unix)] +pub fn are_hardlinks_or_one_way_symlink_to_same_file(source: &Path, target: &Path) -> bool { let source_metadata = match fs::metadata(source) { Ok(metadata) => metadata, Err(_) => return false, }; - let target_metadata = match fs::metadata(target) { + let target_metadata = match fs::symlink_metadata(target) { Ok(metadata) => metadata, Err(_) => return false, }; diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 89f1d6e71..6f831fb92 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -194,15 +194,10 @@ impl MountInfo { } #[cfg(unix)] { - if self.dev_name.find(':').is_some() + self.remote = self.dev_name.find(':').is_some() || (self.dev_name.starts_with("//") && self.fs_type == "smbfs" || self.fs_type == "cifs") - || self.dev_name == "-hosts" - { - self.remote = true; - } else { - self.remote = false; - } + || self.dev_name == "-hosts"; } } @@ -371,9 +366,9 @@ extern "C" { fn get_mount_info(mount_buffer_p: *mut *mut StatFs, flags: c_int) -> c_int; #[cfg(any( - all(target_os = "freebsd"), - all(target_os = "netbsd"), - all(target_os = "openbsd"), + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", all(target_vendor = "apple", target_arch = "aarch64") ))] #[link_name = "getmntinfo"] // spell-checker:disable-line diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/floatf.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/floatf.rs index e13629af5..cca2750dc 100644 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/floatf.rs +++ b/src/uucore/src/lib/features/tokenize/num_format/formatters/floatf.rs @@ -10,7 +10,7 @@ use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnaly pub struct Floatf; impl Floatf { pub fn new() -> Self { - Self::default() + Self } } impl Formatter for Floatf { diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/scif.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/scif.rs index c5b88b5a7..c871dc4e5 100644 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/scif.rs +++ b/src/uucore/src/lib/features/tokenize/num_format/formatters/scif.rs @@ -10,7 +10,7 @@ pub struct Scif; impl Scif { pub fn new() -> Self { - Self::default() + Self } } impl Formatter for Scif { diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index e76e540c8..02724e1bf 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -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")] @@ -85,6 +86,10 @@ use std::sync::atomic::Ordering; use once_cell::sync::Lazy; +/// Execute utility code for `util`. +/// +/// This macro expands to a main function that invokes the `uumain` function in `util` +/// Exits with code returned by `uumain`. #[macro_export] macro_rules! bin { ($util:ident) => { diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index 2d161c43f..9998c7560 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -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)); + } } diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index e564c7bb5..f0f62569d 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -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(); diff --git a/src/uucore/src/lib/mods/version_cmp.rs b/src/uucore/src/lib/mods/version_cmp.rs index 99b8c8b40..828b7f2a6 100644 --- a/src/uucore/src/lib/mods/version_cmp.rs +++ b/src/uucore/src/lib/mods/version_cmp.rs @@ -106,7 +106,7 @@ pub fn version_cmp(mut a: &str, mut b: &str) -> Ordering { // 1. Compare leading non-numerical part // 2. Compare leading numerical part // 3. Repeat - loop { + while !a.is_empty() || !b.is_empty() { let a_numerical_start = a.find(|c: char| c.is_ascii_digit()).unwrap_or(a.len()); let b_numerical_start = b.find(|c: char| c.is_ascii_digit()).unwrap_or(b.len()); @@ -139,12 +139,9 @@ pub fn version_cmp(mut a: &str, mut b: &str) -> Ordering { a = &a[a_numerical_end..]; b = &b[b_numerical_end..]; - - if a.is_empty() && b.is_empty() { - // Default to the lexical comparison. - return str_cmp; - } } + + Ordering::Equal } #[cfg(test)] @@ -229,14 +226,14 @@ mod tests { // Leading zeroes assert_eq!( version_cmp("012", "12"), - Ordering::Less, - "A single leading zero can make a difference" + Ordering::Equal, + "A single leading zero does not make a difference" ); assert_eq!( version_cmp("000800", "0000800"), - Ordering::Greater, - "Leading number of zeroes is used even if both non-zero number of zeros" + Ordering::Equal, + "Multiple leading zeros do not make a difference" ); // Numbers and other characters combined @@ -280,14 +277,8 @@ mod tests { assert_eq!( version_cmp("aa10aa0022", "aa010aa022"), - Ordering::Greater, - "The leading zeroes of the first number has priority." - ); - - assert_eq!( - version_cmp("aa10aa0022", "aa10aa022"), - Ordering::Less, - "The leading zeroes of other numbers than the first are used." + Ordering::Equal, + "Test multiple numeric values with leading zeros" ); assert_eq!( @@ -307,7 +298,7 @@ mod tests { assert_eq!( version_cmp("aa2000000000000000000000bb", "aa002000000000000000000000bb"), - Ordering::Greater, + Ordering::Equal, "Leading zeroes for numbers larger than u64::MAX are \ handled correctly without crashing" ); diff --git a/src/uucore/src/lib/parser.rs b/src/uucore/src/lib/parser.rs index 8eae16bbf..fc3e46b5c 100644 --- a/src/uucore/src/lib/parser.rs +++ b/src/uucore/src/lib/parser.rs @@ -1,3 +1,4 @@ pub mod parse_glob; pub mod parse_size; pub mod parse_time; +pub mod shortcut_value_parser; diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 60209d849..2ea84e389 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -3,7 +3,7 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore (ToDO) hdsf ghead gtail +// spell-checker:ignore (ToDO) hdsf ghead gtail ACDBK hexdigit use std::error::Error; use std::fmt; @@ -25,6 +25,12 @@ pub struct Parser<'parser> { pub default_unit: Option<&'parser str>, } +enum NumberSystem { + Decimal, + Octal, + Hexadecimal, +} + impl<'parser> Parser<'parser> { pub fn with_allow_list(&mut self, allow_list: &'parser [&str]) -> &mut Self { self.allow_list = Some(allow_list); @@ -62,31 +68,26 @@ impl<'parser> Parser<'parser> { /// assert_eq!(Ok(123), parse_size("123")); /// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000 /// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024 + /// assert_eq!(Ok(44251 * 1024), parse_size("0xACDBK")); /// ``` pub fn parse(&self, size: &str) -> Result { if size.is_empty() { return Err(ParseSizeError::parse_failure(size)); } - // Get the numeric part of the size argument. For example, if the - // argument is "123K", then the numeric part is "123". - let numeric_string: String = size.chars().take_while(|c| c.is_ascii_digit()).collect(); - let number: u64 = if numeric_string.is_empty() { - 1 - } else { - match numeric_string.parse() { - Ok(n) => n, - Err(_) => return Err(ParseSizeError::parse_failure(size)), - } - }; - // Get the alphabetic units part of the size argument and compute - // the factor it represents. For example, if the argument is "123K", - // then the unit part is "K" and the factor is 1024. This may be the - // empty string, in which case, the factor is 1. - // - // The lowercase "b" (used by `od`, `head`, `tail`, etc.) means - // "block" and the Posix block size is 512. The uppercase "B" - // means "byte". + let number_system: NumberSystem = self.determine_number_system(size); + + // Split the size argument into numeric and unit parts + // For example, if the argument is "123K", the numeric part is "123", and + // the unit is "K" + let numeric_string: String = match number_system { + NumberSystem::Hexadecimal => size + .chars() + .take(2) + .chain(size.chars().skip(2).take_while(|c| c.is_ascii_hexdigit())) + .collect(), + _ => size.chars().take_while(|c| c.is_ascii_digit()).collect(), + }; let mut unit: &str = &size[numeric_string.len()..]; if let Some(default_unit) = self.default_unit { @@ -115,6 +116,12 @@ impl<'parser> Parser<'parser> { } } + // Compute the factor the unit represents. + // empty string means the factor is 1. + // + // The lowercase "b" (used by `od`, `head`, `tail`, etc.) means + // "block" and the Posix block size is 512. The uppercase "B" + // means "byte". let (base, exponent): (u128, u32) = match unit { "" => (1, 0), "B" if self.capital_b_bytes => (1, 0), @@ -142,10 +149,62 @@ impl<'parser> Parser<'parser> { Ok(n) => n, Err(_) => return Err(ParseSizeError::size_too_big(size)), }; + + // parse string into u64 + let number: u64 = match number_system { + NumberSystem::Decimal => { + if numeric_string.is_empty() { + 1 + } else { + self.parse_number(&numeric_string, 10, size)? + } + } + NumberSystem::Octal => { + let trimmed_string = numeric_string.trim_start_matches('0'); + self.parse_number(trimmed_string, 8, size)? + } + NumberSystem::Hexadecimal => { + let trimmed_string = numeric_string.trim_start_matches("0x"); + self.parse_number(trimmed_string, 16, size)? + } + }; + number .checked_mul(factor) .ok_or_else(|| ParseSizeError::size_too_big(size)) } + + fn determine_number_system(&self, size: &str) -> NumberSystem { + if size.len() <= 1 { + return NumberSystem::Decimal; + } + + if size.starts_with("0x") { + return NumberSystem::Hexadecimal; + } + + let num_digits: usize = size + .chars() + .take_while(|c| c.is_ascii_digit()) + .collect::() + .len(); + let all_zeros = size.chars().all(|c| c == '0'); + if size.starts_with('0') && num_digits > 1 && !all_zeros { + return NumberSystem::Octal; + } + + NumberSystem::Decimal + } + + fn parse_number( + &self, + numeric_string: &str, + radix: u32, + original_size: &str, + ) -> Result { + u64::from_str_radix(numeric_string, radix) + .map_err(|_| ParseSizeError::ParseFailure(original_size.to_string())) + } } /// Parse a size string into a number of bytes. @@ -336,7 +395,7 @@ mod tests { #[test] fn invalid_suffix() { - let test_strings = ["328hdsf3290", "5mib", "1e2", "1H", "1.2"]; + let test_strings = ["5mib", "1eb", "1H"]; for &test_string in &test_strings { assert_eq!( parse_size(test_string).unwrap_err(), @@ -450,4 +509,18 @@ mod tests { assert!(parser.parse("1B").is_err()); assert!(parser.parse("B").is_err()); } + + #[test] + fn parse_octal_size() { + assert_eq!(Ok(63), parse_size("077")); + assert_eq!(Ok(528), parse_size("01020")); + assert_eq!(Ok(668 * 1024), parse_size("01234K")); + } + + #[test] + fn parse_hex_size() { + assert_eq!(Ok(10), parse_size("0xA")); + assert_eq!(Ok(94722), parse_size("0x17202")); + assert_eq!(Ok(44251 * 1024), parse_size("0xACDBK")); + } } diff --git a/src/uucore/src/lib/parser/shortcut_value_parser.rs b/src/uucore/src/lib/parser/shortcut_value_parser.rs new file mode 100644 index 000000000..07ed49cb3 --- /dev/null +++ b/src/uucore/src/lib/parser/shortcut_value_parser.rs @@ -0,0 +1,165 @@ +// spell-checker:ignore abcdefgh +use clap::{ + builder::{PossibleValue, TypedValueParser}, + error::{ContextKind, ContextValue, ErrorKind}, +}; + +#[derive(Clone)] +pub struct ShortcutValueParser(Vec); + +/// `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 { + values.into() + } + + fn generate_clap_error( + &self, + cmd: &clap::Command, + arg: Option<&clap::Arg>, + value: &str, + ) -> clap::Error { + 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 + } +} + +impl TypedValueParser for ShortcutValueParser { + type Value = String; + + fn parse_ref( + &self, + cmd: &clap::Command, + arg: Option<&clap::Arg>, + value: &std::ffi::OsStr, + ) -> Result { + 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(); + + match matched_values.len() { + 0 => Err(self.generate_clap_error(cmd, arg, value)), + 1 => Ok(matched_values[0].get_name().to_string()), + _ => { + if let Some(direct_match) = matched_values.iter().find(|x| x.get_name() == value) { + Ok(direct_match.get_name().to_string()) + } else { + Err(self.generate_clap_error(cmd, arg, value)) + } + } + } + } + + fn possible_values(&self) -> Option + '_>> { + Some(Box::new(self.0.iter().cloned())) + } +} + +impl From for ShortcutValueParser +where + I: IntoIterator, + T: Into, +{ + 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] + fn test_parse_ref_with_ambiguous_value_that_is_a_possible_value() { + let cmd = Command::new("cmd"); + let parser = ShortcutValueParser::new(["abcd", "abcdefgh"]); + let result = parser.parse_ref(&cmd, None, OsStr::new("abcd")); + assert_eq!("abcd", 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()); + } +} diff --git a/src/uucore_procs/Cargo.toml b/src/uucore_procs/Cargo.toml index bb41a82a6..976ea014d 100644 --- a/src/uucore_procs/Cargo.toml +++ b/src/uucore_procs/Cargo.toml @@ -1,6 +1,7 @@ +# spell-checker:ignore uuhelp [package] name = "uucore_procs" -version = "0.0.18" +version = "0.0.20" authors = ["Roy Ivy III "] license = "MIT" description = "uutils ~ 'uucore' proc-macros" @@ -18,4 +19,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -help_parser = { path="../help_parser", version="0.0.18" } +uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.20" } diff --git a/src/uucore_procs/src/lib.rs b/src/uucore_procs/src/lib.rs index 4bc2521b8..b78da7822 100644 --- a/src/uucore_procs/src/lib.rs +++ b/src/uucore_procs/src/lib.rs @@ -1,5 +1,5 @@ // Copyright (C) ~ Roy Ivy III ; MIT license -// spell-checker:ignore backticks +// spell-checker:ignore backticks uuhelp use std::{fs::File, io::Read, path::PathBuf}; @@ -58,7 +58,7 @@ fn render_markdown(s: &str) -> String { pub fn help_about(input: TokenStream) -> TokenStream { let input: Vec = input.into_iter().collect(); let filename = get_argument(&input, 0, "filename"); - let text: String = help_parser::parse_about(&read_help(&filename)); + let text: String = uuhelp_parser::parse_about(&read_help(&filename)); TokenTree::Literal(Literal::string(&text)).into() } @@ -72,7 +72,7 @@ pub fn help_about(input: TokenStream) -> TokenStream { pub fn help_usage(input: TokenStream) -> TokenStream { let input: Vec = input.into_iter().collect(); let filename = get_argument(&input, 0, "filename"); - let text: String = help_parser::parse_usage(&read_help(&filename)); + let text: String = uuhelp_parser::parse_usage(&read_help(&filename)); TokenTree::Literal(Literal::string(&text)).into() } @@ -106,7 +106,7 @@ pub fn help_section(input: TokenStream) -> TokenStream { let section = get_argument(&input, 0, "section"); let filename = get_argument(&input, 1, "filename"); - if let Some(text) = help_parser::parse_section(§ion, &read_help(&filename)) { + if let Some(text) = uuhelp_parser::parse_section(§ion, &read_help(&filename)) { let rendered = render_markdown(&text); TokenTree::Literal(Literal::string(&rendered)).into() } else { diff --git a/src/uuhelp_parser/Cargo.toml b/src/uuhelp_parser/Cargo.toml new file mode 100644 index 000000000..6a7aecd80 --- /dev/null +++ b/src/uuhelp_parser/Cargo.toml @@ -0,0 +1,7 @@ +# spell-checker:ignore uuhelp +[package] +name = "uuhelp_parser" +version = "0.0.20" +edition = "2021" +license = "MIT" +description = "A collection of functions to parse the markdown code of help files" diff --git a/src/help_parser/src/lib.rs b/src/uuhelp_parser/src/lib.rs similarity index 100% rename from src/help_parser/src/lib.rs rename to src/uuhelp_parser/src/lib.rs diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index 69224b0bd..cf6c62ff7 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -1,7 +1,7 @@ // spell-checker:ignore (words) nosuchgroup groupname use crate::common::util::TestScenario; -use rust_users::get_effective_gid; +use uucore::process::getegid; #[test] fn test_invalid_option() { @@ -53,7 +53,7 @@ fn test_invalid_group() { #[test] fn test_1() { - if get_effective_gid() != 0 { + if getegid() != 0 { new_ucmd!().arg("bin").arg(DIR).fails().stderr_contains( // linux fails with "Operation not permitted (os error 1)" // because of insufficient permissions, @@ -66,7 +66,7 @@ fn test_1() { #[test] fn test_fail_silently() { - if get_effective_gid() != 0 { + if getegid() != 0 { for opt in ["-f", "--silent", "--quiet", "--sil", "--qui"] { new_ucmd!() .arg(opt) @@ -137,7 +137,7 @@ fn test_reference() { // skip for root or MS-WSL // * MS-WSL is bugged (as of 2019-12-25), allowing non-root accounts su-level privileges for `chgrp` // * for MS-WSL, succeeds and stdout == 'group of /etc retained as root' - if !(get_effective_gid() == 0 || uucore::os::is_wsl_1()) { + if !(getegid() == 0 || uucore::os::is_wsl_1()) { new_ucmd!() .arg("-v") .arg("--reference=/etc/passwd") @@ -203,7 +203,7 @@ fn test_missing_files() { #[test] #[cfg(target_os = "linux")] fn test_big_p() { - if get_effective_gid() != 0 { + if getegid() != 0 { new_ucmd!() .arg("-RP") .arg("bin") @@ -218,7 +218,7 @@ fn test_big_p() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] fn test_big_h() { - if get_effective_gid() != 0 { + if getegid() != 0 { assert!( new_ucmd!() .arg("-RH") diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 191e4a86b..7a1a4a6bd 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -2,7 +2,7 @@ use crate::common::util::{is_ci, run_ucmd_as_root, CmdResult, TestScenario}; #[cfg(any(target_os = "linux", target_os = "android"))] -use rust_users::get_effective_uid; +use uucore::process::geteuid; // Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. // If we are running inside the CI and "needle" is in "stderr" skipping this test is @@ -701,7 +701,7 @@ fn test_root_preserve() { #[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn test_big_p() { - if get_effective_uid() != 0 { + if geteuid() != 0 { new_ucmd!() .arg("-RP") .arg("bin") diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index ef35f6c2d..18f3829a2 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -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!(); @@ -456,6 +470,29 @@ fn test_cp_arg_interactive_update() { .no_stdout(); } +#[test] +#[cfg(not(target_os = "android"))] +fn test_cp_arg_interactive_verbose() { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("a"); + at.touch("b"); + ucmd.args(&["-vi", "a", "b"]) + .pipe_in("N\n") + .fails() + .stdout_is("skipped 'b'\n"); +} + +#[test] +#[cfg(not(target_os = "android"))] +fn test_cp_arg_interactive_verbose_clobber() { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("a"); + at.touch("b"); + ucmd.args(&["-vin", "a", "b"]) + .fails() + .stdout_is("skipped 'b'\n"); +} + #[test] #[cfg(target_os = "linux")] fn test_cp_arg_link() { @@ -487,7 +524,8 @@ fn test_cp_arg_no_clobber() { ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE) .arg("--no-clobber") - .succeeds(); + .fails() + .stderr_contains("not replacing"); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); } @@ -498,7 +536,7 @@ fn test_cp_arg_no_clobber_inferred_arg() { ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE) .arg("--no-clob") - .succeeds(); + .fails(); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); } @@ -525,7 +563,7 @@ fn test_cp_arg_no_clobber_twice() { .arg("--no-clobber") .arg("source.txt") .arg("dest.txt") - .succeeds(); + .fails(); assert_eq!(at.read("source.txt"), "some-content"); // Should be empty as the "no-clobber" should keep @@ -1086,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); @@ -1127,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); @@ -1199,6 +1237,33 @@ fn test_cp_preserve_no_args() { } } +#[test] +fn test_cp_preserve_no_args_before_opts() { + let (at, mut ucmd) = at_and_ucmd!(); + let src_file = "a"; + let dst_file = "b"; + + // Prepare the source file + at.touch(src_file); + #[cfg(unix)] + at.set_mode(src_file, 0o0500); + + // Copy + ucmd.arg("--preserve") + .arg(src_file) + .arg(dst_file) + .succeeds(); + + #[cfg(all(unix, not(target_os = "freebsd")))] + { + // Assert that the mode, ownership, and timestamps are preserved + // NOTICE: the ownership is not modified on the src file, because that requires root permissions + let metadata_src = at.metadata(src_file); + let metadata_dst = at.metadata(dst_file); + assert_metadata_eq!(metadata_src, metadata_dst); + } +} + #[test] fn test_cp_preserve_all() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1272,7 +1337,7 @@ fn test_cp_preserve_all_context_fails_on_non_selinux() { } #[test] -#[cfg(any(target_os = "android"))] +#[cfg(target_os = "android")] fn test_cp_preserve_xattr_fails_on_android() { // Because of the SELinux extended attributes used on Android, trying to copy extended // attributes has to fail in this case, since we specify `--preserve=xattr` and this puts it @@ -2294,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 @@ -2673,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")); @@ -2707,7 +2768,7 @@ fn test_same_file_force() { } /// Test that copying file to itself with forced backup succeeds. -#[cfg(all(not(windows)))] +#[cfg(not(windows))] #[test] fn test_same_file_force_backup() { let (at, mut ucmd) = at_and_ucmd!(); @@ -2909,3 +2970,260 @@ fn test_cp_archive_on_directory_ending_dot() { ucmd.args(&["-a", "dir1/.", "dir2"]).succeeds(); assert!(at.file_exists("dir2/file")); } + +#[test] +fn test_cp_debug_default() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let result = ts.ucmd().arg("--debug").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") + { + 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}"); + } + + #[cfg(windows)] + if !stdout_str + .contains("copy offload: unsupported, reflink: unsupported, sparse detection: unsupported") + { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +fn test_cp_debug_multiple_default() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + let dir = "dir"; + at.touch("a"); + at.touch("b"); + at.mkdir(dir); + let result = ts + .ucmd() + .arg("--debug") + .arg("a") + .arg("b") + .arg(dir) + .succeeds(); + + 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}"); + } + + // two files, two occurrences + assert_eq!( + result + .stdout_str() + .matches( + "copy offload: unknown, reflink: unsupported, sparse detection: unsupported" + ) + .count(), + 2 + ); + } + + #[cfg(target_os = "linux")] + { + if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no") + { + panic!("Failure: stdout was \n{stdout_str}"); + } + + // two files, two occurrences + assert_eq!( + result + .stdout_str() + .matches("copy offload: unknown, reflink: unsupported, sparse detection: no") + .count(), + 2 + ); + } + + #[cfg(target_os = "windows")] + { + if !stdout_str.contains( + "copy offload: unsupported, reflink: unsupported, sparse detection: unsupported", + ) { + panic!("Failure: stdout was \n{stdout_str}"); + } + + // two files, two occurrences + assert_eq!( + result + .stdout_str() + .matches("copy offload: unsupported, reflink: unsupported, sparse detection: unsupported") + .count(), + 2 + ); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_debug_sparse_reflink() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let result = ts + .ucmd() + .arg("--debug") + .arg("--sparse=always") + .arg("--reflink=never") + .arg("a") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: zeros") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_debug_sparse_always() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let result = ts + .ucmd() + .arg("--debug") + .arg("--sparse=always") + .arg("a") + .arg("b") + .succeeds(); + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: unsupported, sparse detection: zeros") + { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_debug_sparse_never() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let result = ts + .ucmd() + .arg("--debug") + .arg("--sparse=never") + .arg("a") + .arg("b") + .succeeds(); + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +fn test_cp_debug_sparse_auto() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + ts.ucmd() + .arg("--debug") + .arg("--sparse=auto") + .arg("a") + .arg("b") + .succeeds(); + + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + let result = 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") + { + 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}"); + } + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] +fn test_cp_debug_reflink_auto() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=auto") + .arg("a") + .arg("b") + .succeeds(); + + #[cfg(target_os = "linux")] + { + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no") + { + panic!("Failure: stdout was \n{stdout_str}"); + } + } + + #[cfg(target_os = "macos")] + { + let stdout_str = result.stdout_str(); + if !stdout_str + .contains("copy offload: unknown, reflink: unsupported, sparse detection: unsupported") + { + panic!("Failure: stdout was \n{stdout_str}"); + } + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_debug_sparse_always_reflink_auto() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let result = ts + .ucmd() + .arg("--debug") + .arg("--sparse=always") + .arg("--reflink=auto") + .arg("a") + .arg("b") + .succeeds(); + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: unsupported, sparse detection: zeros") + { + panic!("Failure: stdout was \n{stdout_str}"); + } +} diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index c8c33aa89..669f02e33 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -1,7 +1,7 @@ use crate::common::util::TestScenario; use regex::Regex; #[cfg(all(unix, not(target_os = "macos")))] -use rust_users::get_effective_uid; +use uucore::process::geteuid; #[test] fn test_invalid_arg() { @@ -213,7 +213,7 @@ fn test_date_format_literal() { #[test] #[cfg(all(unix, not(target_os = "macos")))] fn test_date_set_valid() { - if get_effective_uid() == 0 { + if geteuid() == 0 { new_ucmd!() .arg("--set") .arg("2020-03-12 13:30:00+08:00") @@ -234,7 +234,7 @@ fn test_date_set_invalid() { #[test] #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] fn test_date_set_permissions_error() { - if !(get_effective_uid() == 0 || uucore::os::is_wsl_1()) { + if !(geteuid() == 0 || uucore::os::is_wsl_1()) { let result = new_ucmd!() .arg("--set") .arg("2020-03-11 21:45:00+08:00") @@ -261,7 +261,7 @@ fn test_date_set_mac_unavailable() { #[cfg(all(unix, not(target_os = "macos")))] /// TODO: expected to fail currently; change to succeeds() when required. fn test_date_set_valid_2() { - if get_effective_uid() == 0 { + if geteuid() == 0 { let result = new_ucmd!() .arg("--set") .arg("Sat 20 Mar 2021 14:53:01 AWST") // spell-checker:disable-line @@ -325,7 +325,7 @@ fn test_date_for_file() { #[cfg(all(unix, not(target_os = "macos")))] /// TODO: expected to fail currently; change to succeeds() when required. fn test_date_set_valid_3() { - if get_effective_uid() == 0 { + if geteuid() == 0 { let result = new_ucmd!() .arg("--set") .arg("Sat 20 Mar 2021 14:53:01") // Local timezone @@ -339,7 +339,7 @@ fn test_date_set_valid_3() { #[cfg(all(unix, not(target_os = "macos")))] /// TODO: expected to fail currently; change to succeeds() when required. fn test_date_set_valid_4() { - if get_effective_uid() == 0 { + if geteuid() == 0 { let result = new_ucmd!() .arg("--set") .arg("2020-03-11 21:45:00") // Local timezone diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index a5100e471..926d1be5d 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -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") diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 699746f03..d365bd87e 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -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] diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index 1c7541370..cf3744964 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -233,7 +233,7 @@ fn test_random_big() { // to generate an even split of this range, generate n-1 random elements // in the range, add the desired total value to the end, sort this list, // and then compute the sequential differences. - let mut f_bits = Vec::new(); + let mut f_bits = Vec::with_capacity(n_factors + 1); for _ in 0..n_factors { f_bits.push(extra_range.sample(&mut rng)); } @@ -256,7 +256,7 @@ fn test_random_big() { let mut n_bits = 0; let mut product = 1_u64; - let mut factors = Vec::new(); + let mut factors = Vec::with_capacity(f_bits.len()); for bit in f_bits { assert!(bit < 37); n_bits += 14 + bit; diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 571bfb3a8..0e1eafc86 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -189,6 +189,15 @@ fn test_no_such_file_or_directory() { .stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory"); } +#[test] +fn test_lines_leading_zeros() { + new_ucmd!() + .arg("--lines=010") + .pipe_in("\n\n\n\n\n\n\n\n\n\n\n\n") + .succeeds() + .stdout_is("\n\n\n\n\n\n\n\n\n\n"); +} + /// Test that each non-existent files gets its own error message printed. #[test] fn test_multiple_nonexistent_files() { diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index a30737f05..d76ce1e01 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -2,12 +2,12 @@ use crate::common::util::{is_ci, TestScenario}; use filetime::FileTime; -use rust_users::{get_effective_gid, get_effective_uid}; use std::os::unix::fs::PermissionsExt; #[cfg(not(any(windows, target_os = "freebsd")))] use std::process::Command; #[cfg(any(target_os = "linux", target_os = "android"))] use std::thread::sleep; +use uucore::process::{getegid, geteuid}; #[test] fn test_invalid_arg() { @@ -322,7 +322,7 @@ fn test_install_target_new_file_with_group() { let (at, mut ucmd) = at_and_ucmd!(); let file = "file"; let dir = "target_dir"; - let gid = get_effective_gid(); + let gid = getegid(); at.touch(file); at.mkdir(dir); @@ -349,7 +349,7 @@ fn test_install_target_new_file_with_owner() { let (at, mut ucmd) = at_and_ucmd!(); let file = "file"; let dir = "target_dir"; - let uid = get_effective_uid(); + let uid = geteuid(); at.touch(file); at.mkdir(dir); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 45ced867a..15ded8e6b 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc +// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff #[cfg(any(unix, feature = "feat_selinux"))] use crate::common::util::expected_result; @@ -22,7 +22,6 @@ use std::time::Duration; const LONG_ARGS: &[&str] = &[ "-l", "--long", - "--l", "--format=long", "--for=long", "--format=verbose", @@ -671,6 +670,23 @@ fn test_ls_width() { .stdout_only("test-width-1 test-width-3\ntest-width-2 test-width-4\n"); } + for option in [ + "-w 100000000000000", + "-w=100000000000000", + "--width=100000000000000", + "--width 100000000000000", + "-w 07777777777777777777", + "-w=07777777777777777777", + "--width=07777777777777777777", + "--width 07777777777777777777", + ] { + scene + .ucmd() + .args(&option.split(' ').collect::>()) + .arg("-C") + .succeeds() + .stdout_only("test-width-1 test-width-2 test-width-3 test-width-4\n"); + } scene .ucmd() .arg("-w=bad") @@ -1564,6 +1580,28 @@ fn test_ls_sort_name() { .stdout_is(".a\n.b\na\nb\n"); } +#[test] +fn test_ls_sort_width() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("aaaaa"); + at.touch("bbb"); + at.touch("cccc"); + at.touch("eee"); + at.touch("d"); + at.touch("fffff"); + at.touch("abc"); + at.touch("zz"); + at.touch("bcdef"); + + scene + .ucmd() + .arg("--sort=width") + .succeeds() + .stdout_is("d\nzz\nabc\nbbb\neee\ncccc\naaaaa\nbcdef\nfffff\n"); +} + #[test] fn test_ls_order_size() { let scene = TestScenario::new(util_name!()); @@ -2389,6 +2427,7 @@ fn test_ls_quoting_style() { ("--quoting-style=literal", "one?two"), ("-N", "one?two"), ("--literal", "one?two"), + ("--l", "one?two"), ("--quoting-style=c", "\"one\\ntwo\""), ("-Q", "\"one\\ntwo\""), ("--quote-name", "\"one\\ntwo\""), @@ -2413,6 +2452,7 @@ fn test_ls_quoting_style() { ("--quoting-style=literal", "one\ntwo"), ("-N", "one\ntwo"), ("--literal", "one\ntwo"), + ("--l", "one\ntwo"), ("--quoting-style=shell", "one\ntwo"), // FIXME: GNU ls quotes this case ("--quoting-style=shell-always", "'one\ntwo'"), ] { @@ -2474,6 +2514,7 @@ fn test_ls_quoting_style() { ("--quoting-style=literal", "one two"), ("-N", "one two"), ("--literal", "one two"), + ("--l", "one two"), ("--quoting-style=c", "\"one two\""), ("-Q", "\"one two\""), ("--quote-name", "\"one two\""), @@ -3115,6 +3156,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") diff --git a/tests/by-util/test_mkdir.rs b/tests/by-util/test_mkdir.rs index 425be8c35..11a860d5a 100644 --- a/tests/by-util/test_mkdir.rs +++ b/tests/by-util/test_mkdir.rs @@ -12,20 +12,6 @@ use std::sync::Mutex; // when writing a test case, acquire this mutex before proceeding with the main logic of the test static TEST_MUTEX: Lazy> = Lazy::new(|| Mutex::new(())); -static TEST_DIR1: &str = "mkdir_test1"; -static TEST_DIR2: &str = "mkdir_test2"; -static TEST_DIR3: &str = "mkdir_test3"; -static TEST_DIR4: &str = "mkdir_test4/mkdir_test4_1"; -static TEST_DIR5: &str = "mkdir_test5/mkdir_test5_1"; -static TEST_DIR6: &str = "mkdir_test6"; -static TEST_FILE7: &str = "mkdir_test7"; -static TEST_DIR8: &str = "mkdir_test8/mkdir_test8_1/mkdir_test8_2"; -static TEST_DIR9: &str = "mkdir_test9/../mkdir_test9_1/../mkdir_test9_2"; -static TEST_DIR10: &str = "mkdir_test10/."; -static TEST_DIR11: &str = "mkdir_test11/.."; -#[cfg(not(windows))] -static TEST_DIR12: &str = "mkdir_test12"; - #[test] fn test_invalid_arg() { let _guard = TEST_MUTEX.lock(); @@ -35,15 +21,15 @@ fn test_invalid_arg() { #[test] fn test_mkdir_mkdir() { let _guard = TEST_MUTEX.lock(); - new_ucmd!().arg(TEST_DIR1).succeeds(); + new_ucmd!().arg("test_dir").succeeds(); } #[test] fn test_mkdir_verbose() { let _guard = TEST_MUTEX.lock(); - let expected = "mkdir: created directory 'mkdir_test1'\n"; + let expected = "mkdir: created directory 'test_dir'\n"; new_ucmd!() - .arg(TEST_DIR1) + .arg("test_dir") .arg("-v") .run() .stdout_is(expected); @@ -52,39 +38,47 @@ fn test_mkdir_verbose() { #[test] fn test_mkdir_dup_dir() { let _guard = TEST_MUTEX.lock(); + let scene = TestScenario::new(util_name!()); - scene.ucmd().arg(TEST_DIR2).succeeds(); - scene.ucmd().arg(TEST_DIR2).fails(); + let test_dir = "test_dir"; + + scene.ucmd().arg(test_dir).succeeds(); + scene.ucmd().arg(test_dir).fails(); } #[test] fn test_mkdir_mode() { let _guard = TEST_MUTEX.lock(); - new_ucmd!().arg("-m").arg("755").arg(TEST_DIR3).succeeds(); + new_ucmd!().arg("-m").arg("755").arg("test_dir").succeeds(); } #[test] fn test_mkdir_parent() { let _guard = TEST_MUTEX.lock(); let scene = TestScenario::new(util_name!()); - scene.ucmd().arg("-p").arg(TEST_DIR4).succeeds(); - scene.ucmd().arg("-p").arg(TEST_DIR4).succeeds(); - scene.ucmd().arg("--parent").arg(TEST_DIR4).succeeds(); - scene.ucmd().arg("--parents").arg(TEST_DIR4).succeeds(); + let test_dir = "parent_dir/child_dir"; + + scene.ucmd().arg("-p").arg(test_dir).succeeds(); + scene.ucmd().arg("-p").arg(test_dir).succeeds(); + scene.ucmd().arg("--parent").arg(test_dir).succeeds(); + scene.ucmd().arg("--parents").arg(test_dir).succeeds(); } #[test] fn test_mkdir_no_parent() { let _guard = TEST_MUTEX.lock(); - new_ucmd!().arg(TEST_DIR5).fails(); + new_ucmd!().arg("parent_dir/child_dir").fails(); } #[test] fn test_mkdir_dup_dir_parent() { let _guard = TEST_MUTEX.lock(); + let scene = TestScenario::new(util_name!()); - scene.ucmd().arg(TEST_DIR6).succeeds(); - scene.ucmd().arg("-p").arg(TEST_DIR6).succeeds(); + let test_dir = "test_dir"; + + scene.ucmd().arg(test_dir).succeeds(); + scene.ucmd().arg("-p").arg(test_dir).succeeds(); } #[cfg(not(windows))] @@ -158,12 +152,16 @@ fn test_mkdir_parent_mode_check_existing_parent() { #[test] fn test_mkdir_dup_file() { let _guard = TEST_MUTEX.lock(); + let scene = TestScenario::new(util_name!()); - scene.fixtures.touch(TEST_FILE7); - scene.ucmd().arg(TEST_FILE7).fails(); + let test_file = "test_file.txt"; + + scene.fixtures.touch(test_file); + + scene.ucmd().arg(test_file).fails(); // mkdir should fail for a file even if -p is specified. - scene.ucmd().arg("-p").arg(TEST_FILE7).fails(); + scene.ucmd().arg("-p").arg(test_file).fails(); } #[test] @@ -171,9 +169,10 @@ fn test_mkdir_dup_file() { fn test_symbolic_mode() { let _guard = TEST_MUTEX.lock(); let (at, mut ucmd) = at_and_ucmd!(); + let test_dir = "test_dir"; - ucmd.arg("-m").arg("a=rwx").arg(TEST_DIR1).succeeds(); - let perms = at.metadata(TEST_DIR1).permissions().mode(); + ucmd.arg("-m").arg("a=rwx").arg(test_dir).succeeds(); + let perms = at.metadata(test_dir).permissions().mode(); assert_eq!(perms, 0o40777); } @@ -182,12 +181,13 @@ fn test_symbolic_mode() { fn test_symbolic_alteration() { let _guard = TEST_MUTEX.lock(); let (at, mut ucmd) = at_and_ucmd!(); + let test_dir = "test_dir"; let default_umask = 0o022; let original_umask = unsafe { umask(default_umask) }; - ucmd.arg("-m").arg("-w").arg(TEST_DIR1).succeeds(); - let perms = at.metadata(TEST_DIR1).permissions().mode(); + ucmd.arg("-m").arg("-w").arg(test_dir).succeeds(); + let perms = at.metadata(test_dir).permissions().mode(); assert_eq!(perms, 0o40577); unsafe { umask(original_umask) }; @@ -198,61 +198,62 @@ fn test_symbolic_alteration() { fn test_multi_symbolic() { let _guard = TEST_MUTEX.lock(); let (at, mut ucmd) = at_and_ucmd!(); + let test_dir = "test_dir"; - ucmd.arg("-m") - .arg("u=rwx,g=rx,o=") - .arg(TEST_DIR1) - .succeeds(); - let perms = at.metadata(TEST_DIR1).permissions().mode(); + ucmd.arg("-m").arg("u=rwx,g=rx,o=").arg(test_dir).succeeds(); + let perms = at.metadata(test_dir).permissions().mode(); assert_eq!(perms, 0o40750); } #[test] fn test_recursive_reporting() { let _guard = TEST_MUTEX.lock(); + let test_dir = "test_dir/test_dir_a/test_dir_b"; + new_ucmd!() .arg("-p") .arg("-v") - .arg(TEST_DIR8) + .arg(test_dir) .succeeds() - .stdout_contains("created directory 'mkdir_test8'") - .stdout_contains("created directory 'mkdir_test8/mkdir_test8_1'") - .stdout_contains("created directory 'mkdir_test8/mkdir_test8_1/mkdir_test8_2'"); - new_ucmd!().arg("-v").arg(TEST_DIR8).fails().no_stdout(); + .stdout_contains("created directory 'test_dir'") + .stdout_contains("created directory 'test_dir/test_dir_a'") + .stdout_contains("created directory 'test_dir/test_dir_a/test_dir_b'"); + new_ucmd!().arg("-v").arg(test_dir).fails().no_stdout(); + + let test_dir = "test_dir/../test_dir_a/../test_dir_b"; + new_ucmd!() .arg("-p") .arg("-v") - .arg(TEST_DIR9) + .arg(test_dir) .succeeds() - .stdout_contains("created directory 'mkdir_test9'") - .stdout_contains("created directory 'mkdir_test9/../mkdir_test9_1'") - .stdout_contains("created directory 'mkdir_test9/../mkdir_test9_1/../mkdir_test9_2'"); + .stdout_contains("created directory 'test_dir'") + .stdout_contains("created directory 'test_dir/../test_dir_a'") + .stdout_contains("created directory 'test_dir/../test_dir_a/../test_dir_b'"); } #[test] fn test_mkdir_trailing_dot() { let _guard = TEST_MUTEX.lock(); - let scene2 = TestScenario::new("ls"); - new_ucmd!() - .arg("-p") - .arg("-v") - .arg("mkdir_test10-2") - .succeeds(); + + new_ucmd!().arg("-p").arg("-v").arg("test_dir").succeeds(); new_ucmd!() .arg("-p") .arg("-v") - .arg(TEST_DIR10) + .arg("test_dir_a/.") .succeeds() - .stdout_contains("created directory 'mkdir_test10'"); + .stdout_contains("created directory 'test_dir_a'"); new_ucmd!() .arg("-p") .arg("-v") - .arg(TEST_DIR11) + .arg("test_dir_b/..") .succeeds() - .stdout_contains("created directory 'mkdir_test11'"); - let result = scene2.ucmd().arg("-al").run(); + .stdout_contains("created directory 'test_dir_b'"); + + let scene = TestScenario::new("ls"); + let result = scene.ucmd().arg("-al").run(); println!("ls dest {}", result.stdout_str()); } @@ -261,12 +262,14 @@ fn test_mkdir_trailing_dot() { fn test_umask_compliance() { fn test_single_case(umask_set: mode_t) { let _guard = TEST_MUTEX.lock(); + + let test_dir = "test_dir"; let (at, mut ucmd) = at_and_ucmd!(); let original_umask = unsafe { umask(umask_set) }; - ucmd.arg(TEST_DIR12).succeeds(); - let perms = at.metadata(TEST_DIR12).permissions().mode() as mode_t; + ucmd.arg(test_dir).succeeds(); + let perms = at.metadata(test_dir).permissions().mode() as mode_t; assert_eq!(perms, (!umask_set & 0o0777) + 0o40000); // before compare, add the set GUID, UID bits unsafe { diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 6bcb37f45..4544b6b30 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -901,3 +901,82 @@ fn test_t_ensure_tmpdir_has_higher_priority_than_p() { println!("stdout = {stdout}"); assert!(stdout.contains(&pathname)); } + +#[test] +fn test_missing_xs_tmpdir_template() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .arg("--tmpdir") + .arg(TEST_TEMPLATE3) + .fails() + .no_stdout() + .stderr_contains("too few X's in template"); + scene + .ucmd() + .arg("--tmpdir=foobar") + .fails() + .no_stdout() + .stderr_contains("failed to create file via template"); +} + +#[test] +fn test_both_tmpdir_flags_present() { + let scene = TestScenario::new(util_name!()); + + #[cfg(not(windows))] + let template = format!(".{MAIN_SEPARATOR}foobarXXXX"); + + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd + .env(TMPDIR, ".") + .arg("-p") + .arg("nonsense") + .arg("--tmpdir") + .arg("foobarXXXX") + .succeeds(); + let filename = result.no_stderr().stdout_str().trim_end(); + + #[cfg(not(windows))] + assert_matches_template!(&template, filename); + #[cfg(windows)] + assert_suffix_matches_template!("foobarXXXX", filename); + + assert!(at.file_exists(filename)); + + scene + .ucmd() + .arg("-p") + .arg(".") + .arg("--tmpdir=does_not_exist") + .fails() + .no_stdout() + .stderr_contains("failed to create file via template"); + + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd + .arg("--tmpdir") + .arg("foobarXXXX") + .arg("-p") + .arg(".") + .succeeds(); + let filename = result.no_stderr().stdout_str().trim_end(); + + #[cfg(not(windows))] + assert_matches_template!(&template, filename); + #[cfg(windows)] + assert_suffix_matches_template!("foobarXXXX", filename); + + assert!(at.file_exists(filename)); +} + +#[test] +fn test_missing_short_tmpdir_flag() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .arg("-p") + .fails() + .no_stdout() + .stderr_contains("a value is required for '-p ' but none was supplied"); +} diff --git a/tests/by-util/test_more.rs b/tests/by-util/test_more.rs index 43f405d9a..d94a92185 100644 --- a/tests/by-util/test_more.rs +++ b/tests/by-util/test_more.rs @@ -20,6 +20,62 @@ 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(); + } +} + +#[test] +fn test_invalid_arg() { + if std::io::stdout().is_terminal() { + new_ucmd!().arg("--invalid").fails(); + + 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"); } } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index f73d3249d..ceaa4ba22 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -417,6 +417,83 @@ fn test_mv_same_hardlink() { .stderr_is(format!("mv: '{file_a}' and '{file_b}' are the same file\n",)); } +#[test] +#[cfg(all(unix, not(target_os = "android")))] +fn test_mv_same_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_same_file_a"; + let file_b = "test_mv_same_file_b"; + let file_c = "test_mv_same_file_c"; + + at.touch(file_a); + + at.symlink_file(file_a, file_b); + + ucmd.arg(file_b) + .arg(file_a) + .fails() + .stderr_is(format!("mv: '{file_b}' and '{file_a}' are the same file\n",)); + + let (at2, mut ucmd2) = at_and_ucmd!(); + at2.touch(file_a); + + at2.symlink_file(file_a, file_b); + ucmd2.arg(file_a).arg(file_b).succeeds(); + assert!(at2.file_exists(file_b)); + assert!(!at2.file_exists(file_a)); + + let (at3, mut ucmd3) = at_and_ucmd!(); + at3.touch(file_a); + + at3.symlink_file(file_a, file_b); + at3.symlink_file(file_b, file_c); + + ucmd3.arg(file_c).arg(file_b).succeeds(); + assert!(!at3.symlink_exists(file_c)); + assert!(at3.symlink_exists(file_b)); + + let (at4, mut ucmd4) = at_and_ucmd!(); + at4.touch(file_a); + + at4.symlink_file(file_a, file_b); + at4.symlink_file(file_b, file_c); + + ucmd4 + .arg(file_c) + .arg(file_a) + .fails() + .stderr_is(format!("mv: '{file_c}' and '{file_a}' are the same file\n",)); +} + +#[test] +#[cfg(all(unix, not(target_os = "android")))] +fn test_mv_hardlink_to_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "file"; + let symlink_file = "symlink"; + let hardlink_to_symlink_file = "hardlink_to_symlink"; + + at.touch(file); + at.symlink_file(file, symlink_file); + at.hard_link(symlink_file, hardlink_to_symlink_file); + + ucmd.arg(symlink_file).arg(hardlink_to_symlink_file).fails(); + + let (at2, mut ucmd2) = at_and_ucmd!(); + + at2.touch(file); + at2.symlink_file(file, symlink_file); + at2.hard_link(symlink_file, hardlink_to_symlink_file); + + ucmd2 + .arg("--backup") + .arg(symlink_file) + .arg(hardlink_to_symlink_file) + .succeeds(); + assert!(!at2.symlink_exists(symlink_file)); + assert!(at2.symlink_exists(&format!("{hardlink_to_symlink_file}~"))); +} + #[test] #[cfg(all(unix, not(target_os = "android")))] fn test_mv_same_hardlink_backup_simple() { @@ -433,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!(); diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index 7317d8cca..ab27fe148 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -1,3 +1,4 @@ +// spell-checker:ignore iinvalid linvalid ninvalid vinvalid winvalid use crate::common::util::TestScenario; #[test] @@ -12,6 +13,7 @@ fn test_stdin_no_newline() { .run() .stdout_is(" 1\tNo Newline\n"); } + #[test] fn test_stdin_newline() { new_ucmd!() @@ -50,15 +52,15 @@ fn test_sections_and_styles() { for (fixture, output) in [ ( "section.txt", - "\nHEADER1\nHEADER2\n\n1 |BODY1\n2 \ - |BODY2\n\nFOOTER1\nFOOTER2\n\nNEXTHEADER1\nNEXTHEADER2\n\n1 \ - |NEXTBODY1\n2 |NEXTBODY2\n\nNEXTFOOTER1\nNEXTFOOTER2\n", + "\n HEADER1\n HEADER2\n\n1 |BODY1\n2 \ + |BODY2\n\n FOOTER1\n FOOTER2\n\n NEXTHEADER1\n NEXTHEADER2\n\n1 \ + |NEXTBODY1\n2 |NEXTBODY2\n\n NEXTFOOTER1\n NEXTFOOTER2\n", ), ( "joinblanklines.txt", - "1 |Nonempty\n2 |Nonempty\n3 |Followed by 10x empty\n\n\n\n\n4 \ - |\n\n\n\n\n5 |\n6 |Followed by 5x empty\n\n\n\n\n7 |\n8 \ - |Followed by 4x empty\n\n\n\n\n9 |Nonempty\n10 |Nonempty\n11 \ + "1 |Nonempty\n2 |Nonempty\n3 |Followed by 10x empty\n \n \n \n \n4 \ + |\n \n \n \n \n5 |\n6 |Followed by 5x empty\n \n \n \n \n7 |\n8 \ + |Followed by 4x empty\n \n \n \n \n9 |Nonempty\n10 |Nonempty\n11 \ |Nonempty.\n", ), ] { @@ -71,3 +73,352 @@ fn test_sections_and_styles() { } // spell-checker:enable } + +#[test] +fn test_no_renumber() { + for arg in ["-p", "--no-renumber"] { + new_ucmd!().arg(arg).succeeds(); + } +} + +#[test] +fn test_number_format_ln() { + for arg in ["-nln", "--number-format=ln"] { + new_ucmd!() + .arg(arg) + .pipe_in("test") + .succeeds() + .stdout_is("1 \ttest\n"); + } +} + +#[test] +fn test_number_format_rn() { + for arg in ["-nrn", "--number-format=rn"] { + new_ucmd!() + .arg(arg) + .pipe_in("test") + .succeeds() + .stdout_is(" 1\ttest\n"); + } +} + +#[test] +fn test_number_format_rz() { + for arg in ["-nrz", "--number-format=rz"] { + new_ucmd!() + .arg(arg) + .pipe_in("test") + .succeeds() + .stdout_is("000001\ttest\n"); + } +} + +#[test] +fn test_number_format_rz_with_negative_line_number() { + for arg in ["-nrz", "--number-format=rz"] { + new_ucmd!() + .arg(arg) + .arg("-v-12") + .pipe_in("test") + .succeeds() + .stdout_is("-00012\ttest\n"); + } +} + +#[test] +fn test_invalid_number_format() { + for arg in ["-ninvalid", "--number-format=invalid"] { + new_ucmd!() + .arg(arg) + .fails() + .stderr_contains("invalid value 'invalid'"); + } +} + +#[test] +fn test_number_width() { + for width in 1..10 { + for arg in [format!("-w{width}"), format!("--number-width={width}")] { + let spaces = " ".repeat(width - 1); + new_ucmd!() + .arg(arg) + .pipe_in("test") + .succeeds() + .stdout_is(format!("{spaces}1\ttest\n")); + } + } +} + +#[test] +fn test_number_width_zero() { + for arg in ["-w0", "--number-width=0"] { + new_ucmd!() + .arg(arg) + .fails() + .stderr_contains("Invalid line number field width: ‘0’: Numerical result out of range"); + } +} + +#[test] +fn test_invalid_number_width() { + for arg in ["-winvalid", "--number-width=invalid"] { + new_ucmd!() + .arg(arg) + .fails() + .stderr_contains("invalid value 'invalid'"); + } +} + +#[test] +fn test_number_separator() { + for arg in ["-s:-:", "--number-separator=:-:"] { + new_ucmd!() + .arg(arg) + .pipe_in("test") + .succeeds() + .stdout_is(" 1:-:test\n"); + } +} + +#[test] +fn test_starting_line_number() { + for arg in ["-v10", "--starting-line-number=10"] { + new_ucmd!() + .arg(arg) + .pipe_in("test") + .succeeds() + .stdout_is(" 10\ttest\n"); + } +} + +#[test] +fn test_negative_starting_line_number() { + for arg in ["-v-10", "--starting-line-number=-10"] { + new_ucmd!() + .arg(arg) + .pipe_in("test") + .succeeds() + .stdout_is(" -10\ttest\n"); + } +} + +#[test] +fn test_invalid_starting_line_number() { + for arg in ["-vinvalid", "--starting-line-number=invalid"] { + new_ucmd!() + .arg(arg) + .fails() + .stderr_contains("invalid value 'invalid'"); + } +} + +#[test] +fn test_line_increment() { + for arg in ["-i10", "--line-increment=10"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\nb") + .succeeds() + .stdout_is(" 1\ta\n 11\tb\n"); + } +} + +#[test] +fn test_line_increment_from_negative_starting_line() { + for arg in ["-i10", "--line-increment=10"] { + new_ucmd!() + .arg(arg) + .arg("-v-19") + .pipe_in("a\nb\nc") + .succeeds() + .stdout_is(" -19\ta\n -9\tb\n 1\tc\n"); + } +} + +#[test] +fn test_negative_line_increment() { + for arg in ["-i-10", "--line-increment=-10"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\nb\nc") + .succeeds() + .stdout_is(" 1\ta\n -9\tb\n -19\tc\n"); + } +} + +#[test] +fn test_invalid_line_increment() { + for arg in ["-iinvalid", "--line-increment=invalid"] { + new_ucmd!() + .arg(arg) + .fails() + .stderr_contains("invalid value 'invalid'"); + } +} + +#[test] +fn test_join_blank_lines() { + for arg in ["-l3", "--join-blank-lines=3"] { + new_ucmd!() + .arg(arg) + .arg("--body-numbering=a") + .pipe_in("\n\n\n\n\n\n") + .succeeds() + .stdout_is(concat!( + " \n", + " \n", + " 1\t\n", + " \n", + " \n", + " 2\t\n", + )); + } +} + +#[test] +fn test_join_blank_lines_zero() { + for arg in ["-l0", "--join-blank-lines=0"] { + new_ucmd!().arg(arg).fails().stderr_contains( + "Invalid line number of blank lines: ‘0’: Numerical result out of range", + ); + } +} + +#[test] +fn test_invalid_join_blank_lines() { + for arg in ["-linvalid", "--join-blank-lines=invalid"] { + new_ucmd!() + .arg(arg) + .fails() + .stderr_contains("invalid value 'invalid'"); + } +} + +#[test] +fn test_default_body_numbering() { + new_ucmd!() + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" 1\ta\n \n 2\tb\n"); +} + +#[test] +fn test_body_numbering_all_lines_without_delimiter() { + for arg in ["-ba", "--body-numbering=a"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" 1\ta\n 2\t\n 3\tb\n"); + } +} + +#[test] +fn test_body_numbering_no_lines_without_delimiter() { + for arg in ["-bn", "--body-numbering=n"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" a\n \n b\n"); + } +} + +#[test] +fn test_body_numbering_non_empty_lines_without_delimiter() { + for arg in ["-bt", "--body-numbering=t"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" 1\ta\n \n 2\tb\n"); + } +} + +#[test] +fn test_body_numbering_matched_lines_without_delimiter() { + for arg in ["-bp^[ac]", "--body-numbering=p^[ac]"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\nb\nc") + .succeeds() + .stdout_is(" 1\ta\n b\n 2\tc\n"); + } +} + +#[test] +fn test_numbering_all_lines() { + let delimiters_and_args = [ + ("\\:\\:\\:\n", ["-ha", "--header-numbering=a"]), + ("\\:\\:\n", ["-ba", "--body-numbering=a"]), + ("\\:\n", ["-fa", "--footer-numbering=a"]), + ]; + + for (delimiter, args) in delimiters_and_args { + for arg in args { + new_ucmd!() + .arg(arg) + .pipe_in(format!("{delimiter}a\n\nb")) + .succeeds() + .stdout_is("\n 1\ta\n 2\t\n 3\tb\n"); + } + } +} + +#[test] +fn test_numbering_no_lines() { + let delimiters_and_args = [ + ("\\:\\:\\:\n", ["-hn", "--header-numbering=n"]), + ("\\:\\:\n", ["-bn", "--body-numbering=n"]), + ("\\:\n", ["-fn", "--footer-numbering=n"]), + ]; + + for (delimiter, args) in delimiters_and_args { + for arg in args { + new_ucmd!() + .arg(arg) + .pipe_in(format!("{delimiter}a\n\nb")) + .succeeds() + .stdout_is("\n a\n \n b\n"); + } + } +} + +#[test] +fn test_numbering_non_empty_lines() { + let delimiters_and_args = [ + ("\\:\\:\\:\n", ["-ht", "--header-numbering=t"]), + ("\\:\\:\n", ["-bt", "--body-numbering=t"]), + ("\\:\n", ["-ft", "--footer-numbering=t"]), + ]; + + for (delimiter, args) in delimiters_and_args { + for arg in args { + new_ucmd!() + .arg(arg) + .pipe_in(format!("{delimiter}a\n\nb")) + .succeeds() + .stdout_is("\n 1\ta\n \n 2\tb\n"); + } + } +} + +#[test] +fn test_numbering_matched_lines() { + let delimiters_and_args = [ + ("\\:\\:\\:\n", ["-hp^[ac]", "--header-numbering=p^[ac]"]), + ("\\:\\:\n", ["-bp^[ac]", "--body-numbering=p^[ac]"]), + ("\\:\n", ["-fp^[ac]", "--footer-numbering=p^[ac]"]), + ]; + + for (delimiter, args) in delimiters_and_args { + for arg in args { + new_ucmd!() + .arg(arg) + .pipe_in(format!("{delimiter}a\nb\nc")) + .succeeds() + .stdout_is("\n 1\ta\n b\n 2\tc\n"); + } + } +} diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index fbfe68427..561752db3 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -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] diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index 24626cd76..54ac06384 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -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"; diff --git a/tests/by-util/test_pwd.rs b/tests/by-util/test_pwd.rs index 076e72089..1e43f5be6 100644 --- a/tests/by-util/test_pwd.rs +++ b/tests/by-util/test_pwd.rs @@ -116,7 +116,7 @@ fn test_symlinked_default_posix_p() { .env("POSIXLY_CORRECT", "1") .arg("-P") .succeeds() - .stdout_is(env.symdir + "\n"); + .stdout_is(env.subdir + "\n"); } #[cfg(not(windows))] diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 63015b24a..de0781912 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -7,6 +7,14 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } +#[test] +fn test_no_args() { + new_ucmd!() + .fails() + .code_is(1) + .stderr_contains("missing operand"); +} + #[test] fn test_hex_rejects_sign_after_identifier() { new_ucmd!() @@ -208,10 +216,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] diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index 58db09cbd..d98b840c4 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -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)); @@ -51,3 +51,14 @@ fn test_shred_force() { // file_a was deleted. assert!(!at.file_exists(file)); } + +#[test] +fn test_hex() { + let (at, mut ucmd) = at_and_ucmd!(); + + let file = "test_hex"; + + at.touch(file); + + ucmd.arg("--size=0x10").arg(file).succeeds(); +} diff --git a/tests/by-util/test_sleep.rs b/tests/by-util/test_sleep.rs index 13aa882b9..76e98e012 100644 --- a/tests/by-util/test_sleep.rs +++ b/tests/by-util/test_sleep.rs @@ -10,7 +10,7 @@ fn test_invalid_time_interval() { new_ucmd!() .arg("xyz") .fails() - .usage_error("invalid time interval 'xyz': Invalid input: 'xyz' at position 1"); + .usage_error("invalid time interval 'xyz': Invalid input: xyz"); new_ucmd!() .args(&["--", "-1"]) .fails() @@ -211,7 +211,7 @@ fn test_sleep_when_input_has_only_whitespace_then_error(#[case] input: &str) { #[test] fn test_sleep_when_multiple_input_some_with_error_then_shows_all_errors() { - let expected = "invalid time interval 'abc': Invalid input: 'abc' at position 1\n\ + let expected = "invalid time interval 'abc': Invalid input: abc\n\ sleep: invalid time interval '1years': Invalid time unit: 'years' at position 2\n\ sleep: invalid time interval ' ': Found only whitespace in input"; diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index e66a405ab..0c8af8969 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -134,6 +134,25 @@ fn test_version_empty_lines() { test_helper("version-empty-lines", &["-V", "--version-sort"]); } +#[test] +fn test_version_sort_unstable() { + new_ucmd!() + .arg("--sort=version") + .pipe_in("0.1\n0.02\n0.2\n0.002\n0.3\n") + .succeeds() + .stdout_is("0.1\n0.002\n0.02\n0.2\n0.3\n"); +} + +#[test] +fn test_version_sort_stable() { + new_ucmd!() + .arg("--stable") + .arg("--sort=version") + .pipe_in("0.1\n0.02\n0.2\n0.002\n0.3\n") + .succeeds() + .stdout_is("0.1\n0.02\n0.2\n0.002\n0.3\n"); +} + #[test] fn test_human_numeric_whitespace() { test_helper( diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 1395a4fa2..35e5ebb05 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -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"); +} diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 2527dc7cd..d932b35d6 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -178,7 +178,7 @@ fn test_char() { DEV_FORMAT_STR, #[cfg(target_os = "linux")] "/dev/pts/ptmx", - #[cfg(any(target_vendor = "apple"))] + #[cfg(target_vendor = "apple")] "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s (/%T) %u %U %W %X %y %Y %z %Z", #[cfg(any(target_os = "android", target_vendor = "apple"))] "/dev/ptmx", @@ -198,7 +198,7 @@ fn test_date() { "%z", #[cfg(target_os = "linux")] "/bin/sh", - #[cfg(any(target_vendor = "apple"))] + #[cfg(target_vendor = "apple")] "%z", #[cfg(any(target_os = "android", target_vendor = "apple"))] "/bin/sh", @@ -213,7 +213,7 @@ fn test_date() { "%z", #[cfg(target_os = "linux")] "/dev/ptmx", - #[cfg(any(target_vendor = "apple"))] + #[cfg(target_vendor = "apple")] "%z", #[cfg(any(target_os = "android", target_vendor = "apple"))] "/dev/ptmx", @@ -304,6 +304,19 @@ fn test_stdin_pipe_fifo2() { .succeeded(); } +#[test] +#[cfg(all(unix, not(target_os = "android")))] +fn test_stdin_with_fs_option() { + // $ stat -f - + new_ucmd!() + .arg("-f") + .arg("-") + .set_stdin(std::process::Stdio::null()) + .fails() + .code_is(1) + .stderr_contains("using '-' to denote standard input does not work in file system mode"); +} + #[test] #[cfg(all( unix, diff --git a/tests/by-util/test_sync.rs b/tests/by-util/test_sync.rs index d55a874ba..9cae3b8a0 100644 --- a/tests/by-util/test_sync.rs +++ b/tests/by-util/test_sync.rs @@ -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"))] diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index b7740f614..75abb8eb6 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -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(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!()); diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 0e4eade3d..0ebbee1d7 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -1,16 +1,8 @@ // spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms datetime mktime -// This test relies on -// --cfg unsound_local_offset -// https://github.com/time-rs/time/blob/deb8161b84f355b31e39ce09e40c4d6ce3fea837/src/sys/local_offset_at/unix.rs#L112-L120= -// See https://github.com/time-rs/time/issues/293#issuecomment-946382614= -// Defined in .cargo/config - -use filetime::FileTime; - -use time::macros::format_description; - use crate::common::util::{AtPath, TestScenario}; +use chrono::TimeZone; +use filetime::{self, FileTime}; use std::fs::remove_file; use std::path::PathBuf; @@ -35,21 +27,9 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) { filetime::set_file_times(at.plus_as_string(path), atime, mtime).unwrap(); } -// Adjusts for local timezone fn str_to_filetime(format: &str, s: &str) -> FileTime { - let format_description = match format { - "%y%m%d%H%M" => format_description!("[year repr:last_two][month][day][hour][minute]"), - "%y%m%d%H%M.%S" => { - format_description!("[year repr:last_two][month][day][hour][minute].[second]") - } - "%Y%m%d%H%M" => format_description!("[year][month][day][hour][minute]"), - "%Y%m%d%H%M.%S" => format_description!("[year][month][day][hour][minute].[second]"), - _ => panic!("unexpected dt format"), - }; - let tm = time::PrimitiveDateTime::parse(s, &format_description).unwrap(); - let d = time::OffsetDateTime::now_utc(); - let offset_dt = tm.assume_offset(d.offset()); - FileTime::from_unix_time(offset_dt.unix_timestamp(), tm.nanosecond()) + let tm = chrono::Utc.datetime_from_str(s, format).unwrap(); + FileTime::from_unix_time(tm.timestamp(), tm.timestamp_subsec_nanos()) } #[test] @@ -204,19 +184,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 +285,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] @@ -659,7 +647,7 @@ fn test_touch_mtime_dst_succeeds() { } #[test] -#[ignore = "not implemented"] +#[cfg(unix)] fn test_touch_mtime_dst_fails() { let (_at, mut ucmd) = at_and_ucmd!(); let file = "test_touch_set_mtime_dst_fails"; diff --git a/tests/by-util/test_tsort.rs b/tests/by-util/test_tsort.rs index 8b01e2a2d..62a74c31d 100644 --- a/tests/by-util/test_tsort.rs +++ b/tests/by-util/test_tsort.rs @@ -20,6 +20,14 @@ fn test_sort_self_loop() { .stdout_only("first\nsecond\n"); } +#[test] +fn test_sort_floating_nodes() { + new_ucmd!() + .pipe_in("d d\nc c\na a\nb b") + .succeeds() + .stdout_only("a\nb\nc\nd\n"); +} + #[test] fn test_no_such_file() { new_ucmd!() diff --git a/tests/by-util/test_yes.rs b/tests/by-util/test_yes.rs index 89a68e7e1..a674f8245 100644 --- a/tests/by-util/test_yes.rs +++ b/tests/by-util/test_yes.rs @@ -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"); diff --git a/tests/common/util.rs b/tests/common/util.rs index 0898a4ad7..995312f08 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -659,6 +659,17 @@ impl CmdResult { self } + #[track_caller] + pub fn stdout_contains_line>(&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>(&self, cmp: T) -> &Self { assert!( diff --git a/util/analyze-gnu-results.sh b/util/analyze-gnu-results.sh index 2bc08a9a4..045e3b93d 100644 --- a/util/analyze-gnu-results.sh +++ b/util/analyze-gnu-results.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # spell-checker:ignore xpass XPASS testsuite set -e @@ -67,9 +67,13 @@ function get_error { echo $((NON_ROOT + AS_ROOT)) } -export TOTAL=$(get_total) -export PASS=$(get_pass) -export SKIP=$(get_skip) -export FAIL=$(get_fail) -export XPASS=$(get_xpass) -export ERROR=$(get_error) +# we don't need the return codes indeed, ignore them +# shellcheck disable=SC2155 +{ + export TOTAL=$(get_total) + export PASS=$(get_pass) + export SKIP=$(get_skip) + export FAIL=$(get_fail) + export XPASS=$(get_xpass) + export ERROR=$(get_error) +} diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 13fef7bb9..d852ed66f 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -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}'" @@ -40,7 +43,14 @@ UU_BUILD_DIR="${path_UUTILS}/target/${UU_MAKE_PROFILE}" echo "UU_BUILD_DIR='${UU_BUILD_DIR}'" cd "${path_UUTILS}" && echo "[ pwd:'${PWD}' ]" -SELINUX_ENABLED=1 make PROFILE="${UU_MAKE_PROFILE}" + +if [ "$(uname)" == "Linux" ]; then + # only set on linux + export SELINUX_ENABLED=1 +fi + +make PROFILE="${UU_MAKE_PROFILE}" + cp "${UU_BUILD_DIR}/install" "${UU_BUILD_DIR}/ginstall" # The GNU tests rename this script before running, to avoid confusion with the make target # Create *sum binaries for sum in b2sum b3sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum; do @@ -212,7 +222,6 @@ sed -i -e "s/provoked error./provoked error\ncat pat |sort -u > pat/" tests/misc # Update the GNU error message to match ours sed -i -e "s/link-to-dir: hard link not allowed for directory/failed to create hard link 'link-to-dir' =>/" -e "s|link-to-dir/: hard link not allowed for directory|failed to create hard link 'link-to-dir/' =>|" tests/ln/hard-to-sym.sh - # GNU sleep accepts some crazy string, not sure we should match this behavior sed -i -e "s/timeout 10 sleep 0x.002p1/#timeout 10 sleep 0x.002p1/" tests/misc/sleep.sh @@ -232,3 +241,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 ' 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 diff --git a/util/publish.sh b/util/publish.sh index d9039fa60..71830f1f9 100755 --- a/util/publish.sh +++ b/util/publish.sh @@ -1,4 +1,5 @@ #!/bin/sh +# spell-checker:ignore uuhelp ARG="" if test "$1" != "--do-it"; then ARG="--dry-run --allow-dirty" @@ -35,7 +36,7 @@ TOTAL_ORDER=$(echo -e $PARTIAL_ORDER | tsort | tac) TOTAL_ORDER=${TOTAL_ORDER#ROOT} set -e -for dir in src/uucore_procs/ src/uucore/ src/uu/stdbuf/src/libstdbuf/; do +for dir in src/uuhelp_parser/ src/uucore_procs/ src/uucore/ src/uu/stdbuf/src/libstdbuf/; do ( cd "$dir" #shellcheck disable=SC2086 diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh index ac736abe1..55ba8cefc 100755 --- a/util/run-gnu-test.sh +++ b/util/run-gnu-test.sh @@ -36,7 +36,7 @@ if test $# -ge 1; then SPECIFIC_TESTS="$SPECIFIC_TESTS $t" done # trim it - SPECIFIC_TESTS=$(echo $SPECIFIC_TESTS| xargs) + SPECIFIC_TESTS=$(echo $SPECIFIC_TESTS | xargs) echo "Running specific tests: $SPECIFIC_TESTS" fi @@ -46,14 +46,14 @@ fi #shellcheck disable=SC2086 if test "$1" != "run-root"; then -# run the regular tests + # run the regular tests if test $# -ge 1; then timeout -sKILL 4h make -j "$(nproc)" check TESTS="$SPECIFIC_TESTS" SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no gl_public_submodule_commit="" srcdir="${path_GNU}" || : # Kill after 4 hours in case something gets stuck in make else timeout -sKILL 4h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no gl_public_submodule_commit="" srcdir="${path_GNU}" || : # Kill after 4 hours in case something gets stuck in make fi else -# in case we would like to run tests requiring root + # in case we would like to run tests requiring root if test -z "$1" -o "$1" == "run-root"; then if test -n "$CI"; then echo "Running check-root to run only root tests" diff --git a/util/update-version.sh b/util/update-version.sh index 2e2dca903..8b6168782 100755 --- a/util/update-version.sh +++ b/util/update-version.sh @@ -1,4 +1,6 @@ #!/bin/sh +# spell-checker:ignore uuhelp + # This is a stupid helper. I will mass replace all versions (including other crates) # So, it should be triple-checked @@ -7,12 +9,13 @@ # 2) run it: sh util/update-version.sh # 3) Do a spot check with "git diff" # 4) cargo test --release --features unix -# 5) Run util/publish.sh in dry mode (it will fail as packages needs more recent version of uucore) -# 6) Run util/publish.sh --do-it -# 7) In some cases, you might have to fix dependencies and run import +# 5) git commit -m "New release" +# 6) Run util/publish.sh in dry mode (it will fail as packages needs more recent version of uucore) +# 7) Run util/publish.sh --do-it +# 8) In some cases, you might have to fix dependencies and run import -FROM="0.0.17" -TO="0.0.18" +FROM="0.0.19" +TO="0.0.20" PROGS=$(ls -1d src/uu/*/Cargo.toml src/uu/stdbuf/src/libstdbuf/Cargo.toml src/uucore/Cargo.toml Cargo.toml) @@ -23,6 +26,9 @@ sed -i -e "s|version = \"$FROM\"|version = \"$TO\"|" $PROGS # Update uucore_procs sed -i -e "s|version = \"$FROM\"|version = \"$TO\"|" src/uucore_procs/Cargo.toml +# Update uuhelp_parser +sed -i -e "s|version = \"$FROM\"|version = \"$TO\"|" src/uuhelp_parser/Cargo.toml + # Update the stdbuf stuff sed -i -e "s|libstdbuf = { version=\"$FROM\"|libstdbuf = { version=\"$TO\"|" src/uu/stdbuf/Cargo.toml sed -i -e "s|= { optional=true, version=\"$FROM\", package=\"uu_|= { optional=true, version=\"$TO\", package=\"uu_|g" Cargo.toml @@ -41,4 +47,3 @@ sed -i -e "s|uucore = { version=\">=$FROM\",|uucore = { version=\">=$TO\",|" $PR # Update crates using uucore_procs #shellcheck disable=SC2086 sed -i -e "s|uucore_procs = { version=\">=$FROM\",|uucore_procs = { version=\">=$TO\",|" $PROGS -