diff --git a/clippy.toml b/.clippy.toml similarity index 100% rename from clippy.toml rename to .clippy.toml diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index d096ff43c18..2ab6cd86a7e 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -2,10 +2,10 @@ name: CICD # spell-checker:ignore (acronyms) CICD MSVC musl # spell-checker:ignore (env/flags) Awarnings Ccodegen Coverflow Cpanic RUSTDOCFLAGS RUSTFLAGS Zpanic -# spell-checker:ignore (jargon) SHAs deps softprops toolchain +# spell-checker:ignore (jargon) SHAs deps dequote softprops subshell toolchain # spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy # spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs -# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend runtest tempfile testsuite uutils +# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend pell runtest tempfile testsuite uutils # ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45 @@ -13,19 +13,28 @@ env: PROJECT_NAME: coreutils PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_AUTH: "uutils" - RUST_MIN_SRV: "1.47.0" ## MSRV v1.47.0 + RUST_MIN_SRV: "1.54.0" ## MSRV v1.54.0 + # * style job configuration + STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis on: [push, pull_request] jobs: - code_deps: - name: Style/dependencies + style_deps: + ## ToDO: [2021-11-10; rivy] 'Style/deps' needs more informative output and better integration of results into the GHA dashboard + name: Style/deps runs-on: ${{ matrix.job.os }} + # env: + # STYLE_FAIL_ON_FAULT: false # overrides workflow default strategy: fail-fast: false matrix: job: - - { os: ubuntu-latest , features: feat_os_unix } + # note: `cargo-udeps` panics when processing stdbuf/libstdbuf ("uu_stdbuf_libstdbuf"); either b/c of the 'cpp' crate or 'libstdbuf' itself + # ... b/c of the panic, a more limited feature set is tested (though only excluding `stdbuf`) + - { os: ubuntu-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" } + - { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" } + - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v2 - name: Initialize workflow variables @@ -34,27 +43,50 @@ jobs: run: | ## VARs setup outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE # 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 + ## note: requires 'nightly' toolchain b/c `cargo-udeps` uses the `rustc` '-Z save-analysis' option + ## * ... ref: - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly default: true - profile: minimal # minimal component installation (ie, no documentation) - - name: "`cargo update` testing" + profile: minimal + - name: Install `cargo-udeps` + uses: actions-rs/install@v0.1 + with: + crate: cargo-udeps + version: latest + use-tool-cache: false + env: + RUSTUP_TOOLCHAIN: stable + - name: Detect unused dependencies shell: bash run: | - ## `cargo update` testing - # * convert any warnings to GHA UI annotations; ref: - cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::'Cargo.lock' file requires update (use \`cargo +${{ env.RUST_MIN_SRV }} update\`)" ; exit 1 ; } + ## Detect unused dependencies + unset fault + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # + cargo +nightly udeps ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all-targets &> udeps.log || cat udeps.log + grep --ignore-case "all deps seem to have been used" udeps.log || { printf "%s\n" "::${fault_type} ::${fault_prefix}: \`cargo udeps\`: style violation (unused dependency found)" ; fault=true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi - code_format: + style_format: name: Style/format runs-on: ${{ matrix.job.os }} + # env: + # STYLE_FAIL_ON_FAULT: false # overrides workflow default strategy: fail-fast: false matrix: @@ -68,6 +100,12 @@ jobs: run: | ## VARs setup outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='' ; @@ -80,90 +118,140 @@ jobs: default: true profile: minimal # minimal component installation (ie, no documentation) components: rustfmt - - name: "`fmt` testing" + - name: "`cargo fmt` testing" shell: bash run: | - ## `fmt` testing - # * convert any warnings to GHA UI annotations; ref: - S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::error file=\1,line=\2::ERROR: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; exit 1 ; } - - name: "`fmt` testing of tests" + ## `cargo fmt` testing + unset fault + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # * convert any errors/warnings to GHA UI annotations; ref: + S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt -- \"\1\"\`)/p" ; fault=true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi + - name: "`cargo fmt` testing of integration tests" if: success() || failure() # run regardless of prior step success/failure shell: bash run: | - ## `fmt` testing of tests - # * convert any warnings to GHA UI annotations; ref: - S=$(find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::error file=\1,line=\2::ERROR: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; exit 1 ; } + ## `cargo fmt` testing of integration tests + unset fault + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # 'tests' is the standard/usual integration test directory + if [ -d tests ]; then + # * convert any errors/warnings to GHA UI annotations; ref: + S=$(find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; fault=true ; } + fi + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi - code_lint: + style_lint: name: Style/lint runs-on: ${{ matrix.job.os }} + # env: + # STYLE_FAIL_ON_FAULT: false # overrides workflow default strategy: fail-fast: false matrix: job: - - { os: ubuntu-latest } + - { os: ubuntu-latest , features: feat_os_unix } - { os: macos-latest , features: feat_os_macos } - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v2 - - name: Install/setup prerequisites - shell: bash - run: | - case '${{ matrix.job.os }}' in - macos-latest) brew install coreutils ;; # needed for show-utils.sh - esac - name: Initialize workflow variables id: vars shell: bash run: | ## VARs setup outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='--all-features' ; - if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features ${{ matrix.job.features }}' ; fi + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi outputs CARGO_FEATURES_OPTION # * determine sub-crate utility list UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" echo UTILITY_LIST=${UTILITY_LIST} CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" outputs CARGO_UTILITY_LIST_OPTIONS + - name: Install/setup prerequisites + shell: bash + run: | + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for show-utils.sh + esac - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable default: true profile: minimal # minimal component installation (ie, no documentation) components: clippy - - name: "`clippy` lint testing" + - name: "`cargo clippy` lint testing" shell: bash run: | - ## `clippy` lint testing + ## `cargo clippy` lint testing + unset fault + 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 +nightly clippy --all-targets ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -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:]]+${PWD//\//\\/}\/(.*):([0-9]+):([0-9]+).*$/::error file=\2,line=\3,col=\4::ERROR: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; exit 1 ; } + S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- -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 - code_spellcheck: + style_spellcheck: name: Style/spelling runs-on: ${{ matrix.job.os }} + # env: + # STYLE_FAIL_ON_FAULT: false # overrides workflow default strategy: matrix: job: - - { os: ubuntu-latest } + - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE - name: Install/setup prerequisites shell: bash run: | ## Install/setup prerequisites - sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell -g ; + # * pin installed cspell to v4.2.8 (cspell v5+ is broken for NodeJS < v12) + ## maint: [2021-11-10; rivy] `cspell` version may be advanced to v5 when used with NodeJS >= v12 + sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell@4.2.8 -g ; - name: Run `cspell` shell: bash run: | ## Run `cspell` - cspell --config .vscode/cSpell.json --no-summary --no-progress "**/*" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::error file=\1,line=\2,col=\3::ERROR: \4 (file:'\1', line:\2)/p" + unset fault + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # * find cspell configuration ; note: avoid quotes around ${cfg_file} b/c `cspell` (v4) doesn't correctly dequote the config argument (or perhaps a subshell expansion issue?) + cfg_files=($(shopt -s nullglob ; echo {.vscode,.}/{,.}c[sS]pell{.json,.config{.js,.cjs,.json,.yaml,.yml},.yaml,.yml} ;)) + cfg_file=${cfg_files[0]} + unset CSPELL_CFG_OPTION ; if [ -n "$cfg_file" ]; then CSPELL_CFG_OPTION="--config $cfg_file" ; fi + # * `cspell` + ## maint: [2021-11-10; rivy] the `--no-progress` option for `cspell` is a `cspell` v5+ option + # S=$(cspell ${CSPELL_CFG_OPTION} --no-summary --no-progress "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; } + S=$(cspell ${CSPELL_CFG_OPTION} --no-summary "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi min_version: - name: MinRustV # Minimum supported rust version + name: MinRustV # Minimum supported rust version (aka, MinSRV or MSRV) runs-on: ${{ matrix.job.os }} strategy: matrix: @@ -171,6 +259,17 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # target-specific options + # * CARGO_FEATURES_OPTION + unset CARGO_FEATURES_OPTION + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi + outputs CARGO_FEATURES_OPTION - name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }}) uses: actions-rs/toolchain@v1 with: @@ -208,25 +307,25 @@ jobs: cargo-tree tree -V # dependencies echo "## dependency list" - cargo fetch --locked --quiet ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors - RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique + RUSTUP_TOOLCHAIN=stable cargo fetch --locked --quiet + RUSTUP_TOOLCHAIN=stable cargo-tree tree --all --locked --no-dev-dependencies --no-indent ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} | grep -vE "$PWD" | sort --unique - name: Test uses: actions-rs/cargo@v1 with: command: test - args: --features "feat_os_unix" -p uucore -p coreutils + args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils env: - RUSTFLAGS: '-Awarnings' + RUSTFLAGS: "-Awarnings" - build_makefile: - name: Build/Makefile + deps: + name: Dependencies runs-on: ${{ matrix.job.os }} strategy: fail-fast: false matrix: job: - - { os: ubuntu-latest } + - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 - name: Install `rust` toolchain @@ -235,11 +334,35 @@ jobs: toolchain: stable default: true profile: minimal # minimal component installation (ie, no documentation) + - name: "`cargo update` testing" + shell: bash + run: | + ## `cargo update` testing + # * convert any errors/warnings to GHA UI annotations; ref: + cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::'Cargo.lock' file requires update (use \`cargo +${{ env.RUST_MIN_SRV }} update\`)" ; exit 1 ; } + + build_makefile: + name: Build/Makefile + needs: [ min_version, deps ] + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v2 - name: Install/setup prerequisites shell: bash run: | ## Install/setup prerequisites sudo apt-get -y update ; sudo apt-get -y install python3-sphinx ; + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + profile: minimal # minimal component installation (ie, no documentation) - name: "`make build`" shell: bash run: | @@ -251,13 +374,14 @@ jobs: build: name: Build + needs: [ min_version, deps ] runs-on: ${{ matrix.job.os }} strategy: fail-fast: false matrix: job: - # { os, target, cargo-options, features, use-cross, toolchain } - - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , features: feat_os_unix_gnueabihf , use-cross: use-cross } + # { os , target , cargo-options , features , use-cross , toolchain } + - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf, features: feat_os_unix_gnueabihf, use-cross: use-cross, } - { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross } - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } # - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_selinux , use-cross: use-cross } @@ -270,21 +394,10 @@ jobs: - { os: macos-latest , target: x86_64-apple-darwin , features: feat_os_macos } - { os: windows-latest , target: i686-pc-windows-gnu , features: feat_os_windows } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } - - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly + - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } steps: - uses: actions/checkout@v2 - - name: Install/setup prerequisites - shell: bash - run: | - ## Install/setup prerequisites - case '${{ matrix.job.target }}' in - arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; - aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; - esac - case '${{ matrix.job.os }}' in - macos-latest) brew install coreutils ;; # needed for testing - esac - name: Initialize workflow variables id: vars shell: bash @@ -373,6 +486,17 @@ jobs: *-pc-windows-msvc) STRIP="" ;; esac; outputs STRIP + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + case '${{ matrix.job.target }}' in + arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; + aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; + esac + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for testing + esac - name: Create all needed build/work directories shell: bash run: | @@ -380,12 +504,23 @@ jobs: mkdir -p '${{ steps.vars.outputs.STAGING }}' mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}' mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg' + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + case '${{ matrix.job.target }}' in + arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; + aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; + esac + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for testing + esac - name: rust toolchain ~ install uses: actions-rs/toolchain@v1 - env: - # Override auto-detection of RAM for Rustc install. - # https://github.com/rust-lang/rustup/issues/2229#issuecomment-585855925 - RUSTUP_UNPACK_RAM: "21474836480" + # env: + # # Override auto-detection of RAM for Rustc install. + # # https://github.com/rust-lang/rustup/issues/2229#issuecomment-585855925 + # RUSTUP_UNPACK_RAM: "21474836480" with: toolchain: ${{ steps.vars.outputs.TOOLCHAIN }} target: ${{ matrix.job.target }} @@ -502,6 +637,7 @@ jobs: test_busybox: name: Tests/BusyBox test suite + needs: [ min_version, deps ] runs-on: ${{ matrix.job.os }} strategy: fail-fast: false @@ -510,16 +646,17 @@ jobs: - { os: ubuntu-latest } steps: - uses: actions/checkout@v2 + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + make prepare-busytest - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable default: true profile: minimal # minimal component installation (ie, no documentation) - - name: Install/setup prerequisites - shell: bash - run: | - make prepare-busytest - name: "Run BusyBox test suite" shell: bash run: | @@ -532,53 +669,75 @@ jobs: if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi test_freebsd: - runs-on: macos-latest name: Tests/FreeBSD test suite + needs: [ min_version, deps ] + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: macos-10.15 , features: unix } ## GHA MacOS-11.0 VM won't have VirtualBox; refs: , env: mem: 2048 steps: - uses: actions/checkout@v2 - name: Prepare, build and test - id: test + ## spell-checker:ignore (ToDO) sshfs usesh vmactions uses: vmactions/freebsd-vm@v0.1.5 with: usesh: true + # sync: sshfs prepare: pkg install -y curl gmake sudo run: | - # Need to be run in the same block. Otherwise, we are back on the mac host. + ## Prepare, build, and test + # implementation modelled after ref: + # * NOTE: All steps need to be run in this block, otherwise, we are operating back on the mac host set -e - pw adduser -n cuuser -d /root/ -g wheel -c "Coreutils user to build" -w random - chown -R cuuser:wheel /root/ /Users/runner/work/coreutils/ + # + TEST_USER=tester + REPO_NAME=${GITHUB_WORKSPACE##*/} + WORKSPACE_PARENT="/Users/runner/work/${REPO_NAME}" + WORKSPACE="${WORKSPACE_PARENT}/${REPO_NAME}" + # + pw adduser -n ${TEST_USER} -d /root/ -g wheel -c "Coreutils user to build" -w random + # chown -R ${TEST_USER}:wheel /root/ "${WORKSPACE_PARENT}"/ + chown -R ${TEST_USER}:wheel /root/ "/Users/runner/work/${REPO_NAME}"/ whoami - - # Needs to be done in a sudo as we are changing users - sudo -i -u cuuser sh << EOF + # + # Further work needs to be done in a sudo as we are changing users + sudo -i -u ${TEST_USER} sh << EOF set -e whoami curl https://sh.rustup.rs -sSf --output rustup.sh sh rustup.sh -y --profile=minimal + . $HOME/.cargo/env ## Info # environment echo "## environment" echo "CI='${CI}'" - # tooling info display - echo "## tooling" - . $HOME/.cargo/env + echo "REPO_NAME='${REPO_NAME}'" + echo "TEST_USER='${TEST_USER}'" + echo "WORKSPACE_PARENT='${WORKSPACE_PARENT}'" + echo "WORKSPACE='${WORKSPACE}'" + env | sort + # tooling info + echo "## tooling info" cargo -V rustc -V - env - - # where the files are resynced - cd /Users/runner/work/coreutils/coreutils/ - cargo build - cargo test --features feat_os_unix -p uucore -p coreutils + # + cd "${WORKSPACE}" + unset FAULT + cargo build || FAULT=1 + cargo test --features "${{ matrix.job.features }}" || FAULT=1 + cargo test --features "${{ matrix.job.features }}" -p uucore || FAULT=1 # Clean to avoid to rsync back the files cargo clean + if (test -n "$FAULT"); then exit 1 ; fi EOF - coverage: name: Code Coverage + needs: build runs-on: ${{ matrix.job.os }} strategy: fail-fast: true @@ -590,13 +749,6 @@ jobs: - { os: windows-latest , features: windows } steps: - uses: actions/checkout@v2 - - name: Install/setup prerequisites - shell: bash - run: | - ## Install/setup prerequisites - case '${{ matrix.job.os }}' in - macos-latest) brew install coreutils ;; # needed for testing - esac # - name: Reattach HEAD ## may be needed for accurate code coverage info # run: git checkout ${{ github.head_ref }} - name: Initialize workflow variables @@ -615,11 +767,6 @@ jobs: # staging directory STAGING='_staging' outputs STAGING - ## # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see ) - ## # note: CODECOV_TOKEN / HAS_CODECOV_TOKEN is not needed for public repositories when using AppVeyor, Azure Pipelines, CircleCI, GitHub Actions, Travis (see ) - ## unset HAS_CODECOV_TOKEN - ## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi - ## outputs HAS_CODECOV_TOKEN # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage @@ -628,6 +775,13 @@ jobs: # * CODECOV_FLAGS CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' ) outputs CODECOV_FLAGS + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for testing + esac - name: rust toolchain ~ install uses: actions-rs/toolchain@v1 with: @@ -650,10 +804,10 @@ jobs: command: test args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast -p uucore env: - CARGO_INCREMENTAL: '0' - RUSTC_WRAPPER: '' - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort' - RUSTDOCFLAGS: '-Cpanic=abort' + CARGO_INCREMENTAL: "0" + RUSTC_WRAPPER: "" + RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTDOCFLAGS: "-Cpanic=abort" # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }} - name: Test uses: actions-rs/cargo@v1 @@ -661,10 +815,10 @@ jobs: command: test args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast env: - CARGO_INCREMENTAL: '0' - RUSTC_WRAPPER: '' - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort' - RUSTDOCFLAGS: '-Cpanic=abort' + CARGO_INCREMENTAL: "0" + RUSTC_WRAPPER: "" + RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTDOCFLAGS: "-Cpanic=abort" # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }} - name: Test individual utilities uses: actions-rs/cargo@v1 @@ -672,10 +826,10 @@ jobs: command: test args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} env: - CARGO_INCREMENTAL: '0' - RUSTC_WRAPPER: '' - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort' - RUSTDOCFLAGS: '-Cpanic=abort' + CARGO_INCREMENTAL: "0" + RUSTC_WRAPPER: "" + RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTDOCFLAGS: "-Cpanic=abort" # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }} - name: "`grcov` ~ install" uses: actions-rs/install@v0.1 @@ -690,13 +844,13 @@ jobs: ## Generate coverage data COVERAGE_REPORT_DIR="target/debug" COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info" - # GRCOV_IGNORE_OPTION='--ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?) + # GRCOV_IGNORE_OPTION='--ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?) # GRCOV_EXCLUDE_OPTION='--excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"' ## `grcov` ignores these params when passed as an environment variable (why?) mkdir -p "${COVERAGE_REPORT_DIR}" # display coverage files - grcov . --output-type files --ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique + grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique # generate coverage report - grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" + grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" echo ::set-output name=report::${COVERAGE_REPORT_FILE} - name: Upload coverage results (to Codecov.io) uses: codecov/codecov-action@v1 @@ -708,35 +862,3 @@ jobs: flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} name: codecov-umbrella fail_ci_if_error: false - - unused_deps: - name: Unused deps - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - - { os: ubuntu-latest , features: feat_os_unix } - - { os: macos-latest , features: feat_os_macos } - - { os: windows-latest , features: feat_os_windows } - steps: - - uses: actions/checkout@v2 - - name: Install `rust` toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - default: true - profile: minimal - - name: Install `cargo-udeps` - uses: actions-rs/install@v0.1 - with: - crate: cargo-udeps - version: latest - use-tool-cache: true - env: - RUSTUP_TOOLCHAIN: stable - - name: Confirms there isn't any unused deps - shell: bash - run: | - cargo +nightly udeps --all-targets &> udeps.log || cat udeps.log - grep "seem to have been used" udeps.log diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json index 4983601391d..95b6f04853a 100644 --- a/.vscode/cSpell.json +++ b/.vscode/cSpell.json @@ -11,7 +11,7 @@ { "name": "workspace", "path": "./cspell.dictionaries/workspace.wordlist.txt" } ], // ignorePaths - a list of globs to specify which files are to be ignored - "ignorePaths": ["Cargo.lock", "target/**", "tests/**/fixtures/**", "src/uu/dd/test-resources/**"], + "ignorePaths": ["Cargo.lock", "target/**", "tests/**/fixtures/**", "src/uu/dd/test-resources/**", "vendor/**"], // ignoreWords - a list of words to be ignored (even if they are in the flagWords) "ignoreWords": [], // words - list of words to be always considered correct diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 9b1d0a8da43..69c72d17d99 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -59,6 +59,7 @@ kibibytes libacl lcase lossily +lstat mebi mebibytes mergeable diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index d37a59465e7..b68da6eb717 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -182,6 +182,7 @@ getgrgid getgrnam getgrouplist getgroups +getpwent getpwnam getpwuid getuid diff --git a/Cargo.lock b/Cargo.lock index 4a40fd88391..065f0cd1f0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ - "memchr 2.4.0", + "memchr 2.4.1", ] [[package]] @@ -29,15 +29,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -76,6 +67,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "bigdecimal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "binary-heap-plus" version = "0.4.1" @@ -87,21 +89,21 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.59.1" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" +checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" dependencies = [ "bitflags", "cexpr", "clang-sys", "clap", - "env_logger 0.8.4", + "env_logger 0.9.0", "lazy_static", "lazycell", "log", "peeking_take_while", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "regex", "rustc-hash", "shlex", @@ -129,18 +131,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitvec" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "blake2b_simd" version = "0.5.11" @@ -164,12 +154,12 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", - "memchr 2.4.0", + "memchr 2.4.1", "regex-automata", ] @@ -181,9 +171,9 @@ checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" [[package]] name = "byte-unit" -version = "4.0.12" +version = "4.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063197e6eb4b775b64160dedde7a0986bb2836cce140e9492e9e96f28e18bcd8" +checksum = "956ffc5b0ec7d7a6949e3f21fd63ba5af4cffdc2ba1e0b7bf62b481458c4ae7f" dependencies = [ "utf8-width", ] @@ -202,15 +192,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.69" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" [[package]] name = "cexpr" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] @@ -242,9 +232,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" dependencies = [ "glob", "libc", @@ -253,11 +243,11 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.3" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "ansi_term 0.11.0", + "ansi_term", "atty", "bitflags", "strsim", @@ -299,7 +289,7 @@ dependencies = [ [[package]] name = "coreutils" -version = "0.0.8" +version = "0.0.12" dependencies = [ "atty", "chrono", @@ -309,7 +299,7 @@ dependencies = [ "glob", "lazy_static", "libc", - "nix 0.20.0", + "nix 0.23.1", "pretty_assertions", "rand 0.7.3", "regex", @@ -494,7 +484,7 @@ dependencies = [ "if_rust_version", "lazy_static", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", ] @@ -531,9 +521,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -552,9 +542,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -565,9 +555,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" dependencies = [ "cfg-if 1.0.0", "lazy_static", @@ -575,9 +565,9 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.20.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" +checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c" dependencies = [ "bitflags", "crossterm_winapi", @@ -591,30 +581,30 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" dependencies = [ "winapi 0.3.9", ] [[package]] name = "ctor" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" dependencies = [ - "quote 1.0.9", + "quote 1.0.14", "syn", ] [[package]] name = "ctrlc" -version = "3.1.9" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a" +checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" dependencies = [ - "nix 0.20.0", + "nix 0.23.1", "winapi 0.3.9", ] @@ -650,17 +640,6 @@ dependencies = [ "syn", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote 1.0.9", - "syn", -] - [[package]] name = "diff" version = "0.1.12" @@ -687,9 +666,9 @@ dependencies = [ [[package]] name = "dns-lookup" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093d88961fd18c4ecacb8c80cd0b356463ba941ba11e0e01f9cf5271380b79dc" +checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872" dependencies = [ "cfg-if 1.0.0", "libc", @@ -721,9 +700,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.8.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", "humantime", @@ -753,6 +732,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fastrand" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" +dependencies = [ + "instant", +] + [[package]] name = "file_diff" version = "1.0.0" @@ -799,17 +787,14 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - [[package]] name = "gcd" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7cd301bf2ab11ae4e5bdfd79c221d97a25e46c089144a62ee9d09cb32d2b92" +checksum = "f37978dab2ca789938a83b2f8bc1ef32db6633af9051a6cd409eff72cbaaa79a" +dependencies = [ + "paste 1.0.6", +] [[package]] name = "generic-array" @@ -843,9 +828,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if 1.0.0", "libc", @@ -858,24 +843,11 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "globset" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - [[package]] name = "half" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" @@ -935,9 +907,9 @@ checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec" [[package]] name = "instant" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", ] @@ -959,9 +931,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] @@ -990,15 +962,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.101" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" [[package]] name = "libloading" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi 0.3.9", @@ -1006,9 +978,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" dependencies = [ "scopeguard", ] @@ -1028,7 +1000,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", ] [[package]] @@ -1037,12 +1009,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "md5" version = "0.3.8" @@ -1060,33 +1026,39 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap2" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" -version = "0.7.7" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", @@ -1106,33 +1078,22 @@ dependencies = [ [[package]] name = "nix" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", -] - -[[package]] -name = "nix" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" dependencies = [ "bitflags", "cc", "cfg-if 1.0.0", "libc", + "memoffset", ] [[package]] name = "nix" -version = "0.21.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ "bitflags", "cc", @@ -1149,13 +1110,12 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "nom" -version = "6.1.2" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ - "bitvec", - "funty", - "memchr 2.4.0", + "memchr 2.4.1", + "minimal-lexical", "version_check", ] @@ -1170,9 +1130,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg", "num-integer", @@ -1200,9 +1160,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", @@ -1210,23 +1170,22 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" +checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" dependencies = [ - "derivative", "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" +checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" dependencies = [ "proc-macro-crate", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", ] @@ -1244,9 +1203,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "once_cell" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onig" @@ -1280,6 +1239,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "os_display" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "748cc1d0dc55247316a5bedd8dc8c5478c8a0c2e2001176b38ce7c0ed732c7a5" +dependencies = [ + "unicode-width", +] + [[package]] name = "ouroboros" version = "0.10.1" @@ -1300,7 +1268,7 @@ dependencies = [ "Inflector", "proc-macro-error", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", ] @@ -1315,9 +1283,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", @@ -1326,15 +1294,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if 1.0.0", "instant", "libc", "redox_syscall", - "smallvec 1.6.1", + "smallvec", "winapi 0.3.9", ] @@ -1348,6 +1316,12 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + [[package]] name = "paste-impl" version = "0.1.18" @@ -1365,15 +1339,15 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pkg-config" -version = "0.3.19" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" [[package]] name = "platform-info" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ea9cd21d89bffb387b6c7363d23bead0807be9de676c671b474dd29e7436d3" +checksum = "84332c4de03d567e6f5ea143e35e63ceed534a34f768218aabf57879d7edf2a0" dependencies = [ "libc", "winapi 0.3.9", @@ -1381,17 +1355,17 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pretty_assertions" -version = "0.7.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" +checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", "ctor", "diff", "output_vt100", @@ -1399,9 +1373,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" dependencies = [ "thiserror", "toml", @@ -1415,7 +1389,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", "version_check", ] @@ -1427,7 +1401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "version_check", ] @@ -1439,9 +1413,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid 0.2.2", ] @@ -1478,19 +1452,13 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" [[package]] name = "quote" -version = "1.0.9" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" - [[package]] name = "rand" version = "0.5.6" @@ -1580,7 +1548,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.4", ] [[package]] @@ -1666,7 +1634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", - "memchr 2.4.0", + "memchr 2.4.1", "regex-syntax", ] @@ -1693,9 +1661,9 @@ dependencies = [ [[package]] name = "retain_mut" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b" +checksum = "53552c6c49e1e13f1a203ef0080ab3bbef0beb570a528993e83df057a9d9bba1" [[package]] name = "rlimit" @@ -1740,9 +1708,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "selinux" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cf704a543fe60d898f3253f1cc37655d0f0e9cdb68ef6230557e0e031b80608" +checksum = "09715d6b4356e916047e61e4dce40a67ac93036851957b91713d3d9c282d1548" dependencies = [ "bitflags", "libc", @@ -1766,21 +1734,21 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.130" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", ] @@ -1817,15 +1785,15 @@ dependencies = [ [[package]] name = "shlex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.9" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" +checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" dependencies = [ "libc", "signal-hook-registry", @@ -1853,18 +1821,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] - -[[package]] -name = "smallvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smawk" @@ -1874,11 +1833,10 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" [[package]] name = "socket2" -version = "0.3.19" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" dependencies = [ - "cfg-if 1.0.0", "libc", "winapi 0.3.9", ] @@ -1909,36 +1867,30 @@ checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" dependencies = [ "heck", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", ] [[package]] name = "syn" -version = "1.0.74" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "unicode-xid 0.2.2", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", + "fastrand", "libc", - "rand 0.8.4", "redox_syscall", "remove_dir_all", "winapi 0.3.9", @@ -2031,21 +1983,21 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", ] @@ -2070,9 +2022,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicode-linebreak" @@ -2091,9 +2043,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" @@ -2147,7 +2099,7 @@ checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" [[package]] name = "uu_arch" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "platform-info", @@ -2157,7 +2109,7 @@ dependencies = [ [[package]] name = "uu_base32" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2166,7 +2118,7 @@ dependencies = [ [[package]] name = "uu_base64" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uu_base32", @@ -2176,7 +2128,7 @@ dependencies = [ [[package]] name = "uu_basename" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2185,7 +2137,7 @@ dependencies = [ [[package]] name = "uu_basenc" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uu_base32", @@ -2195,11 +2147,11 @@ dependencies = [ [[package]] name = "uu_cat" -version = "0.0.8" +version = "0.0.12" dependencies = [ "atty", "clap", - "nix 0.20.0", + "nix 0.23.1", "thiserror", "unix_socket", "uucore", @@ -2209,7 +2161,7 @@ dependencies = [ [[package]] name = "uu_chcon" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "fts-sys", @@ -2222,7 +2174,7 @@ dependencies = [ [[package]] name = "uu_chgrp" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2231,7 +2183,7 @@ dependencies = [ [[package]] name = "uu_chmod" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2242,7 +2194,7 @@ dependencies = [ [[package]] name = "uu_chown" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2251,7 +2203,7 @@ dependencies = [ [[package]] name = "uu_chroot" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2260,7 +2212,7 @@ dependencies = [ [[package]] name = "uu_cksum" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2270,7 +2222,7 @@ dependencies = [ [[package]] name = "uu_comm" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2280,7 +2232,7 @@ dependencies = [ [[package]] name = "uu_cp" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "exacl", @@ -2298,7 +2250,7 @@ dependencies = [ [[package]] name = "uu_csplit" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "regex", @@ -2309,19 +2261,19 @@ dependencies = [ [[package]] name = "uu_cut" -version = "0.0.8" +version = "0.0.12" dependencies = [ "atty", "bstr", "clap", - "memchr 2.4.0", + "memchr 2.4.1", "uucore", "uucore_procs", ] [[package]] name = "uu_date" -version = "0.0.8" +version = "0.0.12" dependencies = [ "chrono", "clap", @@ -2333,7 +2285,7 @@ dependencies = [ [[package]] name = "uu_dd" -version = "0.0.8" +version = "0.0.12" dependencies = [ "byte-unit", "clap", @@ -2347,7 +2299,7 @@ dependencies = [ [[package]] name = "uu_df" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "number_prefix", @@ -2357,7 +2309,7 @@ dependencies = [ [[package]] name = "uu_dircolors" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "glob", @@ -2367,7 +2319,7 @@ dependencies = [ [[package]] name = "uu_dirname" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2377,7 +2329,7 @@ dependencies = [ [[package]] name = "uu_du" -version = "0.0.8" +version = "0.0.12" dependencies = [ "chrono", "clap", @@ -2388,7 +2340,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2397,7 +2349,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2408,7 +2360,7 @@ dependencies = [ [[package]] name = "uu_expand" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "unicode-width", @@ -2418,7 +2370,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2431,22 +2383,22 @@ dependencies = [ [[package]] name = "uu_factor" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "coz", "num-traits", - "paste", + "paste 0.1.18", "quickcheck", "rand 0.7.3", - "smallvec 0.6.14", + "smallvec", "uucore", "uucore_procs", ] [[package]] name = "uu_false" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2455,7 +2407,7 @@ dependencies = [ [[package]] name = "uu_fmt" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2466,7 +2418,7 @@ dependencies = [ [[package]] name = "uu_fold" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2475,7 +2427,7 @@ dependencies = [ [[package]] name = "uu_groups" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2484,7 +2436,7 @@ dependencies = [ [[package]] name = "uu_hashsum" -version = "0.0.8" +version = "0.0.12" dependencies = [ "blake2b_simd", "clap", @@ -2492,7 +2444,7 @@ dependencies = [ "hex", "libc", "md5", - "memchr 2.4.0", + "memchr 2.4.1", "regex", "regex-syntax", "sha1", @@ -2504,17 +2456,17 @@ dependencies = [ [[package]] name = "uu_head" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", - "memchr 2.4.0", + "memchr 2.4.1", "uucore", "uucore_procs", ] [[package]] name = "uu_hostid" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2524,7 +2476,7 @@ dependencies = [ [[package]] name = "uu_hostname" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "hostname", @@ -2536,7 +2488,7 @@ dependencies = [ [[package]] name = "uu_id" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "selinux", @@ -2546,7 +2498,7 @@ dependencies = [ [[package]] name = "uu_install" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "file_diff", @@ -2559,7 +2511,7 @@ dependencies = [ [[package]] name = "uu_join" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2568,7 +2520,7 @@ dependencies = [ [[package]] name = "uu_kill" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2578,7 +2530,7 @@ dependencies = [ [[package]] name = "uu_link" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2588,7 +2540,7 @@ dependencies = [ [[package]] name = "uu_ln" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2598,7 +2550,7 @@ dependencies = [ [[package]] name = "uu_logname" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2608,12 +2560,12 @@ dependencies = [ [[package]] name = "uu_ls" -version = "0.0.8" +version = "0.0.12" dependencies = [ "atty", "chrono", "clap", - "globset", + "glob", "lazy_static", "lscolors", "number_prefix", @@ -2628,7 +2580,7 @@ dependencies = [ [[package]] name = "uu_mkdir" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2638,7 +2590,7 @@ dependencies = [ [[package]] name = "uu_mkfifo" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2648,7 +2600,7 @@ dependencies = [ [[package]] name = "uu_mknod" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2658,7 +2610,7 @@ dependencies = [ [[package]] name = "uu_mktemp" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "rand 0.5.6", @@ -2669,12 +2621,12 @@ dependencies = [ [[package]] name = "uu_more" -version = "0.0.8" +version = "0.0.12" dependencies = [ "atty", "clap", "crossterm", - "nix 0.19.1", + "nix 0.23.1", "redox_syscall", "redox_termios", "unicode-segmentation", @@ -2685,7 +2637,7 @@ dependencies = [ [[package]] name = "uu_mv" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "fs_extra", @@ -2695,23 +2647,23 @@ dependencies = [ [[package]] name = "uu_nice" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", - "nix 0.20.0", + "nix 0.23.1", "uucore", "uucore_procs", ] [[package]] name = "uu_nl" -version = "0.0.8" +version = "0.0.12" dependencies = [ "aho-corasick", "clap", "libc", - "memchr 2.4.0", + "memchr 2.4.1", "regex", "regex-syntax", "uucore", @@ -2720,7 +2672,7 @@ dependencies = [ [[package]] name = "uu_nohup" -version = "0.0.8" +version = "0.0.12" dependencies = [ "atty", "clap", @@ -2731,7 +2683,7 @@ dependencies = [ [[package]] name = "uu_nproc" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2742,7 +2694,7 @@ dependencies = [ [[package]] name = "uu_numfmt" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2751,7 +2703,7 @@ dependencies = [ [[package]] name = "uu_od" -version = "0.0.8" +version = "0.0.12" dependencies = [ "byteorder", "clap", @@ -2763,7 +2715,7 @@ dependencies = [ [[package]] name = "uu_paste" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2772,7 +2724,7 @@ dependencies = [ [[package]] name = "uu_pathchk" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2782,7 +2734,7 @@ dependencies = [ [[package]] name = "uu_pinky" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2791,12 +2743,12 @@ dependencies = [ [[package]] name = "uu_pr" -version = "0.0.8" +version = "0.0.12" dependencies = [ "chrono", "clap", "getopts", - "itertools 0.10.1", + "itertools 0.10.3", "quick-error 2.0.1", "regex", "uucore", @@ -2805,7 +2757,7 @@ dependencies = [ [[package]] name = "uu_printenv" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2814,7 +2766,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "itertools 0.8.2", @@ -2824,12 +2776,12 @@ dependencies = [ [[package]] name = "uu_ptx" -version = "0.0.8" +version = "0.0.12" dependencies = [ "aho-corasick", "clap", "libc", - "memchr 2.4.0", + "memchr 2.4.1", "regex", "regex-syntax", "uucore", @@ -2838,7 +2790,7 @@ dependencies = [ [[package]] name = "uu_pwd" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2847,7 +2799,7 @@ dependencies = [ [[package]] name = "uu_readlink" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2857,7 +2809,7 @@ dependencies = [ [[package]] name = "uu_realpath" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2866,7 +2818,7 @@ dependencies = [ [[package]] name = "uu_relpath" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2875,7 +2827,7 @@ dependencies = [ [[package]] name = "uu_rm" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "remove_dir_all", @@ -2887,7 +2839,7 @@ dependencies = [ [[package]] name = "uu_rmdir" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2897,7 +2849,7 @@ dependencies = [ [[package]] name = "uu_runcon" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "fts-sys", @@ -2910,8 +2862,9 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.8" +version = "0.0.12" dependencies = [ + "bigdecimal", "clap", "num-bigint", "num-traits", @@ -2921,7 +2874,7 @@ dependencies = [ [[package]] name = "uu_shred" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -2932,7 +2885,7 @@ dependencies = [ [[package]] name = "uu_shuf" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "rand 0.5.6", @@ -2942,7 +2895,7 @@ dependencies = [ [[package]] name = "uu_sleep" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2951,15 +2904,15 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.8" +version = "0.0.12" dependencies = [ "binary-heap-plus", "clap", "compare", "ctrlc", "fnv", - "itertools 0.10.1", - "memchr 2.4.0", + "itertools 0.10.3", + "memchr 2.4.1", "ouroboros", "rand 0.7.3", "rayon", @@ -2971,7 +2924,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2980,7 +2933,7 @@ dependencies = [ [[package]] name = "uu_stat" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -2989,7 +2942,7 @@ dependencies = [ [[package]] name = "uu_stdbuf" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "tempfile", @@ -3000,7 +2953,7 @@ dependencies = [ [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.8" +version = "0.0.12" dependencies = [ "cpp", "cpp_build", @@ -3011,7 +2964,7 @@ dependencies = [ [[package]] name = "uu_sum" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -3020,7 +2973,7 @@ dependencies = [ [[package]] name = "uu_sync" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -3031,10 +2984,10 @@ dependencies = [ [[package]] name = "uu_tac" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", - "memchr 2.4.0", + "memchr 2.4.1", "memmap2", "regex", "uucore", @@ -3043,11 +2996,11 @@ dependencies = [ [[package]] name = "uu_tail" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", - "nix 0.20.0", + "nix 0.23.1", "redox_syscall", "uucore", "uucore_procs", @@ -3056,7 +3009,7 @@ dependencies = [ [[package]] name = "uu_tee" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -3067,7 +3020,7 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -3078,18 +3031,18 @@ dependencies = [ [[package]] name = "uu_timeout" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", - "nix 0.20.0", + "nix 0.23.1", "uucore", "uucore_procs", ] [[package]] name = "uu_touch" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "filetime", @@ -3100,7 +3053,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.8" +version = "0.0.12" dependencies = [ "bit-set", "clap", @@ -3111,7 +3064,7 @@ dependencies = [ [[package]] name = "uu_true" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -3120,7 +3073,7 @@ dependencies = [ [[package]] name = "uu_truncate" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -3129,7 +3082,7 @@ dependencies = [ [[package]] name = "uu_tsort" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -3138,7 +3091,7 @@ dependencies = [ [[package]] name = "uu_tty" -version = "0.0.8" +version = "0.0.12" dependencies = [ "atty", "clap", @@ -3149,7 +3102,7 @@ dependencies = [ [[package]] name = "uu_uname" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "platform-info", @@ -3159,7 +3112,7 @@ dependencies = [ [[package]] name = "uu_unexpand" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "unicode-width", @@ -3169,7 +3122,7 @@ dependencies = [ [[package]] name = "uu_uniq" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "strum", @@ -3180,7 +3133,7 @@ dependencies = [ [[package]] name = "uu_unlink" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -3189,7 +3142,7 @@ dependencies = [ [[package]] name = "uu_uptime" -version = "0.0.8" +version = "0.0.12" dependencies = [ "chrono", "clap", @@ -3199,7 +3152,7 @@ dependencies = [ [[package]] name = "uu_users" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -3208,12 +3161,12 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.8" +version = "0.0.12" dependencies = [ "bytecount", "clap", "libc", - "nix 0.20.0", + "nix 0.23.1", "unicode-width", "utf-8", "uucore", @@ -3222,7 +3175,7 @@ dependencies = [ [[package]] name = "uu_who" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "uucore", @@ -3231,7 +3184,7 @@ dependencies = [ [[package]] name = "uu_whoami" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", "libc", @@ -3242,43 +3195,44 @@ dependencies = [ [[package]] name = "uu_yes" -version = "0.0.8" +version = "0.0.12" dependencies = [ "clap", - "nix 0.20.0", + "nix 0.23.1", "uucore", "uucore_procs", ] [[package]] name = "uucore" -version = "0.0.10" +version = "0.0.12" dependencies = [ "clap", "data-encoding", "data-encoding-macro", "dns-lookup", "dunce", - "getopts", "lazy_static", "libc", - "nix 0.20.0", + "nix 0.23.1", "once_cell", + "os_display", "termion", "thiserror", "time", "walkdir", "wild", "winapi 0.3.9", + "winapi-util", "z85", ] [[package]] name = "uucore_procs" -version = "0.0.7" +version = "0.0.12" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", ] @@ -3296,9 +3250,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" @@ -3325,10 +3279,12 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "which" -version = "3.1.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" dependencies = [ + "either", + "lazy_static", "libc", ] @@ -3384,12 +3340,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - [[package]] name = "xattr" version = "0.2.2" @@ -3401,6 +3351,6 @@ dependencies = [ [[package]] name = "z85" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b56e4f9906a4ef5412875e9ce448364023335cec645fd457ecf51d4f2781" +checksum = "af896e93db81340b74b65f74276a99b210c086f3d34ed0abf433182a462af856" diff --git a/Cargo.toml b/Cargo.toml index 9ecea79c296..e6c31e6f607 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "coreutils" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" @@ -247,110 +247,110 @@ test = [ "uu_test" ] clap = { version = "2.33", features = ["wrap_help"] } lazy_static = { version="1.3" } textwrap = { version="0.14", features=["terminal_size"] } -uucore = { version=">=0.0.10", package="uucore", path="src/uucore" } +uucore = { version=">=0.0.11", package="uucore", path="src/uucore" } selinux = { version="0.2.3", optional = true } # * uutils -uu_test = { optional=true, version="0.0.8", package="uu_test", path="src/uu/test" } +uu_test = { optional=true, version="0.0.12", package="uu_test", path="src/uu/test" } # -arch = { optional=true, version="0.0.8", package="uu_arch", path="src/uu/arch" } -base32 = { optional=true, version="0.0.8", package="uu_base32", path="src/uu/base32" } -base64 = { optional=true, version="0.0.8", package="uu_base64", path="src/uu/base64" } -basename = { optional=true, version="0.0.8", package="uu_basename", path="src/uu/basename" } -basenc = { optional=true, version="0.0.8", package="uu_basenc", path="src/uu/basenc" } -cat = { optional=true, version="0.0.8", package="uu_cat", path="src/uu/cat" } -chcon = { optional=true, version="0.0.8", package="uu_chcon", path="src/uu/chcon" } -chgrp = { optional=true, version="0.0.8", package="uu_chgrp", path="src/uu/chgrp" } -chmod = { optional=true, version="0.0.8", package="uu_chmod", path="src/uu/chmod" } -chown = { optional=true, version="0.0.8", package="uu_chown", path="src/uu/chown" } -chroot = { optional=true, version="0.0.8", package="uu_chroot", path="src/uu/chroot" } -cksum = { optional=true, version="0.0.8", package="uu_cksum", path="src/uu/cksum" } -comm = { optional=true, version="0.0.8", package="uu_comm", path="src/uu/comm" } -cp = { optional=true, version="0.0.8", package="uu_cp", path="src/uu/cp" } -csplit = { optional=true, version="0.0.8", package="uu_csplit", path="src/uu/csplit" } -cut = { optional=true, version="0.0.8", package="uu_cut", path="src/uu/cut" } -date = { optional=true, version="0.0.8", package="uu_date", path="src/uu/date" } -dd = { optional=true, version="0.0.8", package="uu_dd", path="src/uu/dd" } -df = { optional=true, version="0.0.8", package="uu_df", path="src/uu/df" } -dircolors= { optional=true, version="0.0.8", package="uu_dircolors", path="src/uu/dircolors" } -dirname = { optional=true, version="0.0.8", package="uu_dirname", path="src/uu/dirname" } -du = { optional=true, version="0.0.8", package="uu_du", path="src/uu/du" } -echo = { optional=true, version="0.0.8", package="uu_echo", path="src/uu/echo" } -env = { optional=true, version="0.0.8", package="uu_env", path="src/uu/env" } -expand = { optional=true, version="0.0.8", package="uu_expand", path="src/uu/expand" } -expr = { optional=true, version="0.0.8", package="uu_expr", path="src/uu/expr" } -factor = { optional=true, version="0.0.8", package="uu_factor", path="src/uu/factor" } -false = { optional=true, version="0.0.8", package="uu_false", path="src/uu/false" } -fmt = { optional=true, version="0.0.8", package="uu_fmt", path="src/uu/fmt" } -fold = { optional=true, version="0.0.8", package="uu_fold", path="src/uu/fold" } -groups = { optional=true, version="0.0.8", package="uu_groups", path="src/uu/groups" } -hashsum = { optional=true, version="0.0.8", package="uu_hashsum", path="src/uu/hashsum" } -head = { optional=true, version="0.0.8", package="uu_head", path="src/uu/head" } -hostid = { optional=true, version="0.0.8", package="uu_hostid", path="src/uu/hostid" } -hostname = { optional=true, version="0.0.8", package="uu_hostname", path="src/uu/hostname" } -id = { optional=true, version="0.0.8", package="uu_id", path="src/uu/id" } -install = { optional=true, version="0.0.8", package="uu_install", path="src/uu/install" } -join = { optional=true, version="0.0.8", package="uu_join", path="src/uu/join" } -kill = { optional=true, version="0.0.8", package="uu_kill", path="src/uu/kill" } -link = { optional=true, version="0.0.8", package="uu_link", path="src/uu/link" } -ln = { optional=true, version="0.0.8", package="uu_ln", path="src/uu/ln" } -ls = { optional=true, version="0.0.8", package="uu_ls", path="src/uu/ls" } -logname = { optional=true, version="0.0.8", package="uu_logname", path="src/uu/logname" } -mkdir = { optional=true, version="0.0.8", package="uu_mkdir", path="src/uu/mkdir" } -mkfifo = { optional=true, version="0.0.8", package="uu_mkfifo", path="src/uu/mkfifo" } -mknod = { optional=true, version="0.0.8", package="uu_mknod", path="src/uu/mknod" } -mktemp = { optional=true, version="0.0.8", package="uu_mktemp", path="src/uu/mktemp" } -more = { optional=true, version="0.0.8", package="uu_more", path="src/uu/more" } -mv = { optional=true, version="0.0.8", package="uu_mv", path="src/uu/mv" } -nice = { optional=true, version="0.0.8", package="uu_nice", path="src/uu/nice" } -nl = { optional=true, version="0.0.8", package="uu_nl", path="src/uu/nl" } -nohup = { optional=true, version="0.0.8", package="uu_nohup", path="src/uu/nohup" } -nproc = { optional=true, version="0.0.8", package="uu_nproc", path="src/uu/nproc" } -numfmt = { optional=true, version="0.0.8", package="uu_numfmt", path="src/uu/numfmt" } -od = { optional=true, version="0.0.8", package="uu_od", path="src/uu/od" } -paste = { optional=true, version="0.0.8", package="uu_paste", path="src/uu/paste" } -pathchk = { optional=true, version="0.0.8", package="uu_pathchk", path="src/uu/pathchk" } -pinky = { optional=true, version="0.0.8", package="uu_pinky", path="src/uu/pinky" } -pr = { optional=true, version="0.0.8", package="uu_pr", path="src/uu/pr" } -printenv = { optional=true, version="0.0.8", package="uu_printenv", path="src/uu/printenv" } -printf = { optional=true, version="0.0.8", package="uu_printf", path="src/uu/printf" } -ptx = { optional=true, version="0.0.8", package="uu_ptx", path="src/uu/ptx" } -pwd = { optional=true, version="0.0.8", package="uu_pwd", path="src/uu/pwd" } -readlink = { optional=true, version="0.0.8", package="uu_readlink", path="src/uu/readlink" } -realpath = { optional=true, version="0.0.8", package="uu_realpath", path="src/uu/realpath" } -relpath = { optional=true, version="0.0.8", package="uu_relpath", path="src/uu/relpath" } -rm = { optional=true, version="0.0.8", package="uu_rm", path="src/uu/rm" } -rmdir = { optional=true, version="0.0.8", package="uu_rmdir", path="src/uu/rmdir" } -runcon = { optional=true, version="0.0.8", package="uu_runcon", path="src/uu/runcon" } -seq = { optional=true, version="0.0.8", package="uu_seq", path="src/uu/seq" } -shred = { optional=true, version="0.0.8", package="uu_shred", path="src/uu/shred" } -shuf = { optional=true, version="0.0.8", package="uu_shuf", path="src/uu/shuf" } -sleep = { optional=true, version="0.0.8", package="uu_sleep", path="src/uu/sleep" } -sort = { optional=true, version="0.0.8", package="uu_sort", path="src/uu/sort" } -split = { optional=true, version="0.0.8", package="uu_split", path="src/uu/split" } -stat = { optional=true, version="0.0.8", package="uu_stat", path="src/uu/stat" } -stdbuf = { optional=true, version="0.0.8", package="uu_stdbuf", path="src/uu/stdbuf" } -sum = { optional=true, version="0.0.8", package="uu_sum", path="src/uu/sum" } -sync = { optional=true, version="0.0.8", package="uu_sync", path="src/uu/sync" } -tac = { optional=true, version="0.0.8", package="uu_tac", path="src/uu/tac" } -tail = { optional=true, version="0.0.8", package="uu_tail", path="src/uu/tail" } -tee = { optional=true, version="0.0.8", package="uu_tee", path="src/uu/tee" } -timeout = { optional=true, version="0.0.8", package="uu_timeout", path="src/uu/timeout" } -touch = { optional=true, version="0.0.8", package="uu_touch", path="src/uu/touch" } -tr = { optional=true, version="0.0.8", package="uu_tr", path="src/uu/tr" } -true = { optional=true, version="0.0.8", package="uu_true", path="src/uu/true" } -truncate = { optional=true, version="0.0.8", package="uu_truncate", path="src/uu/truncate" } -tsort = { optional=true, version="0.0.8", package="uu_tsort", path="src/uu/tsort" } -tty = { optional=true, version="0.0.8", package="uu_tty", path="src/uu/tty" } -uname = { optional=true, version="0.0.8", package="uu_uname", path="src/uu/uname" } -unexpand = { optional=true, version="0.0.8", package="uu_unexpand", path="src/uu/unexpand" } -uniq = { optional=true, version="0.0.8", package="uu_uniq", path="src/uu/uniq" } -unlink = { optional=true, version="0.0.8", package="uu_unlink", path="src/uu/unlink" } -uptime = { optional=true, version="0.0.8", package="uu_uptime", path="src/uu/uptime" } -users = { optional=true, version="0.0.8", package="uu_users", path="src/uu/users" } -wc = { optional=true, version="0.0.8", package="uu_wc", path="src/uu/wc" } -who = { optional=true, version="0.0.8", package="uu_who", path="src/uu/who" } -whoami = { optional=true, version="0.0.8", package="uu_whoami", path="src/uu/whoami" } -yes = { optional=true, version="0.0.8", package="uu_yes", path="src/uu/yes" } +arch = { optional=true, version="0.0.12", package="uu_arch", path="src/uu/arch" } +base32 = { optional=true, version="0.0.12", package="uu_base32", path="src/uu/base32" } +base64 = { optional=true, version="0.0.12", package="uu_base64", path="src/uu/base64" } +basename = { optional=true, version="0.0.12", package="uu_basename", path="src/uu/basename" } +basenc = { optional=true, version="0.0.12", package="uu_basenc", path="src/uu/basenc" } +cat = { optional=true, version="0.0.12", package="uu_cat", path="src/uu/cat" } +chcon = { optional=true, version="0.0.12", package="uu_chcon", path="src/uu/chcon" } +chgrp = { optional=true, version="0.0.12", package="uu_chgrp", path="src/uu/chgrp" } +chmod = { optional=true, version="0.0.12", package="uu_chmod", path="src/uu/chmod" } +chown = { optional=true, version="0.0.12", package="uu_chown", path="src/uu/chown" } +chroot = { optional=true, version="0.0.12", package="uu_chroot", path="src/uu/chroot" } +cksum = { optional=true, version="0.0.12", package="uu_cksum", path="src/uu/cksum" } +comm = { optional=true, version="0.0.12", package="uu_comm", path="src/uu/comm" } +cp = { optional=true, version="0.0.12", package="uu_cp", path="src/uu/cp" } +csplit = { optional=true, version="0.0.12", package="uu_csplit", path="src/uu/csplit" } +cut = { optional=true, version="0.0.12", package="uu_cut", path="src/uu/cut" } +date = { optional=true, version="0.0.12", package="uu_date", path="src/uu/date" } +dd = { optional=true, version="0.0.12", package="uu_dd", path="src/uu/dd" } +df = { optional=true, version="0.0.12", package="uu_df", path="src/uu/df" } +dircolors= { optional=true, version="0.0.12", package="uu_dircolors", path="src/uu/dircolors" } +dirname = { optional=true, version="0.0.12", package="uu_dirname", path="src/uu/dirname" } +du = { optional=true, version="0.0.12", package="uu_du", path="src/uu/du" } +echo = { optional=true, version="0.0.12", package="uu_echo", path="src/uu/echo" } +env = { optional=true, version="0.0.12", package="uu_env", path="src/uu/env" } +expand = { optional=true, version="0.0.12", package="uu_expand", path="src/uu/expand" } +expr = { optional=true, version="0.0.12", package="uu_expr", path="src/uu/expr" } +factor = { optional=true, version="0.0.12", package="uu_factor", path="src/uu/factor" } +false = { optional=true, version="0.0.12", package="uu_false", path="src/uu/false" } +fmt = { optional=true, version="0.0.12", package="uu_fmt", path="src/uu/fmt" } +fold = { optional=true, version="0.0.12", package="uu_fold", path="src/uu/fold" } +groups = { optional=true, version="0.0.12", package="uu_groups", path="src/uu/groups" } +hashsum = { optional=true, version="0.0.12", package="uu_hashsum", path="src/uu/hashsum" } +head = { optional=true, version="0.0.12", package="uu_head", path="src/uu/head" } +hostid = { optional=true, version="0.0.12", package="uu_hostid", path="src/uu/hostid" } +hostname = { optional=true, version="0.0.12", package="uu_hostname", path="src/uu/hostname" } +id = { optional=true, version="0.0.12", package="uu_id", path="src/uu/id" } +install = { optional=true, version="0.0.12", package="uu_install", path="src/uu/install" } +join = { optional=true, version="0.0.12", package="uu_join", path="src/uu/join" } +kill = { optional=true, version="0.0.12", package="uu_kill", path="src/uu/kill" } +link = { optional=true, version="0.0.12", package="uu_link", path="src/uu/link" } +ln = { optional=true, version="0.0.12", package="uu_ln", path="src/uu/ln" } +ls = { optional=true, version="0.0.12", package="uu_ls", path="src/uu/ls" } +logname = { optional=true, version="0.0.12", package="uu_logname", path="src/uu/logname" } +mkdir = { optional=true, version="0.0.12", package="uu_mkdir", path="src/uu/mkdir" } +mkfifo = { optional=true, version="0.0.12", package="uu_mkfifo", path="src/uu/mkfifo" } +mknod = { optional=true, version="0.0.12", package="uu_mknod", path="src/uu/mknod" } +mktemp = { optional=true, version="0.0.12", package="uu_mktemp", path="src/uu/mktemp" } +more = { optional=true, version="0.0.12", package="uu_more", path="src/uu/more" } +mv = { optional=true, version="0.0.12", package="uu_mv", path="src/uu/mv" } +nice = { optional=true, version="0.0.12", package="uu_nice", path="src/uu/nice" } +nl = { optional=true, version="0.0.12", package="uu_nl", path="src/uu/nl" } +nohup = { optional=true, version="0.0.12", package="uu_nohup", path="src/uu/nohup" } +nproc = { optional=true, version="0.0.12", package="uu_nproc", path="src/uu/nproc" } +numfmt = { optional=true, version="0.0.12", package="uu_numfmt", path="src/uu/numfmt" } +od = { optional=true, version="0.0.12", package="uu_od", path="src/uu/od" } +paste = { optional=true, version="0.0.12", package="uu_paste", path="src/uu/paste" } +pathchk = { optional=true, version="0.0.12", package="uu_pathchk", path="src/uu/pathchk" } +pinky = { optional=true, version="0.0.12", package="uu_pinky", path="src/uu/pinky" } +pr = { optional=true, version="0.0.12", package="uu_pr", path="src/uu/pr" } +printenv = { optional=true, version="0.0.12", package="uu_printenv", path="src/uu/printenv" } +printf = { optional=true, version="0.0.12", package="uu_printf", path="src/uu/printf" } +ptx = { optional=true, version="0.0.12", package="uu_ptx", path="src/uu/ptx" } +pwd = { optional=true, version="0.0.12", package="uu_pwd", path="src/uu/pwd" } +readlink = { optional=true, version="0.0.12", package="uu_readlink", path="src/uu/readlink" } +realpath = { optional=true, version="0.0.12", package="uu_realpath", path="src/uu/realpath" } +relpath = { optional=true, version="0.0.12", package="uu_relpath", path="src/uu/relpath" } +rm = { optional=true, version="0.0.12", package="uu_rm", path="src/uu/rm" } +rmdir = { optional=true, version="0.0.12", package="uu_rmdir", path="src/uu/rmdir" } +runcon = { optional=true, version="0.0.12", package="uu_runcon", path="src/uu/runcon" } +seq = { optional=true, version="0.0.12", package="uu_seq", path="src/uu/seq" } +shred = { optional=true, version="0.0.12", package="uu_shred", path="src/uu/shred" } +shuf = { optional=true, version="0.0.12", package="uu_shuf", path="src/uu/shuf" } +sleep = { optional=true, version="0.0.12", package="uu_sleep", path="src/uu/sleep" } +sort = { optional=true, version="0.0.12", package="uu_sort", path="src/uu/sort" } +split = { optional=true, version="0.0.12", package="uu_split", path="src/uu/split" } +stat = { optional=true, version="0.0.12", package="uu_stat", path="src/uu/stat" } +stdbuf = { optional=true, version="0.0.12", package="uu_stdbuf", path="src/uu/stdbuf" } +sum = { optional=true, version="0.0.12", package="uu_sum", path="src/uu/sum" } +sync = { optional=true, version="0.0.12", package="uu_sync", path="src/uu/sync" } +tac = { optional=true, version="0.0.12", package="uu_tac", path="src/uu/tac" } +tail = { optional=true, version="0.0.12", package="uu_tail", path="src/uu/tail" } +tee = { optional=true, version="0.0.12", package="uu_tee", path="src/uu/tee" } +timeout = { optional=true, version="0.0.12", package="uu_timeout", path="src/uu/timeout" } +touch = { optional=true, version="0.0.12", package="uu_touch", path="src/uu/touch" } +tr = { optional=true, version="0.0.12", package="uu_tr", path="src/uu/tr" } +true = { optional=true, version="0.0.12", package="uu_true", path="src/uu/true" } +truncate = { optional=true, version="0.0.12", package="uu_truncate", path="src/uu/truncate" } +tsort = { optional=true, version="0.0.12", package="uu_tsort", path="src/uu/tsort" } +tty = { optional=true, version="0.0.12", package="uu_tty", path="src/uu/tty" } +uname = { optional=true, version="0.0.12", package="uu_uname", path="src/uu/uname" } +unexpand = { optional=true, version="0.0.12", package="uu_unexpand", path="src/uu/unexpand" } +uniq = { optional=true, version="0.0.12", package="uu_uniq", path="src/uu/uniq" } +unlink = { optional=true, version="0.0.12", package="uu_unlink", path="src/uu/unlink" } +uptime = { optional=true, version="0.0.12", package="uu_uptime", path="src/uu/uptime" } +users = { optional=true, version="0.0.12", package="uu_users", path="src/uu/users" } +wc = { optional=true, version="0.0.12", package="uu_wc", path="src/uu/wc" } +who = { optional=true, version="0.0.12", package="uu_who", path="src/uu/who" } +whoami = { optional=true, version="0.0.12", package="uu_whoami", path="src/uu/whoami" } +yes = { optional=true, version="0.0.12", 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" } @@ -361,19 +361,19 @@ yes = { optional=true, version="0.0.8", 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 = "0.4.11" +chrono = "^0.4.11" conv = "0.3" filetime = "0.2" glob = "0.3.0" libc = "0.2" -pretty_assertions = "0.7.2" +pretty_assertions = "1" rand = "0.7" regex = "1.0" sha1 = { version="0.6", features=["std"] } tempfile = "3.2.0" time = "0.1" unindent = "0.1" -uucore = { version=">=0.0.10", package="uucore", path="src/uucore", features=["entries", "process"] } +uucore = { version=">=0.0.11", package="uucore", path="src/uucore", features=["entries", "process"] } walkdir = "2.2" atty = "0.2" @@ -381,12 +381,10 @@ atty = "0.2" rlimit = "0.4.0" [target.'cfg(unix)'.dev-dependencies] -nix = "0.20.0" - +nix = "0.23.1" rust-users = { version="0.10", package="users" } unix_socket = "0.5.0" - [[bin]] name = "coreutils" path = "src/bin/coreutils.rs" diff --git a/GNUmakefile b/GNUmakefile index 367568ca834..12cd950139b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -47,12 +47,12 @@ BUSYBOX_VER := 1.32.1 BUSYBOX_SRC := $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER) ifeq ($(SELINUX_ENABLED),) - SELINUX_ENABLED := 0 - ifneq ($(OS),Windows_NT) - ifeq ($(shell /sbin/selinuxenabled 2>/dev/null ; echo $$?),0) - SELINUX_ENABLED := 1 - endif - endif + SELINUX_ENABLED := 0 + ifneq ($(OS),Windows_NT) + ifeq ($(shell /sbin/selinuxenabled 2>/dev/null ; echo $$?),0) + SELINUX_ENABLED := 1 + endif + endif endif # Possible programs @@ -161,11 +161,11 @@ SELINUX_PROGS := \ runcon ifneq ($(OS),Windows_NT) - PROGS := $(PROGS) $(UNIX_PROGS) + PROGS := $(PROGS) $(UNIX_PROGS) endif ifeq ($(SELINUX_ENABLED),1) - PROGS := $(PROGS) $(SELINUX_PROGS) + PROGS := $(PROGS) $(SELINUX_PROGS) endif UTILS ?= $(PROGS) diff --git a/README.md b/README.md index 7e420bb333f..306ca08a786 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ to compile anywhere, and this is as good a way as any to try and learn it. ### Rust Version uutils follows Rust's release channels and is tested against stable, beta and nightly. -The current oldest supported version of the Rust compiler is `1.47`. +The current oldest supported version of the Rust compiler is `1.54`. On both Windows and Redox, only the nightly version is tested currently. diff --git a/build.rs b/build.rs index 4fbb27cce9f..517070fd5fb 100644 --- a/build.rs +++ b/build.rs @@ -18,7 +18,7 @@ pub fn main() { let out_dir = env::var("OUT_DIR").unwrap(); // println!("cargo:warning=out_dir={}", out_dir); - let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap().replace("\\", "/"); + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap().replace('\\', "/"); // println!("cargo:warning=manifest_dir={}", manifest_dir); let util_tests_dir = format!("{}/tests/by-util", manifest_dir); // println!("cargo:warning=util_tests_dir={}", util_tests_dir); @@ -46,6 +46,8 @@ pub fn main() { "type UtilityMap = HashMap<&'static str, (fn(T) -> i32, fn() -> App<'static, 'static>)>;\n\ \n\ fn util_map() -> UtilityMap {\n\ + \t#[allow(unused_mut)]\n\ + \t#[allow(clippy::let_and_return)]\n\ \tlet mut map = UtilityMap::new();\n\ " .as_bytes(), @@ -81,7 +83,7 @@ pub fn main() { mf.write_all( format!( "\tmap.insert(\"{k}\", ({krate}::uumain, {krate}::uu_app));\n", - k = krate[override_prefix.len()..].to_string(), + k = &krate[override_prefix.len()..], krate = krate ) .as_bytes(), @@ -90,7 +92,7 @@ pub fn main() { tf.write_all( format!( "#[path=\"{dir}/test_{k}.rs\"]\nmod test_{k};\n", - k = krate[override_prefix.len()..].to_string(), + k = &krate[override_prefix.len()..], dir = util_tests_dir, ) .as_bytes(), diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index c7fc4f9f959..0961e8d97ad 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_arch" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "arch ~ (uutils) display machine architecture" @@ -15,11 +15,14 @@ edition = "2018" path = "src/arch.rs" [dependencies] -platform-info = "0.1" +platform-info = "0.2" clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "arch" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index d5fc40024b3..65e54e4742b 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base32" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" @@ -16,13 +16,12 @@ path = "src/base32.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features = ["encoding"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features = ["encoding"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "base32" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index c8d71b85a46..e51c75c36b6 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base64" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" @@ -16,8 +16,8 @@ path = "src/base64.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features = ["encoding"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features = ["encoding"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } uu_base32 = { version=">=0.0.8", package="uu_base32", path="../base32"} [[bin]] @@ -25,5 +25,4 @@ name = "base64" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index b7510579088..cb625e3467c 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basename" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" @@ -16,13 +16,12 @@ path = "src/basename.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "basename" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 464c0e4f152..9f3ce3cc4e0 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -7,11 +7,10 @@ // spell-checker:ignore (ToDO) fullname -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::path::{is_separator, PathBuf}; +use uucore::display::Quotable; +use uucore::error::{UResult, UUsageError}; use uucore::InvalidEncodingHandling; static SUMMARY: &str = "Print NAME with any leading directory components removed @@ -32,7 +31,8 @@ pub mod options { pub static ZERO: &str = "zero"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -44,12 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // too few arguments if !matches.is_present(options::NAME) { - crash!( - 1, - "{1}\nTry '{0} --help' for more information.", - uucore::execution_phrase(), - "missing operand" - ); + return Err(UUsageError::new(1, "missing operand".to_string())); } let opt_suffix = matches.is_present(options::SUFFIX); @@ -58,12 +53,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let multiple_paths = opt_suffix || opt_multiple; // too many arguments if !multiple_paths && matches.occurrences_of(options::NAME) > 2 { - crash!( + return Err(UUsageError::new( 1, - "extra operand '{1}'\nTry '{0} --help' for more information.", - uucore::execution_phrase(), - matches.values_of(options::NAME).unwrap().nth(2).unwrap() - ); + format!( + "extra operand {}", + matches + .values_of(options::NAME) + .unwrap() + .nth(2) + .unwrap() + .quote() + ), + )); } let suffix = if opt_suffix { @@ -89,7 +90,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { print!("{}{}", basename(path, suffix), line_ending); } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -131,21 +132,15 @@ fn basename(fullname: &str, suffix: &str) -> String { // Convert to path buffer and get last path component let pb = PathBuf::from(path); match pb.components().last() { - Some(c) => strip_suffix(c.as_os_str().to_str().unwrap(), suffix), - None => "".to_owned(), - } -} + Some(c) => { + let name = c.as_os_str().to_str().unwrap(); + if name == suffix { + name.to_string() + } else { + name.strip_suffix(suffix).unwrap_or(name).to_string() + } + } -// can be replaced with strip_suffix once MSRV is 1.45 -#[allow(clippy::manual_strip)] -fn strip_suffix(name: &str, suffix: &str) -> String { - if name == suffix { - return name.to_owned(); - } - - if name.ends_with(suffix) { - return name[..name.len() - suffix.len()].to_owned(); + None => "".to_owned(), } - - name.to_owned() } diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index 165d3d3a096..384b5ed75f1 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basenc" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "basenc ~ (uutils) decode/encode input" @@ -16,8 +16,8 @@ path = "src/basenc.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features = ["encoding"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features = ["encoding"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } uu_base32 = { version=">=0.0.8", package="uu_base32", path="../base32"} [[bin]] @@ -25,5 +25,4 @@ name = "basenc" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index b6b0165efb2..81e2fe63d56 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cat" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "cat ~ (uutils) concatenate and display input" @@ -18,12 +18,12 @@ path = "src/cat.rs" clap = { version = "2.33", features = ["wrap_help"] } thiserror = "1.0" atty = "0.2" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs", "pipes"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "pipes"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(unix)'.dependencies] unix_socket = "0.5.0" -nix = "0.20.0" +nix = "0.23.1" [target.'cfg(windows)'.dependencies] winapi-util = "0.1.5" @@ -31,3 +31,6 @@ winapi-util = "0.1.5" [[bin]] name = "cat" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index af84890db32..f647906ba82 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -20,6 +20,7 @@ use std::io::{self, Read, Write}; use thiserror::Error; use uucore::display::Quotable; use uucore::error::UResult; +use uucore::fs::FileInformation; #[cfg(unix)] use std::os::unix::io::AsRawFd; @@ -317,8 +318,7 @@ fn cat_path( path: &str, options: &OutputOptions, state: &mut OutputState, - #[cfg(unix)] out_info: &nix::sys::stat::FileStat, - #[cfg(windows)] out_info: &winapi_util::file::Information, + out_info: Option<&FileInformation>, ) -> CatResult<()> { if path == "-" { let stdin = io::stdin(); @@ -342,10 +342,15 @@ fn cat_path( } _ => { let file = File::open(path)?; - #[cfg(any(windows, unix))] - if same_file(out_info, &file) { - return Err(CatError::OutputIsInput); + + if let Some(out_info) = out_info { + if out_info.file_size() != 0 + && FileInformation::from_file(&file).as_ref() == Some(out_info) + { + return Err(CatError::OutputIsInput); + } } + let mut handle = InputHandle { reader: file, is_interactive: false, @@ -355,25 +360,8 @@ fn cat_path( } } -#[cfg(unix)] -fn same_file(a_info: &nix::sys::stat::FileStat, b: &File) -> bool { - let b_info = nix::sys::stat::fstat(b.as_raw_fd()).unwrap(); - b_info.st_size != 0 && b_info.st_dev == a_info.st_dev && b_info.st_ino == a_info.st_ino -} - -#[cfg(windows)] -fn same_file(a_info: &winapi_util::file::Information, b: &File) -> bool { - let b_info = winapi_util::file::information(b).unwrap(); - b_info.file_size() != 0 - && b_info.volume_serial_number() == a_info.volume_serial_number() - && b_info.file_index() == a_info.file_index() -} - fn cat_files(files: Vec, options: &OutputOptions) -> UResult<()> { - #[cfg(windows)] - let out_info = winapi_util::file::information(&std::io::stdout()).unwrap(); - #[cfg(unix)] - let out_info = nix::sys::stat::fstat(std::io::stdout().as_raw_fd()).unwrap(); + let out_info = FileInformation::from_file(&std::io::stdout()); let mut state = OutputState { line_number: 1, @@ -384,7 +372,7 @@ fn cat_files(files: Vec, options: &OutputOptions) -> UResult<()> { let mut error_messages: Vec = Vec::new(); for path in &files { - if let Err(err) = cat_path(path, options, &mut state, &out_info) { + if let Err(err) = cat_path(path, options, &mut state, out_info.as_ref()) { error_messages.push(format!("{}: {}", path.maybe_quote(), err)); } } diff --git a/src/uu/chcon/Cargo.toml b/src/uu/chcon/Cargo.toml index a359f142fe5..bb52d173853 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chcon" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "chcon ~ (uutils) change file security context" @@ -25,3 +25,6 @@ libc = { version = "0.2" } [[bin]] name = "chcon" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 9664f69f5cd..32fa23ef8fc 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -2,7 +2,8 @@ #![allow(clippy::upper_case_acronyms)] -use uucore::{display::Quotable, show_error, show_usage_error, show_warning}; +use uucore::error::{UResult, USimpleError, UUsageError}; +use uucore::{display::Quotable, show_error, show_warning}; use clap::{App, Arg}; use selinux::{OpaqueSecurityContext, SecurityContext}; @@ -60,7 +61,8 @@ fn get_usage() -> String { ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = get_usage(); let config = uu_app().usage(usage.as_ref()); @@ -72,14 +74,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match r.kind { clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => { println!("{}", r); - return libc::EXIT_SUCCESS; + return Ok(()); } _ => {} } } - show_usage_error!("{}.\n", r); - return libc::EXIT_FAILURE; + return Err(UUsageError::new(libc::EXIT_FAILURE, format!("{}.\n", r))); } }; @@ -98,8 +99,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match result { Err(r) => { - show_error!("{}.", report_full_error(&r)); - return libc::EXIT_FAILURE; + return Err(USimpleError::new( + libc::EXIT_FAILURE, + format!("{}.", report_full_error(&r)), + )); } Ok(file_context) => SELinuxSecurityContext::File(file_context), @@ -111,14 +114,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(context) => context, Err(_r) => { - show_error!("Invalid security context {}.", context.quote()); - return libc::EXIT_FAILURE; + return Err(USimpleError::new( + libc::EXIT_FAILURE, + format!("Invalid security context {}.", context.quote()), + )); } }; if SecurityContext::from_c_str(&c_context, false).check() == Some(false) { - show_error!("Invalid security context {}.", context.quote()); - return libc::EXIT_FAILURE; + return Err(USimpleError::new( + libc::EXIT_FAILURE, + format!("Invalid security context {}.", context.quote()), + )); } SELinuxSecurityContext::String(Some(c_context)) @@ -132,8 +139,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(r) => Some(r), Err(r) => { - show_error!("{}.", report_full_error(&r)); - return libc::EXIT_FAILURE; + return Err(USimpleError::new( + libc::EXIT_FAILURE, + format!("{}.", report_full_error(&r)), + )); } } } else { @@ -142,13 +151,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let results = process_files(&options, &context, root_dev_ino); if results.is_empty() { - return libc::EXIT_SUCCESS; + return Ok(()); } for result in &results { show_error!("{}.", report_full_error(result)); } - libc::EXIT_FAILURE + Err(libc::EXIT_FAILURE.into()) } pub fn uu_app() -> App<'static, 'static> { @@ -707,7 +716,7 @@ fn root_dev_ino_warn(dir_name: &Path) { // When a program like chgrp performs a recursive traversal that requires traversing symbolic links, // it is *not* a problem. // However, when invoked with "-P -R", it deserves a warning. -// The fts_options parameter records the options that control this aspect of fts's behavior, +// The fts_options parameter records the options that control this aspect of fts behavior, // so test that. fn cycle_warning_required(fts_options: c_int, entry: &fts::EntryRef) -> bool { // When dereferencing no symlinks, or when dereferencing only those listed on the command line diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index acc03472ca9..5d7a38d7040 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chgrp" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" @@ -16,9 +16,12 @@ path = "src/chgrp.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "chgrp" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index 64961e6dd10..7f23dbb14f3 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chmod" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "chmod ~ (uutils) change mode of FILE" @@ -17,10 +17,13 @@ path = "src/chmod.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs", "mode"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } walkdir = "2.2" [[bin]] name = "chmod" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 68c55b4cbe5..09ed3cda684 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -7,19 +7,17 @@ // spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::fs; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::Path; use uucore::display::Quotable; +use uucore::error::{ExitCode, UResult, USimpleError, UUsageError}; use uucore::fs::display_permissions_unix; use uucore::libc::mode_t; #[cfg(not(windows))] use uucore::mode; -use uucore::InvalidEncodingHandling; +use uucore::{show_error, InvalidEncodingHandling}; use walkdir::WalkDir; static ABOUT: &str = "Change the mode of each FILE to MODE. @@ -50,7 +48,8 @@ fn get_long_usage() -> String { String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.") } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -72,12 +71,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let verbose = matches.is_present(options::VERBOSE); let preserve_root = matches.is_present(options::PRESERVE_ROOT); let recursive = matches.is_present(options::RECURSIVE); - let fmode = matches - .value_of(options::REFERENCE) - .and_then(|fref| match fs::metadata(fref) { + let fmode = match matches.value_of(options::REFERENCE) { + Some(fref) => match fs::metadata(fref) { Ok(meta) => Some(meta.mode()), - Err(err) => crash!(1, "cannot stat attributes of {}: {}", fref.quote(), err), - }); + Err(err) => { + return Err(USimpleError::new( + 1, + format!("cannot stat attributes of {}: {}", fref.quote(), err), + )) + } + }, + None => None, + }; let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required let cmode = if mode_had_minus_prefix { // clap parsing is finished, now put prefix back @@ -100,7 +105,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; if files.is_empty() { - crash!(1, "missing operand"); + return Err(UUsageError::new(1, "missing operand".to_string())); } let chmoder = Chmoder { @@ -112,12 +117,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fmode, cmode, }; - match chmoder.chmod(files) { - Ok(()) => {} - Err(e) => return e, - } - 0 + chmoder.chmod(files) } pub fn uu_app() -> App<'static, 'static> { @@ -191,7 +192,7 @@ struct Chmoder { } impl Chmoder { - fn chmod(&self, files: Vec) -> Result<(), i32> { + fn chmod(&self, files: Vec) -> UResult<()> { let mut r = Ok(()); for filename in &files { @@ -204,22 +205,30 @@ impl Chmoder { filename.quote() ); if !self.quiet { - show_error!("cannot operate on dangling symlink {}", filename.quote()); + return Err(USimpleError::new( + 1, + format!("cannot operate on dangling symlink {}", filename.quote()), + )); } } else if !self.quiet { - show_error!( - "cannot access {}: No such file or directory", - filename.quote() - ); + return Err(USimpleError::new( + 1, + format!( + "cannot access {}: No such file or directory", + filename.quote() + ), + )); } - return Err(1); + return Err(ExitCode::new(1)); } if self.recursive && self.preserve_root && filename == "/" { - show_error!( - "it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe", - filename.quote() - ); - return Err(1); + return Err(USimpleError::new( + 1, + format!( + "it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe", + filename.quote() + ) + )); } if !self.recursive { r = self.chmod_file(file).and(r); @@ -234,14 +243,14 @@ impl Chmoder { } #[cfg(windows)] - fn chmod_file(&self, file: &Path) -> Result<(), i32> { + fn chmod_file(&self, file: &Path) -> UResult<()> { // chmod is useless on Windows // it doesn't set any permissions at all // instead it just sets the readonly attribute on the file - Err(0) + Ok(()) } #[cfg(unix)] - fn chmod_file(&self, file: &Path) -> Result<(), i32> { + fn chmod_file(&self, file: &Path) -> UResult<()> { use uucore::mode::get_umask; let fperm = match fs::metadata(file) { @@ -258,11 +267,13 @@ impl Chmoder { } else if err.kind() == std::io::ErrorKind::PermissionDenied { // These two filenames would normally be conditionally // quoted, but GNU's tests expect them to always be quoted - show_error!("{}: Permission denied", file.quote()); + return Err(USimpleError::new( + 1, + format!("{}: Permission denied", file.quote()), + )); } else { - show_error!("{}: {}", file.quote(), err); + return Err(USimpleError::new(1, format!("{}: {}", file.quote(), err))); } - return Err(1); } }; match self.fmode { @@ -296,22 +307,25 @@ impl Chmoder { } Err(f) => { if !self.quiet { - show_error!("{}", f); + return Err(USimpleError::new(1, f)); + } else { + return Err(ExitCode::new(1)); } - return Err(1); } } } self.change_file(fperm, new_mode, file)?; // if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail if (new_mode & !naively_expected_new_mode) != 0 { - show_error!( - "{}: new permissions are {}, not {}", - file.maybe_quote(), - display_permissions_unix(new_mode as mode_t, false), - display_permissions_unix(naively_expected_new_mode as mode_t, false) - ); - return Err(1); + return Err(USimpleError::new( + 1, + format!( + "{}: new permissions are {}, not {}", + file.maybe_quote(), + display_permissions_unix(new_mode as mode_t, false), + display_permissions_unix(naively_expected_new_mode as mode_t, false) + ), + )); } } } diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index 20381c66068..0f6a3fedf20 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chown" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" @@ -16,9 +16,12 @@ path = "src/chown.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "chown" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index f24c4ec8940..7b0c94810ee 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -183,7 +183,7 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { let uid = if !user.is_empty() { Some(match Passwd::locate(user) { - Ok(u) => u.uid(), // We have been able to get the uid + Ok(u) => u.uid, // We have been able to get the uid Err(_) => // we have NOT been able to find the uid // but we could be in the case where we have user.group @@ -208,7 +208,7 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { Some( Group::locate(group) .map_err(|_| USimpleError::new(1, format!("invalid group: {}", spec.quote())))? - .gid(), + .gid, ) } else { None diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index 362e43b591f..ff7f7ab3188 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chroot" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" @@ -16,9 +16,12 @@ path = "src/chroot.rs" [dependencies] clap= "2.33" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "chroot" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 55097c1bb8a..66105b62052 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -7,15 +7,15 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) NEWROOT Userspec pstatus +mod error; -#[macro_use] -extern crate uucore; +use crate::error::ChrootError; use clap::{crate_version, App, Arg}; use std::ffi::CString; use std::io::Error; use std::path::Path; use std::process::Command; -use uucore::display::Quotable; +use uucore::error::{set_exit_code, UResult}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; use uucore::{entries, InvalidEncodingHandling}; @@ -31,7 +31,8 @@ mod options { pub const COMMAND: &str = "command"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -44,19 +45,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let newroot: &Path = match matches.value_of(options::NEWROOT) { Some(v) => Path::new(v), - None => crash!( - 1, - "Missing operand: NEWROOT\nTry '{} --help' for more information.", - uucore::execution_phrase() - ), + None => return Err(ChrootError::MissingNewRoot.into()), }; if !newroot.is_dir() { - crash!( - 1, - "cannot change root directory to {}: no such directory", - newroot.quote() - ); + return Err(ChrootError::NoSuchDirectory(format!("{}", newroot.display())).into()); } let commands = match matches.values_of(options::COMMAND) { @@ -82,29 +75,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let chroot_args = &command[1..]; // NOTE: Tests can only trigger code beyond this point if they're invoked with root permissions - set_context(newroot, &matches); + set_context(newroot, &matches)?; - let pstatus = Command::new(chroot_command) - .args(chroot_args) - .status() - .unwrap_or_else(|e| { - // TODO: Exit status: - // 125 if chroot itself fails - // 126 if command is found but cannot be invoked - // 127 if command cannot be found - crash!( - 1, - "failed to run command {}: {}", - command[0].to_string().quote(), - e - ) - }); + let pstatus = match Command::new(chroot_command).args(chroot_args).status() { + Ok(status) => status, + Err(e) => return Err(ChrootError::CommandFailed(command[0].to_string(), e).into()), + }; - if pstatus.success() { + let code = if pstatus.success() { 0 } else { pstatus.code().unwrap_or(-1) - } + }; + set_exit_code(code); + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -157,7 +141,7 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn set_context(root: &Path, options: &clap::ArgMatches) { +fn set_context(root: &Path, options: &clap::ArgMatches) -> UResult<()> { let userspec_str = options.value_of(options::USERSPEC); let user_str = options.value_of(options::USER).unwrap_or_default(); let group_str = options.value_of(options::GROUP).unwrap_or_default(); @@ -166,7 +150,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { Some(u) => { let s: Vec<&str> = u.split(':').collect(); if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) { - crash!(1, "invalid userspec: {}", u.quote()) + return Err(ChrootError::InvalidUserspec(u.to_string()).into()); }; s } @@ -179,44 +163,40 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { (userspec[0], userspec[1]) }; - enter_chroot(root); + enter_chroot(root)?; - set_groups_from_str(groups_str); - set_main_group(group); - set_user(user); + set_groups_from_str(groups_str)?; + set_main_group(group)?; + set_user(user)?; + Ok(()) } -fn enter_chroot(root: &Path) { +fn enter_chroot(root: &Path) -> UResult<()> { std::env::set_current_dir(root).unwrap(); let err = unsafe { chroot(CString::new(".").unwrap().as_bytes_with_nul().as_ptr() as *const libc::c_char) }; - if err != 0 { - crash!( - 1, - "cannot chroot to {}: {}", - root.quote(), - Error::last_os_error() - ) - }; + if err == 0 { + Ok(()) + } else { + Err(ChrootError::CannotEnter(format!("{}", root.display()), Error::last_os_error()).into()) + } } -fn set_main_group(group: &str) { +fn set_main_group(group: &str) -> UResult<()> { if !group.is_empty() { let group_id = match entries::grp2gid(group) { Ok(g) => g, - _ => crash!(1, "no such group: {}", group.maybe_quote()), + _ => return Err(ChrootError::NoSuchGroup(group.to_string()).into()), }; let err = unsafe { setgid(group_id) }; if err != 0 { - crash!( - 1, - "cannot set gid to {}: {}", - group_id, - Error::last_os_error() - ) + return Err( + ChrootError::SetGidFailed(group_id.to_string(), Error::last_os_error()).into(), + ); } } + Ok(()) } #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] @@ -229,33 +209,33 @@ fn set_groups(groups: Vec) -> libc::c_int { unsafe { setgroups(groups.len() as libc::size_t, groups.as_ptr()) } } -fn set_groups_from_str(groups: &str) { +fn set_groups_from_str(groups: &str) -> UResult<()> { if !groups.is_empty() { - let groups_vec: Vec = groups - .split(',') - .map(|x| match entries::grp2gid(x) { + let mut groups_vec = vec![]; + for group in groups.split(',') { + let gid = match entries::grp2gid(group) { Ok(g) => g, - _ => crash!(1, "no such group: {}", x), - }) - .collect(); + Err(_) => return Err(ChrootError::NoSuchGroup(group.to_string()).into()), + }; + groups_vec.push(gid); + } let err = set_groups(groups_vec); if err != 0 { - crash!(1, "cannot set groups: {}", Error::last_os_error()) + return Err(ChrootError::SetGroupsFailed(Error::last_os_error()).into()); } } + Ok(()) } -fn set_user(user: &str) { +fn set_user(user: &str) -> UResult<()> { if !user.is_empty() { let user_id = entries::usr2uid(user).unwrap(); let err = unsafe { setuid(user_id as libc::uid_t) }; if err != 0 { - crash!( - 1, - "cannot set user to {}: {}", - user.maybe_quote(), - Error::last_os_error() - ) + return Err( + ChrootError::SetUserFailed(user.to_string(), Error::last_os_error()).into(), + ); } } + Ok(()) } diff --git a/src/uu/chroot/src/error.rs b/src/uu/chroot/src/error.rs new file mode 100644 index 00000000000..ebf8f621256 --- /dev/null +++ b/src/uu/chroot/src/error.rs @@ -0,0 +1,81 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +// spell-checker:ignore NEWROOT Userspec userspec +//! Errors returned by chroot. +use std::fmt::Display; +use std::io::Error; +use uucore::display::Quotable; +use uucore::error::UError; + +/// Errors that can happen while executing chroot. +#[derive(Debug)] +pub enum ChrootError { + /// Failed to enter the specified directory. + CannotEnter(String, Error), + + /// Failed to execute the specified command. + CommandFailed(String, Error), + + /// The given user and group specification was invalid. + InvalidUserspec(String), + + /// The new root directory was not given. + MissingNewRoot, + + /// Failed to find the specified group. + NoSuchGroup(String), + + /// The given directory does not exist. + NoSuchDirectory(String), + + /// The call to `setgid()` failed. + SetGidFailed(String, Error), + + /// The call to `setgroups()` failed. + SetGroupsFailed(Error), + + /// The call to `setuid()` failed. + SetUserFailed(String, Error), +} + +impl std::error::Error for ChrootError {} + +impl UError for ChrootError { + // TODO: Exit status: + // 125 if chroot itself fails + // 126 if command is found but cannot be invoked + // 127 if command cannot be found + fn code(&self) -> i32 { + 1 + } +} + +impl Display for ChrootError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ChrootError::CannotEnter(s, e) => write!(f, "cannot chroot to {}: {}", s.quote(), e,), + ChrootError::CommandFailed(s, e) => { + write!(f, "failed to run command {}: {}", s.to_string().quote(), e,) + } + ChrootError::InvalidUserspec(s) => write!(f, "invalid userspec: {}", s.quote(),), + ChrootError::MissingNewRoot => write!( + f, + "Missing operand: NEWROOT\nTry '{} --help' for more information.", + uucore::execution_phrase(), + ), + ChrootError::NoSuchGroup(s) => write!(f, "no such group: {}", s.maybe_quote(),), + ChrootError::NoSuchDirectory(s) => write!( + f, + "cannot change root directory to {}: no such directory", + s.quote(), + ), + ChrootError::SetGidFailed(s, e) => write!(f, "cannot set gid to {}: {}", s, e), + ChrootError::SetGroupsFailed(e) => write!(f, "cannot set groups: {}", e), + ChrootError::SetUserFailed(s, e) => { + write!(f, "cannot set user to {}: {}", s.maybe_quote(), e) + } + } + } +} diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index b7f81d74b59..059ffc5a0b3 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cksum" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" @@ -17,13 +17,12 @@ path = "src/cksum.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "cksum" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 55c4f980f7c..ca87da2a80d 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -6,15 +6,13 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) fname - -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{self, stdin, BufReader, Read}; use std::path::Path; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult}; +use uucore::show; use uucore::InvalidEncodingHandling; // NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 @@ -25,60 +23,15 @@ const NAME: &str = "cksum"; const SYNTAX: &str = "[OPTIONS] [FILE]..."; const SUMMARY: &str = "Print CRC and size for each file"; -// this is basically a hack to get "loops" to work on Rust 1.33. Once we update to Rust 1.46 or -// greater, we can just use while loops -macro_rules! unroll { - (256, |$i:ident| $s:expr) => {{ - unroll!(@ 32, 0 * 32, $i, $s); - unroll!(@ 32, 1 * 32, $i, $s); - unroll!(@ 32, 2 * 32, $i, $s); - unroll!(@ 32, 3 * 32, $i, $s); - unroll!(@ 32, 4 * 32, $i, $s); - unroll!(@ 32, 5 * 32, $i, $s); - unroll!(@ 32, 6 * 32, $i, $s); - unroll!(@ 32, 7 * 32, $i, $s); - }}; - (8, |$i:ident| $s:expr) => {{ - unroll!(@ 8, 0, $i, $s); - }}; - - (@ 32, $start:expr, $i:ident, $s:expr) => {{ - unroll!(@ 8, $start + 0 * 8, $i, $s); - unroll!(@ 8, $start + 1 * 8, $i, $s); - unroll!(@ 8, $start + 2 * 8, $i, $s); - unroll!(@ 8, $start + 3 * 8, $i, $s); - }}; - (@ 8, $start:expr, $i:ident, $s:expr) => {{ - unroll!(@ 4, $start, $i, $s); - unroll!(@ 4, $start + 4, $i, $s); - }}; - (@ 4, $start:expr, $i:ident, $s:expr) => {{ - unroll!(@ 2, $start, $i, $s); - unroll!(@ 2, $start + 2, $i, $s); - }}; - (@ 2, $start:expr, $i:ident, $s:expr) => {{ - unroll!(@ 1, $start, $i, $s); - unroll!(@ 1, $start + 1, $i, $s); - }}; - (@ 1, $start:expr, $i:ident, $s:expr) => {{ - let $i = $start; - let _ = $s; - }}; -} - const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] { let mut table = [0; CRC_TABLE_LEN]; - // NOTE: works on Rust 1.46 - //let mut i = 0; - //while i < CRC_TABLE_LEN { - // table[i] = crc_entry(i as u8) as u32; - // - // i += 1; - //} - unroll!(256, |i| { + let mut i = 0; + while i < CRC_TABLE_LEN { table[i] = crc_entry(i as u8) as u32; - }); + + i += 1; + } table } @@ -86,19 +39,8 @@ const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] { const fn crc_entry(input: u8) -> u32 { let mut crc = (input as u32) << 24; - // NOTE: this does not work on Rust 1.33, but *does* on 1.46 - //let mut i = 0; - //while i < 8 { - // if crc & 0x8000_0000 != 0 { - // crc <<= 1; - // crc ^= 0x04c1_1db7; - // } else { - // crc <<= 1; - // } - // - // i += 1; - //} - unroll!(8, |_i| { + let mut i = 0; + while i < 8 { let if_condition = crc & 0x8000_0000; let if_body = (crc << 1) ^ 0x04c1_1db7; let else_body = crc << 1; @@ -108,7 +50,8 @@ const fn crc_entry(input: u8) -> u32 { let condition_table = [else_body, if_body]; crc = condition_table[(if_condition != 0) as usize]; - }); + i += 1; + } crc } @@ -137,27 +80,18 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> { let mut crc = 0u32; let mut size = 0usize; - let file; let mut rd: Box = match fname { "-" => Box::new(stdin()), _ => { - let path = &Path::new(fname); - if path.is_dir() { - return Err(std::io::Error::new( - io::ErrorKind::InvalidInput, - "Is a directory", - )); - }; - // Silent the warning as we want to the error message - #[allow(clippy::question_mark)] - if path.metadata().is_err() { - return Err(std::io::Error::new( - io::ErrorKind::NotFound, - "No such file or directory", - )); - }; - file = File::open(&path)?; - Box::new(BufReader::new(file)) + let p = Path::new(fname); + + // Directories should not give an error, but should be interpreted + // as empty files to match GNU semantics. + if p.is_dir() { + Box::new(BufReader::new(io::empty())) as Box + } else { + Box::new(BufReader::new(File::open(p)?)) as Box + } } }; @@ -178,7 +112,8 @@ mod options { pub static FILE: &str = "file"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -191,28 +126,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; if files.is_empty() { - match cksum("-") { - Ok((crc, size)) => println!("{} {}", crc, size), - Err(err) => { - show_error!("-: {}", err); - return 2; - } - } - return 0; + let (crc, size) = cksum("-")?; + println!("{} {}", crc, size); + return Ok(()); } - let mut exit_code = 0; for fname in &files { - match cksum(fname.as_ref()) { + match cksum(fname.as_ref()).map_err_context(|| format!("{}", fname.maybe_quote())) { Ok((crc, size)) => println!("{} {} {}", crc, size, fname), - Err(err) => { - show_error!("{}: {}", fname.maybe_quote(), err); - exit_code = 2; - } - } + Err(err) => show!(err), + }; } - - exit_code + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index abcbff57b96..4c82ecbe59c 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_comm" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "comm ~ (uutils) compare sorted inputs" @@ -17,13 +17,12 @@ path = "src/comm.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "comm" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 56af42fd9ea..2f800da8aff 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -11,6 +11,8 @@ use std::cmp::Ordering; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; +use uucore::error::FromIo; +use uucore::error::UResult; use uucore::InvalidEncodingHandling; use clap::{crate_version, App, Arg, ArgMatches}; @@ -128,20 +130,21 @@ fn open_file(name: &str) -> io::Result { } } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); - - let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap(); - let mut f2 = open_file(matches.value_of(options::FILE_2).unwrap()).unwrap(); + let filename1 = matches.value_of(options::FILE_1).unwrap(); + let filename2 = matches.value_of(options::FILE_2).unwrap(); + let mut f1 = open_file(filename1).map_err_context(|| filename1.to_string())?; + let mut f2 = open_file(filename2).map_err_context(|| filename2.to_string())?; comm(&mut f1, &mut f2, &matches); - - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 891bf0244fa..b4209e11ee4 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cp" -version = "0.0.8" +version = "0.0.12" authors = [ "Jordy Dickinson ", "Joshua S. Miller ", @@ -24,8 +24,8 @@ filetime = "0.2" libc = "0.2.85" quick-error = "1.2.3" selinux = { version="0.2.3", optional=true } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } walkdir = "2.2" [target.'cfg(target_os = "linux")'.dependencies] @@ -47,5 +47,4 @@ feat_selinux = ["selinux"] feat_acl = ["exacl"] [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 518a2262ccc..4f4f101863d 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -8,7 +8,7 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -// spell-checker:ignore (ToDO) ficlone linkgs lstat nlink nlinks pathbuf reflink strs xattrs +// spell-checker:ignore (ToDO) ficlone linkgs lstat nlink nlinks pathbuf reflink strs xattrs symlinked #[cfg(target_os = "linux")] #[macro_use] @@ -19,6 +19,7 @@ extern crate quick_error; extern crate uucore; use uucore::display::Quotable; +use uucore::fs::FileInformation; #[cfg(windows)] use winapi::um::fileapi::CreateFileW; #[cfg(windows)] @@ -63,7 +64,7 @@ quick_error! { #[derive(Debug)] pub enum Error { /// Simple io::Error wrapper - IoErr(err: io::Error) { from() cause(err) display("{}", err) } + IoErr(err: io::Error) { from() cause(err) display("{}", err)} /// Wrapper for io::Error with path context IoErrContext(err: io::Error, path: String) { @@ -838,8 +839,10 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu let mut non_fatal_errors = false; let mut seen_sources = HashSet::with_capacity(sources.len()); + let mut symlinked_files = HashSet::new(); for source in sources { if seen_sources.contains(source) { + // FIXME: compare sources by the actual file they point to, not their path. (e.g. dir/file == dir/../dir/file in most cases) show_warning!("source {} specified more than once", source.quote()); } else { let mut found_hard_link = false; @@ -848,7 +851,9 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu preserve_hardlinks(&mut hard_links, source, dest, &mut found_hard_link).unwrap(); } if !found_hard_link { - if let Err(error) = copy_source(source, target, &target_type, options) { + if let Err(error) = + copy_source(source, target, &target_type, options, &mut symlinked_files) + { match error { // When using --no-clobber, we don't want to show // an error message @@ -909,15 +914,16 @@ fn copy_source( target: &TargetSlice, target_type: &TargetType, options: &Options, + symlinked_files: &mut HashSet, ) -> CopyResult<()> { let source_path = Path::new(&source); if source_path.is_dir() { // Copy as directory - copy_directory(source, target, options) + copy_directory(source, target, options, symlinked_files) } else { // Copy as file let dest = construct_dest_path(source_path, target, target_type, options)?; - copy_file(source_path, dest.as_path(), options) + copy_file(source_path, dest.as_path(), options, symlinked_files) } } @@ -947,14 +953,19 @@ fn adjust_canonicalization(p: &Path) -> Cow { /// /// Any errors encountered copying files in the tree will be logged but /// will not cause a short-circuit. -fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyResult<()> { +fn copy_directory( + root: &Path, + target: &TargetSlice, + options: &Options, + symlinked_files: &mut HashSet, +) -> CopyResult<()> { if !options.recursive { return Err(format!("omitting directory {}", root.quote()).into()); } // if no-dereference is enabled and this is a symlink, copy it as a file if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink() { - return copy_file(root, target, options); + return copy_file(root, target, options, symlinked_files); } let current_dir = @@ -1011,7 +1022,7 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR let local_to_target = target.join(&local_to_root_parent); if is_symlink && !options.dereference { - copy_link(&path, &local_to_target)?; + copy_link(&path, &local_to_target, symlinked_files)?; } else if path.is_dir() && !local_to_target.exists() { or_continue!(fs::create_dir_all(local_to_target)); } else if !path.is_dir() { @@ -1021,7 +1032,12 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR let dest = local_to_target.as_path().to_path_buf(); preserve_hardlinks(&mut hard_links, &source, dest, &mut found_hard_link).unwrap(); if !found_hard_link { - match copy_file(path.as_path(), local_to_target.as_path(), options) { + match copy_file( + path.as_path(), + local_to_target.as_path(), + options, + symlinked_files, + ) { Ok(_) => Ok(()), Err(err) => { if fs::symlink_metadata(&source)?.file_type().is_symlink() { @@ -1036,7 +1052,12 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR }?; } } else { - copy_file(path.as_path(), local_to_target.as_path(), options)?; + copy_file( + path.as_path(), + local_to_target.as_path(), + options, + symlinked_files, + )?; } } } @@ -1145,18 +1166,24 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu Ok(()) } -#[cfg(not(windows))] -#[allow(clippy::unnecessary_wraps)] // needed for windows version -fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { - match std::os::unix::fs::symlink(source, dest).context(context) { - Ok(_) => Ok(()), - Err(_) => Ok(()), +fn symlink_file( + source: &Path, + dest: &Path, + context: &str, + symlinked_files: &mut HashSet, +) -> CopyResult<()> { + #[cfg(not(windows))] + { + std::os::unix::fs::symlink(source, dest).context(context)?; } -} - -#[cfg(windows)] -fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { - Ok(std::os::windows::fs::symlink_file(source, dest).context(context)?) + #[cfg(windows)] + { + std::os::windows::fs::symlink_file(source, dest).context(context)?; + } + if let Some(file_info) = FileInformation::from_path(dest, false) { + symlinked_files.insert(file_info); + } + Ok(()) } fn context_for(src: &Path, dest: &Path) -> String { @@ -1183,6 +1210,7 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe } match options.overwrite { + // FIXME: print that the file was removed if --verbose is enabled OverwriteMode::Clobber(ClobberMode::Force) => { if fs::metadata(dest)?.permissions().readonly() { fs::remove_file(dest)?; @@ -1206,11 +1234,39 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe /// /// The original permissions of `source` will be copied to `dest` /// after a successful copy. -fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { +fn copy_file( + source: &Path, + dest: &Path, + options: &Options, + symlinked_files: &mut HashSet, +) -> CopyResult<()> { if dest.exists() { handle_existing_dest(source, dest, options)?; } + // Fail if dest is a dangling symlink or a symlink this program created previously + if fs::symlink_metadata(dest) + .map(|m| m.file_type().is_symlink()) + .unwrap_or(false) + { + if FileInformation::from_path(dest, false) + .map(|info| symlinked_files.contains(&info)) + .unwrap_or(false) + { + return Err(Error::Error(format!( + "will not copy '{}' through just-created symlink '{}'", + source.display(), + dest.display() + ))); + } + if !dest.exists() { + return Err(Error::Error(format!( + "not writing through dangling symlink '{}'", + dest.display() + ))); + } + } + if options.verbose { println!("{}", context_for(source, dest)); } @@ -1255,10 +1311,10 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { fs::hard_link(&source, &dest).context(context)?; } CopyMode::Copy => { - copy_helper(&source, &dest, options, context)?; + copy_helper(&source, &dest, options, context, symlinked_files)?; } CopyMode::SymLink => { - symlink_file(&source, &dest, context)?; + symlink_file(&source, &dest, context, symlinked_files)?; } CopyMode::Sparse => return Err(Error::NotImplemented(options::SPARSE.to_string())), CopyMode::Update => { @@ -1271,10 +1327,10 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { if src_time <= dest_time { return Ok(()); } else { - copy_helper(&source, &dest, options, context)?; + copy_helper(&source, &dest, options, context, symlinked_files)?; } } else { - copy_helper(&source, &dest, options, context)?; + copy_helper(&source, &dest, options, context, symlinked_files)?; } } CopyMode::AttrOnly => { @@ -1292,7 +1348,13 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { .map(|meta| !meta.file_type().is_symlink()) .unwrap_or(false) { - fs::set_permissions(&dest, dest_permissions).unwrap(); + // Here, to match GNU semantics, we quietly ignore an error + // if a user does not have the correct ownership to modify + // the permissions of a file. + // + // FWIW, the OS will throw an error later, on the write op, if + // the user does not have permission to write to the file. + fs::set_permissions(&dest, dest_permissions).ok(); } for attribute in &options.preserve_attributes { copy_attribute(&source, &dest, attribute)?; @@ -1302,7 +1364,13 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// copy-on-write scheme if --reflink is specified and the filesystem supports it. -fn copy_helper(source: &Path, dest: &Path, options: &Options, context: &str) -> CopyResult<()> { +fn copy_helper( + source: &Path, + dest: &Path, + options: &Options, + context: &str, + symlinked_files: &mut HashSet, +) -> CopyResult<()> { if options.parents { let parent = dest.parent().unwrap_or(dest); fs::create_dir_all(parent)?; @@ -1312,9 +1380,9 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options, context: &str) -> /* workaround a limitation of fs::copy * https://github.com/rust-lang/rust/issues/79390 */ - File::create(dest)?; + File::create(dest).context(dest.display().to_string())?; } else if is_symlink { - copy_link(source, dest)?; + copy_link(source, dest, symlinked_files)?; } else if options.reflink_mode != ReflinkMode::Never { #[cfg(not(any(target_os = "linux", target_os = "macos")))] return Err("--reflink is only supported on linux and macOS" @@ -1332,7 +1400,11 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options, context: &str) -> Ok(()) } -fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> { +fn copy_link( + source: &Path, + dest: &Path, + symlinked_files: &mut HashSet, +) -> CopyResult<()> { // Here, we will copy the symlink itself (actually, just recreate it) let link = fs::read_link(&source)?; let dest: Cow<'_, Path> = if dest.is_dir() { @@ -1352,7 +1424,7 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> { } dest.into() }; - symlink_file(&link, &dest, &*context_for(&link, &dest)) + symlink_file(&link, &dest, &*context_for(&link, &dest), symlinked_files) } /// Copies `source` to `dest` using copy-on-write if possible. diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index b76e942ee59..66ca2eef0d6 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_csplit" -version = "0.0.8" +version = "0.0.12" 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" @@ -18,13 +18,12 @@ path = "src/csplit.rs" clap = { version = "2.33", features = ["wrap_help"] } thiserror = "1.0" regex = "1.0.0" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "csplit" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 0d99154df37..b4bf72d967e 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -2,15 +2,19 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg, ArgMatches}; -use regex::Regex; + use std::cmp::Ordering; use std::io::{self, BufReader}; use std::{ fs::{remove_file, File}, io::{BufRead, BufWriter, Write}, }; + +use clap::{crate_version, App, Arg, ArgMatches}; +use regex::Regex; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult}; +use uucore::InvalidEncodingHandling; mod csplit_error; mod patterns; @@ -18,7 +22,6 @@ mod split_name; use crate::csplit_error::CsplitError; use crate::split_name::SplitName; -use uucore::InvalidEncodingHandling; static SUMMARY: &str = "split a file into sections determined by context lines"; static LONG_HELP: &str = "Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output."; @@ -712,7 +715,8 @@ mod tests { } } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::Ignore) @@ -729,20 +733,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap() .map(str::to_string) .collect(); - let patterns = crash_if_err!(1, patterns::get_patterns(&patterns[..])); + let patterns = patterns::get_patterns(&patterns[..])?; let options = CsplitOptions::new(&matches); if file_name == "-" { let stdin = io::stdin(); - crash_if_err!(1, csplit(&options, patterns, stdin.lock())); + Ok(csplit(&options, patterns, stdin.lock())?) } else { - let file = crash_if_err!(1, File::open(file_name)); - let file_metadata = crash_if_err!(1, file.metadata()); + 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() { - crash!(1, "{} is not a regular file", file_name.quote()); + return Err(CsplitError::NotRegularFile(file_name.to_string()).into()); } - crash_if_err!(1, csplit(&options, patterns, BufReader::new(file))); - }; - 0 + Ok(csplit(&options, patterns, BufReader::new(file))?) + } } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/csplit/src/csplit_error.rs b/src/uu/csplit/src/csplit_error.rs index 1d4823ee2d7..53d48a02698 100644 --- a/src/uu/csplit/src/csplit_error.rs +++ b/src/uu/csplit/src/csplit_error.rs @@ -2,6 +2,7 @@ use std::io; use thiserror::Error; use uucore::display::Quotable; +use uucore::error::UError; /// Errors thrown by the csplit command #[derive(Debug, Error)] @@ -28,6 +29,8 @@ pub enum CsplitError { SuffixFormatIncorrect, #[error("too many % conversion specifications in suffix")] SuffixFormatTooManyPercents, + #[error("{} is not a regular file", ._0.quote())] + NotRegularFile(String), } impl From for CsplitError { @@ -35,3 +38,9 @@ impl From for CsplitError { CsplitError::IoError(error) } } + +impl UError for CsplitError { + fn code(&self) -> i32 { + 1 + } +} diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 991e3c449e4..bd532b6a857 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cut" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" @@ -16,8 +16,8 @@ path = "src/cut.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } memchr = "2" bstr = "0.2" atty = "0.2" @@ -27,5 +27,4 @@ name = "cut" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 35d92b83f98..8dfdf25f85b 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -16,6 +16,7 @@ use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::Path; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError}; use self::searcher::Searcher; use uucore::ranges::Range; @@ -142,7 +143,7 @@ fn list_to_ranges(list: &str, complement: bool) -> Result, String> { } } -fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> i32 { +fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> { let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; let buf_in = BufReader::new(reader); let mut out = stdout_writer(); @@ -152,7 +153,7 @@ fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> i32 { .map_or("", String::as_str) .as_bytes(); - let res = buf_in.for_byte_record(newline_char, |line| { + let result = buf_in.for_byte_record(newline_char, |line| { let mut print_delim = false; for &Range { low, high } in ranges { if low > line.len() { @@ -171,8 +172,12 @@ fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> i32 { out.write_all(&[newline_char])?; Ok(true) }); - crash_if_err!(1, res); - 0 + + if let Err(e) = result { + return Err(USimpleError::new(1, e.to_string())); + } + + Ok(()) } #[allow(clippy::cognitive_complexity)] @@ -183,7 +188,7 @@ fn cut_fields_delimiter( only_delimited: bool, newline_char: u8, out_delim: &str, -) -> i32 { +) -> UResult<()> { let buf_in = BufReader::new(reader); let mut out = stdout_writer(); let input_delim_len = delim.len(); @@ -246,12 +251,16 @@ fn cut_fields_delimiter( out.write_all(&[newline_char])?; Ok(true) }); - crash_if_err!(1, result); - 0 + + if let Err(e) = result { + return Err(USimpleError::new(1, e.to_string())); + } + + Ok(()) } #[allow(clippy::cognitive_complexity)] -fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32 { +fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> UResult<()> { let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; if let Some(ref o_delim) = opts.out_delimiter { return cut_fields_delimiter( @@ -323,13 +332,16 @@ fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32 out.write_all(&[newline_char])?; Ok(true) }); - crash_if_err!(1, result); - 0 + + if let Err(e) = result { + return Err(USimpleError::new(1, e.to_string())); + } + + Ok(()) } -fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { +fn cut_files(mut filenames: Vec, mode: Mode) -> UResult<()> { let mut stdin_read = false; - let mut exit_code = 0; if filenames.is_empty() { filenames.push("-".to_owned()); @@ -341,11 +353,11 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { continue; } - exit_code |= match mode { + show_if_err!(match mode { Mode::Bytes(ref ranges, ref opts) => cut_bytes(stdin(), ranges, opts), Mode::Characters(ref ranges, ref opts) => cut_bytes(stdin(), ranges, opts), Mode::Fields(ref ranges, ref opts) => cut_fields(stdin(), ranges, opts), - }; + }); stdin_read = true; } else { @@ -356,28 +368,19 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { continue; } - if path.metadata().is_err() { - show_error!("{}: No such file or directory", filename.maybe_quote()); - continue; - } - - let file = match File::open(&path) { - Ok(f) => f, - Err(e) => { - show_error!("opening {}: {}", filename.quote(), e); - continue; - } - }; - - exit_code |= match mode { - Mode::Bytes(ref ranges, ref opts) => cut_bytes(file, ranges, opts), - Mode::Characters(ref ranges, ref opts) => cut_bytes(file, ranges, opts), - Mode::Fields(ref ranges, ref opts) => cut_fields(file, ranges, opts), - }; + show_if_err!(File::open(&path) + .map_err_context(|| filename.maybe_quote().to_string()) + .and_then(|file| { + match &mode { + Mode::Bytes(ref ranges, ref opts) => cut_bytes(file, ranges, opts), + Mode::Characters(ref ranges, ref opts) => cut_bytes(file, ranges, opts), + Mode::Fields(ref ranges, ref opts) => cut_fields(file, ranges, opts), + } + })); } } - exit_code + Ok(()) } mod options { @@ -392,7 +395,8 @@ mod options { pub const FILE: &str = "file"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -462,12 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { delim = "="; } if delim.chars().count() > 1 { - Err(msg_opt_invalid_should_be!( - "empty or 1 character long", - "a value 2 characters or longer", - "--delimiter", - "-d" - )) + Err("invalid input: The '--delimiter' ('-d') option expects empty or 1 character long, but was provided a value 2 characters or longer".into()) } else { let delim = if delim.is_empty() { "\0".to_owned() @@ -499,13 +498,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }) } (ref b, ref c, ref f) if b.is_some() || c.is_some() || f.is_some() => Err( - msg_expects_no_more_than_one_of!("--fields (-f)", "--chars (-c)", "--bytes (-b)"), + "invalid usage: expects no more than one of --fields (-f), --chars (-c) or --bytes (-b)".into() ), - _ => Err(msg_expects_one_of!( - "--fields (-f)", - "--chars (-c)", - "--bytes (-b)" - )), + _ => Err("invalid usage: expects one of --fields (-f), --chars (-c) or --bytes (-b)".into()), }; let mode_parse = match mode_parse { @@ -514,20 +509,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.is_present(options::DELIMITER) => { - Err(msg_opt_only_usable_if!( - "printing a sequence of fields", - "--delimiter", - "-d" - )) + Err("invalid input: The '--delimiter' ('-d') option only usable if printing a sequence of fields".into()) } Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.is_present(options::ONLY_DELIMITED) => { - Err(msg_opt_only_usable_if!( - "printing a sequence of fields", - "--only-delimited", - "-s" - )) + Err("invalid input: The '--only-delimited' ('-s') option only usable if printing a sequence of fields".into()) } _ => Ok(mode), }, @@ -541,10 +528,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match mode_parse { Ok(mode) => cut_files(files, mode), - Err(err_msg) => { - show_error!("{}", err_msg); - 1 - } + Err(e) => Err(USimpleError::new(1, e)), } } diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index b1077fbe1be..e39951faac0 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_date" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "date ~ (uutils) display or set the current time" @@ -15,10 +15,10 @@ edition = "2018" path = "src/date.rs" [dependencies] -chrono = "0.4.4" +chrono = "^0.4.11" clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(unix)'.dependencies] libc = "0.2" @@ -31,5 +31,4 @@ name = "date" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index adcf770249c..bd814353f41 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -18,6 +18,9 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; use uucore::display::Quotable; +#[cfg(not(any(target_os = "macos", target_os = "redox")))] +use uucore::error::FromIo; +use uucore::error::{UResult, USimpleError}; use uucore::show_error; #[cfg(windows)] use winapi::{ @@ -137,7 +140,8 @@ impl<'a> From<&'a str> for Rfc3339Format { } } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let syntax = format!( "{0} [OPTION]... [+FORMAT]... {0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]", @@ -147,8 +151,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let format = if let Some(form) = matches.value_of(OPT_FORMAT) { if !form.starts_with('+') { - show_error!("invalid date {}", form.quote()); - return 1; + return Err(USimpleError::new( + 1, + format!("invalid date {}", form.quote()), + )); } let form = form[1..].to_string(); Format::Custom(form) @@ -176,8 +182,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let set_to = match matches.value_of(OPT_SET).map(parse_date) { None => None, Some(Err((input, _err))) => { - show_error!("invalid date {}", input.quote()); - return 1; + return Err(USimpleError::new( + 1, + format!("invalid date {}", input.quote()), + )); } Some(Ok(date)) => Some(date), }; @@ -241,14 +249,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let formatted = date.format(format_string).to_string().replace("%f", "%N"); println!("{}", formatted); } - Err((input, _err)) => { - show_error!("invalid date {}", input.quote()); - } + Err((input, _err)) => show_error!("invalid date {}", input.quote()), } } } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -348,20 +354,24 @@ fn parse_date + Clone>( } #[cfg(not(any(unix, windows)))] -fn set_system_datetime(_date: DateTime) -> i32 { +fn set_system_datetime(_date: DateTime) -> UResult<()> { unimplemented!("setting date not implemented (unsupported target)"); } #[cfg(target_os = "macos")] -fn set_system_datetime(_date: DateTime) -> i32 { - show_error!("setting the date is not supported by macOS"); - 1 +fn set_system_datetime(_date: DateTime) -> UResult<()> { + Err(USimpleError::new( + 1, + "setting the date is not supported by macOS".to_string(), + )) } #[cfg(target_os = "redox")] -fn set_system_datetime(_date: DateTime) -> i32 { - show_error!("setting the date is not supported by Redox"); - 1 +fn set_system_datetime(_date: DateTime) -> UResult<()> { + Err(USimpleError::new( + 1, + "setting the date is not supported by Redox".to_string(), + )) } #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] @@ -370,7 +380,7 @@ fn set_system_datetime(_date: DateTime) -> i32 { /// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html /// https://linux.die.net/man/3/clock_settime /// https://www.gnu.org/software/libc/manual/html_node/Time-Types.html -fn set_system_datetime(date: DateTime) -> i32 { +fn set_system_datetime(date: DateTime) -> UResult<()> { let timespec = timespec { tv_sec: date.timestamp() as _, tv_nsec: date.timestamp_subsec_nanos() as _, @@ -379,11 +389,9 @@ fn set_system_datetime(date: DateTime) -> i32 { let result = unsafe { clock_settime(CLOCK_REALTIME, ×pec) }; if result != 0 { - let error = std::io::Error::last_os_error(); - show_error!("cannot set date: {}", error); - error.raw_os_error().unwrap() + Err(std::io::Error::last_os_error().map_err_context(|| "cannot set date".to_string())) } else { - 0 + Ok(()) } } @@ -392,7 +400,7 @@ fn set_system_datetime(date: DateTime) -> i32 { /// See here for more: /// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-setsystemtime /// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime -fn set_system_datetime(date: DateTime) -> i32 { +fn set_system_datetime(date: DateTime) -> UResult<()> { let system_time = SYSTEMTIME { wYear: date.year() as WORD, wMonth: date.month() as WORD, @@ -409,10 +417,8 @@ fn set_system_datetime(date: DateTime) -> i32 { let result = unsafe { SetSystemTime(&system_time) }; if result == 0 { - let error = std::io::Error::last_os_error(); - show_error!("cannot set date: {}", error); - error.raw_os_error().unwrap() + Err(std::io::Error::last_os_error().map_err_context(|| "cannot set date".to_string())) } else { - 0 + Ok(()) } } diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index e26c141cbc2..5dd340c0e65 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dd" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "dd ~ (uutils) copy and convert files" @@ -33,5 +33,4 @@ name = "dd" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/dd/src/datastructures.rs b/src/uu/dd/src/datastructures.rs index b4410d210d3..8fab1ffecbe 100644 --- a/src/uu/dd/src/datastructures.rs +++ b/src/uu/dd/src/datastructures.rs @@ -6,11 +6,13 @@ // file that was distributed with this source code. // spell-checker:ignore ctable, outfile -use crate::conversion_tables::*; - use std::error::Error; use std::time; +use uucore::error::UError; + +use crate::conversion_tables::*; + pub struct ProgUpdate { pub read_stat: ReadStat, pub write_stat: WriteStat, @@ -154,6 +156,7 @@ impl std::fmt::Display for InternalError { } impl Error for InternalError {} +impl UError for InternalError {} pub mod options { pub const INFILE: &str = "if"; diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 9f1d28714b5..644d7abb03b 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -7,8 +7,6 @@ // spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat -use uucore::InvalidEncodingHandling; - #[cfg(test)] mod dd_unit_tests; @@ -21,14 +19,10 @@ use parseargs::Matches; mod conversion_tables; use conversion_tables::*; -use byte_unit::Byte; -use clap::{self, crate_version}; -use gcd::Gcd; -#[cfg(target_os = "linux")] -use signal_hook::consts::signal; use std::cmp; use std::convert::TryInto; use std::env; +#[cfg(target_os = "linux")] use std::error::Error; use std::fs::{File, OpenOptions}; use std::io::{self, Read, Seek, Write}; @@ -41,10 +35,17 @@ use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc}; use std::thread; use std::time; +use byte_unit::Byte; +use clap::{self, crate_version}; +use gcd::Gcd; +#[cfg(target_os = "linux")] +use signal_hook::consts::signal; +use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::InvalidEncodingHandling; + const ABOUT: &str = "copy, and optionally convert, a file system resource"; const BUF_INIT_BYTE: u8 = 0xDD; -const RTN_SUCCESS: i32 = 0; -const RTN_FAILURE: i32 = 1; const NEWLINE: u8 = b'\n'; const SPACE: u8 = b' '; @@ -59,7 +60,7 @@ struct Input { } impl Input { - fn new(matches: &Matches) -> Result> { + fn new(matches: &Matches) -> UResult { let ibs = parseargs::parse_ibs(matches)?; let non_ascii = parseargs::parse_input_non_ascii(matches)?; let print_level = parseargs::parse_status_level(matches)?; @@ -80,8 +81,8 @@ impl Input { if let Some(amt) = skip { let mut buf = vec![BUF_INIT_BYTE; amt]; - - i.force_fill(&mut buf, amt)?; + i.force_fill(&mut buf, amt) + .map_err_context(|| "failed to read input".to_string())?; } Ok(i) @@ -125,7 +126,7 @@ fn make_linux_iflags(iflags: &IFlags) -> Option { } impl Input { - fn new(matches: &Matches) -> Result> { + fn new(matches: &Matches) -> UResult { let ibs = parseargs::parse_ibs(matches)?; let non_ascii = parseargs::parse_input_non_ascii(matches)?; let print_level = parseargs::parse_status_level(matches)?; @@ -144,12 +145,16 @@ impl Input { opts.custom_flags(libc_flags); } - opts.open(fname)? + opts.open(fname) + .map_err_context(|| "failed to open input file".to_string())? }; if let Some(amt) = skip { - let amt: u64 = amt.try_into()?; - src.seek(io::SeekFrom::Start(amt))?; + let amt: u64 = amt + .try_into() + .map_err(|_| USimpleError::new(1, "failed to parse seek amount"))?; + src.seek(io::SeekFrom::Start(amt)) + .map_err_context(|| "failed to seek in input file".to_string())?; } let i = Input { @@ -196,7 +201,7 @@ impl Input { /// Fills a given buffer. /// Reads in increments of 'self.ibs'. /// The start of each ibs-sized read follows the previous one. - fn fill_consecutive(&mut self, buf: &mut Vec) -> Result> { + fn fill_consecutive(&mut self, buf: &mut Vec) -> std::io::Result { let mut reads_complete = 0; let mut reads_partial = 0; let mut bytes_total = 0; @@ -227,7 +232,7 @@ impl Input { /// Fills a given buffer. /// Reads in increments of 'self.ibs'. /// The start of each ibs-sized read is aligned to multiples of ibs; remaining space is filled with the 'pad' byte. - fn fill_blocks(&mut self, buf: &mut Vec, pad: u8) -> Result> { + fn fill_blocks(&mut self, buf: &mut Vec, pad: u8) -> std::io::Result { let mut reads_complete = 0; let mut reads_partial = 0; let mut base_idx = 0; @@ -263,7 +268,7 @@ impl Input { /// interpreted as EOF. /// Note: This will not return unless the source (eventually) produces /// enough bytes to meet target_len. - fn force_fill(&mut self, buf: &mut [u8], target_len: usize) -> Result> { + fn force_fill(&mut self, buf: &mut [u8], target_len: usize) -> std::io::Result { let mut base_idx = 0; while base_idx < target_len { base_idx += self.read(&mut buf[base_idx..target_len])?; @@ -274,7 +279,7 @@ impl Input { } trait OutputTrait: Sized + Write { - fn new(matches: &Matches) -> Result>; + fn new(matches: &Matches) -> UResult; fn fsync(&mut self) -> io::Result<()>; fn fdatasync(&mut self) -> io::Result<()>; } @@ -286,7 +291,7 @@ struct Output { } impl OutputTrait for Output { - fn new(matches: &Matches) -> Result> { + fn new(matches: &Matches) -> UResult { let obs = parseargs::parse_obs(matches)?; let cflags = parseargs::parse_conv_flag_output(matches)?; @@ -333,7 +338,7 @@ where }) } - fn dd_out(mut self, mut i: Input) -> Result<(), Box> { + fn dd_out(mut self, mut i: Input) -> UResult<()> { let mut rstat = ReadStat { reads_complete: 0, reads_partial: 0, @@ -366,24 +371,30 @@ where _, ) => break, (rstat_update, buf) => { - let wstat_update = self.write_blocks(buf)?; + let wstat_update = self + .write_blocks(buf) + .map_err_context(|| "failed to write output".to_string())?; rstat += rstat_update; wstat += wstat_update; } }; // Update Prog - prog_tx.send(ProgUpdate { - read_stat: rstat, - write_stat: wstat, - duration: start.elapsed(), - })?; + prog_tx + .send(ProgUpdate { + read_stat: rstat, + write_stat: wstat, + duration: start.elapsed(), + }) + .map_err(|_| USimpleError::new(1, "failed to write output"))?; } if self.cflags.fsync { - self.fsync()?; + self.fsync() + .map_err_context(|| "failed to write output".to_string())?; } else if self.cflags.fdatasync { - self.fdatasync()?; + self.fdatasync() + .map_err_context(|| "failed to write output".to_string())?; } match i.print_level { @@ -439,7 +450,7 @@ fn make_linux_oflags(oflags: &OFlags) -> Option { } impl OutputTrait for Output { - fn new(matches: &Matches) -> Result> { + fn new(matches: &Matches) -> UResult { fn open_dst(path: &Path, cflags: &OConvFlags, oflags: &OFlags) -> Result { let mut opts = OpenOptions::new(); opts.write(true) @@ -461,11 +472,15 @@ impl OutputTrait for Output { let seek = parseargs::parse_seek_amt(&obs, &oflags, matches)?; if let Some(fname) = matches.value_of(options::OUTFILE) { - let mut dst = open_dst(Path::new(&fname), &cflags, &oflags)?; + let mut dst = open_dst(Path::new(&fname), &cflags, &oflags) + .map_err_context(|| format!("failed to open {}", fname.quote()))?; if let Some(amt) = seek { - let amt: u64 = amt.try_into()?; - dst.seek(io::SeekFrom::Start(amt))?; + let amt: u64 = amt + .try_into() + .map_err(|_| USimpleError::new(1, "failed to parse seek amount"))?; + dst.seek(io::SeekFrom::Start(amt)) + .map_err_context(|| "failed to seek in output file".to_string())?; } Ok(Output { dst, obs, cflags }) @@ -580,7 +595,7 @@ fn conv_block_unblock_helper( mut buf: Vec, i: &mut Input, rstat: &mut ReadStat, -) -> Result, Box> { +) -> Result, InternalError> { // Local Predicate Fns ------------------------------------------------- fn should_block_then_conv(i: &Input) -> bool { !i.non_ascii && i.cflags.block.is_some() @@ -664,15 +679,12 @@ fn conv_block_unblock_helper( // by the parser before making it this far. // Producing this error is an alternative to risking an unwrap call // on 'cbs' if the required data is not provided. - Err(Box::new(InternalError::InvalidConvBlockUnblockCase)) + Err(InternalError::InvalidConvBlockUnblockCase) } } /// Read helper performs read operations common to all dd reads, and dispatches the buffer to relevant helper functions as dictated by the operations requested by the user. -fn read_helper( - i: &mut Input, - bsize: usize, -) -> Result<(ReadStat, Vec), Box> { +fn read_helper(i: &mut Input, bsize: usize) -> UResult<(ReadStat, Vec)> { // Local Predicate Fns ----------------------------------------------- fn is_conv(i: &Input) -> bool { i.cflags.ctable.is_some() @@ -693,8 +705,12 @@ fn read_helper( // Read let mut buf = vec![BUF_INIT_BYTE; bsize]; let mut rstat = match i.cflags.sync { - Some(ch) => i.fill_blocks(&mut buf, ch)?, - _ => i.fill_consecutive(&mut buf)?, + Some(ch) => i + .fill_blocks(&mut buf, ch) + .map_err_context(|| "failed to write output".to_string())?, + _ => i + .fill_consecutive(&mut buf) + .map_err_context(|| "failed to write output".to_string())?, }; // Return early if no data if rstat.reads_complete == 0 && rstat.reads_partial == 0 { @@ -877,28 +893,8 @@ fn append_dashes_if_not_present(mut acc: Vec, mut s: String) -> Vec - {{ - match ($i, $o) - { - (Ok(i), Ok(o)) => - (i,o), - (Err(e), _) => - { - eprintln!("dd Error: {}", e); - return RTN_FAILURE; - }, - (_, Err(e)) => - { - eprintln!("dd Error: {}", e); - return RTN_FAILURE; - }, - } - }}; -); - -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let dashed_args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any() @@ -909,47 +905,30 @@ pub fn uumain(args: impl uucore::Args) -> i32 { //.after_help(TODO: Add note about multiplier strings here.) .get_matches_from(dashed_args); - let result = match ( + match ( matches.is_present(options::INFILE), matches.is_present(options::OUTFILE), ) { (true, true) => { - let (i, o) = - unpack_or_rtn!(Input::::new(&matches), Output::::new(&matches)); - + let i = Input::::new(&matches)?; + let o = Output::::new(&matches)?; o.dd_out(i) } (false, true) => { - let (i, o) = unpack_or_rtn!( - Input::::new(&matches), - Output::::new(&matches) - ); - + let i = Input::::new(&matches)?; + let o = Output::::new(&matches)?; o.dd_out(i) } (true, false) => { - let (i, o) = unpack_or_rtn!( - Input::::new(&matches), - Output::::new(&matches) - ); - + let i = Input::::new(&matches)?; + let o = Output::::new(&matches)?; o.dd_out(i) } (false, false) => { - let (i, o) = unpack_or_rtn!( - Input::::new(&matches), - Output::::new(&matches) - ); - + let i = Input::::new(&matches)?; + let o = Output::::new(&matches)?; o.dd_out(i) } - }; - match result { - Ok(_) => RTN_SUCCESS, - Err(e) => { - eprintln!("dd exiting with error:\n\t{}", e); - RTN_FAILURE - } } } diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 3683023ac09..ef2d5f35648 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -11,6 +11,7 @@ mod unit_tests; use super::*; use std::error::Error; +use uucore::error::UError; pub type Matches = clap::ArgMatches<'static>; @@ -20,7 +21,7 @@ pub enum ParseError { MultipleFmtTable, MultipleUCaseLCase, MultipleBlockUnblock, - MultipleExclNoCreat, + MultipleExclNoCreate, FlagNoMatch(String), ConvFlagNoMatch(String), MultiplierStringParseFailure(String), @@ -45,7 +46,7 @@ impl std::fmt::Display for ParseError { Self::MultipleBlockUnblock => { write!(f, "Only one of conv=block or conv=unblock may be specified") } - Self::MultipleExclNoCreat => { + Self::MultipleExclNoCreate => { write!(f, "Only one ov conv=excl or conv=nocreat may be specified") } Self::FlagNoMatch(arg) => { @@ -79,6 +80,12 @@ impl std::fmt::Display for ParseError { impl Error for ParseError {} +impl UError for ParseError { + fn code(&self) -> i32 { + 1 + } +} + /// Some flags specified as part of a conv=CONV[,CONV]... block /// relate to the input file, others to the output file. #[derive(Debug, PartialEq)] @@ -523,14 +530,14 @@ pub fn parse_conv_flag_output(matches: &Matches) -> Result { if !oconvflags.excl { oconvflags.nocreat = true; } else { - return Err(ParseError::MultipleExclNoCreat); + return Err(ParseError::MultipleExclNoCreate); } } ConvFlag::NoTrunc => oconvflags.notrunc = true, diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index 9486640d0d9..cf2745267c6 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_df" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "df ~ (uutils) display file system information" @@ -17,9 +17,12 @@ path = "src/df.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } number_prefix = "0.4" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["libc", "fsext"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc", "fsext"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "df" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index 0bf53d91abf..7806def7fd5 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dircolors" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "dircolors ~ (uutils) display commands to set LS_COLORS" @@ -17,13 +17,12 @@ path = "src/dircolors.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } glob = "0.3.0" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "dircolors" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 5d0b3ac3e43..270e62acab5 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -8,9 +8,6 @@ // spell-checker:ignore (ToDO) clrtoeol dircolors eightbit endcode fnmatch leftcode multihardlink rightcode setenv sgid suid -#[macro_use] -extern crate uucore; - use std::borrow::Borrow; use std::env; use std::fs::File; @@ -18,6 +15,7 @@ use std::io::{BufRead, BufReader}; use clap::{crate_version, App, Arg}; use uucore::display::Quotable; +use uucore::error::{UResult, USimpleError, UUsageError}; mod options { pub const BOURNE_SHELL: &str = "bourne-shell"; @@ -67,7 +65,8 @@ fn usage() -> String { format!("{0} {1}", uucore::execution_phrase(), SYNTAX) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -85,24 +84,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if (matches.is_present(options::C_SHELL) || matches.is_present(options::BOURNE_SHELL)) && matches.is_present(options::PRINT_DATABASE) { - show_usage_error!( + return Err(UUsageError::new( + 1, "the options to output dircolors' internal database and\nto select a shell \ - syntax are mutually exclusive" - ); - return 1; + syntax are mutually exclusive", + )); } if matches.is_present(options::PRINT_DATABASE) { if !files.is_empty() { - show_usage_error!( - "extra operand {}\nfile operands cannot be combined with \ - --print-database (-p)", - files[0].quote() - ); - return 1; + return Err(UUsageError::new( + 1, + format!( + "extra operand {}\nfile operands cannot be combined with \ + --print-database (-p)", + files[0].quote() + ), + )); } println!("{}", INTERNAL_DB); - return 0; + return Ok(()); } let mut out_format = OutputFmt::Unknown; @@ -115,8 +116,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if out_format == OutputFmt::Unknown { match guess_syntax() { OutputFmt::Unknown => { - show_error!("no SHELL environment variable, and no shell type option given"); - return 1; + return Err(USimpleError::new( + 1, + "no SHELL environment variable, and no shell type option given", + )); } fmt => out_format = fmt, } @@ -127,8 +130,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { result = parse(INTERNAL_DB.lines(), out_format, "") } else { if files.len() > 1 { - show_usage_error!("extra operand {}", files[1].quote()); - return 1; + return Err(UUsageError::new( + 1, + format!("extra operand {}", files[1].quote()), + )); } match File::open(files[0]) { Ok(f) => { @@ -136,19 +141,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { result = parse(fin.lines().filter_map(Result::ok), out_format, files[0]) } Err(e) => { - show_error!("{}: {}", files[0].maybe_quote(), e); - return 1; + return Err(USimpleError::new( + 1, + format!("{}: {}", files[0].maybe_quote(), e), + )); } } } + match result { Ok(s) => { println!("{}", s); - 0 + Ok(()) } Err(s) => { - show_error!("{}", s); - 1 + return Err(USimpleError::new(1, s)); } } } diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index 7946459f355..c7126c0d9a4 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dirname" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "dirname ~ (uutils) display parent directory of PATHNAME" @@ -17,8 +17,8 @@ path = "src/dirname.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "dirname" diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index c9da0462c38..83a371c2598 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_du" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "du ~ (uutils) display disk usage" @@ -16,9 +16,9 @@ path = "src/du.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -chrono = "0.4" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +chrono = "^0.4.11" +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(target_os = "windows")'.dependencies] winapi = { version="0.3", features=[] } diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 9fd44b00105..58d01701fb7 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -18,7 +18,7 @@ use std::env; use std::fs; #[cfg(not(windows))] use std::fs::Metadata; -use std::io::{stderr, ErrorKind, Result, Write}; +use std::io::{ErrorKind, Result}; use std::iter; #[cfg(not(windows))] use std::os::unix::fs::MetadataExt; @@ -292,8 +292,7 @@ fn du( let read = match fs::read_dir(&my_stat.path) { Ok(read) => read, Err(e) => { - safe_writeln!( - stderr(), + eprintln!( "{}: cannot read directory {}: {}", options.util_name, my_stat.path.quote(), diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index 05dd1eba1b8..af95e5fbd9f 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_echo" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "echo ~ (uutils) display TEXT" @@ -16,8 +16,8 @@ path = "src/echo.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "echo" diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index 374a4eda957..eab43e445fd 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_env" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND" @@ -18,8 +18,8 @@ path = "src/env.rs" clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" rust-ini = "0.17.0" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "env" diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index aec97fc24f9..55dfce62580 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -1,13 +1,14 @@ // This file is part of the uutils coreutils package. // // (c) Jordi Boggiano +// (c) Thomas Queiroz // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. /* last synced with: env (GNU coreutils) 8.13 */ -// spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets +// spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets setenv putenv spawnp #[macro_use] extern crate clap; @@ -23,7 +24,7 @@ use std::io::{self, Write}; use std::iter::Iterator; use std::process::Command; use uucore::display::Quotable; -use uucore::error::{UResult, USimpleError}; +use uucore::error::{UResult, USimpleError, UUsageError}; const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]"; const AFTER_HELP: &str = "\ @@ -50,7 +51,7 @@ fn print_env(null: bool) { } } -fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result { +fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> UResult { // is it a NAME=VALUE like opt ? if let Some(idx) = opt.find('=') { // yes, so push name, value pair @@ -64,17 +65,12 @@ fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32> { +fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> UResult<()> { if opts.null { - eprintln!( - "{}: cannot specify --null (-0) with command", - uucore::util_name() - ); - eprintln!( - "Type \"{} --help\" for detailed information", - uucore::execution_phrase() - ); - Err(1) + Err(UUsageError::new( + 125, + "cannot specify --null (-0) with command".to_string(), + )) } else { opts.program.push(opt); Ok(()) @@ -93,10 +89,8 @@ fn load_config_file(opts: &mut Options) -> UResult<()> { Ini::load_from_file(file) }; - let conf = conf.map_err(|error| { - show_error!("{}: {}", file.maybe_quote(), error); - 1 - })?; + let conf = + conf.map_err(|e| USimpleError::new(1, format!("{}: {}", file.maybe_quote(), e)))?; for (_, prop) in &conf { // ignore all INI section lines (treat them as comments) @@ -138,7 +132,7 @@ pub fn uu_app() -> App<'static, 'static> { .long("ignore-environment") .help("start with an empty environment")) .arg(Arg::with_name("chdir") - .short("c") + .short("C") // GNU env compatibility .long("chdir") .takes_value(true) .number_of_values(1) @@ -236,6 +230,14 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { } } + // GNU env tests this behavior + if opts.program.is_empty() && running_directory.is_some() { + return Err(UUsageError::new( + 125, + "must specify command with --chdir (-C)".to_string(), + )); + } + // NOTE: we manually set and unset the env vars below rather than using Command::env() to more // easily handle the case where no command is given @@ -251,12 +253,44 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { // unset specified env vars for name in &opts.unsets { + if name.is_empty() || name.contains(0 as char) || name.contains('=') { + return Err(USimpleError::new( + 125, + format!("cannot unset {}: Invalid argument", name.quote()), + )); + } + env::remove_var(name); } // set specified env vars for &(name, val) in &opts.sets { - // FIXME: set_var() panics if name is an empty string + /* + * set_var panics if name is an empty string + * set_var internally calls setenv (on unix at least), while GNU env calls putenv instead. + * + * putenv returns successfully if provided with something like "=a" and modifies the environ + * variable to contain "=a" inside it, effectively modifying the process' current environment + * to contain a malformed string in it. Using GNU's implementation, the command `env =a` + * prints out the malformed string and even invokes the child process with that environment. + * This can be seen by using `env -i =a env` or `env -i =a cat /proc/self/environ` + * + * POSIX.1-2017 doesn't seem to mention what to do if the string is malformed (at least + * not in "Chapter 8, Environment Variables" or in the definition for environ and various + * exec*'s or in the description of env in the "Shell & Utilities" volume). + * + * It also doesn't specify any checks for putenv before modifying the environ variable, which + * is likely why glibc doesn't do so. However, the first set_var argument cannot point to + * an empty string or a string containing '='. + * + * There is no benefit in replicating GNU's env behavior, since it will only modify the + * environment in weird ways + */ + + if name.is_empty() { + show_warning!("no name specified for value {}", val.quote()); + continue; + } env::set_var(name, val); } @@ -264,7 +298,12 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { // we need to execute a command let (prog, args) = build_command(&mut opts.program); - // FIXME: this should just use execvp() (no fork()) on Unix-like systems + /* + * On Unix-like systems Command::status either ends up calling either fork or posix_spawnp + * (which ends up calling clone). Keep using the current process would be ideal, but the + * standard library contains many checks and fail-safes to ensure the process ends up being + * created. This is much simpler than dealing with the hassles of calling execvp directly. + */ match Command::new(&*prog).args(args).status() { Ok(exit) if !exit.success() => return Err(exit.code().unwrap().into()), Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127.into()), diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index efd338dab95..08fa46b6f6f 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expand" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "expand ~ (uutils) convert input tabs to spaces" @@ -17,13 +17,12 @@ path = "src/expand.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } unicode-width = "0.1.5" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "expand" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index b09b8aaabad..42517909208 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -18,6 +18,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::str::from_utf8; use unicode_width::UnicodeWidthChar; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult}; static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output. With no FILE, or when FILE is -, read standard input."; @@ -170,12 +171,12 @@ impl Options { } } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); - expand(Options::new(&matches)); - 0 + expand(Options::new(&matches)).map_err_context(|| "failed to write output".to_string()) } pub fn uu_app() -> App<'static, 'static> { @@ -269,7 +270,7 @@ enum CharType { Other, } -fn expand(options: Options) { +fn expand(options: Options) -> std::io::Result<()> { use self::CharType::*; let mut output = BufWriter::new(stdout()); @@ -330,15 +331,12 @@ fn expand(options: Options) { // now dump out either spaces if we're expanding, or a literal tab if we're not if init || !options.iflag { if nts <= options.tspaces.len() { - crash_if_err!( - 1, - output.write_all(options.tspaces[..nts].as_bytes()) - ); + output.write_all(options.tspaces[..nts].as_bytes())? } else { - crash_if_err!(1, output.write_all(" ".repeat(nts).as_bytes())); + output.write_all(" ".repeat(nts).as_bytes())?; }; } else { - crash_if_err!(1, output.write_all(&buf[byte..byte + nbytes])); + output.write_all(&buf[byte..byte + nbytes])?; } } _ => { @@ -356,17 +354,18 @@ fn expand(options: Options) { init = false; } - crash_if_err!(1, output.write_all(&buf[byte..byte + nbytes])); + output.write_all(&buf[byte..byte + nbytes])?; } } byte += nbytes; // advance the pointer } - crash_if_err!(1, output.flush()); + output.flush()?; buf.truncate(0); // clear the buffer } } + Ok(()) } #[cfg(test)] diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 90a53f2ce23..a2f771c4f99 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expr" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "expr ~ (uutils) display the value of EXPRESSION" @@ -20,13 +20,12 @@ libc = "0.2.42" num-bigint = "0.4.0" num-traits = "0.2.14" onig = "~4.3.2" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "expr" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/factor/BENCHMARKING.md b/src/uu/factor/BENCHMARKING.md index 6bf9cbf90b5..0f9afaff0e4 100644 --- a/src/uu/factor/BENCHMARKING.md +++ b/src/uu/factor/BENCHMARKING.md @@ -44,7 +44,8 @@ on Daniel Lemire's [*Microbenchmarking calls for idealized conditions*][lemire], which I recommend reading if you want to add benchmarks to `factor`. 1. Select a small, self-contained, deterministic component - `gcd` and `table::factor` are good example of such: + (`gcd` and `table::factor` are good examples): + - no I/O or access to external data structures ; - no call into other components ; - behavior is deterministic: no RNG, no concurrency, ... ; @@ -53,16 +54,19 @@ which I recommend reading if you want to add benchmarks to `factor`. maximizing the numbers of samples we can take in a given time. 2. Benchmarks are immutable (once merged in `uutils`) + Modifying a benchmark means previously-collected values cannot meaningfully be compared, silently giving nonsensical results. If you must modify an existing benchmark, rename it. 3. Test common cases + We are interested in overall performance, rather than specific edge-cases; use **reproducibly-randomized inputs**, sampling from either all possible input values or some subset of interest. 4. Use [`criterion`], `criterion::black_box`, ... + `criterion` isn't perfect, but it is also much better than ad-hoc solutions in each benchmark. diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 2d2fa236b91..48c8e239276 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_factor" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "factor ~ (uutils) display the prime factors of each NUMBER" @@ -15,13 +15,13 @@ edition = "2018" num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs [dependencies] +clap = { version = "2.33", features = ["wrap_help"] } coz = { version = "0.1.3", optional = true } num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd" rand = { version = "0.7", features = ["small_rng"] } -smallvec = { version = "0.6.14, < 1.0" } +smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or later. uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" } -uucore_procs = { version=">=0.0.7", package = "uucore_procs", path = "../../uucore_procs" } -clap = { version = "2.33", features = ["wrap_help"] } +uucore_procs = { version=">=0.0.8", package = "uucore_procs", path = "../../uucore_procs" } [dev-dependencies] paste = "0.1.18" @@ -36,5 +36,4 @@ path = "src/main.rs" path = "src/cli.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index 30541c244ed..0aa0b2474fb 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -17,6 +17,7 @@ mod factor; use clap::{crate_version, App, Arg}; pub use factor::*; use uucore::display::Quotable; +use uucore::error::UResult; mod miller_rabin; pub mod numeric; @@ -43,7 +44,8 @@ fn print_factors_str( }) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().get_matches_from(args); let stdout = stdout(); // We use a smaller buffer here to pass a gnu test. 4KiB appears to be the default pipe size for bash. @@ -72,7 +74,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { show_error!("{}", e); } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/factor/src/numeric/gcd.rs b/src/uu/factor/src/numeric/gcd.rs index 004ef15154f..78197c722c1 100644 --- a/src/uu/factor/src/numeric/gcd.rs +++ b/src/uu/factor/src/numeric/gcd.rs @@ -52,7 +52,7 @@ pub fn gcd(mut u: u64, mut v: u64) -> u64 { #[cfg(test)] mod tests { use super::*; - use quickcheck::quickcheck; + use quickcheck::{quickcheck, TestResult}; quickcheck! { fn euclidean(a: u64, b: u64) -> bool { @@ -76,13 +76,12 @@ mod tests { gcd(0, a) == a } - fn divisor(a: u64, b: u64) -> () { + fn divisor(a: u64, b: u64) -> TestResult { // Test that gcd(a, b) divides a and b, unless a == b == 0 - if a == 0 && b == 0 { return; } + if a == 0 && b == 0 { return TestResult::discard(); } // restrict test domain to !(a == b == 0) let g = gcd(a, b); - assert_eq!(a % g, 0); - assert_eq!(b % g, 0); + TestResult::from_bool( g != 0 && a % g == 0 && b % g == 0 ) } fn commutative(a: u64, b: u64) -> bool { diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index 2a725e2b01e..e7f753cc91d 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_false" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "false ~ (uutils) do nothing and fail" @@ -16,8 +16,8 @@ path = "src/false.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "false" diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index a4700cb5e78..52d6227b20e 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fmt" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "fmt ~ (uutils) reformat each paragraph of input" @@ -18,13 +18,12 @@ path = "src/fmt.rs" clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" unicode-width = "0.1.5" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "fmt" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 669c98b1466..91fc08d28fc 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -16,19 +16,11 @@ use std::fs::File; use std::io::{stdin, stdout, Write}; use std::io::{BufReader, BufWriter, Read}; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError}; use self::linebreak::break_lines; use self::parasplit::ParagraphStream; -macro_rules! silent_unwrap( - ($exp:expr) => ( - match $exp { - Ok(_) => (), - Err(_) => ::std::process::exit(1), - } - ) -); - mod linebreak; mod parasplit; @@ -74,8 +66,9 @@ pub struct FmtOptions { tabwidth: usize, } +#[uucore_procs::gen_uumain] #[allow(clippy::cognitive_complexity)] -pub fn uumain(args: impl uucore::Args) -> i32 { +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -133,15 +126,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fmt_opts.width = match s.parse::() { Ok(t) => t, Err(e) => { - crash!(1, "Invalid WIDTH specification: {}: {}", s.quote(), e); + return Err(USimpleError::new( + 1, + format!("Invalid WIDTH specification: {}: {}", s.quote(), e), + )); } }; if fmt_opts.width > MAX_WIDTH { - crash!( + return Err(USimpleError::new( 1, - "invalid width: '{}': Numerical result out of range", - fmt_opts.width - ); + format!( + "invalid width: '{}': Numerical result out of range", + fmt_opts.width, + ), + )); } fmt_opts.goal = cmp::min(fmt_opts.width * 94 / 100, fmt_opts.width - 3); }; @@ -150,13 +148,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fmt_opts.goal = match s.parse::() { Ok(t) => t, Err(e) => { - crash!(1, "Invalid GOAL specification: {}: {}", s.quote(), e); + return Err(USimpleError::new( + 1, + format!("Invalid GOAL specification: {}: {}", s.quote(), e), + )); } }; if !matches.is_present(OPT_WIDTH) { fmt_opts.width = cmp::max(fmt_opts.goal * 100 / 94, fmt_opts.goal + 3); } else if fmt_opts.goal > fmt_opts.width { - crash!(1, "GOAL cannot be greater than WIDTH."); + return Err(USimpleError::new(1, "GOAL cannot be greater than WIDTH.")); } }; @@ -164,7 +165,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fmt_opts.tabwidth = match s.parse::() { Ok(t) => t, Err(e) => { - crash!(1, "Invalid TABWIDTH specification: {}: {}", s.quote(), e); + return Err(USimpleError::new( + 1, + format!("Invalid TABWIDTH specification: {}: {}", s.quote(), e), + )); } }; }; @@ -197,18 +201,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for para_result in p_stream { match para_result { Err(s) => { - silent_unwrap!(ostream.write_all(s.as_bytes())); - silent_unwrap!(ostream.write_all(b"\n")); + ostream + .write_all(s.as_bytes()) + .map_err_context(|| "failed to write output".to_string())?; + ostream + .write_all(b"\n") + .map_err_context(|| "failed to write output".to_string())?; } - Ok(para) => break_lines(¶, &fmt_opts, &mut ostream), + Ok(para) => break_lines(¶, &fmt_opts, &mut ostream) + .map_err_context(|| "failed to write output".to_string())?, } } // flush the output after each file - silent_unwrap!(ostream.flush()); + ostream + .flush() + .map_err_context(|| "failed to write output".to_string())?; } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/fmt/src/linebreak.rs b/src/uu/fmt/src/linebreak.rs index fe9f8568e01..d24d9279818 100644 --- a/src/uu/fmt/src/linebreak.rs +++ b/src/uu/fmt/src/linebreak.rs @@ -40,7 +40,11 @@ impl<'a> BreakArgs<'a> { } } -pub fn break_lines(para: &Paragraph, opts: &FmtOptions, ostream: &mut BufWriter) { +pub fn break_lines( + para: &Paragraph, + opts: &FmtOptions, + ostream: &mut BufWriter, +) -> std::io::Result<()> { // indent let p_indent = ¶.indent_str[..]; let p_indent_len = para.indent_len; @@ -54,26 +58,25 @@ pub fn break_lines(para: &Paragraph, opts: &FmtOptions, ostream: &mut BufWriter< let (w, w_len) = match p_words_words.next() { Some(winfo) => (winfo.word, winfo.word_nchars), None => { - silent_unwrap!(ostream.write_all(b"\n")); - return; + return ostream.write_all(b"\n"); } }; // print the init, if it exists, and get its length let p_init_len = w_len + if opts.crown || opts.tagged { // handle "init" portion - silent_unwrap!(ostream.write_all(para.init_str.as_bytes())); + ostream.write_all(para.init_str.as_bytes())?; para.init_len } else if !para.mail_header { // for non-(crown, tagged) that's the same as a normal indent - silent_unwrap!(ostream.write_all(p_indent.as_bytes())); + ostream.write_all(p_indent.as_bytes())?; p_indent_len } else { // except that mail headers get no indent at all 0 }; // write first word after writing init - silent_unwrap!(ostream.write_all(w.as_bytes())); + ostream.write_all(w.as_bytes())?; // does this paragraph require uniform spacing? let uniform = para.mail_header || opts.uniform; @@ -88,26 +91,29 @@ pub fn break_lines(para: &Paragraph, opts: &FmtOptions, ostream: &mut BufWriter< }; if opts.quick || para.mail_header { - break_simple(p_words_words, &mut break_args); + break_simple(p_words_words, &mut break_args) } else { - break_knuth_plass(p_words_words, &mut break_args); + break_knuth_plass(p_words_words, &mut break_args) } } // break_simple implements a "greedy" breaking algorithm: print words until // maxlength would be exceeded, then print a linebreak and indent and continue. -fn break_simple<'a, T: Iterator>>(iter: T, args: &mut BreakArgs<'a>) { - iter.fold((args.init_len, false), |l, winfo| { +fn break_simple<'a, T: Iterator>>( + mut iter: T, + args: &mut BreakArgs<'a>, +) -> std::io::Result<()> { + iter.try_fold((args.init_len, false), |l, winfo| { accum_words_simple(args, l, winfo) - }); - silent_unwrap!(args.ostream.write_all(b"\n")); + })?; + args.ostream.write_all(b"\n") } fn accum_words_simple<'a>( args: &mut BreakArgs<'a>, (l, prev_punct): (usize, bool), winfo: &'a WordInfo<'a>, -) -> (usize, bool) { +) -> std::io::Result<(usize, bool)> { // compute the length of this word, considering how tabs will expand at this position on the line let wlen = winfo.word_nchars + args.compute_width(winfo, l, false); @@ -119,12 +125,12 @@ fn accum_words_simple<'a>( ); if l + wlen + slen > args.opts.width { - write_newline(args.indent_str, args.ostream); - write_with_spaces(&winfo.word[winfo.word_start..], 0, args.ostream); - (args.indent_len + winfo.word_nchars, winfo.ends_punct) + write_newline(args.indent_str, args.ostream)?; + write_with_spaces(&winfo.word[winfo.word_start..], 0, args.ostream)?; + Ok((args.indent_len + winfo.word_nchars, winfo.ends_punct)) } else { - write_with_spaces(winfo.word, slen, args.ostream); - (l + wlen + slen, winfo.ends_punct) + write_with_spaces(winfo.word, slen, args.ostream)?; + Ok((l + wlen + slen, winfo.ends_punct)) } } @@ -135,16 +141,16 @@ fn accum_words_simple<'a>( fn break_knuth_plass<'a, T: Clone + Iterator>>( mut iter: T, args: &mut BreakArgs<'a>, -) { +) -> std::io::Result<()> { // run the algorithm to get the breakpoints let breakpoints = find_kp_breakpoints(iter.clone(), args); // iterate through the breakpoints (note that breakpoints is in reverse break order, so we .rev() it - let (mut prev_punct, mut fresh) = breakpoints.iter().rev().fold( + let result: std::io::Result<(bool, bool)> = breakpoints.iter().rev().try_fold( (false, false), |(mut prev_punct, mut fresh), &(next_break, break_before)| { if fresh { - write_newline(args.indent_str, args.ostream); + write_newline(args.indent_str, args.ostream)?; } // at each breakpoint, keep emitting words until we find the word matching this breakpoint for winfo in &mut iter { @@ -167,26 +173,27 @@ fn break_knuth_plass<'a, T: Clone + Iterator>>( if winfo_ptr == next_break_ptr { // OK, we found the matching word if break_before { - write_newline(args.indent_str, args.ostream); - write_with_spaces(&winfo.word[winfo.word_start..], 0, args.ostream); + write_newline(args.indent_str, args.ostream)?; + write_with_spaces(&winfo.word[winfo.word_start..], 0, args.ostream)?; } else { // breaking after this word, so that means "fresh" is true for the next iteration - write_with_spaces(word, slen, args.ostream); + write_with_spaces(word, slen, args.ostream)?; fresh = true; } break; } else { - write_with_spaces(word, slen, args.ostream); + write_with_spaces(word, slen, args.ostream)?; } } - (prev_punct, fresh) + Ok((prev_punct, fresh)) }, ); + let (mut prev_punct, mut fresh) = result?; // after the last linebreak, write out the rest of the final line. for winfo in iter { if fresh { - write_newline(args.indent_str, args.ostream); + write_newline(args.indent_str, args.ostream)?; } let (slen, word) = slice_if_fresh( fresh, @@ -199,9 +206,9 @@ fn break_knuth_plass<'a, T: Clone + Iterator>>( ); prev_punct = winfo.ends_punct; fresh = false; - write_with_spaces(word, slen, args.ostream); + write_with_spaces(word, slen, args.ostream)?; } - silent_unwrap!(args.ostream.write_all(b"\n")); + args.ostream.write_all(b"\n") } struct LineBreak<'a> { @@ -494,17 +501,21 @@ fn slice_if_fresh( } // Write a newline and add the indent. -fn write_newline(indent: &str, ostream: &mut BufWriter) { - silent_unwrap!(ostream.write_all(b"\n")); - silent_unwrap!(ostream.write_all(indent.as_bytes())); +fn write_newline(indent: &str, ostream: &mut BufWriter) -> std::io::Result<()> { + ostream.write_all(b"\n")?; + ostream.write_all(indent.as_bytes()) } // Write the word, along with slen spaces. -fn write_with_spaces(word: &str, slen: usize, ostream: &mut BufWriter) { +fn write_with_spaces( + word: &str, + slen: usize, + ostream: &mut BufWriter, +) -> std::io::Result<()> { if slen == 2 { - silent_unwrap!(ostream.write_all(b" ")); + ostream.write_all(b" ")?; } else if slen == 1 { - silent_unwrap!(ostream.write_all(b" ")); + ostream.write_all(b" ")?; } - silent_unwrap!(ostream.write_all(word.as_bytes())); + ostream.write_all(word.as_bytes()) } diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index ce2aeead412..4e63d4c4d8c 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fold" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "fold ~ (uutils) wrap each line of input" @@ -16,13 +16,12 @@ path = "src/fold.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "fold" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index c4cc1646910..30a1012af88 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -7,13 +7,12 @@ // spell-checker:ignore (ToDOs) ncount routput -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; +use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError}; use uucore::InvalidEncodingHandling; const TAB_WIDTH: usize = 8; @@ -30,7 +29,8 @@ mod options { pub const FILE: &str = "file"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -46,10 +46,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; let width = match poss_width { - Some(inp_width) => match inp_width.parse::() { - Ok(width) => width, - Err(e) => crash!(1, "illegal width value (\"{}\"): {}", inp_width, e), - }, + Some(inp_width) => inp_width.parse::().map_err(|e| { + USimpleError::new( + 1, + format!("illegal width value ({}): {}", inp_width.quote(), e), + ) + })?, None => 80, }; @@ -58,9 +60,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { None => vec!["-".to_owned()], }; - fold(files, bytes, spaces, width); - - 0 + fold(files, bytes, spaces, width) } pub fn uu_app() -> App<'static, 'static> { @@ -110,7 +110,7 @@ fn handle_obsolete(args: &[String]) -> (Vec, Option) { (args.to_vec(), None) } -fn fold(filenames: Vec, bytes: bool, spaces: bool, width: usize) { +fn fold(filenames: Vec, bytes: bool, spaces: bool, width: usize) -> UResult<()> { for filename in &filenames { let filename: &str = filename; let mut stdin_buf; @@ -119,16 +119,17 @@ fn fold(filenames: Vec, bytes: bool, spaces: bool, width: usize) { stdin_buf = stdin(); &mut stdin_buf as &mut dyn Read } else { - file_buf = crash_if_err!(1, File::open(Path::new(filename))); + file_buf = File::open(Path::new(filename)).map_err_context(|| filename.to_string())?; &mut file_buf as &mut dyn Read }); if bytes { - fold_file_bytewise(buffer, spaces, width); + fold_file_bytewise(buffer, spaces, width)?; } else { - fold_file(buffer, spaces, width); + fold_file(buffer, spaces, width)?; } } + Ok(()) } /// Fold `file` to fit `width` (number of columns), counting all characters as @@ -139,11 +140,15 @@ fn fold(filenames: Vec, bytes: bool, spaces: bool, width: usize) { /// to all other characters in the stream. /// /// If `spaces` is `true`, attempt to break lines at whitespace boundaries. -fn fold_file_bytewise(mut file: BufReader, spaces: bool, width: usize) { +fn fold_file_bytewise(mut file: BufReader, spaces: bool, width: usize) -> UResult<()> { let mut line = String::new(); loop { - if let Ok(0) = file.read_line(&mut line) { + if file + .read_line(&mut line) + .map_err_context(|| "failed to read line".to_string())? + == 0 + { break; } @@ -190,6 +195,8 @@ fn fold_file_bytewise(mut file: BufReader, spaces: bool, width: usiz line.truncate(0); } + + Ok(()) } /// Fold `file` to fit `width` (number of columns). @@ -200,7 +207,7 @@ fn fold_file_bytewise(mut file: BufReader, spaces: bool, width: usiz /// /// If `spaces` is `true`, attempt to break lines at whitespace boundaries. #[allow(unused_assignments)] -fn fold_file(mut file: BufReader, spaces: bool, width: usize) { +fn fold_file(mut file: BufReader, spaces: bool, width: usize) -> UResult<()> { let mut line = String::new(); let mut output = String::new(); let mut col_count = 0; @@ -230,7 +237,11 @@ fn fold_file(mut file: BufReader, spaces: bool, width: usize) { } loop { - if let Ok(0) = file.read_line(&mut line) { + if file + .read_line(&mut line) + .map_err_context(|| "failed to read line".to_string())? + == 0 + { break; } @@ -281,4 +292,6 @@ fn fold_file(mut file: BufReader, spaces: bool, width: usize) { line.truncate(0); } + + Ok(()) } diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 6ac210692b8..9f56cd1a658 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_groups" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "groups ~ (uutils) display group memberships for USERNAME" @@ -15,10 +15,13 @@ edition = "2018" path = "src/groups.rs" [dependencies] -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "process"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "process"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } clap = { version = "2.33", features = ["wrap_help"] } [[bin]] name = "groups" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 43e2a2239ed..70980780d16 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -17,9 +17,12 @@ #[macro_use] extern crate uucore; +use std::error::Error; +use std::fmt::Display; use uucore::{ display::Quotable, entries::{get_groups_gnu, gid2grp, Locate, Passwd}, + error::{UError, UResult}, }; use clap::{crate_version, App, Arg}; @@ -35,7 +38,39 @@ fn usage() -> String { format!("{0} [OPTION]... [USERNAME]...", uucore::execution_phrase()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[derive(Debug)] +enum GroupsError { + GetGroupsFailed, + GroupNotFound(u32), + UserNotFound(String), +} + +impl Error for GroupsError {} +impl UError for GroupsError {} + +impl Display for GroupsError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + GroupsError::GetGroupsFailed => write!(f, "failed to fetch groups"), + GroupsError::GroupNotFound(gid) => write!(f, "cannot find name for group ID {}", gid), + GroupsError::UserNotFound(user) => write!(f, "{}: no such user", user.quote()), + } + } +} + +fn infallible_gid2grp(gid: &u32) -> String { + match gid2grp(*gid) { + Ok(grp) => grp, + Err(_) => { + // The `show!()` macro sets the global exit code for the program. + show!(GroupsError::GroupNotFound(*gid)); + gid.to_string() + } + } +} + +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -45,46 +80,29 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - let mut exit_code = 0; - if users.is_empty() { - println!( - "{}", - get_groups_gnu(None) - .unwrap() - .iter() - .map(|&gid| gid2grp(gid).unwrap_or_else(|_| { - show_error!("cannot find name for group ID {}", gid); - exit_code = 1; - gid.to_string() - })) - .collect::>() - .join(" ") - ); - return exit_code; + let gids = match get_groups_gnu(None) { + Ok(v) => v, + Err(_) => return Err(GroupsError::GetGroupsFailed.into()), + }; + let groups: Vec = gids.iter().map(infallible_gid2grp).collect(); + println!("{}", groups.join(" ")); + return Ok(()); } for user in users { - if let Ok(p) = Passwd::locate(user.as_str()) { - println!( - "{} : {}", - user, - p.belongs_to() - .iter() - .map(|&gid| gid2grp(gid).unwrap_or_else(|_| { - show_error!("cannot find name for group ID {}", gid); - exit_code = 1; - gid.to_string() - })) - .collect::>() - .join(" ") - ); - } else { - show_error!("{}: no such user", user.quote()); - exit_code = 1; + match Passwd::locate(user.as_str()) { + Ok(p) => { + let groups: Vec = p.belongs_to().iter().map(infallible_gid2grp).collect(); + println!("{} : {}", user, groups.join(" ")); + } + Err(_) => { + // The `show!()` macro sets the global exit code for the program. + show!(GroupsError::UserNotFound(user)); + } } } - exit_code + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 67b0f9e02a5..f5379de336e 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hashsum" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "hashsum ~ (uutils) display or check input digests" @@ -27,13 +27,12 @@ sha1 = "0.6.0" sha2 = "0.6.0" sha3 = "0.6.0" blake2b_simd = "0.5.11" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "hashsum" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 07070ed1bce..d4dacc298e0 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -28,6 +28,7 @@ use sha1::Sha1; use sha2::{Sha224, Sha256, Sha384, Sha512}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}; use std::cmp::Ordering; +use std::error::Error; use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Read}; @@ -35,6 +36,7 @@ use std::iter; use std::num::ParseIntError; use std::path::Path; use uucore::display::Quotable; +use uucore::error::{FromIo, UError, UResult}; const NAME: &str = "hashsum"; @@ -274,7 +276,8 @@ fn is_valid_bit_num(arg: String) -> Result<(), String> { .map_err(|e| format!("{}", e)) } -pub fn uumain(mut args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +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) @@ -324,14 +327,9 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { warn, }; - let res = match matches.values_of_os("FILE") { + match matches.values_of_os("FILE") { Some(files) => hashsum(opts, files), None => hashsum(opts, iter::once(OsStr::new("-"))), - }; - - match res { - Ok(()) => 0, - Err(e) => e, } } @@ -453,8 +451,26 @@ fn uu_app(binary_name: &str) -> App<'static, 'static> { } } +#[derive(Debug)] +enum HashsumError { + InvalidRegex, + InvalidFormat, +} + +impl Error for HashsumError {} +impl UError for HashsumError {} + +impl std::fmt::Display for HashsumError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + HashsumError::InvalidRegex => write!(f, "invalid regular expression"), + HashsumError::InvalidFormat => Ok(()), + } + } +} + #[allow(clippy::cognitive_complexity)] -fn hashsum<'a, I>(mut options: Options, files: I) -> Result<(), i32> +fn hashsum<'a, I>(mut options: Options, files: I) -> UResult<()> where I: Iterator, { @@ -470,7 +486,8 @@ where stdin_buf = stdin(); Box::new(stdin_buf) as Box } else { - file_buf = crash_if_err!(1, File::open(filename)); + file_buf = + File::open(filename).map_err_context(|| "failed to open file".to_string())?; Box::new(file_buf) as Box }); if options.check { @@ -487,25 +504,24 @@ where } else { "+".to_string() }; - let gnu_re = crash_if_err!( - 1, - Regex::new(&format!( - r"^(?P[a-fA-F0-9]{}) (?P[ \*])(?P.*)", - modifier, - )) - ); - let bsd_re = crash_if_err!( - 1, - Regex::new(&format!( - r"^{algorithm} \((?P.*)\) = (?P[a-fA-F0-9]{digest_size})", - algorithm = options.algoname, - digest_size = modifier, - )) - ); + let gnu_re = Regex::new(&format!( + r"^(?P[a-fA-F0-9]{}) (?P[ \*])(?P.*)", + modifier, + )) + .map_err(|_| HashsumError::InvalidRegex)?; + let bsd_re = Regex::new(&format!( + r"^{algorithm} \((?P.*)\) = (?P[a-fA-F0-9]{digest_size})", + algorithm = options.algoname, + digest_size = modifier, + )) + .map_err(|_| HashsumError::InvalidRegex)?; let buffer = file; - for (i, line) in buffer.lines().enumerate() { - let line = crash_if_err!(1, line); + for (i, maybe_line) in buffer.lines().enumerate() { + let line = match maybe_line { + Ok(l) => l, + Err(e) => return Err(e.map_err_context(|| "failed to read file".to_string())), + }; let (ck_filename, sum, binary_check) = match gnu_re.captures(&line) { Some(caps) => ( caps.name("fileName").unwrap().as_str(), @@ -521,7 +537,7 @@ where None => { bad_format += 1; if options.strict { - return Err(1); + return Err(HashsumError::InvalidFormat.into()); } if options.warn { show_warning!( @@ -535,17 +551,16 @@ where } }, }; - let f = crash_if_err!(1, File::open(ck_filename)); + let f = File::open(ck_filename) + .map_err_context(|| "failed to open file".to_string())?; let mut ckf = BufReader::new(Box::new(f) as Box); - let real_sum = crash_if_err!( - 1, - digest_reader( - &mut options.digest, - &mut ckf, - binary_check, - options.output_bits - ) + let real_sum = digest_reader( + &mut options.digest, + &mut ckf, + binary_check, + options.output_bits, ) + .map_err_context(|| "failed to read input".to_string())? .to_ascii_lowercase(); // FIXME: Filenames with newlines should be treated specially. // GNU appears to replace newlines by \n and backslashes by @@ -568,15 +583,13 @@ where } } } else { - let sum = crash_if_err!( - 1, - digest_reader( - &mut options.digest, - &mut file, - options.binary, - options.output_bits - ) - ); + let sum = digest_reader( + &mut options.digest, + &mut file, + options.binary, + options.output_bits, + ) + .map_err_context(|| "failed to read input".to_string())?; if options.tag { println!("{} ({}) = {}", options.algoname, filename.display(), sum); } else { diff --git a/src/uu/head/BENCHMARKING.md b/src/uu/head/BENCHMARKING.md index 49574eb794b..29ce4c46b43 100644 --- a/src/uu/head/BENCHMARKING.md +++ b/src/uu/head/BENCHMARKING.md @@ -20,7 +20,7 @@ and most other parts of the world. This particular file has about 170,000 lines, each of which is no longer than 96 characters: - $ wc -lL shakespeare.txt + $ wc -lL shakespeare.txt 170592 96 shakespeare.txt You could use files of different shapes and sizes to test the diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index aa32a899a97..b46fc4c7820 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_head" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "head ~ (uutils) display the first lines of input" @@ -17,13 +17,12 @@ path = "src/head.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } memchr = "2" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["ringbuffer"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["ringbuffer"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "head" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index c56649742fb..08a09544683 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostid" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "hostid ~ (uutils) display the numeric identifier of the current host" @@ -17,8 +17,8 @@ path = "src/hostid.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "hostid" diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index daaeb48f73c..8fd9c3de88a 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostname" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "hostname ~ (uutils) display or set the host name of the current host" @@ -18,10 +18,13 @@ path = "src/hostname.rs" clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" hostname = { version = "0.3", features = ["set"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["wide"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["wide"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } winapi = { version="0.3", features=["sysinfoapi", "winsock2"] } [[bin]] name = "hostname" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs", "winapi"] diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index 0039cfc8e1c..2d1a57024c3 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_id" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "id ~ (uutils) display user and group information for USER" @@ -16,8 +16,8 @@ path = "src/id.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "process"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "process"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } selinux = { version="0.2.1", optional = true } [[bin]] diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 1229b577eb2..efe9a5d4e68 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -245,7 +245,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // GNU's `id` does not support the flags: -p/-P/-A. if matches.is_present(options::OPT_PASSWORD) { // BSD's `id` ignores all but the first specified user - pline(possible_pw.map(|v| v.uid())); + pline(possible_pw.as_ref().map(|v| v.uid)); return Ok(()); }; if matches.is_present(options::OPT_HUMAN_READABLE) { @@ -259,7 +259,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Ok(()); } - let (uid, gid) = possible_pw.map(|p| (p.uid(), p.gid())).unwrap_or(( + let (uid, gid) = possible_pw.as_ref().map(|p| (p.uid, p.gid)).unwrap_or(( if state.rflag { getuid() } else { geteuid() }, if state.rflag { getgid() } else { getegid() }, )); @@ -302,7 +302,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let groups = entries::get_groups_gnu(Some(gid)).unwrap(); let groups = if state.user_specified { - possible_pw.map(|p| p.belongs_to()).unwrap() + possible_pw.as_ref().map(|p| p.belongs_to()).unwrap() } else { groups.clone() }; @@ -453,7 +453,7 @@ pub fn uu_app() -> App<'static, 'static> { fn pretty(possible_pw: Option) { if let Some(p) = possible_pw { - print!("uid\t{}\ngroups\t", p.name()); + print!("uid\t{}\ngroups\t", p.name); println!( "{}", p.belongs_to() @@ -466,10 +466,10 @@ fn pretty(possible_pw: Option) { let login = cstr2cow!(getlogin() as *const _); let rid = getuid(); if let Ok(p) = Passwd::locate(rid) { - if login == p.name() { + if login == p.name { println!("login\t{}", login); } - println!("uid\t{}", p.name()); + println!("uid\t{}", p.name); } else { println!("uid\t{}", rid); } @@ -477,7 +477,7 @@ fn pretty(possible_pw: Option) { let eid = getegid(); if eid == rid { if let Ok(p) = Passwd::locate(eid) { - println!("euid\t{}", p.name()); + println!("euid\t{}", p.name); } else { println!("euid\t{}", eid); } @@ -486,7 +486,7 @@ fn pretty(possible_pw: Option) { let rid = getgid(); if rid != eid { if let Ok(g) = Group::locate(rid) { - println!("euid\t{}", g.name()); + println!("euid\t{}", g.name); } else { println!("euid\t{}", rid); } @@ -511,16 +511,16 @@ fn pline(possible_uid: Option) { println!( "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}", - pw.name(), - pw.user_passwd(), - pw.uid(), - pw.gid(), - pw.user_access_class(), - pw.passwd_change_time(), - pw.expiration(), - pw.user_info(), - pw.user_dir(), - pw.user_shell() + pw.name, + pw.user_passwd, + pw.uid, + pw.gid, + pw.user_access_class, + pw.passwd_change_time, + pw.expiration, + pw.user_info, + pw.user_dir, + pw.user_shell ); } @@ -531,13 +531,7 @@ fn pline(possible_uid: Option) { println!( "{}:{}:{}:{}:{}:{}:{}", - pw.name(), - pw.user_passwd(), - pw.uid(), - pw.gid(), - pw.user_info(), - pw.user_dir(), - pw.user_shell() + pw.name, pw.user_passwd, pw.uid, pw.gid, pw.user_info, pw.user_dir, pw.user_shell ); } diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 0ae11b3c46e..723ac459d1d 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_install" -version = "0.0.8" +version = "0.0.12" authors = [ "Ben Eills ", "uutils developers", @@ -22,8 +22,8 @@ clap = { version = "2.33", features = ["wrap_help"] } filetime = "0.2" file_diff = "1.0.0" libc = ">= 0.2" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs", "mode", "perms", "entries"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode", "perms", "entries"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [dev-dependencies] time = "0.1.40" diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index 1c140f5442d..667902bbf18 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_join" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "join ~ (uutils) merge lines from inputs with matching join fields" @@ -16,13 +16,12 @@ path = "src/join.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "join" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 51dfeec6f17..a338a22c4e6 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -13,8 +13,9 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::cmp::Ordering; use std::fs::File; -use std::io::{stdin, BufRead, BufReader, Lines, Stdin}; +use std::io::{stdin, stdout, BufRead, BufReader, Split, Stdin, Write}; use uucore::display::Quotable; +use uucore::error::{set_exit_code, UResult, USimpleError}; static NAME: &str = "join"; @@ -24,9 +25,16 @@ enum FileNum { File2, } +#[repr(u8)] +#[derive(Copy, Clone)] +enum LineEnding { + Nul = 0, + Newline = b'\n', +} + #[derive(Copy, Clone)] enum Sep { - Char(char), + Char(u8), Line, Whitespaces, } @@ -45,10 +53,11 @@ struct Settings { print_unpaired2: bool, print_joined: bool, ignore_case: bool, + line_ending: LineEnding, separator: Sep, autoformat: bool, format: Vec, - empty: String, + empty: Vec, check_order: CheckOrder, headers: bool, } @@ -62,10 +71,11 @@ impl Default for Settings { print_unpaired2: false, print_joined: true, ignore_case: false, + line_ending: LineEnding::Newline, separator: Sep::Whitespaces, autoformat: false, format: vec![], - empty: String::new(), + empty: vec![], check_order: CheckOrder::Default, headers: false, } @@ -74,14 +84,21 @@ impl Default for Settings { /// Output representation. struct Repr<'a> { - separator: char, + line_ending: LineEnding, + separator: u8, format: &'a [Spec], - empty: &'a str, + empty: &'a [u8], } impl<'a> Repr<'a> { - fn new(separator: char, format: &'a [Spec], empty: &'a str) -> Repr<'a> { + fn new( + line_ending: LineEnding, + separator: u8, + format: &'a [Spec], + empty: &'a [u8], + ) -> Repr<'a> { Repr { + line_ending, separator, format, empty, @@ -93,32 +110,34 @@ impl<'a> Repr<'a> { } /// Print the field or empty filler if the field is not set. - fn print_field(&self, field: Option<&str>) { + fn print_field(&self, field: Option<&Vec>) -> Result<(), std::io::Error> { let value = match field { Some(field) => field, None => self.empty, }; - print!("{}", value); + stdout().write_all(value) } /// Print each field except the one at the index. - fn print_fields(&self, line: &Line, index: usize) { + fn print_fields(&self, line: &Line, index: usize) -> Result<(), std::io::Error> { for i in 0..line.fields.len() { if i != index { - print!("{}{}", self.separator, line.fields[i]); + stdout().write_all(&[self.separator])?; + stdout().write_all(&line.fields[i])?; } } + Ok(()) } /// Print each field or the empty filler if the field is not set. - fn print_format(&self, f: F) + fn print_format(&self, f: F) -> Result<(), std::io::Error> where - F: Fn(&Spec) -> Option<&'a str>, + F: Fn(&Spec) -> Option<&'a Vec>, { for i in 0..self.format.len() { if i > 0 { - print!("{}", self.separator); + stdout().write_all(&[self.separator])?; } let field = match f(&self.format[i]) { @@ -126,8 +145,13 @@ impl<'a> Repr<'a> { None => self.empty, }; - print!("{}", field); + stdout().write_all(field)?; } + Ok(()) + } + + fn print_line_ending(&self) -> Result<(), std::io::Error> { + stdout().write_all(&[self.line_ending as u8]) } } @@ -147,10 +171,12 @@ impl Input { } } - fn compare(&self, field1: Option<&str>, field2: Option<&str>) -> Ordering { + fn compare(&self, field1: Option<&Vec>, field2: Option<&Vec>) -> Ordering { if let (Some(field1), Some(field2)) = (field1, field2) { if self.ignore_case { - field1.to_lowercase().cmp(&field2.to_lowercase()) + field1 + .to_ascii_lowercase() + .cmp(&field2.to_ascii_lowercase()) } else { field1.cmp(field2) } @@ -172,40 +198,55 @@ enum Spec { } impl Spec { - fn parse(format: &str) -> Spec { + fn parse(format: &str) -> UResult { let mut chars = format.chars(); let file_num = match chars.next() { Some('0') => { // Must be all alone without a field specifier. if chars.next().is_none() { - return Spec::Key; + return Ok(Spec::Key); } - - crash!(1, "invalid field specifier: {}", format.quote()); + return Err(USimpleError::new( + 1, + format!("invalid field specifier: {}", format.quote()), + )); } Some('1') => FileNum::File1, Some('2') => FileNum::File2, - _ => crash!(1, "invalid file number in field spec: {}", format.quote()), + _ => { + return Err(USimpleError::new( + 1, + format!("invalid file number in field spec: {}", format.quote()), + )); + } }; if let Some('.') = chars.next() { - return Spec::Field(file_num, parse_field_number(chars.as_str())); + return Ok(Spec::Field(file_num, parse_field_number(chars.as_str())?)); } - crash!(1, "invalid field specifier: {}", format.quote()); + Err(USimpleError::new( + 1, + format!("invalid field specifier: {}", format.quote()), + )) } } struct Line { - fields: Vec, + fields: Vec>, } impl Line { - fn new(string: String, separator: Sep) -> Line { + fn new(string: Vec, separator: Sep) -> Line { let fields = match separator { - Sep::Whitespaces => string.split_whitespace().map(String::from).collect(), - Sep::Char(sep) => string.split(sep).map(String::from).collect(), + Sep::Whitespaces => string + // GNU join uses Bourne shell field splitters by default + .split(|c| matches!(*c, b' ' | b'\t' | b'\n')) + .filter(|f| !f.is_empty()) + .map(Vec::from) + .collect(), + Sep::Char(sep) => string.split(|c| *c == sep).map(Vec::from).collect(), Sep::Line => vec![string], }; @@ -213,7 +254,7 @@ impl Line { } /// Get field at index. - fn get_field(&self, index: usize) -> Option<&str> { + fn get_field(&self, index: usize) -> Option<&Vec> { if index < self.fields.len() { Some(&self.fields[index]) } else { @@ -227,7 +268,7 @@ struct State<'a> { file_name: &'a str, file_num: FileNum, print_unpaired: bool, - lines: Lines>, + lines: Split>, seq: Vec, line_num: usize, has_failed: bool, @@ -239,6 +280,7 @@ impl<'a> State<'a> { name: &'a str, stdin: &'a Stdin, key: usize, + line_ending: LineEnding, print_unpaired: bool, ) -> State<'a> { let f = if name == "-" { @@ -255,7 +297,7 @@ impl<'a> State<'a> { file_name: name, file_num, print_unpaired, - lines: f.lines(), + lines: f.split(line_ending as u8), seq: Vec::new(), line_num: 0, has_failed: false, @@ -263,12 +305,13 @@ impl<'a> State<'a> { } /// Skip the current unpaired line. - fn skip_line(&mut self, input: &Input, repr: &Repr) { + fn skip_line(&mut self, input: &Input, repr: &Repr) -> Result<(), std::io::Error> { if self.print_unpaired { - self.print_first_line(repr); + self.print_first_line(repr)?; } self.reset_next_line(input); + Ok(()) } /// Keep reading line sequence until the key does not change, return @@ -288,20 +331,22 @@ impl<'a> State<'a> { } /// Print lines in the buffers as headers. - fn print_headers(&self, other: &State, repr: &Repr) { + fn print_headers(&self, other: &State, repr: &Repr) -> Result<(), std::io::Error> { if self.has_line() { if other.has_line() { - self.combine(other, repr); + self.combine(other, repr)?; } else { - self.print_first_line(repr); + self.print_first_line(repr)?; } } else if other.has_line() { - other.print_first_line(repr); + other.print_first_line(repr)?; } + + Ok(()) } /// Combine two line sequences. - fn combine(&self, other: &State, repr: &Repr) { + fn combine(&self, other: &State, repr: &Repr) -> Result<(), std::io::Error> { let key = self.get_current_key(); for line1 in &self.seq { @@ -320,16 +365,18 @@ impl<'a> State<'a> { None } - }); + })?; } else { - repr.print_field(key); - repr.print_fields(line1, self.key); - repr.print_fields(line2, other.key); + repr.print_field(key)?; + repr.print_fields(line1, self.key)?; + repr.print_fields(line2, other.key)?; } - println!(); + repr.print_line_ending()?; } } + + Ok(()) } /// Reset with the next line. @@ -366,14 +413,16 @@ impl<'a> State<'a> { 0 } - fn finalize(&mut self, input: &Input, repr: &Repr) { + fn finalize(&mut self, input: &Input, repr: &Repr) -> Result<(), std::io::Error> { if self.has_line() && self.print_unpaired { - self.print_first_line(repr); + self.print_first_line(repr)?; while let Some(line) = self.next_line(input) { - self.print_line(&line, repr); + self.print_line(&line, repr)?; } } + + Ok(()) } /// Get the next line without the order check. @@ -402,7 +451,7 @@ impl<'a> State<'a> { // This is fatal if the check is enabled. if input.check_order == CheckOrder::Enabled { - exit!(1); + std::process::exit(1); } self.has_failed = true; @@ -412,11 +461,11 @@ impl<'a> State<'a> { } /// Gets the key value of the lines stored in seq. - fn get_current_key(&self) -> Option<&str> { + fn get_current_key(&self) -> Option<&Vec> { self.seq[0].get_field(self.key) } - fn print_line(&self, line: &Line, repr: &Repr) { + fn print_line(&self, line: &Line, repr: &Repr) -> Result<(), std::io::Error> { if repr.uses_format() { repr.print_format(|spec| match *spec { Spec::Key => line.get_field(self.key), @@ -427,26 +476,27 @@ impl<'a> State<'a> { None } } - }); + })?; } else { - repr.print_field(line.get_field(self.key)); - repr.print_fields(line, self.key); + repr.print_field(line.get_field(self.key))?; + repr.print_fields(line, self.key)?; } - println!(); + repr.print_line_ending() } - fn print_first_line(&self, repr: &Repr) { - self.print_line(&self.seq[0], repr); + fn print_first_line(&self, repr: &Repr) -> Result<(), std::io::Error> { + self.print_line(&self.seq[0], repr) } } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().get_matches_from(args); - let keys = parse_field_number_option(matches.value_of("j")); - let key1 = parse_field_number_option(matches.value_of("1")); - let key2 = parse_field_number_option(matches.value_of("2")); + let keys = parse_field_number_option(matches.value_of("j"))?; + let key1 = parse_field_number_option(matches.value_of("1"))?; + let key2 = parse_field_number_option(matches.value_of("2"))?; let mut settings: Settings = Default::default(); @@ -459,21 +509,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap_or_default() .chain(matches.values_of("a").unwrap_or_default()); for file_num in unpaired { - match parse_file_number(file_num) { + match parse_file_number(file_num)? { FileNum::File1 => settings.print_unpaired1 = true, FileNum::File2 => settings.print_unpaired2 = true, } } settings.ignore_case = matches.is_present("i"); - settings.key1 = get_field_number(keys, key1); - settings.key2 = get_field_number(keys, key2); + settings.key1 = get_field_number(keys, key1)?; + settings.key2 = get_field_number(keys, key2)?; - if let Some(value) = matches.value_of("t") { + if let Some(value_str) = matches.value_of("t") { + let value = value_str.as_bytes(); settings.separator = match value.len() { 0 => Sep::Line, - 1 => Sep::Char(value.chars().next().unwrap()), - _ => crash!(1, "multi-character tab {}", value), + 1 => Sep::Char(value[0]), + _ => { + return Err(USimpleError::new( + 1, + format!("multi-character tab {}", value_str), + )) + } }; } @@ -481,15 +537,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if format == "auto" { settings.autoformat = true; } else { - settings.format = format - .split(|c| c == ' ' || c == ',' || c == '\t') - .map(Spec::parse) - .collect(); + let mut specs = vec![]; + for part in format.split(|c| c == ' ' || c == ',' || c == '\t') { + specs.push(Spec::parse(part)?); + } + settings.format = specs; } } if let Some(empty) = matches.value_of("e") { - settings.empty = empty.to_string(); + settings.empty = empty.as_bytes().to_vec(); } if matches.is_present("nocheck-order") { @@ -504,14 +561,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.headers = true; } + if matches.is_present("z") { + settings.line_ending = LineEnding::Nul; + } + let file1 = matches.value_of("file1").unwrap(); let file2 = matches.value_of("file2").unwrap(); if file1 == "-" && file2 == "-" { - crash!(1, "both files cannot be standard input"); + return Err(USimpleError::new(1, "both files cannot be standard input")); } - exec(file1, file2, settings) + match exec(file1, file2, settings) { + Ok(_) => Ok(()), + Err(e) => Err(USimpleError::new(1, format!("{}", e))), + } } pub fn uu_app() -> App<'static, 'static> { @@ -607,6 +671,12 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2", "treat the first line in each file as field headers, \ print them without trying to pair them", )) + .arg( + Arg::with_name("z") + .short("z") + .long("zero-terminated") + .help("line delimiter is NUL, not newline"), + ) .arg( Arg::with_name("file1") .required(true) @@ -621,7 +691,7 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2", ) } -fn exec(file1: &str, file2: &str, settings: Settings) -> i32 { +fn exec(file1: &str, file2: &str, settings: Settings) -> Result<(), std::io::Error> { let stdin = stdin(); let mut state1 = State::new( @@ -629,6 +699,7 @@ fn exec(file1: &str, file2: &str, settings: Settings) -> i32 { file1, &stdin, settings.key1, + settings.line_ending, settings.print_unpaired1, ); @@ -637,6 +708,7 @@ fn exec(file1: &str, file2: &str, settings: Settings) -> i32 { file2, &stdin, settings.key2, + settings.line_ending, settings.print_unpaired2, ); @@ -666,16 +738,17 @@ fn exec(file1: &str, file2: &str, settings: Settings) -> i32 { }; let repr = Repr::new( + settings.line_ending, match settings.separator { Sep::Char(sep) => sep, - _ => ' ', + _ => b' ', }, &format, &settings.empty, ); if settings.headers { - state1.print_headers(&state2, &repr); + state1.print_headers(&state2, &repr)?; state1.reset_read_line(&input); state2.reset_read_line(&input); } @@ -685,17 +758,17 @@ fn exec(file1: &str, file2: &str, settings: Settings) -> i32 { match diff { Ordering::Less => { - state1.skip_line(&input, &repr); + state1.skip_line(&input, &repr)?; } Ordering::Greater => { - state2.skip_line(&input, &repr); + state2.skip_line(&input, &repr)?; } Ordering::Equal => { let next_line1 = state1.extend(&input); let next_line2 = state2.extend(&input); if settings.print_joined { - state1.combine(&state2, &repr); + state1.combine(&state2, &repr)?; } state1.reset(next_line1); @@ -704,46 +777,61 @@ fn exec(file1: &str, file2: &str, settings: Settings) -> i32 { } } - state1.finalize(&input, &repr); - state2.finalize(&input, &repr); + state1.finalize(&input, &repr)?; + state2.finalize(&input, &repr)?; - (state1.has_failed || state2.has_failed) as i32 + if state1.has_failed || state2.has_failed { + set_exit_code(1); + } + Ok(()) } /// Check that keys for both files and for a particular file are not /// contradictory and return the key index. -fn get_field_number(keys: Option, key: Option) -> usize { +fn get_field_number(keys: Option, key: Option) -> UResult { if let Some(keys) = keys { if let Some(key) = key { if keys != key { // Show zero-based field numbers as one-based. - crash!(1, "incompatible join fields {}, {}", keys + 1, key + 1); + return Err(USimpleError::new( + 1, + format!("incompatible join fields {}, {}", keys + 1, key + 1), + )); } } - return keys; + return Ok(keys); } - key.unwrap_or(0) + Ok(key.unwrap_or(0)) } /// Parse the specified field string as a natural number and return /// the zero-based field number. -fn parse_field_number(value: &str) -> usize { +fn parse_field_number(value: &str) -> UResult { match value.parse::() { - Ok(result) if result > 0 => result - 1, - _ => crash!(1, "invalid field number: {}", value.quote()), + Ok(result) if result > 0 => Ok(result - 1), + _ => Err(USimpleError::new( + 1, + format!("invalid field number: {}", value.quote()), + )), } } -fn parse_file_number(value: &str) -> FileNum { +fn parse_file_number(value: &str) -> UResult { match value { - "1" => FileNum::File1, - "2" => FileNum::File2, - value => crash!(1, "invalid file number: {}", value.quote()), + "1" => Ok(FileNum::File1), + "2" => Ok(FileNum::File2), + value => Err(USimpleError::new( + 1, + format!("invalid file number: {}", value.quote()), + )), } } -fn parse_field_number_option(value: Option<&str>) -> Option { - Some(parse_field_number(value?)) +fn parse_field_number_option(value: Option<&str>) -> UResult> { + match value { + None => Ok(None), + Some(val) => Ok(Some(parse_field_number(val)?)), + } } diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index 452b0f407eb..c5c7c5eef23 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_kill" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "kill ~ (uutils) send a signal to a process" @@ -17,8 +17,8 @@ path = "src/kill.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["signals"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["signals"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "kill" diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index 275d3b5ccd6..ae49594966f 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_link" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "link ~ (uutils) create a hard (file system) link to FILE" @@ -16,8 +16,8 @@ path = "src/link.rs" [dependencies] libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } clap = { version = "2.33", features = ["wrap_help"] } [[bin]] @@ -25,5 +25,4 @@ name = "link" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 73e81b107e2..a54b7199944 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -4,14 +4,11 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. - -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::fs::hard_link; -use std::io::Error; use std::path::Path; +use uucore::display::Quotable; +use uucore::error::{FromIo, UResult}; static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1."; @@ -23,14 +20,8 @@ fn usage() -> String { format!("{0} FILE1 FILE2", uucore::execution_phrase()) } -pub fn normalize_error_message(e: Error) -> String { - match e.raw_os_error() { - Some(2) => String::from("No such file or directory (os error 2)"), - _ => format!("{}", e), - } -} - -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -41,13 +32,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let old = Path::new(files[0]); let new = Path::new(files[1]); - match hard_link(old, new) { - Ok(_) => 0, - Err(err) => { - show_error!("{}", normalize_error_message(err)); - 1 - } - } + hard_link(old, new) + .map_err_context(|| format!("cannot create link {} to {}", new.quote(), old.quote())) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/ln/Cargo.toml b/src/uu/ln/Cargo.toml index ba2c8de96e5..5857ca16b67 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ln" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "ln ~ (uutils) create a (file system) link to TARGET" @@ -17,8 +17,8 @@ path = "src/ln.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "ln" diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index e480d8f1365..6d91f6fb7a2 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -462,7 +462,7 @@ fn numbered_backup_path(path: &Path) -> PathBuf { } fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf { - let test_path = simple_backup_path(path, &".~1~".to_owned()); + let test_path = simple_backup_path(path, ".~1~"); if test_path.exists() { return numbered_backup_path(path); } diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index ed19ff8875c..230be58e093 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_logname" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "logname ~ (uutils) display the login name of the current user" @@ -17,9 +17,12 @@ path = "src/logname.rs" [dependencies] libc = "0.2.42" clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "logname" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 56866ff62ff..92775393207 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -12,11 +12,11 @@ #[macro_use] extern crate uucore; +use clap::{crate_version, App}; use std::ffi::CStr; +use uucore::error::UResult; use uucore::InvalidEncodingHandling; -use clap::{crate_version, App}; - extern "C" { // POSIX requires using getlogin (or equivalent code) pub fn getlogin() -> *const libc::c_char; @@ -39,7 +39,8 @@ fn usage() -> &'static str { uucore::execution_phrase() } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -51,7 +52,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { None => show_error!("no login name"), } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index b918f65011e..f60d0f9baac 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ls" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "ls ~ (uutils) display directory contents" @@ -21,10 +21,10 @@ unicode-width = "0.1.8" number_prefix = "0.4" term_grid = "0.1.5" termsize = "0.1.6" -globset = "0.4.6" +glob = "0.3.0" lscolors = { version = "0.7.1", features = ["ansi_term"] } uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore", features = ["entries", "fs"] } -uucore_procs = { version=">=0.0.7", package = "uucore_procs", path = "../../uucore_procs" } +uucore_procs = { version=">=0.0.8", package = "uucore_procs", path = "../../uucore_procs" } once_cell = "1.7.2" atty = "0.2" selinux = { version="0.2.1", optional = true } @@ -40,5 +40,4 @@ path = "src/main.rs" feat_selinux = ["selinux"] [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 3fa3b4f8e6e..4cacf3b8b4c 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -16,7 +16,7 @@ extern crate lazy_static; mod quoting_style; use clap::{crate_version, App, Arg}; -use globset::{self, Glob, GlobSet, GlobSetBuilder}; +use glob::Pattern; use lscolors::LsColors; use number_prefix::NumberPrefix; use once_cell::unsync::OnceCell; @@ -29,7 +29,7 @@ use std::{ error::Error, fmt::Display, fs::{self, DirEntry, FileType, Metadata}, - io::{stdout, BufWriter, Stdout, Write}, + io::{stdout, BufWriter, ErrorKind, Stdout, Write}, path::{Path, PathBuf}, time::{SystemTime, UNIX_EPOCH}, }; @@ -142,14 +142,16 @@ const DEFAULT_TERM_WIDTH: u16 = 80; #[derive(Debug)] enum LsError { InvalidLineWidth(String), - NoMetadata(PathBuf), + IOError(std::io::Error), + IOErrorContext(std::io::Error, PathBuf), } impl UError for LsError { fn code(&self) -> i32 { match self { LsError::InvalidLineWidth(_) => 2, - LsError::NoMetadata(_) => 1, + LsError::IOError(_) => 1, + LsError::IOErrorContext(_, _) => 1, } } } @@ -160,7 +162,57 @@ impl Display for LsError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { LsError::InvalidLineWidth(s) => write!(f, "invalid line width: {}", s.quote()), - LsError::NoMetadata(p) => write!(f, "could not open file: {}", p.quote()), + LsError::IOError(e) => write!(f, "general io error: {}", e), + LsError::IOErrorContext(e, p) => { + let error_kind = e.kind(); + let raw_os_error = e.raw_os_error().unwrap_or(13i32); + + match error_kind { + // No such file or directory + ErrorKind::NotFound => { + write!( + f, + "cannot access '{}': No such file or directory", + p.to_string_lossy(), + ) + } + // Permission denied and Operation not permitted + ErrorKind::PermissionDenied => + { + #[allow(clippy::wildcard_in_or_patterns)] + match raw_os_error { + 1i32 => { + write!( + f, + "cannot access '{}': Operation not permitted", + p.to_string_lossy(), + ) + } + 13i32 | _ => { + if p.is_dir() { + write!( + f, + "cannot open directory '{}': Permission denied", + p.to_string_lossy(), + ) + } else { + write!( + f, + "cannot open file '{}': Permission denied", + p.to_string_lossy(), + ) + } + } + } + } + _ => write!( + f, + "unknown io error: '{:?}', '{:?}'", + p.to_string_lossy(), + e + ), + } + } } } } @@ -174,6 +226,7 @@ enum Format { Commas, } +#[derive(PartialEq, Eq)] enum Sort { None, Name, @@ -233,7 +286,7 @@ struct Config { recursive: bool, reverse: bool, dereference: Dereference, - ignore_patterns: GlobSet, + ignore_patterns: Vec, size_format: SizeFormat, directory: bool, time: Time, @@ -259,11 +312,17 @@ struct LongFormat { } struct PaddingCollection { + #[cfg(unix)] + longest_inode_len: usize, longest_link_count_len: usize, longest_uname_len: usize, longest_group_len: usize, longest_context_len: usize, longest_size_len: usize, + #[cfg(unix)] + longest_major_len: usize, + #[cfg(unix)] + longest_minor_len: usize, } impl Config { @@ -547,16 +606,18 @@ impl Config { } else { TimeStyle::Locale }; - let mut ignore_patterns = GlobSetBuilder::new(); + + let mut ignore_patterns: Vec = Vec::new(); + if options.is_present(options::IGNORE_BACKUPS) { - ignore_patterns.add(Glob::new("*~").unwrap()); - ignore_patterns.add(Glob::new(".*~").unwrap()); + ignore_patterns.push(Pattern::new("*~").unwrap()); + ignore_patterns.push(Pattern::new(".*~").unwrap()); } for pattern in options.values_of(options::IGNORE).into_iter().flatten() { - match Glob::new(pattern) { + match Pattern::new(pattern) { Ok(p) => { - ignore_patterns.add(p); + ignore_patterns.push(p); } Err(_) => show_warning!("Invalid pattern for ignore: {}", pattern.quote()), } @@ -564,21 +625,15 @@ impl Config { if files == Files::Normal { for pattern in options.values_of(options::HIDE).into_iter().flatten() { - match Glob::new(pattern) { + match Pattern::new(pattern) { Ok(p) => { - ignore_patterns.add(p); + ignore_patterns.push(p); } Err(_) => show_warning!("Invalid pattern for hide: {}", pattern.quote()), } } } - if files == Files::Normal { - ignore_patterns.add(Glob::new(".*").unwrap()); - } - - let ignore_patterns = ignore_patterns.build().unwrap(); - let dereference = if options.is_present(options::dereference::ALL) { Dereference::All } else if options.is_present(options::dereference::ARGS) { @@ -637,6 +692,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = app.get_matches_from(args); let config = Config::from(&matches)?; + let locs = matches .values_of_os(options::PATHS) .map(|v| v.map(Path::new).collect()) @@ -1111,6 +1167,9 @@ only ignore '.' and '..'.", .long(options::COLOR) .help("Color output based on file type.") .takes_value(true) + .possible_values(&[ + "always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none", + ]) .require_equals(true) .min_values(0), ) @@ -1206,6 +1265,7 @@ only ignore '.' and '..'.", /// Represents a Path along with it's associated data /// Any data that will be reused several times makes sense to be added to this structure /// Caching data here helps eliminate redundant syscalls to fetch same information +#[derive(Debug)] struct PathData { // Result got from symlink_metadata() or metadata() based on config md: OnceCell>, @@ -1254,6 +1314,7 @@ impl PathData { } Dereference::None => false, }; + let ft = match file_type { Some(ft) => OnceCell::from(ft.ok()), None => OnceCell::new(), @@ -1275,15 +1336,24 @@ impl PathData { } } - fn md(&self) -> Option<&Metadata> { + fn md(&self, out: &mut BufWriter) -> Option<&Metadata> { self.md - .get_or_init(|| get_metadata(&self.p_buf, self.must_dereference).ok()) + .get_or_init( + || match get_metadata(self.p_buf.as_path(), self.must_dereference) { + Err(err) => { + let _ = out.flush(); + show!(LsError::IOErrorContext(err, self.p_buf.clone(),)); + None + } + Ok(md) => Some(md), + }, + ) .as_ref() } - fn file_type(&self) -> Option<&FileType> { + fn file_type(&self, out: &mut BufWriter) -> Option<&FileType> { self.ft - .get_or_init(|| self.md().map(|md| md.file_type())) + .get_or_init(|| self.md(out).map(|md| md.file_type())) .as_ref() } } @@ -1291,26 +1361,23 @@ impl PathData { fn list(locs: Vec<&Path>, config: Config) -> UResult<()> { let mut files = Vec::::new(); let mut dirs = Vec::::new(); - let mut out = BufWriter::new(stdout()); + let initial_locs_len = locs.len(); - for loc in &locs { - let p = PathBuf::from(loc); - let path_data = PathData::new(p, None, None, &config, true); + for loc in locs { + let path_data = PathData::new(PathBuf::from(loc), None, None, &config, true); - if path_data.md().is_none() { - // FIXME: Would be nice to use the actual error instead of hardcoding it - // Presumably other errors can happen too? - show_error!( - "cannot access {}: No such file or directory", - path_data.p_buf.quote() - ); - set_exit_code(1); - // We found an error, no need to continue the execution + // Getting metadata here is no big deal as it's just the CWD + // and we really just want to know if the strings exist as files/dirs + // + // Proper GNU handling is don't show if dereferenced symlink DNE + // but only for the base dir, for a child dir show, and print ?s + // in long format + if path_data.md(&mut out).is_none() { continue; } - let show_dir_contents = match path_data.file_type() { + let show_dir_contents = match path_data.file_type(&mut out) { Some(ft) => !config.directory && ft.is_dir(), None => { set_exit_code(1); @@ -1324,33 +1391,37 @@ fn list(locs: Vec<&Path>, config: Config) -> UResult<()> { files.push(path_data); } } - sort_entries(&mut files, &config); + + sort_entries(&mut files, &config, &mut out); + sort_entries(&mut dirs, &config, &mut out); + display_items(&files, &config, &mut out); - sort_entries(&mut dirs, &config); - for dir in dirs { - if locs.len() > 1 || config.recursive { - // FIXME: This should use the quoting style and propagate errors - let _ = writeln!(out, "\n{}:", dir.p_buf.display()); + for (pos, dir) in dirs.iter().enumerate() { + // Print dir heading - name... 'total' comes after error display + if initial_locs_len > 1 || config.recursive { + if pos.eq(&0usize) && files.is_empty() { + let _ = writeln!(out, "{}:", dir.p_buf.display()); + } else { + let _ = writeln!(out, "\n{}:", dir.p_buf.display()); + } } - enter_directory(&dir, &config, &mut out); + enter_directory(dir, &config, &mut out); } Ok(()) } -fn sort_entries(entries: &mut Vec, config: &Config) { +fn sort_entries(entries: &mut Vec, config: &Config, out: &mut BufWriter) { match config.sort { Sort::Time => entries.sort_by_key(|k| { Reverse( - k.md() + k.md(out) .and_then(|md| get_system_time(md, config)) .unwrap_or(UNIX_EPOCH), ) }), - Sort::Size => { - entries.sort_by_key(|k| Reverse(k.md().as_ref().map(|md| md.len()).unwrap_or(0))) - } + Sort::Size => 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 @@ -1369,53 +1440,100 @@ fn sort_entries(entries: &mut Vec, config: &Config) { } } -#[cfg(windows)] fn is_hidden(file_path: &DirEntry) -> bool { - let path = file_path.path(); - let metadata = fs::metadata(&path).unwrap_or_else(|_| fs::symlink_metadata(&path).unwrap()); - let attr = metadata.file_attributes(); - (attr & 0x2) > 0 + #[cfg(windows)] + { + let metadata = file_path.metadata().unwrap(); + let attr = metadata.file_attributes(); + (attr & 0x2) > 0 + } + #[cfg(not(windows))] + { + file_path + .file_name() + .to_str() + .map(|res| res.starts_with('.')) + .unwrap_or(false) + } } fn should_display(entry: &DirEntry, config: &Config) -> bool { - let ffi_name = entry.file_name(); + // check if hidden + if config.files == Files::Normal && is_hidden(entry) { + return false; + } - // For unix, the hidden files are already included in the ignore pattern - #[cfg(windows)] - { - if config.files == Files::Normal && is_hidden(entry) { + // check if explicitly ignored + for pattern in &config.ignore_patterns { + if pattern.matches(entry.file_name().to_str().unwrap()) { return false; - } + }; + continue; } - !config.ignore_patterns.is_match(&ffi_name) + // else default to display + true } fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) { - let mut entries: Vec<_> = if config.files == Files::All { + // Create vec of entries with initial dot files + let mut entries: Vec = if config.files == Files::All { vec![ - PathData::new( - dir.p_buf.clone(), - Some(Ok(*dir.file_type().unwrap())), - Some(".".into()), - config, - false, - ), + PathData::new(dir.p_buf.clone(), None, Some(".".into()), config, false), PathData::new(dir.p_buf.join(".."), None, Some("..".into()), config, false), ] } else { vec![] }; - let mut temp: Vec<_> = crash_if_err!(1, fs::read_dir(&dir.p_buf)) - .map(|res| crash_if_err!(1, res)) - .filter(|e| should_display(e, config)) - .map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), None, config, false)) - .collect(); + // Convert those entries to the PathData struct + let mut vec_path_data = Vec::new(); - sort_entries(&mut temp, config); + // check for errors early, and ignore entries with errors + let read_dir = match fs::read_dir(&dir.p_buf) { + Err(err) => { + // flush buffer because the error may get printed in the wrong order + let _ = out.flush(); + show!(LsError::IOErrorContext(err, dir.p_buf.clone())); + return; + } + Ok(res) => res, + }; - entries.append(&mut temp); + for entry in read_dir { + let unwrapped = match entry { + Ok(path) => path, + Err(err) => { + let _ = out.flush(); + show!(LsError::IOError(err)); + continue; + } + }; + + if should_display(&unwrapped, config) { + // why check the DirEntry file_type()? B/c the call is + // nearly free compared to a metadata() or file_type() call on a dir/file + let path_data = match unwrapped.file_type() { + Err(err) => { + let _ = out.flush(); + show!(LsError::IOErrorContext(err, unwrapped.path())); + continue; + } + Ok(dir_ft) => { + PathData::new(unwrapped.path(), Some(Ok(dir_ft)), None, config, false) + } + }; + vec_path_data.push(path_data); + }; + } + + sort_entries(&mut vec_path_data, config, out); + entries.append(&mut vec_path_data); + + // ...and total + if config.format == Format::Long { + display_total(&entries, config, out); + } display_items(&entries, config, out); @@ -1423,7 +1541,10 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) for e in entries .iter() .skip(if config.files == Files::All { 2 } else { 0 }) - .filter(|p| p.file_type().map(|ft| ft.is_dir()).unwrap_or(false)) + // Already requested file_type for the dir_entries above. So we know the OnceCell is set. + // And can unwrap again because we tested whether path has is_some here + .filter(|p| p.ft.get().unwrap().is_some()) + .filter(|p| p.ft.get().unwrap().unwrap().is_dir()) { let _ = writeln!(out, "\n{}:", e.p_buf.display()); enter_directory(e, config, out); @@ -1431,25 +1552,40 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) } } -fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result { +fn get_metadata(p_buf: &Path, dereference: bool) -> std::io::Result { if dereference { - entry.metadata() + p_buf.metadata() } else { - entry.symlink_metadata() + p_buf.symlink_metadata() } } -fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize, usize, usize) { +fn display_dir_entry_size( + entry: &PathData, + config: &Config, + out: &mut BufWriter, +) -> (usize, usize, usize, usize, usize, usize, usize) { // TODO: Cache/memorize the display_* results so we don't have to recalculate them. - if let Some(md) = entry.md() { + if let Some(md) = entry.md(out) { + let (size_len, major_len, minor_len) = match display_size_or_rdev(md, config) { + SizeOrDeviceId::Device(major, minor) => ( + (major.len() + minor.len() + 2usize), + major.len(), + minor.len(), + ), + SizeOrDeviceId::Size(size) => (size.len(), 0usize, 0usize), + }; ( display_symlink_count(md).len(), display_uname(md, config).len(), display_group(md, config).len(), - display_size_or_rdev(md, config).len(), + size_len, + major_len, + minor_len, + display_inode(md).len(), ) } else { - (0, 0, 0, 0) + (0, 0, 0, 0, 0, 0, 0) } } @@ -1461,12 +1597,36 @@ fn pad_right(string: &str, count: usize) -> String { format!("{:) { + let mut total_size = 0; + for item in items { + total_size += item + .md(out) + .as_ref() + .map_or(0, |md| get_block_size(md, config)); + } + let _ = writeln!(out, "total {}", display_size(total_size, config)); +} + fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter) { // `-Z`, `--context`: // Display the SELinux security context or '?' if none is found. When used with the `-l` // option, print the security context to the left of the size column. if config.format == Format::Long { + #[cfg(unix)] + let ( + mut longest_inode_len, + mut longest_link_count_len, + mut longest_uname_len, + mut longest_group_len, + mut longest_context_len, + mut longest_size_len, + mut longest_major_len, + mut longest_minor_len, + ) = (1, 1, 1, 1, 1, 1, 1, 1); + + #[cfg(not(unix))] let ( mut longest_link_count_len, mut longest_uname_len, @@ -1474,36 +1634,68 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter 0 { - let _ = writeln!(out, "total {}", display_size(total_size, config)); + #[cfg(not(unix))] + for item in items { + let context_len = item.security_context.len(); + let ( + link_count_len, + uname_len, + group_len, + size_len, + _major_len, + _minor_len, + _inode_len, + ) = display_dir_entry_size(item, config, out); + longest_link_count_len = link_count_len.max(longest_link_count_len); + longest_uname_len = uname_len.max(longest_uname_len); + longest_group_len = group_len.max(longest_group_len); + if config.context { + longest_context_len = context_len.max(longest_context_len); + } + longest_size_len = size_len.max(longest_size_len); } for item in items { display_item_long( item, PaddingCollection { + #[cfg(unix)] + longest_inode_len, longest_link_count_len, longest_uname_len, longest_group_len, longest_context_len, longest_size_len, + #[cfg(unix)] + longest_major_len, + #[cfg(unix)] + longest_minor_len, }, config, out, @@ -1520,9 +1712,28 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter = items .iter() - .filter_map(|i| display_file_name(i, config, prefix_context)); + .map(|i| display_file_name(i, config, prefix_context, longest_inode_len, out)) + .collect::>() + .into_iter(); match config.format { Format::Columns => display_grid(names, config.width, Direction::TopToBottom, out), @@ -1651,85 +1862,162 @@ fn display_grid( /// longest_size_len: usize, /// ``` /// that decide the maximum possible character count of each field. +#[allow(clippy::write_literal)] fn display_item_long( item: &PathData, padding: PaddingCollection, config: &Config, out: &mut BufWriter, ) { - let md = match item.md() { - None => { - show!(LsError::NoMetadata(item.p_buf.clone())); - return; - } - Some(md) => md, - }; - - #[cfg(unix)] - { - if config.inode { - let _ = write!(out, "{} ", get_inode(md)); + if let Some(md) = item.md(out) { + #[cfg(unix)] + { + if config.inode { + let _ = write!( + out, + "{} ", + pad_left(&get_inode(md), padding.longest_inode_len), + ); + } } - } - - let _ = write!( - out, - "{}{} {}", - display_permissions(md, true), - if item.security_context.len() > 1 { - // GNU `ls` uses a "." character to indicate a file with a security context, - // but not other alternate access method. - "." - } else { - "" - }, - pad_left(&display_symlink_count(md), padding.longest_link_count_len), - ); - if config.long.owner { let _ = write!( out, - " {}", - pad_right(&display_uname(md, config), padding.longest_uname_len) + "{}{} {}", + display_permissions(md, true), + if item.security_context.len() > 1 { + // GNU `ls` uses a "." character to indicate a file with a security context, + // but not other alternate access method. + "." + } else { + "" + }, + pad_left(&display_symlink_count(md), padding.longest_link_count_len), ); - } - if config.long.group { - let _ = write!( - out, - " {}", - pad_right(&display_group(md, config), padding.longest_group_len) - ); - } + if config.long.owner { + let _ = write!( + out, + " {}", + pad_right(&display_uname(md, config), padding.longest_uname_len), + ); + } + + if config.long.group { + let _ = write!( + out, + " {}", + pad_right(&display_group(md, config), padding.longest_group_len), + ); + } + + if config.context { + let _ = write!( + out, + " {}", + pad_right(&item.security_context, padding.longest_context_len), + ); + } + + // Author is only different from owner on GNU/Hurd, so we reuse + // the owner, since GNU/Hurd is not currently supported by Rust. + if config.long.author { + let _ = write!( + out, + " {}", + pad_right(&display_uname(md, config), padding.longest_uname_len), + ); + } + + match display_size_or_rdev(md, config) { + SizeOrDeviceId::Size(size) => { + let _ = write!(out, " {}", pad_left(&size, padding.longest_size_len),); + } + SizeOrDeviceId::Device(major, minor) => { + let _ = write!( + out, + " {}, {}", + pad_left( + &major, + #[cfg(not(unix))] + 0usize, + #[cfg(unix)] + padding.longest_major_len.max( + padding + .longest_size_len + .saturating_sub(padding.longest_minor_len.saturating_add(2usize)) + ) + ), + pad_left( + &minor, + #[cfg(not(unix))] + 0usize, + #[cfg(unix)] + padding.longest_minor_len, + ), + ); + } + }; + + let dfn = display_file_name(item, config, None, 0, out).contents; + + let _ = writeln!(out, " {} {}", display_date(md, config), dfn); + } else { + // this 'else' is expressly for the case of a dangling symlink/restricted file + #[cfg(unix)] + { + if config.inode { + let _ = write!(out, "{} ", pad_left("?", padding.longest_inode_len),); + } + } - if config.context { let _ = write!( out, - " {}", - pad_right(&item.security_context, padding.longest_context_len) + "{}{} {}", + "l?????????", + if item.security_context.len() > 1 { + // GNU `ls` uses a "." character to indicate a file with a security context, + // but not other alternate access method. + "." + } else { + "" + }, + pad_left("?", padding.longest_link_count_len), ); - } - // Author is only different from owner on GNU/Hurd, so we reuse - // the owner, since GNU/Hurd is not currently supported by Rust. - if config.long.author { - let _ = write!( + if config.long.owner { + let _ = write!(out, " {}", pad_right("?", padding.longest_uname_len)); + } + + if config.long.group { + let _ = write!(out, " {}", pad_right("?", padding.longest_group_len)); + } + + if config.context { + let _ = write!( + out, + " {}", + pad_right(&item.security_context, padding.longest_context_len) + ); + } + + // Author is only different from owner on GNU/Hurd, so we reuse + // the owner, since GNU/Hurd is not currently supported by Rust. + if config.long.author { + let _ = write!(out, " {}", pad_right("?", padding.longest_uname_len)); + } + + let dfn = display_file_name(item, config, None, 0, out).contents; + let date_len = 12; + + let _ = writeln!( out, - " {}", - pad_right(&display_uname(md, config), padding.longest_uname_len) + " {} {} {}", + pad_left("?", padding.longest_size_len), + pad_left("?", date_len), + dfn, ); } - - let _ = writeln!( - out, - " {} {} {}", - pad_left(&display_size_or_rdev(md, config), padding.longest_size_len), - display_date(md, config), - // unwrap is fine because it fails when metadata is not available - // but we already know that it is because it's checked at the - // start of the function. - display_file_name(item, config, None).unwrap().contents, - ); } #[cfg(unix)] @@ -1883,19 +2171,35 @@ fn format_prefixed(prefixed: NumberPrefix) -> String { } } -fn display_size_or_rdev(metadata: &Metadata, config: &Config) -> String { - #[cfg(unix)] +#[allow(dead_code)] +enum SizeOrDeviceId { + Size(String), + Device(String, String), +} + +fn display_size_or_rdev(metadata: &Metadata, config: &Config) -> SizeOrDeviceId { + #[cfg(any(target_os = "macos", target_os = "ios"))] + { + let ft = metadata.file_type(); + if ft.is_char_device() || ft.is_block_device() { + let dev: u64 = metadata.rdev(); + let major = (dev >> 24) as u8; + let minor = (dev & 0xff) as u8; + return SizeOrDeviceId::Device(major.to_string(), minor.to_string()); + } + } + #[cfg(target_os = "linux")] { let ft = metadata.file_type(); if ft.is_char_device() || ft.is_block_device() { let dev: u64 = metadata.rdev(); let major = (dev >> 8) as u8; - let minor = dev as u8; - return format!("{}, {}", major, minor); + let minor = (dev & 0xff) as u8; + return SizeOrDeviceId::Device(major.to_string(), minor.to_string()); } } - display_size(metadata.len(), config) + SizeOrDeviceId::Size(display_size(metadata.len(), config)) } fn display_size(size: u64, config: &Config) -> String { @@ -1918,8 +2222,8 @@ fn file_is_executable(md: &Metadata) -> bool { md.mode() & ((S_IXUSR | S_IXGRP | S_IXOTH) as u32) != 0 } -fn classify_file(path: &PathData) -> Option { - let file_type = path.file_type()?; +fn classify_file(path: &PathData, out: &mut BufWriter) -> Option { + let file_type = path.file_type(out)?; if file_type.is_dir() { Some('/') @@ -1932,7 +2236,7 @@ fn classify_file(path: &PathData) -> Option { Some('=') } else if file_type.is_fifo() { Some('|') - } else if file_type.is_file() && file_is_executable(path.md()?) { + } else if file_type.is_file() && file_is_executable(path.md(out).as_ref().unwrap()) { Some('*') } else { None @@ -1956,31 +2260,43 @@ fn classify_file(path: &PathData) -> Option { /// /// Note that non-unicode sequences in symlink targets are dealt with using /// [`std::path::Path::to_string_lossy`]. +#[allow(unused_variables)] fn display_file_name( path: &PathData, config: &Config, prefix_context: Option, -) -> Option { + longest_inode_len: usize, + out: &mut BufWriter, +) -> Cell { // This is our return value. We start by `&path.display_name` and modify it along the way. let mut name = escape_name(&path.display_name, &config.quoting_style); - #[cfg(unix)] - { - if config.format != Format::Long && config.inode { - name = path.md().map_or_else(|| "?".to_string(), get_inode) + " " + &name; - } - } - // We need to keep track of the width ourselves instead of letting term_grid // infer it because the color codes mess up term_grid's width calculation. let mut width = name.width(); if let Some(ls_colors) = &config.color { - name = color_name(ls_colors, &path.p_buf, name, path.md()?); + if let Ok(metadata) = path.p_buf.symlink_metadata() { + name = color_name(ls_colors, &path.p_buf, name, &metadata); + } + } + + #[cfg(unix)] + { + if config.inode && config.format != Format::Long { + let inode = match path.md(out) { + Some(md) => pad_left(&get_inode(md), longest_inode_len), + None => pad_left("?", longest_inode_len), + }; + // increment width here b/c name was given colors and name.width() is now the wrong + // size for display + width += inode.width(); + name = inode + " " + &name; + } } if config.indicator_style != IndicatorStyle::None { - let sym = classify_file(path); + let sym = classify_file(path, out); let char_opt = match config.indicator_style { IndicatorStyle::Classify => sym, @@ -2007,7 +2323,10 @@ fn display_file_name( } } - if config.format == Format::Long && path.file_type()?.is_symlink() { + if config.format == Format::Long + && path.file_type(out).is_some() + && path.file_type(out).unwrap().is_symlink() + { if let Ok(target) = path.p_buf.read_link() { name.push_str(" -> "); @@ -2030,17 +2349,29 @@ fn display_file_name( // Because we use an absolute path, we can assume this is guaranteed to exist. // Otherwise, we use path.md(), which will guarantee we color to the same // color of non-existent symlinks according to style_for_path_with_metadata. - let target_metadata = match target_data.md() { - Some(md) => md, - None => path.md()?, - }; + if path.md(out).is_none() + && get_metadata(target_data.p_buf.as_path(), target_data.must_dereference) + .is_err() + { + name.push_str(&path.p_buf.read_link().unwrap().to_string_lossy()); + } else { + // Use fn get_metadata instead of md() here and above because ls + // should not exit with an err, if we are unable to obtain the target_metadata + let target_metadata = match get_metadata( + target_data.p_buf.as_path(), + target_data.must_dereference, + ) { + Ok(md) => md, + Err(_) => path.md(out).unwrap().to_owned(), + }; - name.push_str(&color_name( - ls_colors, - &target_data.p_buf, - target.to_string_lossy().into_owned(), - target_metadata, - )); + name.push_str(&color_name( + ls_colors, + &target_data.p_buf, + target.to_string_lossy().into_owned(), + &target_metadata, + )); + } } else { // If no coloring is required, we just use target as is. name.push_str(&target.to_string_lossy()); @@ -2062,10 +2393,10 @@ fn display_file_name( } } - Some(Cell { + Cell { contents: name, width, - }) + } } fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String { @@ -2087,6 +2418,18 @@ fn display_symlink_count(metadata: &Metadata) -> String { metadata.nlink().to_string() } +#[allow(unused_variables)] +fn display_inode(metadata: &Metadata) -> String { + #[cfg(unix)] + { + get_inode(metadata) + } + #[cfg(not(unix))] + { + "".to_string() + } +} + // This returns the SELinux security context as UTF8 `String`. // In the long term this should be changed to `OsStr`, see discussions at #2621/#2656 #[allow(unused_variables)] @@ -2095,7 +2438,7 @@ fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) - if config.selinux_supported { #[cfg(feature = "selinux")] { - match selinux::SecurityContext::of_path(p_buf, must_dereference, false) { + match selinux::SecurityContext::of_path(p_buf, must_dereference.to_owned(), false) { Err(_r) => { // TODO: show the actual reason why it failed show_warning!("failed to get security context of: {}", p_buf.quote()); @@ -2103,11 +2446,9 @@ fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) - } Ok(None) => substitute_string, Ok(Some(context)) => { - let mut context = context.as_bytes(); - if context.ends_with(&[0]) { - // TODO: replace with `strip_prefix()` when MSRV >= 1.51 - context = &context[..context.len() - 1] - }; + let context = context.as_bytes(); + + let context = context.strip_suffix(&[0]).unwrap_or(context); String::from_utf8(context.to_vec()).unwrap_or_else(|e| { show_warning!( "getting security context of: {}: {}", diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index c0e6586ab1c..e40ef8769c9 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkdir" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "mkdir ~ (uutils) create DIRECTORY" @@ -17,8 +17,8 @@ path = "src/mkdir.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs", "mode"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "mkdir" diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index acf38f6bc84..5ca37dc65c9 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkfifo" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "mkfifo ~ (uutils) create FIFOs (named pipes)" @@ -17,9 +17,12 @@ path = "src/mkfifo.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "mkfifo" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 66c3f7bb6c9..dfb595a727b 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -11,6 +11,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use libc::mkfifo; use std::ffi::CString; +use uucore::error::{UResult, USimpleError}; use uucore::{display::Quotable, InvalidEncodingHandling}; static NAME: &str = "mkfifo"; @@ -24,7 +25,8 @@ mod options { pub static FIFO: &str = "fifo"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -32,41 +34,39 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = uu_app().get_matches_from(args); if matches.is_present(options::CONTEXT) { - crash!(1, "--context is not implemented"); + return Err(USimpleError::new(1, "--context is not implemented")); } if matches.is_present(options::SE_LINUX_SECURITY_CONTEXT) { - crash!(1, "-Z is not implemented"); + return Err(USimpleError::new(1, "-Z is not implemented")); } let mode = match matches.value_of(options::MODE) { Some(m) => match usize::from_str_radix(m, 8) { Ok(m) => m, - Err(e) => { - show_error!("invalid mode: {}", e); - return 1; - } + Err(e) => return Err(USimpleError::new(1, format!("invalid mode: {}", e))), }, None => 0o666, }; let fifos: Vec = match matches.values_of(options::FIFO) { Some(v) => v.clone().map(|s| s.to_owned()).collect(), - None => crash!(1, "missing operand"), + None => return Err(USimpleError::new(1, "missing operand")), }; - let mut exit_code = 0; for f in fifos { let err = unsafe { let name = CString::new(f.as_bytes()).unwrap(); mkfifo(name.as_ptr(), mode as libc::mode_t) }; if err == -1 { - show_error!("cannot create fifo {}: File exists", f.quote()); - exit_code = 1; + show!(USimpleError::new( + 1, + format!("cannot create fifo {}: File exists", f.quote()) + )); } } - exit_code + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 046e4b8d0a3..ec59841292e 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mknod" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "mknod ~ (uutils) create special file NAME of TYPE" @@ -18,9 +18,12 @@ path = "src/mknod.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "^0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["mode"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["mode"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "mknod" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index dd529c3bad7..b93716a63c1 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -7,9 +7,6 @@ // spell-checker:ignore (ToDO) parsemode makedev sysmacros perror IFBLK IFCHR IFIFO -#[macro_use] -extern crate uucore; - use std::ffi::CString; use clap::{crate_version, App, Arg, ArgMatches}; @@ -17,6 +14,7 @@ use libc::{dev_t, mode_t}; use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; use uucore::display::Quotable; +use uucore::error::{set_exit_code, UResult, USimpleError, UUsageError}; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Create the special file NAME of the given TYPE."; @@ -81,8 +79,8 @@ fn _mknod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 { } } -#[allow(clippy::cognitive_complexity)] -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -92,13 +90,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = uu_app().get_matches_from(args); - let mode = match get_mode(&matches) { - Ok(mode) => mode, - Err(err) => { - show_error!("{}", err); - return 1; - } - }; + let mode = get_mode(&matches).map_err(|e| USimpleError::new(1, e))?; let file_name = matches.value_of("name").expect("Missing argument 'NAME'"); @@ -113,31 +105,29 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if ch == 'p' { if matches.is_present("major") || matches.is_present("minor") { - eprintln!("Fifos do not have major and minor device numbers."); - eprintln!( - "Try '{} --help' for more information.", - uucore::execution_phrase() - ); - 1 + Err(UUsageError::new( + 1, + "Fifos do not have major and minor device numbers.", + )) } else { - _mknod(file_name, S_IFIFO | mode, 0) + let exit_code = _mknod(file_name, S_IFIFO | mode, 0); + set_exit_code(exit_code); + Ok(()) } } else { match (matches.value_of("major"), matches.value_of("minor")) { (None, None) | (_, None) | (None, _) => { - eprintln!("Special files require major and minor device numbers."); - eprintln!( - "Try '{} --help' for more information.", - uucore::execution_phrase() - ); - 1 + return Err(UUsageError::new( + 1, + "Special files require major and minor device numbers.", + )); } (Some(major), Some(minor)) => { let major = major.parse::().expect("validated by clap"); let minor = minor.parse::().expect("validated by clap"); let dev = makedev(major, minor); - if ch == 'b' { + let exit_code = if ch == 'b' { // block special file _mknod(file_name, S_IFBLK | mode, dev) } else if ch == 'c' || ch == 'u' { @@ -145,7 +135,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { _mknod(file_name, S_IFCHR | mode, dev) } else { unreachable!("{} was validated to be only b, c or u", ch); - } + }; + set_exit_code(exit_code); + Ok(()) } } } diff --git a/src/uu/mktemp/Cargo.toml b/src/uu/mktemp/Cargo.toml index de896dba7f5..0aeeaed4c29 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mktemp" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "mktemp ~ (uutils) create and display a temporary file or directory from TEMPLATE" @@ -18,8 +18,8 @@ path = "src/mktemp.rs" clap = { version = "2.33", features = ["wrap_help"] } rand = "0.5" tempfile = "3.1" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "mktemp" diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 0111376f9b6..7d1130d0b38 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_more" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "more ~ (uutils) input perusal filter" @@ -17,7 +17,7 @@ path = "src/more.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" } -uucore_procs = { version=">=0.0.7", package = "uucore_procs", path = "../../uucore_procs" } +uucore_procs = { version=">=0.0.8", package = "uucore_procs", path = "../../uucore_procs" } crossterm = ">=0.19" atty = "0.2" unicode-width = "0.1.7" @@ -28,12 +28,11 @@ redox_termios = "0.1" redox_syscall = "0.2" [target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies] -nix = "0.19" +nix = "0.23.1" [[bin]] name = "more" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index d424d5a77e9..dc5acbff882 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -7,9 +7,6 @@ // spell-checker:ignore (methods) isnt -#[macro_use] -extern crate uucore; - use std::{ fs::File, io::{stdin, stdout, BufReader, Read, Stdout, Write}, @@ -31,6 +28,7 @@ use crossterm::{ use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; use uucore::display::Quotable; +use uucore::error::{UResult, USimpleError, UUsageError}; const BELL: &str = "\x07"; @@ -51,7 +49,8 @@ pub mod options { const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n"; -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().get_matches_from(args); let mut buff = String::new(); @@ -65,32 +64,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let file = Path::new(file); if file.is_dir() { terminal::disable_raw_mode().unwrap(); - show_usage_error!("{} is a directory.", file.quote()); - return 1; + return Err(UUsageError::new( + 1, + format!("{} is a directory.", file.quote()), + )); } if !file.exists() { terminal::disable_raw_mode().unwrap(); - show_error!("cannot open {}: No such file or directory", file.quote()); - return 1; + return Err(USimpleError::new( + 1, + 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 mut reader = BufReader::new(File::open(file).unwrap()); reader.read_to_string(&mut buff).unwrap(); - more(&buff, &mut stdout, next_file.copied(), silent); + more(&buff, &mut stdout, next_file.copied(), silent)?; buff.clear(); } reset_term(&mut stdout); } else if atty::isnt(atty::Stream::Stdin) { stdin().read_to_string(&mut buff).unwrap(); let mut stdout = setup_term(); - more(&buff, &mut stdout, None, silent); + more(&buff, &mut stdout, None, silent)?; reset_term(&mut stdout); } else { - show_usage_error!("bad usage"); + return Err(UUsageError::new(1, "bad usage")); } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -210,14 +213,14 @@ fn reset_term(stdout: &mut std::io::Stdout) { #[inline(always)] fn reset_term(_: &mut usize) {} -fn more(buff: &str, stdout: &mut Stdout, next_file: Option<&str>, silent: bool) { +fn more(buff: &str, stdout: &mut Stdout, next_file: Option<&str>, silent: bool) -> UResult<()> { let (cols, rows) = terminal::size().unwrap(); let lines = break_buff(buff, usize::from(cols)); let mut pager = Pager::new(rows, lines, next_file, silent); pager.draw(stdout, None); if pager.should_close() { - return; + return Ok(()); } loop { @@ -244,7 +247,7 @@ fn more(buff: &str, stdout: &mut Stdout, next_file: Option<&str>, silent: bool) modifiers: KeyModifiers::NONE, }) => { if pager.should_close() { - return; + return Ok(()); } else { pager.page_down(); } @@ -255,6 +258,22 @@ fn more(buff: &str, stdout: &mut Stdout, next_file: Option<&str>, silent: bool) }) => { pager.page_up(); } + Event::Key(KeyEvent { + code: KeyCode::Char('j'), + modifiers: KeyModifiers::NONE, + }) => { + if pager.should_close() { + return Ok(()); + } else { + pager.next_line(); + } + } + Event::Key(KeyEvent { + code: KeyCode::Char('k'), + modifiers: KeyModifiers::NONE, + }) => { + pager.prev_line(); + } Event::Resize(col, row) => { pager.page_resize(col, row); } @@ -301,6 +320,17 @@ impl<'a> Pager<'a> { } fn page_down(&mut self) { + // If the next page down position __after redraw__ is greater than the total line count, + // the upper mark must not grow past top of the screen at the end of the open file. + if self + .upper_mark + .saturating_add(self.content_rows as usize * 2) + .ge(&self.line_count) + { + self.upper_mark = self.line_count - self.content_rows as usize; + return; + } + self.upper_mark = self.upper_mark.saturating_add(self.content_rows.into()); } @@ -308,6 +338,14 @@ impl<'a> Pager<'a> { self.upper_mark = self.upper_mark.saturating_sub(self.content_rows.into()); } + fn next_line(&mut self) { + self.upper_mark = self.upper_mark.saturating_add(1); + } + + fn prev_line(&mut self) { + self.upper_mark = self.upper_mark.saturating_sub(1); + } + // TODO: Deal with column size changes. fn page_resize(&mut self, _: u16, row: u16) { self.content_rows = row.saturating_sub(1); diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index 6dd129af9d4..53ef6454f3e 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mv" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "mv ~ (uutils) move (rename) SOURCE to DESTINATION" @@ -17,13 +17,12 @@ path = "src/mv.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } fs_extra = "1.1.0" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "mv" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/mv/src/error.rs b/src/uu/mv/src/error.rs new file mode 100644 index 00000000000..6a605626eca --- /dev/null +++ b/src/uu/mv/src/error.rs @@ -0,0 +1,43 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +use std::error::Error; +use std::fmt::{Display, Formatter, Result}; + +use uucore::error::UError; + +#[derive(Debug)] +pub enum MvError { + NoSuchFile(String), + SameFile(String, String), + SelfSubdirectory(String), + DirectoryToNonDirectory(String), + NonDirectoryToDirectory(String, String), + NotADirectory(String), +} + +impl Error for MvError {} +impl UError for MvError {} +impl Display for MvError { + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + MvError::NoSuchFile(s) => write!(f, "cannot stat {}: No such file or directory", s), + MvError::SameFile(s, t) => write!(f, "{} and {} are the same file", s, t), + MvError::SelfSubdirectory(s) => write!( + f, + "cannot move '{s}' to a subdirectory of itself, '{s}/{s}'", + s = s + ), + MvError::DirectoryToNonDirectory(t) => { + write!(f, "cannot overwrite directory {} with non-directory", t) + } + MvError::NonDirectoryToDirectory(s, t) => write!( + f, + "cannot overwrite non-directory {} with directory {}", + t, s + ), + MvError::NotADirectory(t) => write!(f, "target {} is not a directory", t), + } + } +} diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 9d23f86de22..6f0fa03e847 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -8,11 +8,14 @@ // spell-checker:ignore (ToDO) sourcepath targetpath +mod error; + #[macro_use] extern crate uucore; use clap::{crate_version, App, Arg, ArgMatches}; use std::env; +use std::ffi::OsString; use std::fs; use std::io::{self, stdin}; #[cfg(unix)] @@ -22,17 +25,21 @@ use std::os::windows; use std::path::{Path, PathBuf}; use uucore::backup_control::{self, BackupMode}; use uucore::display::Quotable; +use uucore::error::{FromIo, UError, UResult, USimpleError, UUsageError}; use fs_extra::dir::{move_dir, CopyOptions as DirCopyOptions}; +use crate::error::MvError; + pub struct Behavior { overwrite: OverwriteMode, backup: BackupMode, suffix: String, update: bool, - target_dir: Option, + target_dir: Option, no_target_dir: bool, verbose: bool, + strip_slashes: bool, } #[derive(Clone, Eq, PartialEq)] @@ -65,7 +72,8 @@ fn usage() -> String { ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app() @@ -77,23 +85,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .usage(&usage[..]) .get_matches_from(args); - let files: Vec = matches - .values_of(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); + let files: Vec = matches + .values_of_os(ARG_FILES) + .unwrap_or_default() + .map(|v| v.to_os_string()) + .collect(); let overwrite_mode = determine_overwrite_mode(&matches); - let backup_mode = match backup_control::determine_backup_mode(&matches) { - Err(e) => { - show!(e); - return 1; - } - Ok(mode) => mode, - }; + let backup_mode = backup_control::determine_backup_mode(&matches)?; if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { - show_usage_error!("options --backup and --no-clobber are mutually exclusive"); - return 1; + return Err(UUsageError::new( + 1, + "options --backup and --no-clobber are mutually exclusive", + )); } let backup_suffix = backup_control::determine_backup_suffix(&matches); @@ -103,26 +108,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { backup: backup_mode, suffix: backup_suffix, update: matches.is_present(OPT_UPDATE), - target_dir: matches.value_of(OPT_TARGET_DIRECTORY).map(String::from), + target_dir: matches + .value_of_os(OPT_TARGET_DIRECTORY) + .map(OsString::from), no_target_dir: matches.is_present(OPT_NO_TARGET_DIRECTORY), verbose: matches.is_present(OPT_VERBOSE), + strip_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES), }; - let paths: Vec = { - fn strip_slashes(p: &Path) -> &Path { - p.components().as_path() - } - let to_owned = |p: &Path| p.to_owned(); - let paths = files.iter().map(Path::new); - - if matches.is_present(OPT_STRIP_TRAILING_SLASHES) { - paths.map(strip_slashes).map(to_owned).collect() - } else { - paths.map(to_owned).collect() - } - }; - - exec(&paths[..], behavior) + exec(&files[..], behavior) } pub fn uu_app() -> App<'static, 'static> { @@ -210,117 +204,107 @@ fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { } } -fn exec(files: &[PathBuf], b: Behavior) -> i32 { +fn exec(files: &[OsString], b: Behavior) -> UResult<()> { + let paths: Vec = { + let paths = files.iter().map(Path::new); + + // Strip slashes from path, if strip opt present + if b.strip_slashes { + paths + .map(|p| p.components().as_path().to_owned()) + .collect::>() + } else { + paths.map(|p| p.to_owned()).collect::>() + } + }; + if let Some(ref name) = b.target_dir { - return move_files_into_dir(files, &PathBuf::from(name), &b); + return move_files_into_dir(&paths, &PathBuf::from(name), &b); } - match files.len() { + match paths.len() { /* case 0/1 are not possible thanks to clap */ 2 => { - let source = &files[0]; - let target = &files[1]; + let source = &paths[0]; + let target = &paths[1]; // Here we use the `symlink_metadata()` method instead of `exists()`, // since it handles dangling symlinks correctly. The method gives an // `Ok()` results unless the source does not exist, or the user // lacks permission to access metadata. if source.symlink_metadata().is_err() { - show_error!("cannot stat {}: No such file or directory", source.quote()); - return 1; + return Err(MvError::NoSuchFile(source.quote().to_string()).into()); + } + + // GNU semantics are: if the source and target are the same, no move occurs and we print an error + if source.eq(target) { + // Done to match GNU semantics for the dot file + if source.eq(Path::new(".")) || source.ends_with("/.") || source.is_file() { + return Err(MvError::SameFile( + source.quote().to_string(), + target.quote().to_string(), + ) + .into()); + } else { + return Err(MvError::SelfSubdirectory(source.display().to_string()).into()); + } } if target.is_dir() { if b.no_target_dir { if !source.is_dir() { - show_error!( - "cannot overwrite directory {} with non-directory", - target.quote() - ); - return 1; + Err(MvError::DirectoryToNonDirectory(target.quote().to_string()).into()) + } else { + rename(source, target, &b).map_err_context(|| { + format!("cannot move {} to {}", source.quote(), target.quote()) + }) } - - return match rename(source, target, &b) { - Err(e) => { - show_error!( - "cannot move {} to {}: {}", - source.quote(), - target.quote(), - e.to_string() - ); - 1 - } - _ => 0, - }; + } else { + move_files_into_dir(&[source.clone()], target, &b) } - - return move_files_into_dir(&[source.clone()], target, &b); } else if target.exists() && source.is_dir() { - show_error!( - "cannot overwrite non-directory {} with directory {}", - target.quote(), - source.quote() - ); - return 1; - } - - if let Err(e) = rename(source, target, &b) { - show_error!("{}", e); - return 1; + Err(MvError::NonDirectoryToDirectory( + source.quote().to_string(), + target.quote().to_string(), + ) + .into()) + } else { + rename(source, target, &b).map_err(|e| USimpleError::new(1, format!("{}", e))) } } _ => { if b.no_target_dir { - show_error!( - "mv: extra operand {}\n\ - Try '{} --help' for more information.", - files[2].quote(), - uucore::execution_phrase() - ); - return 1; + return Err(UUsageError::new( + 1, + format!("mv: extra operand {}", files[2].quote()), + )); } - let target_dir = files.last().unwrap(); - move_files_into_dir(&files[..files.len() - 1], target_dir, &b); + let target_dir = paths.last().unwrap(); + move_files_into_dir(&paths[..paths.len() - 1], target_dir, &b) } } - 0 } -fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 { +fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UResult<()> { if !target_dir.is_dir() { - show_error!("target {} is not a directory", target_dir.quote()); - return 1; + return Err(MvError::NotADirectory(target_dir.quote().to_string()).into()); } - let mut all_successful = true; for sourcepath in files.iter() { let targetpath = match sourcepath.file_name() { Some(name) => target_dir.join(name), None => { - show_error!( - "cannot stat {}: No such file or directory", - sourcepath.quote() - ); - - all_successful = false; + show!(MvError::NoSuchFile(sourcepath.quote().to_string())); continue; } }; - - if let Err(e) = rename(sourcepath, &targetpath, b) { - show_error!( - "cannot move {} to {}: {}", + show_if_err!( + rename(sourcepath, &targetpath, b).map_err_context(|| format!( + "cannot move {} to {}", sourcepath.quote(), - targetpath.quote(), - e.to_string() - ); - all_successful = false; - } - } - - if all_successful { - 0 - } else { - 1 + targetpath.quote() + )) + ) } + Ok(()) } fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> { diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index ac719a76847..03515073f25 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nice" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "nice ~ (uutils) run PROGRAM with modified scheduling priority" @@ -17,10 +17,13 @@ path = "src/nice.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -nix = "0.20" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +nix = "0.23.1" +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "nice" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index fbc2be0e5e4..dbbb0d7e6a3 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -16,6 +16,7 @@ use std::io::Error; use std::ptr; use clap::{crate_version, App, AppSettings, Arg}; +use uucore::error::{set_exit_code, UResult, USimpleError, UUsageError}; pub mod options { pub static ADJUSTMENT: &str = "adjustment"; @@ -35,7 +36,8 @@ process).", ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -45,31 +47,34 @@ pub fn uumain(args: impl uucore::Args) -> i32 { libc::getpriority(PRIO_PROCESS, 0) }; if Error::last_os_error().raw_os_error().unwrap() != 0 { - show_error!("getpriority: {}", Error::last_os_error()); - return 125; + return Err(USimpleError::new( + 125, + format!("getpriority: {}", Error::last_os_error()), + )); } let adjustment = match matches.value_of(options::ADJUSTMENT) { Some(nstr) => { if !matches.is_present(options::COMMAND) { - show_error!( - "A command must be given with an adjustment.\nTry '{} --help' for more information.", - uucore::execution_phrase() - ); - return 125; + return Err(UUsageError::new( + 125, + "A command must be given with an adjustment.", + )); } match nstr.parse() { Ok(num) => num, Err(e) => { - show_error!("\"{}\" is not a valid number: {}", nstr, e); - return 125; + return Err(USimpleError::new( + 125, + format!("\"{}\" is not a valid number: {}", nstr, e), + )) } } } None => { if !matches.is_present(options::COMMAND) { println!("{}", niceness); - return 0; + return Ok(()); } 10_i32 } @@ -93,11 +98,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } show_error!("execvp: {}", Error::last_os_error()); - if Error::last_os_error().raw_os_error().unwrap() as c_int == libc::ENOENT { + let exit_code = if Error::last_os_error().raw_os_error().unwrap() as c_int == libc::ENOENT { 127 } else { 126 - } + }; + set_exit_code(exit_code); + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index 781f6502d0e..f5680f407aa 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nl" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "nl ~ (uutils) display input with added line numbers" @@ -21,13 +21,12 @@ libc = "0.2.42" memchr = "2.2.0" regex = "1.0.1" regex-syntax = "0.6.7" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "nl" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 600ebace06b..b004d2b74e3 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -8,14 +8,12 @@ // spell-checker:ignore (ToDO) corasick memchr -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; 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::InvalidEncodingHandling; mod helper; @@ -83,7 +81,8 @@ pub mod options { pub const NUMBER_WIDTH: &str = "number-width"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -109,11 +108,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // program if some options could not successfully be parsed. let parse_errors = helper::parse_options(&mut settings, &matches); if !parse_errors.is_empty() { - show_error!("Invalid arguments supplied."); - for message in &parse_errors { - println!("{}", message); - } - return 1; + return Err(USimpleError::new( + 1, + format!("Invalid arguments supplied.\n{}", parse_errors.join("\n")), + )); } let mut read_stdin = false; @@ -130,16 +128,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { continue; } let path = Path::new(file); - let reader = File::open(path).unwrap(); + let reader = File::open(path).map_err_context(|| file.to_string())?; let mut buffer = BufReader::new(reader); - nl(&mut buffer, &settings); + nl(&mut buffer, &settings)?; } if read_stdin { let mut buffer = BufReader::new(stdin()); - nl(&mut buffer, &settings); + nl(&mut buffer, &settings)?; } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -227,7 +225,7 @@ pub fn uu_app() -> App<'static, 'static> { } // nl implements the main functionality for an individual buffer. -fn nl(reader: &mut BufReader, settings: &Settings) { +fn nl(reader: &mut BufReader, settings: &Settings) -> UResult<()> { let regexp: regex::Regex = regex::Regex::new(r".?").unwrap(); let mut line_no = settings.starting_line_number; // The current line number's width as a string. Using to_string is inefficient @@ -248,7 +246,8 @@ fn nl(reader: &mut BufReader, settings: &Settings) { _ => ®exp, }; let mut line_filter: fn(&str, ®ex::Regex) -> bool = pass_regex; - for mut l in reader.lines().map(|r| r.unwrap()) { + 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(); @@ -372,6 +371,7 @@ fn nl(reader: &mut BufReader, settings: &Settings) { line_no_width += 1; } } + Ok(()) } fn pass_regex(line: &str, re: ®ex::Regex) -> bool { diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index c2f3c329e82..283e67246c2 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nohup" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "nohup ~ (uutils) run COMMAND, ignoring hangup signals" @@ -18,9 +18,12 @@ path = "src/nohup.rs" clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" atty = "0.2" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "nohup" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index d83170ae8d2..505911b3e97 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -15,11 +15,13 @@ use libc::{c_char, dup2, execvp, signal}; use libc::{SIGHUP, SIG_IGN}; use std::env; use std::ffi::CString; +use std::fmt::{Display, Formatter}; use std::fs::{File, OpenOptions}; use std::io::Error; use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; use uucore::display::Quotable; +use uucore::error::{set_exit_code, UError, UResult}; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Run COMMAND ignoring hangup signals."; @@ -40,7 +42,47 @@ mod options { pub const CMD: &str = "cmd"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[derive(Debug)] +enum NohupError { + CannotDetach, + CannotReplace(&'static str, std::io::Error), + OpenFailed(i32, std::io::Error), + OpenFailed2(i32, std::io::Error, String, std::io::Error), +} + +impl std::error::Error for NohupError {} + +impl UError for NohupError { + fn code(&self) -> i32 { + match self { + NohupError::OpenFailed(code, _) | NohupError::OpenFailed2(code, _, _, _) => *code, + _ => 2, + } + } +} + +impl Display for NohupError { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + NohupError::CannotDetach => write!(f, "Cannot detach from console"), + NohupError::CannotReplace(s, e) => write!(f, "Cannot replace {}: {}", s, e), + NohupError::OpenFailed(_, e) => { + write!(f, "failed to open {}: {}", NOHUP_OUT.quote(), e) + } + NohupError::OpenFailed2(_, e1, s, e2) => write!( + f, + "failed to open {}: {}\nfailed to open {}: {}", + NOHUP_OUT.quote(), + e1, + s.quote(), + e2 + ), + } + } +} + +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) @@ -48,12 +90,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = uu_app().usage(&usage[..]).get_matches_from(args); - replace_fds(); + replace_fds()?; unsafe { signal(SIGHUP, SIG_IGN) }; if unsafe { !_vprocmgr_detach_from_console(0).is_null() } { - crash!(2, "Cannot detach from console") + return Err(NohupError::CannotDetach.into()); }; let cstrs: Vec = matches @@ -66,9 +108,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let ret = unsafe { execvp(args[0], args.as_mut_ptr()) }; match ret { - libc::ENOENT => EXIT_ENOENT, - _ => EXIT_CANNOT_INVOKE, + libc::ENOENT => set_exit_code(EXIT_ENOENT), + _ => set_exit_code(EXIT_CANNOT_INVOKE), } + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -85,32 +128,31 @@ pub fn uu_app() -> App<'static, 'static> { .setting(AppSettings::TrailingVarArg) } -fn replace_fds() { +fn replace_fds() -> UResult<()> { if atty::is(atty::Stream::Stdin) { - let new_stdin = match File::open(Path::new("/dev/null")) { - Ok(t) => t, - Err(e) => crash!(2, "Cannot replace STDIN: {}", e), - }; + let new_stdin = File::open(Path::new("/dev/null")) + .map_err(|e| NohupError::CannotReplace("STDIN", e))?; if unsafe { dup2(new_stdin.as_raw_fd(), 0) } != 0 { - crash!(2, "Cannot replace STDIN: {}", Error::last_os_error()) + return Err(NohupError::CannotReplace("STDIN", Error::last_os_error()).into()); } } if atty::is(atty::Stream::Stdout) { - let new_stdout = find_stdout(); + let new_stdout = find_stdout()?; let fd = new_stdout.as_raw_fd(); if unsafe { dup2(fd, 1) } != 1 { - crash!(2, "Cannot replace STDOUT: {}", Error::last_os_error()) + return Err(NohupError::CannotReplace("STDOUT", Error::last_os_error()).into()); } } if atty::is(atty::Stream::Stderr) && unsafe { dup2(1, 2) } != 2 { - crash!(2, "Cannot replace STDERR: {}", Error::last_os_error()) + return Err(NohupError::CannotReplace("STDERR", Error::last_os_error()).into()); } + Ok(()) } -fn find_stdout() -> File { +fn find_stdout() -> UResult { let internal_failure_code = match std::env::var("POSIXLY_CORRECT") { Ok(_) => POSIX_NOHUP_FAILURE, Err(_) => EXIT_CANCELED, @@ -127,14 +169,11 @@ fn find_stdout() -> File { "ignoring input and appending output to {}", NOHUP_OUT.quote() ); - t + Ok(t) } Err(e1) => { let home = match env::var("HOME") { - Err(_) => { - show_error!("failed to open {}: {}", NOHUP_OUT.quote(), e1); - exit!(internal_failure_code) - } + Err(_) => return Err(NohupError::OpenFailed(internal_failure_code, e1).into()), Ok(h) => h, }; let mut homeout = PathBuf::from(home); @@ -151,13 +190,15 @@ fn find_stdout() -> File { "ignoring input and appending output to {}", homeout_str.quote() ); - t - } - Err(e2) => { - show_error!("failed to open {}: {}", NOHUP_OUT.quote(), e1); - show_error!("failed to open {}: {}", homeout_str.quote(), e2); - exit!(internal_failure_code) + Ok(t) } + Err(e2) => Err(NohupError::OpenFailed2( + internal_failure_code, + e1, + homeout_str.to_string(), + e2, + ) + .into()), } } } diff --git a/src/uu/nproc/Cargo.toml b/src/uu/nproc/Cargo.toml index 3c44062bd72..01809dd9b6b 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nproc" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "nproc ~ (uutils) display the number of processing units available" @@ -18,9 +18,12 @@ path = "src/nproc.rs" libc = "0.2.42" num_cpus = "1.10" clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "nproc" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 16b8d8c3a92..4ab1378b0b5 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -7,11 +7,10 @@ // spell-checker:ignore (ToDO) NPROCESSORS nprocs numstr threadstr sysconf -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::env; +use uucore::display::Quotable; +use uucore::error::{UResult, USimpleError}; #[cfg(target_os = "linux")] pub const _SC_NPROCESSORS_CONF: libc::c_int = 83; @@ -31,7 +30,8 @@ fn usage() -> String { format!("{0} [OPTIONS]...", uucore::execution_phrase()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -39,8 +39,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(numstr) => match numstr.parse() { Ok(num) => num, Err(e) => { - show_error!("\"{}\" is not a valid number: {}", numstr, e); - return 1; + return Err(USimpleError::new( + 1, + format!("{} is not a valid number: {}", numstr.quote(), e), + )); } }, None => 0, @@ -66,7 +68,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { cores -= ignore; } println!("{}", cores); - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index 60bfb3b1454..dd7d04a6b49 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_numfmt" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "numfmt ~ (uutils) reformat NUMBER" @@ -16,13 +16,12 @@ path = "src/numfmt.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "numfmt" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index bdee83e127a..f66e1ac0aa2 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -45,13 +45,13 @@ impl<'a> Iterator for WhitespaceSplitter<'a> { let (prefix, field) = haystack.split_at( haystack .find(|c: char| !c.is_whitespace()) - .unwrap_or_else(|| haystack.len()), + .unwrap_or(haystack.len()), ); let (field, rest) = field.split_at( field .find(|c: char| c.is_whitespace()) - .unwrap_or_else(|| field.len()), + .unwrap_or(field.len()), ); self.s = if !rest.is_empty() { Some(rest) } else { None }; @@ -220,16 +220,32 @@ fn format_string( options: &NumfmtOptions, implicit_padding: Option, ) -> Result { + // strip the (optional) suffix before applying any transformation + let source_without_suffix = match &options.suffix { + Some(suffix) => source.strip_suffix(suffix).unwrap_or(source), + None => source, + }; + let number = transform_to( - transform_from(source, &options.transform.from)?, + transform_from(source_without_suffix, &options.transform.from)?, &options.transform.to, options.round, )?; + // bring back the suffix before applying padding + let number_with_suffix = match &options.suffix { + Some(suffix) => format!("{}{}", number, suffix), + None => number, + }; + Ok(match implicit_padding.unwrap_or(options.padding) { - 0 => number, - p if p > 0 => format!("{:>padding$}", number, padding = p as usize), - p => format!("{: number_with_suffix, + p if p > 0 => format!("{:>padding$}", number_with_suffix, padding = p as usize), + p => format!( + "{: Result<()> { print!(" "); &prefix[1..] } else { - &prefix + prefix }; let implicit_padding = if !empty_prefix && options.padding == 0 { diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index da2fa813064..b84b9ab1b5b 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -7,15 +7,13 @@ // spell-checker:ignore N'th M'th -#[macro_use] -extern crate uucore; - use crate::format::format_and_print; use crate::options::*; use crate::units::{Result, Unit}; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use std::io::{BufRead, Write}; use uucore::display::Quotable; +use uucore::error::{UResult, USimpleError}; use uucore::ranges::Range; pub mod format; @@ -144,6 +142,8 @@ fn parse_options(args: &ArgMatches) -> Result { _ => unreachable!("Should be restricted by clap"), }; + let suffix = args.value_of(options::SUFFIX).map(|s| s.to_owned()); + Ok(NumfmtOptions { transform, padding, @@ -151,10 +151,12 @@ fn parse_options(args: &ArgMatches) -> Result { fields, delimiter, round, + suffix, }) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -168,10 +170,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match result { Err(e) => { std::io::stdout().flush().expect("error flushing stdout"); - show_error!("{}", e); - 1 + // TODO Change `handle_args()` and `handle_stdin()` so that + // they return `UResult`. + return Err(USimpleError::new(1, e)); } - _ => 0, + _ => Ok(()), } } @@ -242,5 +245,14 @@ pub fn uu_app() -> App<'static, 'static> { .default_value("from-zero") .possible_values(&["up", "down", "from-zero", "towards-zero", "nearest"]), ) + .arg( + Arg::with_name(options::SUFFIX) + .long(options::SUFFIX) + .help( + "print SUFFIX after each formatted number, and accept \ + inputs optionally ending with SUFFIX", + ) + .value_name("SUFFIX"), + ) .arg(Arg::with_name(options::NUMBER).hidden(true).multiple(true)) } diff --git a/src/uu/numfmt/src/options.rs b/src/uu/numfmt/src/options.rs index 59bf9d8d31c..bd76b18b803 100644 --- a/src/uu/numfmt/src/options.rs +++ b/src/uu/numfmt/src/options.rs @@ -11,6 +11,7 @@ pub const HEADER_DEFAULT: &str = "1"; pub const NUMBER: &str = "NUMBER"; pub const PADDING: &str = "padding"; pub const ROUND: &str = "round"; +pub const SUFFIX: &str = "suffix"; pub const TO: &str = "to"; pub const TO_DEFAULT: &str = "none"; @@ -26,6 +27,7 @@ pub struct NumfmtOptions { pub fields: Vec, pub delimiter: Option, pub round: RoundMethod, + pub suffix: Option, } #[derive(Clone, Copy)] diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index a6b5bc433b7..7ac1a2c154a 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_od" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "od ~ (uutils) display formatted representation of input" @@ -19,13 +19,12 @@ byteorder = "1.3.2" clap = { version = "2.33", features = ["wrap_help"] } half = "1.6" libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "od" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index e9983f99106..4a2f6b519d3 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -44,6 +44,7 @@ use crate::peekreader::*; use crate::prn_char::format_ascii_dump; use clap::{self, crate_version, AppSettings, Arg, ArgMatches}; use uucore::display::Quotable; +use uucore::error::{UResult, USimpleError}; use uucore::parse_size::ParseSizeError; use uucore::InvalidEncodingHandling; @@ -120,25 +121,36 @@ struct OdOptions { } impl OdOptions { - fn new(matches: ArgMatches, args: Vec) -> Result { + fn new(matches: ArgMatches, args: Vec) -> UResult { let byte_order = match matches.value_of(options::ENDIAN) { None => ByteOrder::Native, Some("little") => ByteOrder::Little, Some("big") => ByteOrder::Big, Some(s) => { - return Err(format!("Invalid argument --endian={}", s)); + return Err(USimpleError::new( + 1, + format!("Invalid argument --endian={}", s), + )); } }; - let mut skip_bytes = matches.value_of(options::SKIP_BYTES).map_or(0, |s| { - parse_number_of_bytes(s).unwrap_or_else(|e| { - crash!(1, "{}", format_error_message(e, s, options::SKIP_BYTES)) - }) - }); + let mut skip_bytes = match matches.value_of(options::SKIP_BYTES) { + None => 0, + Some(s) => match parse_number_of_bytes(s) { + Ok(n) => n, + Err(e) => { + return Err(USimpleError::new( + 1, + format_error_message(e, s, options::SKIP_BYTES), + )) + } + }, + }; let mut label: Option = None; - let parsed_input = parse_inputs(&matches).map_err(|e| format!("Invalid inputs: {}", e))?; + let parsed_input = parse_inputs(&matches) + .map_err(|e| USimpleError::new(1, format!("Invalid inputs: {}", e)))?; let input_strings = match parsed_input { CommandLineInputs::FileNames(v) => v, CommandLineInputs::FileAndOffset((f, s, l)) => { @@ -148,15 +160,26 @@ impl OdOptions { } }; - let formats = parse_format_flags(&args)?; + let formats = parse_format_flags(&args).map_err(|e| USimpleError::new(1, e))?; - let mut line_bytes = matches.value_of(options::WIDTH).map_or(16, |s| { - if matches.occurrences_of(options::WIDTH) == 0 { - return 16; - }; - parse_number_of_bytes(s) - .unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::WIDTH))) - }); + let mut line_bytes = match matches.value_of(options::WIDTH) { + None => 16, + Some(s) => { + if matches.occurrences_of(options::WIDTH) == 0 { + 16 + } else { + match parse_number_of_bytes(s) { + Ok(n) => n, + Err(e) => { + return Err(USimpleError::new( + 1, + format_error_message(e, s, options::WIDTH), + )) + } + } + } + } + }; let min_bytes = formats.iter().fold(1, |max, next| { cmp::max(max, next.formatter_item_info.byte_size) @@ -168,18 +191,28 @@ impl OdOptions { let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES); - let read_bytes = matches.value_of(options::READ_BYTES).map(|s| { - parse_number_of_bytes(s).unwrap_or_else(|e| { - crash!(1, "{}", format_error_message(e, s, options::READ_BYTES)) - }) - }); + let read_bytes = match matches.value_of(options::READ_BYTES) { + None => None, + Some(s) => match parse_number_of_bytes(s) { + Ok(n) => Some(n), + Err(e) => { + return Err(USimpleError::new( + 1, + format_error_message(e, s, options::READ_BYTES), + )) + } + }, + }; let radix = match matches.value_of(options::ADDRESS_RADIX) { None => Radix::Octal, Some(s) => { let st = s.as_bytes(); if st.len() != 1 { - return Err("Radix must be one of [d, o, n, x]".to_string()); + return Err(USimpleError::new( + 1, + "Radix must be one of [d, o, n, x]".to_string(), + )); } else { let radix: char = *(st.get(0).expect("byte string of length 1 lacks a 0th elem")) as char; @@ -188,7 +221,12 @@ impl OdOptions { 'x' => Radix::Hexadecimal, 'o' => Radix::Octal, 'n' => Radix::NoPrefix, - _ => return Err("Radix must be one of [d, o, n, x]".to_string()), + _ => { + return Err(USimpleError::new( + 1, + "Radix must be one of [d, o, n, x]".to_string(), + )) + } } } } @@ -210,7 +248,8 @@ impl OdOptions { /// parses and validates command line parameters, prepares data structures, /// opens the input and calls `odfunc` to process the input. -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -221,12 +260,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .clone() // Clone to reuse clap_opts to print help .get_matches_from(args.clone()); - let od_options = match OdOptions::new(clap_matches, args) { - Err(s) => { - crash!(1, "{}", s); - } - Ok(o) => o, - }; + let od_options = OdOptions::new(clap_matches, args)?; let mut input_offset = InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label); @@ -482,7 +516,7 @@ fn odfunc( input_offset: &mut InputOffset, input_decoder: &mut InputDecoder, output_info: &OutputInfo, -) -> i32 +) -> UResult<()> where I: PeekRead + HasError, { @@ -540,15 +574,15 @@ where Err(e) => { show_error!("{}", e); input_offset.print_final_offset(); - return 1; + return Err(1.into()); } }; } if input_decoder.has_error() { - 1 + Err(1.into()) } else { - 0 + Ok(()) } } diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index aeba6f68e21..573d66f3af5 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_paste" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "paste ~ (uutils) merge lines from inputs" @@ -16,13 +16,12 @@ path = "src/paste.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "paste" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 9ac5507df2d..a6e2b860476 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -7,14 +7,12 @@ // spell-checker:ignore (ToDO) delim -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::iter::repeat; use std::path::Path; +use uucore::error::{FromIo, UResult}; static ABOUT: &str = "Write lines consisting of the sequentially corresponding lines from each FILE, separated by TABs, to standard output."; @@ -36,7 +34,8 @@ fn read_line( } } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().get_matches_from(args); let serial = matches.is_present(options::SERIAL); @@ -46,9 +45,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap() .map(|s| s.to_owned()) .collect(); - paste(files, serial, delimiters); - - 0 + paste(files, serial, delimiters) } pub fn uu_app() -> App<'static, 'static> { @@ -78,18 +75,18 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn paste(filenames: Vec, serial: bool, delimiters: String) { - let mut files: Vec<_> = filenames - .into_iter() - .map(|name| { - if name == "-" { - None - } else { - let r = crash_if_err!(1, File::open(Path::new(&name))); - Some(BufReader::new(r)) - } - }) - .collect(); +fn paste(filenames: Vec, serial: bool, delimiters: String) -> UResult<()> { + let mut files = vec![]; + for name in filenames { + let file = if name == "-" { + None + } else { + let path = Path::new(&name); + let r = File::open(path).map_err_context(String::new)?; + Some(BufReader::new(r)) + }; + files.push(file); + } let delimiters: Vec = unescape(delimiters) .chars() @@ -108,7 +105,7 @@ fn paste(filenames: Vec, serial: bool, delimiters: String) { output.push_str(line.trim_end()); output.push_str(&delimiters[delim_count % delimiters.len()]); } - Err(e) => crash!(1, "{}", e.to_string()), + Err(e) => return Err(e.map_err_context(String::new)), } delim_count += 1; } @@ -130,7 +127,7 @@ fn paste(filenames: Vec, serial: bool, delimiters: String) { eof_count += 1; } Ok(_) => output.push_str(line.trim_end()), - Err(e) => crash!(1, "{}", e.to_string()), + Err(e) => return Err(e.map_err_context(String::new)), } } output.push_str(&delimiters[delim_count % delimiters.len()]); @@ -143,6 +140,7 @@ fn paste(filenames: Vec, serial: bool, delimiters: String) { delim_count = 0; } } + Ok(()) } // Unescape all special characters @@ -151,5 +149,5 @@ fn unescape(s: String) -> String { s.replace("\\n", "\n") .replace("\\t", "\t") .replace("\\\\", "\\") - .replace("\\", "") + .replace('\\', "") } diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index 79e0c251b29..ed90377681a 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pathchk" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "pathchk ~ (uutils) diagnose invalid or non-portable PATHNAME" @@ -17,9 +17,12 @@ path = "src/pathchk.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "pathchk" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 8afeaff18c7..fa6aaad5b75 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -8,14 +8,11 @@ // * that was distributed with this source code. // spell-checker:ignore (ToDO) lstat - -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::fs; use std::io::{ErrorKind, Write}; use uucore::display::Quotable; +use uucore::error::{set_exit_code, UResult, UUsageError}; use uucore::InvalidEncodingHandling; // operating mode @@ -43,7 +40,8 @@ fn usage() -> String { format!("{0} [OPTION]... NAME...", uucore::execution_phrase()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) @@ -68,34 +66,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // take necessary actions let paths = matches.values_of(options::PATH); - let mut res = if paths.is_none() { - show_error!( - "missing operand\nTry '{} --help' for more information", - uucore::execution_phrase() - ); - false - } else { - true - }; + if paths.is_none() { + return Err(UUsageError::new(1, "missing operand")); + } - if res { - // free strings are path operands - // FIXME: TCS, seems inefficient and overly verbose (?) - for p in paths.unwrap() { - let mut path = Vec::new(); - for path_segment in p.split('/') { - path.push(path_segment.to_string()); - } - res &= check_path(&mode, &path); + // free strings are path operands + // FIXME: TCS, seems inefficient and overly verbose (?) + let mut res = true; + for p in paths.unwrap() { + let mut path = Vec::new(); + for path_segment in p.split('/') { + path.push(path_segment.to_string()); } + res &= check_path(&mode, &path); } // determine error code - if res { - 0 - } else { - 1 + if !res { + set_exit_code(1); } + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index 297d40642df..557179c4160 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pinky" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "pinky ~ (uutils) display user information" @@ -15,10 +15,13 @@ edition = "2018" path = "src/pinky.rs" [dependencies] -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["utmpx", "entries"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["utmpx", "entries"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } clap = { version = "2.33", features = ["wrap_help"] } [[bin]] name = "pinky" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 4aa27affa5a..8220affc572 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -7,9 +7,8 @@ // spell-checker:ignore (ToDO) BUFSIZE gecos fullname, mesg iobuf -#[macro_use] -extern crate uucore; use uucore::entries::{Locate, Passwd}; +use uucore::error::{FromIo, UResult}; use uucore::libc::S_IWGRP; use uucore::utmpx::{self, time, Utmpx}; @@ -52,7 +51,8 @@ fn get_long_usage() -> String { ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -122,10 +122,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; if do_short_format { - pk.short_pinky(); - 0 + match pk.short_pinky() { + Ok(_) => Ok(()), + Err(e) => Err(e.map_err_context(String::new)), + } } else { - pk.long_pinky() + pk.long_pinky(); + Ok(()) } } @@ -242,7 +245,7 @@ fn time_string(ut: &Utmpx) -> String { } impl Pinky { - fn print_entry(&self, ut: &Utmpx) { + fn print_entry(&self, ut: &Utmpx) -> std::io::Result<()> { let mut pts_path = PathBuf::from("/dev"); pts_path.push(ut.tty_device().as_str()); @@ -267,11 +270,11 @@ impl Pinky { if self.include_fullname { if let Ok(pw) = Passwd::locate(ut.user().as_ref()) { - let mut gecos = pw.user_info().into_owned(); + let mut gecos = pw.user_info; if let Some(n) = gecos.find(',') { gecos.truncate(n + 1); } - print!(" {:<19.19}", gecos.replace("&", &pw.name().capitalize())); + print!(" {:<19.19}", gecos.replace("&", &pw.name.capitalize())); } else { print!(" {:19}", " ???"); } @@ -291,11 +294,12 @@ impl Pinky { let mut s = ut.host(); if self.include_where && !s.is_empty() { - s = crash_if_err!(1, ut.canon_host()); + s = ut.canon_host()?; print!(" {}", s); } println!(); + Ok(()) } fn print_heading(&self) { @@ -314,32 +318,33 @@ impl Pinky { println!(); } - fn short_pinky(&self) { + fn short_pinky(&self) -> std::io::Result<()> { if self.include_heading { self.print_heading(); } for ut in Utmpx::iter_all_records() { if ut.is_user_process() { if self.names.is_empty() { - self.print_entry(&ut) + self.print_entry(&ut)? } else if self.names.iter().any(|n| n.as_str() == ut.user()) { - self.print_entry(&ut); + self.print_entry(&ut)?; } } } + Ok(()) } - fn long_pinky(&self) -> i32 { + fn long_pinky(&self) { for u in &self.names { print!("Login name: {:<28}In real life: ", u); if let Ok(pw) = Passwd::locate(u.as_str()) { - println!(" {}", pw.user_info().replace("&", &pw.name().capitalize())); + println!(" {}", pw.user_info.replace("&", &pw.name.capitalize())); if self.include_home_and_shell { - print!("Directory: {:<29}", pw.user_dir()); - println!("Shell: {}", pw.user_shell()); + print!("Directory: {:<29}", pw.user_dir); + println!("Shell: {}", pw.user_shell); } if self.include_project { - let mut p = PathBuf::from(pw.user_dir().as_ref()); + let mut p = PathBuf::from(&pw.user_dir); p.push(".project"); if let Ok(f) = File::open(p) { print!("Project: "); @@ -347,7 +352,7 @@ impl Pinky { } } if self.include_plan { - let mut p = PathBuf::from(pw.user_dir().as_ref()); + let mut p = PathBuf::from(&pw.user_dir); p.push(".plan"); if let Ok(f) = File::open(p) { println!("Plan:"); @@ -359,7 +364,6 @@ impl Pinky { println!(" ???"); } } - 0 } } diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index 499ee77262b..37ff4ff2b43 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pr" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "pr ~ (uutils) convert text files for printing" @@ -17,7 +17,7 @@ path = "src/pr.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } getopts = "0.2.21" chrono = "0.4.19" quick-error = "2.0.1" @@ -29,5 +29,4 @@ name = "pr" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 0886a99919d..ea6fb86a850 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -25,6 +25,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, Lines, Read, Write}; use std::os::unix::fs::FileTypeExt; use uucore::display::Quotable; +use uucore::error::UResult; type IOError = std::io::Error; @@ -174,7 +175,8 @@ pub fn uu_app() -> clap::App<'static, 'static> { clap::App::new(uucore::util_name()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(uucore::InvalidEncodingHandling::Ignore) .accept_any(); @@ -388,7 +390,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if matches.opt_present("version") { println!("{} {}", NAME, VERSION); - return 0; + return Ok(()); } let mut files = matches.free.clone(); @@ -412,7 +414,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(options) => options, Err(err) => { print_error(&matches, err); - return 1; + return Err(1.into()); } }; @@ -430,11 +432,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { _ => 0, }; if status != 0 { - return status; + return Err(status.into()); } } - - 0 + Ok(()) } /// Returns re-written arguments which are passed to the program. @@ -470,7 +471,7 @@ fn print_error(matches: &Matches, err: PrError) { } } -fn print_usage(opts: &mut getopts::Options, matches: &Matches) -> i32 { +fn print_usage(opts: &mut getopts::Options, matches: &Matches) -> UResult<()> { println!("{} {} -- print files", NAME, VERSION); println!(); println!( @@ -508,10 +509,9 @@ fn print_usage(opts: &mut getopts::Options, matches: &Matches) -> i32 { options::COLUMN_OPTION ); if matches.free.is_empty() { - return 1; + return Err(1.into()); } - - 0 + Ok(()) } fn parse_usize(matches: &Matches, opt: &str) -> Option> { @@ -916,8 +916,7 @@ fn read_stream_and_create_pages( Box::new( lines - .map(split_lines_if_form_feed) - .flatten() + .flat_map(split_lines_if_form_feed) .enumerate() .map(move |(i, line)| FileLine { line_number: i + start_line_number, @@ -982,20 +981,18 @@ fn mpr(paths: &[String], options: &OutputOptions) -> Result { .map(|(i, path)| { let lines = BufReader::with_capacity(READ_BUFFER_SIZE, open(path).unwrap()).lines(); - read_stream_and_create_pages(options, lines, i) - .map(move |(x, line)| { - let file_line = line; - let page_number = x + 1; - file_line - .into_iter() - .map(|fl| FileLine { - page_number, - group_key: page_number * n_files + fl.file_id, - ..fl - }) - .collect::>() - }) - .flatten() + read_stream_and_create_pages(options, lines, i).flat_map(move |(x, line)| { + let file_line = line; + let page_number = x + 1; + file_line + .into_iter() + .map(|fl| FileLine { + page_number, + group_key: page_number * n_files + fl.file_id, + ..fl + }) + .collect::>() + }) }) .kmerge_by(|a, b| { if a.group_key == b.group_key { diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index eaf482b21aa..cc86e630a0e 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printenv" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "printenv ~ (uutils) display value of environment VAR" @@ -16,13 +16,12 @@ path = "src/printenv.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "printenv" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 5d32cfbcca7..c3f996865d0 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -9,6 +9,7 @@ use clap::{crate_version, App, Arg}; use std::env; +use uucore::error::UResult; static ABOUT: &str = "Display the values of the specified environment VARIABLE(s), or (with no VARIABLE) display name and value pairs for them all."; @@ -20,7 +21,8 @@ fn usage() -> String { format!("{0} [VARIABLE]... [OPTION]...", uucore::execution_phrase()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -40,15 +42,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for (env_var, value) in env::vars() { print!("{}={}{}", env_var, value, separator); } - return 0; + return Ok(()); } + let mut not_found = false; for env_var in variables { if let Ok(var) = env::var(env_var) { print!("{}{}", var, separator); + } else { + not_found = true; } } - 0 + + if not_found { + Err(1.into()) + } else { + Ok(()) + } } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index 50c454db5e1..d996dc344dc 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printf" -version = "0.0.8" +version = "0.0.12" authors = [ "Nathan Ross", "uutils developers", @@ -20,13 +20,12 @@ path = "src/printf.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } itertools = "0.8.0" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "printf" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index d3c8dca907a..b49057522a3 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -3,6 +3,7 @@ // spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr use clap::{crate_version, App, Arg}; +use uucore::error::{UResult, UUsageError}; use uucore::InvalidEncodingHandling; mod cli; @@ -273,18 +274,14 @@ COPYRIGHT : "; -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); if args.len() <= 1 { - println!( - "{0}: missing operand\nTry '{1} --help' for more information.", - uucore::util_name(), - uucore::execution_phrase() - ); - return 1; + return Err(UUsageError::new(1, "missing operand")); } let formatstr = &args[1]; @@ -296,7 +293,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let printf_args = &args[2..]; memo::Memo::run_all(formatstr, printf_args); } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index 06eb15ee891..048f5f4ee2b 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ptx" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "ptx ~ (uutils) display a permuted index of input" @@ -21,13 +21,12 @@ libc = "0.2.42" memchr = "2.2.0" regex = "1.0.1" regex-syntax = "0.6.7" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "ptx" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 3619b8d4d7d..f1650c81d8f 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -7,17 +7,18 @@ // spell-checker:ignore (ToDOs) corasick memchr Roff trunc oset iset -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use regex::Regex; use std::cmp; use std::collections::{BTreeSet, HashMap, HashSet}; use std::default::Default; +use std::error::Error; +use std::fmt::{Display, Formatter}; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; +use std::num::ParseIntError; use uucore::display::Quotable; +use uucore::error::{FromIo, UError, UResult}; use uucore::InvalidEncodingHandling; static NAME: &str = "ptx"; @@ -68,17 +69,21 @@ impl Default for Config { } } -fn read_word_filter_file(matches: &clap::ArgMatches, option: &str) -> HashSet { +fn read_word_filter_file( + matches: &clap::ArgMatches, + option: &str, +) -> std::io::Result> { let filename = matches .value_of(option) .expect("parsing options failed!") .to_string(); - let reader = BufReader::new(crash_if_err!(1, File::open(filename))); + let file = File::open(filename)?; + let reader = BufReader::new(file); let mut words: HashSet = HashSet::new(); for word in reader.lines() { - words.insert(crash_if_err!(1, word)); + words.insert(word?); } - words + Ok(words) } #[derive(Debug)] @@ -91,19 +96,23 @@ struct WordFilter { } impl WordFilter { - fn new(matches: &clap::ArgMatches, config: &Config) -> WordFilter { + fn new(matches: &clap::ArgMatches, config: &Config) -> UResult { let (o, oset): (bool, HashSet) = if matches.is_present(options::ONLY_FILE) { - (true, read_word_filter_file(matches, options::ONLY_FILE)) + let words = + read_word_filter_file(matches, options::ONLY_FILE).map_err_context(String::new)?; + (true, words) } else { (false, HashSet::new()) }; let (i, iset): (bool, HashSet) = if matches.is_present(options::IGNORE_FILE) { - (true, read_word_filter_file(matches, options::IGNORE_FILE)) + let words = read_word_filter_file(matches, options::IGNORE_FILE) + .map_err_context(String::new)?; + (true, words) } else { (false, HashSet::new()) }; if matches.is_present(options::BREAK_FILE) { - crash!(1, "-b not implemented yet"); + return Err(PtxError::NotImplemented("-b").into()); } // Ignore empty string regex from cmd-line-args let arg_reg: Option = if matches.is_present(options::WORD_REGEXP) { @@ -130,13 +139,13 @@ impl WordFilter { } } }; - WordFilter { + Ok(WordFilter { only_specified: o, ignore_specified: i, only_set: oset, ignore_set: iset, word_regex: reg, - } + }) } } @@ -150,7 +159,29 @@ struct WordRef { filename: String, } -fn get_config(matches: &clap::ArgMatches) -> Config { +#[derive(Debug)] +enum PtxError { + DumbFormat, + NotImplemented(&'static str), + ParseError(ParseIntError), +} + +impl Error for PtxError {} +impl UError for PtxError {} + +impl Display for PtxError { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + PtxError::DumbFormat => { + write!(f, "There is no dumb format with GNU extensions disabled") + } + PtxError::NotImplemented(s) => write!(f, "{} not implemented yet", s), + PtxError::ParseError(e) => e.fmt(f), + } + } +} + +fn get_config(matches: &clap::ArgMatches) -> UResult { let mut config: Config = Default::default(); let err_msg = "parsing options failed"; if matches.is_present(options::TRADITIONAL) { @@ -158,10 +189,10 @@ fn get_config(matches: &clap::ArgMatches) -> Config { config.format = OutFormat::Roff; config.context_regex = "[^ \t\n]+".to_owned(); } else { - crash!(1, "GNU extensions not implemented yet"); + return Err(PtxError::NotImplemented("GNU extensions").into()); } if matches.is_present(options::SENTENCE_REGEXP) { - crash!(1, "-S not implemented yet"); + return Err(PtxError::NotImplemented("-S").into()); } config.auto_ref = matches.is_present(options::AUTO_REFERENCE); config.input_ref = matches.is_present(options::REFERENCES); @@ -180,15 +211,18 @@ fn get_config(matches: &clap::ArgMatches) -> Config { .to_string(); } if matches.is_present(options::WIDTH) { - let width_str = matches.value_of(options::WIDTH).expect(err_msg).to_string(); - config.line_width = crash_if_err!(1, (&width_str).parse::()); + config.line_width = matches + .value_of(options::WIDTH) + .expect(err_msg) + .parse() + .map_err(PtxError::ParseError)?; } if matches.is_present(options::GAP_SIZE) { - let gap_str = matches + config.gap_size = matches .value_of(options::GAP_SIZE) .expect(err_msg) - .to_string(); - config.gap_size = crash_if_err!(1, (&gap_str).parse::()); + .parse() + .map_err(PtxError::ParseError)?; } if matches.is_present(options::FORMAT_ROFF) { config.format = OutFormat::Roff; @@ -196,7 +230,7 @@ fn get_config(matches: &clap::ArgMatches) -> Config { if matches.is_present(options::FORMAT_TEX) { config.format = OutFormat::Tex; } - config + Ok(config) } struct FileContent { @@ -207,7 +241,7 @@ struct FileContent { type FileMap = HashMap; -fn read_input(input_files: &[String], config: &Config) -> FileMap { +fn read_input(input_files: &[String], config: &Config) -> std::io::Result { let mut file_map: FileMap = HashMap::new(); let mut files = Vec::new(); if input_files.is_empty() { @@ -224,10 +258,10 @@ fn read_input(input_files: &[String], config: &Config) -> FileMap { let reader: BufReader> = BufReader::new(if filename == "-" { Box::new(stdin()) } else { - let file = crash_if_err!(1, File::open(filename)); + let file = File::open(filename)?; Box::new(file) }); - let lines: Vec = reader.lines().map(|x| crash_if_err!(1, x)).collect(); + let lines: Vec = reader.lines().collect::>>()?; // Indexing UTF-8 string requires walking from the beginning, which can hurts performance badly when the line is long. // Since we will be jumping around the line a lot, we dump the content into a Vec, which can be indexed in constant time. @@ -243,7 +277,7 @@ fn read_input(input_files: &[String], config: &Config) -> FileMap { ); offset += size } - file_map + Ok(file_map) } /// Go through every lines in the input files and record each match occurrence as a `WordRef`. @@ -526,7 +560,7 @@ fn format_tex_line( } fn format_roff_field(s: &str) -> String { - s.replace("\"", "\"\"") + s.replace('\"', "\"\"") } fn format_roff_line( @@ -571,11 +605,11 @@ fn write_traditional_output( file_map: &FileMap, words: &BTreeSet, output_filename: &str, -) { +) -> UResult<()> { let mut writer: BufWriter> = BufWriter::new(if output_filename == "-" { Box::new(stdout()) } else { - let file = crash_if_err!(1, File::create(output_filename)); + let file = File::create(output_filename).map_err_context(String::new)?; Box::new(file) }); @@ -611,10 +645,13 @@ fn write_traditional_output( &chars_lines[word_ref.local_line_nr], &reference, ), - OutFormat::Dumb => crash!(1, "There is no dumb format with GNU extensions disabled"), + OutFormat::Dumb => { + return Err(PtxError::DumbFormat.into()); + } }; - crash_if_err!(1, writeln!(writer, "{}", output_line)); + writeln!(writer, "{}", output_line).map_err_context(String::new)?; } + Ok(()) } mod options { @@ -637,7 +674,8 @@ mod options { pub static WIDTH: &str = "width"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -650,17 +688,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { None => vec!["-".to_string()], }; - let config = get_config(&matches); - let word_filter = WordFilter::new(&matches, &config); - let file_map = read_input(&input_files, &config); + let config = get_config(&matches)?; + let word_filter = WordFilter::new(&matches, &config)?; + let file_map = read_input(&input_files, &config).map_err_context(String::new)?; let word_set = create_word_set(&config, &word_filter, &file_map); let output_file = if !config.gnu_ext && matches.args.len() == 2 { matches.value_of(options::FILE).unwrap_or("-").to_string() } else { "-".to_owned() }; - write_traditional_output(&config, &file_map, &word_set, &output_file); - 0 + write_traditional_output(&config, &file_map, &word_set, &output_file) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/pwd/Cargo.toml b/src/uu/pwd/Cargo.toml index a168fb5aad3..d5a9b5f0b1f 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pwd" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "pwd ~ (uutils) display current working directory" @@ -16,8 +16,8 @@ path = "src/pwd.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "pwd" diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 8beb65dbd93..4b4819ed58c 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -18,7 +18,7 @@ static OPT_LOGICAL: &str = "logical"; static OPT_PHYSICAL: &str = "physical"; fn physical_path() -> io::Result { - // std::env::current_dir() is a thin wrapper around libc's getcwd(). + // std::env::current_dir() is a thin wrapper around libc::getcwd(). // On Unix, getcwd() must return the physical path: // https://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index d126a3835a8..2ccbfffc7e0 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_readlink" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "readlink ~ (uutils) display resolved path of PATHNAME" @@ -17,13 +17,12 @@ path = "src/readlink.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "readlink" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index d6dd1634ae5..85b436be1a4 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -15,6 +15,7 @@ use std::fs; use std::io::{stdout, Write}; use std::path::{Path, PathBuf}; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; const ABOUT: &str = "Print value of a symbolic link or canonical file name."; @@ -33,7 +34,8 @@ fn usage() -> String { format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -64,11 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); if files.is_empty() { - crash!( - 1, - "missing operand\nTry '{} --help' for more information", - uucore::execution_phrase() - ); + return Err(UUsageError::new(1, "missing operand")); } if no_newline && files.len() > 1 && !silent { @@ -78,30 +76,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for f in &files { let p = PathBuf::from(f); - if res_mode == ResolveMode::None { - match fs::read_link(&p) { - Ok(path) => show(&path, no_newline, use_zero), - Err(err) => { - if verbose { - show_error!("{}: errno {}", f.maybe_quote(), err.raw_os_error().unwrap()); - } - return 1; - } - } + let path_result = if res_mode == ResolveMode::None { + fs::read_link(&p) } else { - match canonicalize(&p, can_mode, res_mode) { - Ok(path) => show(&path, no_newline, use_zero), - Err(err) => { - if verbose { - show_error!("{}: errno {}", f.maybe_quote(), err.raw_os_error().unwrap()); - } - return 1; + canonicalize(&p, can_mode, res_mode) + }; + match path_result { + Ok(path) => show(&path, no_newline, use_zero).map_err_context(String::new)?, + Err(err) => { + if verbose { + return Err(USimpleError::new( + 1, + format!("{}: errno {}", f.maybe_quote(), err.raw_os_error().unwrap()), + )); + } else { + return Err(1.into()); } } } } - - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -168,7 +162,7 @@ pub fn uu_app() -> App<'static, 'static> { .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) } -fn show(path: &Path, no_newline: bool, use_zero: bool) { +fn show(path: &Path, no_newline: bool, use_zero: bool) -> std::io::Result<()> { let path = path.to_str().unwrap(); if use_zero { print!("{}\0", path); @@ -177,5 +171,5 @@ fn show(path: &Path, no_newline: bool, use_zero: bool) { } else { println!("{}", path); } - crash_if_err!(1, stdout().flush()); + stdout().flush() } diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index 4be39f97893..b9da41cb919 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_realpath" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "realpath ~ (uutils) display resolved absolute path of PATHNAME" @@ -16,13 +16,12 @@ path = "src/realpath.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "realpath" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index d13aed6c7ab..de88333413c 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -17,6 +17,7 @@ use std::{ }; use uucore::{ display::{print_verbatim, Quotable}, + error::{FromIo, UResult}, fs::{canonicalize, MissingHandling, ResolveMode}, }; @@ -36,7 +37,8 @@ fn usage() -> String { format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -60,16 +62,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } else { MissingHandling::Normal }; - let mut retcode = 0; for path in &paths { - if let Err(e) = resolve_path(path, strip, zero, logical, can_mode) { - if !quiet { - show_error!("{}: {}", path.maybe_quote(), e); - } - retcode = 1 - }; + let result = resolve_path(path, strip, zero, logical, can_mode); + if !quiet { + show_if_err!(result.map_err_context(|| path.maybe_quote().to_string())); + } } - retcode + // Although we return `Ok`, it is possible that a call to + // `show!()` above has set the exit code for the program to a + // non-zero integer. + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/relpath/Cargo.toml b/src/uu/relpath/Cargo.toml index f1ddf87dca7..97309a17249 100644 --- a/src/uu/relpath/Cargo.toml +++ b/src/uu/relpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_relpath" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "relpath ~ (uutils) display relative path of PATHNAME_TO from PATHNAME_FROM" @@ -16,13 +16,12 @@ path = "src/relpath.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "relpath" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index 16b920861c0..88c1f55067d 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -11,6 +11,7 @@ use clap::{crate_version, App, Arg}; use std::env; use std::path::{Path, PathBuf}; use uucore::display::println_verbatim; +use uucore::error::{FromIo, UResult}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use uucore::InvalidEncodingHandling; @@ -27,7 +28,8 @@ fn usage() -> String { format!("{} [-d DIR] TO [FROM]", uucore::execution_phrase()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -40,17 +42,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(p) => Path::new(p).to_path_buf(), None => env::current_dir().unwrap(), }; - let absto = canonicalize(to, MissingHandling::Normal, ResolveMode::Logical).unwrap(); - let absfrom = canonicalize(from, MissingHandling::Normal, ResolveMode::Logical).unwrap(); + let absto = canonicalize(to, MissingHandling::Normal, ResolveMode::Logical) + .map_err_context(String::new)?; + let absfrom = canonicalize(from, MissingHandling::Normal, ResolveMode::Logical) + .map_err_context(String::new)?; if matches.is_present(options::DIR) { let base = Path::new(&matches.value_of(options::DIR).unwrap()).to_path_buf(); - let absbase = canonicalize(base, MissingHandling::Normal, ResolveMode::Logical).unwrap(); + let absbase = canonicalize(base, MissingHandling::Normal, ResolveMode::Logical) + .map_err_context(String::new)?; if !absto.as_path().starts_with(absbase.as_path()) || !absfrom.as_path().starts_with(absbase.as_path()) { - println_verbatim(absto).unwrap(); - return 0; + return println_verbatim(absto).map_err_context(String::new); } } @@ -75,8 +79,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(|x| result.push(x.as_os_str())) .last(); - println_verbatim(result).unwrap(); - 0 + println_verbatim(result).map_err_context(String::new) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 757fa8b22ec..1927643a422 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rm" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "rm ~ (uutils) remove PATHNAME" @@ -18,8 +18,8 @@ path = "src/rm.rs" clap = { version = "2.33", features = ["wrap_help"] } walkdir = "2.2" remove_dir_all = "0.5.1" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(windows)'.dependencies] winapi = { version="0.3", features=[] } @@ -29,5 +29,4 @@ name = "rm" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 54fce52fff3..3183b0ac01b 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -18,6 +18,7 @@ use std::io::{stderr, stdin, BufRead, Write}; use std::ops::BitOr; use std::path::{Path, PathBuf}; use uucore::display::Quotable; +use uucore::error::{UResult, USimpleError, UUsageError}; use walkdir::{DirEntry, WalkDir}; #[derive(Eq, PartialEq, Clone, Copy)] @@ -50,6 +51,7 @@ static OPT_PROMPT_MORE: &str = "prompt-more"; static OPT_RECURSIVE: &str = "recursive"; static OPT_RECURSIVE_R: &str = "recursive_R"; static OPT_VERBOSE: &str = "verbose"; +static PRESUME_INPUT_TTY: &str = "presume-input-tty"; static ARG_FILES: &str = "files"; @@ -74,7 +76,8 @@ fn get_long_usage() -> String { ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let long_usage = get_long_usage(); @@ -93,9 +96,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if files.is_empty() && !force { // Still check by hand and not use clap // Because "rm -f" is a thing - show_error!("missing an argument"); - show_error!("for help, try '{0} --help'", uucore::execution_phrase()); - return 1; + return Err(UUsageError::new(1, "missing operand")); } else { let options = Options { force, @@ -109,7 +110,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { "none" => InteractiveMode::None, "once" => InteractiveMode::Once, "always" => InteractiveMode::Always, - val => crash!(1, "Invalid argument to interactive ({})", val), + val => { + return Err(USimpleError::new( + 1, + format!("Invalid argument to interactive ({})", val), + )) + } } } else { InteractiveMode::None @@ -128,16 +134,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { "Remove all arguments? " }; if !prompt(msg) { - return 0; + return Ok(()); } } if remove(files, options) { - return 1; + return Err(1.into()); } } - - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -186,6 +191,7 @@ pub fn uu_app() -> App<'static, 'static> { ) .arg( Arg::with_name(OPT_RECURSIVE).short("r") + .multiple(true) .long(OPT_RECURSIVE) .help("remove directories and their contents recursively") ) @@ -207,6 +213,17 @@ pub fn uu_app() -> App<'static, 'static> { .long(OPT_VERBOSE) .help("explain what is being done") ) + // From the GNU source code: + // This is solely for testing. + // Do not document. + // It is relatively difficult to ensure that there is a tty on stdin. + // Since rm acts differently depending on that, without this option, + // it'd be harder to test the parts of rm that depend on that setting. + .arg( + Arg::with_name(PRESUME_INPUT_TTY) + .long(PRESUME_INPUT_TTY) + .hidden(true) + ) .arg( Arg::with_name(ARG_FILES) .multiple(true) diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index bc05773a874..b3a28a023a3 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rmdir" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "rmdir ~ (uutils) remove empty DIRECTORY" @@ -16,8 +16,8 @@ path = "src/rmdir.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } libc = "0.2.42" [[bin]] diff --git a/src/uu/runcon/Cargo.toml b/src/uu/runcon/Cargo.toml index ff06e72a170..79298ad6de1 100644 --- a/src/uu/runcon/Cargo.toml +++ b/src/uu/runcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_runcon" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "runcon ~ (uutils) run command with specified security context" diff --git a/src/uu/runcon/src/errors.rs b/src/uu/runcon/src/errors.rs index 5f8258de00b..082b5505558 100644 --- a/src/uu/runcon/src/errors.rs +++ b/src/uu/runcon/src/errors.rs @@ -1,12 +1,22 @@ use std::ffi::OsString; -use std::fmt::Write; +use std::fmt::{Display, Formatter, Write}; use std::io; use std::str::Utf8Error; use uucore::display::Quotable; +use uucore::error::UError; pub(crate) type Result = std::result::Result; +// This list is NOT exhaustive. This command might perform an `execvp()` to run +// a different program. When that happens successfully, the exit status of this +// process will be the exit status of that program. +pub(crate) mod error_exit_status { + pub const NOT_FOUND: i32 = 127; + pub const COULD_NOT_EXECUTE: i32 = 126; + pub const ANOTHER_ERROR: i32 = libc::EXIT_FAILURE; +} + #[derive(thiserror::Error, Debug)] pub(crate) enum Error { #[error("No command is specified")] @@ -63,13 +73,44 @@ impl Error { } } -pub(crate) fn report_full_error(mut err: &dyn std::error::Error) -> String { - let mut desc = String::with_capacity(256); - write!(&mut desc, "{}", err).unwrap(); +pub(crate) fn write_full_error(writer: &mut W, err: &dyn std::error::Error) -> std::fmt::Result +where + W: Write, +{ + write!(writer, "{}", err)?; + let mut err = err; while let Some(source) = err.source() { err = source; - write!(&mut desc, ": {}", err).unwrap(); + write!(writer, ": {}", err)?; + } + write!(writer, ".")?; + Ok(()) +} + +#[derive(Debug)] +pub(crate) struct RunconError { + inner: Error, + code: i32, +} + +impl RunconError { + pub(crate) fn new(e: Error) -> RunconError { + RunconError::with_code(error_exit_status::ANOTHER_ERROR, e) + } + + pub(crate) fn with_code(code: i32, e: Error) -> RunconError { + RunconError { inner: e, code } + } +} + +impl std::error::Error for RunconError {} +impl UError for RunconError { + fn code(&self) -> i32 { + self.code + } +} +impl Display for RunconError { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write_full_error(f, &self.inner) } - desc.push('.'); - desc } diff --git a/src/uu/runcon/src/runcon.rs b/src/uu/runcon/src/runcon.rs index b2f1468bdab..595cf3e6505 100644 --- a/src/uu/runcon/src/runcon.rs +++ b/src/uu/runcon/src/runcon.rs @@ -1,6 +1,6 @@ // spell-checker:ignore (vars) RFILE -use uucore::{show_error, show_usage_error}; +use uucore::error::{UResult, UUsageError}; use clap::{App, Arg}; use selinux::{OpaqueSecurityContext, SecurityClass, SecurityContext}; @@ -13,7 +13,8 @@ use std::{io, ptr}; mod errors; -use errors::{report_full_error, Error, Result}; +use errors::error_exit_status; +use errors::{Error, Result, RunconError}; const VERSION: &str = env!("CARGO_PKG_VERSION"); const ABOUT: &str = "Run command with specified security context."; @@ -35,16 +36,6 @@ pub mod options { pub const RANGE: &str = "range"; } -// This list is NOT exhaustive. This command might perform an `execvp()` to run -// a different program. When that happens successfully, the exit status of this -// process will be the exit status of that program. -mod error_exit_status { - pub const SUCCESS: i32 = libc::EXIT_SUCCESS; - pub const NOT_FOUND: i32 = 127; - pub const COULD_NOT_EXECUTE: i32 = 126; - pub const ANOTHER_ERROR: i32 = libc::EXIT_FAILURE; -} - fn get_usage() -> String { format!( "{0} [CONTEXT COMMAND [ARG...]]\n \ @@ -53,7 +44,8 @@ fn get_usage() -> String { ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = get_usage(); let config = uu_app().usage(usage.as_ref()); @@ -65,39 +57,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match r.kind { clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => { println!("{}", r); - return error_exit_status::SUCCESS; + return Ok(()); } _ => {} } } - - show_usage_error!("{}.\n", r); - return error_exit_status::ANOTHER_ERROR; + return Err(UUsageError::new( + error_exit_status::ANOTHER_ERROR, + format!("{}", r), + )); } }; match &options.mode { - CommandLineMode::Print => { - if let Err(r) = print_current_context() { - show_error!("{}", report_full_error(&r)); - return error_exit_status::ANOTHER_ERROR; - } - } - + CommandLineMode::Print => print_current_context().map_err(|e| RunconError::new(e).into()), CommandLineMode::PlainContext { context, command } => { - let (exit_status, err) = - if let Err(err) = get_plain_context(context).and_then(set_next_exec_context) { - (error_exit_status::ANOTHER_ERROR, err) - } else { - // On successful execution, the following call never returns, - // and this process image is replaced. - execute_command(command, &options.arguments) - }; - - show_error!("{}", report_full_error(&err)); - return exit_status; + get_plain_context(context) + .and_then(set_next_exec_context) + .map_err(RunconError::new)?; + // On successful execution, the following call never returns, + // and this process image is replaced. + execute_command(command, &options.arguments) } - CommandLineMode::CustomContext { compute_transition_context, user, @@ -106,34 +87,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 { range, command, } => { - if let Some(command) = command { - let (exit_status, err) = if let Err(err) = get_custom_context( - *compute_transition_context, - user.as_deref(), - role.as_deref(), - the_type.as_deref(), - range.as_deref(), - command, - ) - .and_then(set_next_exec_context) - { - (error_exit_status::ANOTHER_ERROR, err) - } else { + match command { + Some(command) => { + get_custom_context( + *compute_transition_context, + user.as_deref(), + role.as_deref(), + the_type.as_deref(), + range.as_deref(), + command, + ) + .and_then(set_next_exec_context) + .map_err(RunconError::new)?; // On successful execution, the following call never returns, // and this process image is replaced. execute_command(command, &options.arguments) - }; - - show_error!("{}", report_full_error(&err)); - return exit_status; - } else if let Err(r) = print_current_context() { - show_error!("{}", report_full_error(&r)); - return error_exit_status::ANOTHER_ERROR; + } + None => print_current_context().map_err(|e| RunconError::new(e).into()), } } } - - error_exit_status::SUCCESS } pub fn uu_app() -> App<'static, 'static> { @@ -406,25 +379,19 @@ fn get_custom_context( Ok(osc) } -/// The actual return type of this function should be `Result` +/// The actual return type of this function should be `UResult`. /// However, until the *never* type is stabilized, one way to indicate to the /// compiler the only valid return type is to say "if this returns, it will /// always return an error". -fn execute_command(command: &OsStr, arguments: &[OsString]) -> (i32, Error) { - let c_command = match os_str_to_c_string(command) { - Ok(v) => v, - Err(r) => return (error_exit_status::ANOTHER_ERROR, r), - }; +fn execute_command(command: &OsStr, arguments: &[OsString]) -> UResult<()> { + let c_command = os_str_to_c_string(command).map_err(RunconError::new)?; - let argv_storage: Vec = match arguments + let argv_storage: Vec = arguments .iter() .map(AsRef::as_ref) .map(os_str_to_c_string) .collect::>() - { - Ok(v) => v, - Err(r) => return (error_exit_status::ANOTHER_ERROR, r), - }; + .map_err(RunconError::new)?; let mut argv: Vec<*const c_char> = Vec::with_capacity(arguments.len().saturating_add(2)); argv.push(c_command.as_ptr()); @@ -441,7 +408,7 @@ fn execute_command(command: &OsStr, arguments: &[OsString]) -> (i32, Error) { }; let err = Error::from_io1("Executing command", command, err); - (exit_status, err) + Err(RunconError::with_code(exit_status, err).into()) } fn os_str_to_c_string(s: &OsStr) -> Result { diff --git a/src/uu/seq/BENCHMARKING.md b/src/uu/seq/BENCHMARKING.md new file mode 100644 index 00000000000..4d2f82afe91 --- /dev/null +++ b/src/uu/seq/BENCHMARKING.md @@ -0,0 +1,19 @@ +# Benchmarking to measure performance + +To compare the performance of the `uutils` version of `seq` with the +GNU version of `seq`, you can use a benchmarking tool like +[hyperfine][0]. On Ubuntu 18.04 or later, you can install `hyperfine` by +running + + sudo apt-get install hyperfine + +Next, build the `seq` binary under the release profile: + + cargo build --release -p uu_seq + +Finally, you can compare the performance of the two versions of `head` +by running, for example, + + hyperfine "seq 1000000" "target/release/seq 1000000" + +[0]: https://github.com/sharkdp/hyperfine diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index f5a23310f11..93d9bb003e7 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -1,6 +1,7 @@ +# spell-checker:ignore bigdecimal [package] name = "uu_seq" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "seq ~ (uutils) display a sequence of numbers" @@ -15,16 +16,16 @@ edition = "2018" path = "src/seq.rs" [dependencies] +bigdecimal = "0.3" clap = { version = "2.33", features = ["wrap_help"] } num-bigint = "0.4.0" num-traits = "0.2.14" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "seq" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/seq/src/digits.rs b/src/uu/seq/src/digits.rs deleted file mode 100644 index bde933978ef..00000000000 --- a/src/uu/seq/src/digits.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! Counting number of digits needed to represent a number. -//! -//! The [`num_integral_digits`] and [`num_fractional_digits`] functions -//! count the number of digits needed to represent a number in decimal -//! notation (like "123.456"). -use std::convert::TryInto; -use std::num::ParseIntError; - -use uucore::display::Quotable; - -/// The number of digits after the decimal point in a given number. -/// -/// The input `s` is a string representing a number, either an integer -/// or a floating point number in either decimal notation or scientific -/// notation. This function returns the number of digits after the -/// decimal point needed to print the number in decimal notation. -/// -/// # Examples -/// -/// ```rust,ignore -/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3); -/// ``` -pub fn num_fractional_digits(s: &str) -> Result { - match (s.find('.'), s.find('e')) { - // For example, "123456". - (None, None) => Ok(0), - - // For example, "123e456". - (None, Some(j)) => { - let exponent: i64 = s[j + 1..].parse()?; - if exponent < 0 { - Ok(-exponent as usize) - } else { - Ok(0) - } - } - - // For example, "123.456". - (Some(i), None) => Ok(s.len() - (i + 1)), - - // For example, "123.456e789". - (Some(i), Some(j)) if i < j => { - // Because of the match guard, this subtraction will not underflow. - let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64; - let exponent: i64 = s[j + 1..].parse()?; - if num_digits_between_decimal_point_and_e < exponent { - Ok(0) - } else { - Ok((num_digits_between_decimal_point_and_e - exponent) - .try_into() - .unwrap()) - } - } - _ => crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - s.quote(), - uucore::execution_phrase() - ), - } -} - -/// The number of digits before the decimal point in a given number. -/// -/// The input `s` is a string representing a number, either an integer -/// or a floating point number in either decimal notation or scientific -/// notation. This function returns the number of digits before the -/// decimal point needed to print the number in decimal notation. -/// -/// # Examples -/// -/// ```rust,ignore -/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 2); -/// ``` -pub fn num_integral_digits(s: &str) -> Result { - match (s.find('.'), s.find('e')) { - // For example, "123456". - (None, None) => Ok(s.len()), - - // For example, "123e456". - (None, Some(j)) => { - let exponent: i64 = s[j + 1..].parse()?; - let total = j as i64 + exponent; - if total < 1 { - Ok(1) - } else { - Ok(total.try_into().unwrap()) - } - } - - // For example, "123.456". - (Some(i), None) => Ok(i), - - // For example, "123.456e789". - (Some(i), Some(j)) => { - let exponent: i64 = s[j + 1..].parse()?; - let minimum: usize = { - let integral_part: f64 = crash_if_err!(1, s[..j].parse()); - if integral_part == -0.0 && integral_part.is_sign_negative() { - 2 - } else { - 1 - } - }; - - let total = i as i64 + exponent; - if total < minimum as i64 { - Ok(minimum) - } else { - Ok(total.try_into().unwrap()) - } - } - } -} - -#[cfg(test)] -mod tests { - - mod test_num_integral_digits { - use crate::num_integral_digits; - - #[test] - fn test_integer() { - assert_eq!(num_integral_digits("123").unwrap(), 3); - } - - #[test] - fn test_decimal() { - assert_eq!(num_integral_digits("123.45").unwrap(), 3); - } - - #[test] - fn test_scientific_no_decimal_positive_exponent() { - assert_eq!(num_integral_digits("123e4").unwrap(), 3 + 4); - } - - #[test] - fn test_scientific_with_decimal_positive_exponent() { - assert_eq!(num_integral_digits("123.45e6").unwrap(), 3 + 6); - } - - #[test] - fn test_scientific_no_decimal_negative_exponent() { - assert_eq!(num_integral_digits("123e-4").unwrap(), 1); - } - - #[test] - fn test_scientific_with_decimal_negative_exponent() { - assert_eq!(num_integral_digits("123.45e-6").unwrap(), 1); - assert_eq!(num_integral_digits("123.45e-1").unwrap(), 2); - } - } - - mod test_num_fractional_digits { - use crate::num_fractional_digits; - - #[test] - fn test_integer() { - assert_eq!(num_fractional_digits("123").unwrap(), 0); - } - - #[test] - fn test_decimal() { - assert_eq!(num_fractional_digits("123.45").unwrap(), 2); - } - - #[test] - fn test_scientific_no_decimal_positive_exponent() { - assert_eq!(num_fractional_digits("123e4").unwrap(), 0); - } - - #[test] - fn test_scientific_with_decimal_positive_exponent() { - assert_eq!(num_fractional_digits("123.45e6").unwrap(), 0); - assert_eq!(num_fractional_digits("123.45e1").unwrap(), 1); - } - - #[test] - fn test_scientific_no_decimal_negative_exponent() { - assert_eq!(num_fractional_digits("123e-4").unwrap(), 4); - assert_eq!(num_fractional_digits("123e-1").unwrap(), 1); - } - - #[test] - fn test_scientific_with_decimal_negative_exponent() { - assert_eq!(num_fractional_digits("123.45e-6").unwrap(), 8); - assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3); - } - } -} diff --git a/src/uu/seq/src/error.rs b/src/uu/seq/src/error.rs new file mode 100644 index 00000000000..837cd5c33a1 --- /dev/null +++ b/src/uu/seq/src/error.rs @@ -0,0 +1,70 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +// spell-checker:ignore numberparse argtype +//! Errors returned by seq. +use std::error::Error; +use std::fmt::Display; + +use uucore::display::Quotable; +use uucore::error::UError; + +use crate::numberparse::ParseNumberError; + +#[derive(Debug)] +pub enum SeqError { + /// An error parsing the input arguments. + /// + /// The parameters are the [`String`] argument as read from the + /// command line and the underlying parsing error itself. + ParseError(String, ParseNumberError), + + /// The increment argument was zero, which is not allowed. + /// + /// The parameter is the increment argument as a [`String`] as read + /// from the command line. + ZeroIncrement(String), +} + +impl SeqError { + /// The [`String`] argument as read from the command-line. + fn arg(&self) -> &str { + match self { + SeqError::ParseError(s, _) => s, + SeqError::ZeroIncrement(s) => s, + } + } + + /// The type of argument that is causing the error. + fn argtype(&self) -> &str { + match self { + SeqError::ParseError(_, e) => match e { + ParseNumberError::Float => "floating point", + ParseNumberError::Nan => "'not-a-number'", + ParseNumberError::Hex => "hexadecimal", + }, + SeqError::ZeroIncrement(_) => "Zero increment", + } + } +} +impl UError for SeqError { + /// Always return 1. + fn code(&self) -> i32 { + 1 + } +} + +impl Error for SeqError {} + +impl Display for SeqError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "invalid {} argument: {}\nTry '{} --help' for more information.", + self.argtype(), + self.arg().quote(), + uucore::execution_phrase(), + ) + } +} diff --git a/src/uu/seq/src/extendedbigdecimal.rs b/src/uu/seq/src/extendedbigdecimal.rs new file mode 100644 index 00000000000..6cad83dad9d --- /dev/null +++ b/src/uu/seq/src/extendedbigdecimal.rs @@ -0,0 +1,290 @@ +// spell-checker:ignore bigdecimal extendedbigdecimal extendedbigint +//! An arbitrary precision float that can also represent infinity, NaN, etc. +//! +//! The finite values are stored as [`BigDecimal`] instances. Because +//! the `bigdecimal` library does not represent infinity, NaN, etc., we +//! need to represent them explicitly ourselves. The +//! [`ExtendedBigDecimal`] enumeration does that. +//! +//! # Examples +//! +//! Addition works for [`ExtendedBigDecimal`] as it does for floats. For +//! example, adding infinity to any finite value results in infinity: +//! +//! ```rust,ignore +//! let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero()); +//! let summand2 = ExtendedBigDecimal::Infinity; +//! assert_eq!(summand1 + summand2, ExtendedBigDecimal::Infinity); +//! ``` +use std::cmp::Ordering; +use std::fmt::Display; +use std::ops::Add; + +use bigdecimal::BigDecimal; +use num_bigint::BigInt; +use num_bigint::ToBigInt; +use num_traits::One; +use num_traits::Zero; + +use crate::extendedbigint::ExtendedBigInt; + +#[derive(Debug, Clone)] +pub enum ExtendedBigDecimal { + /// Arbitrary precision floating point number. + BigDecimal(BigDecimal), + + /// Floating point positive infinity. + /// + /// This is represented as its own enumeration member instead of as + /// a [`BigDecimal`] because the `bigdecimal` library does not + /// support infinity, see [here][0]. + /// + /// [0]: https://github.com/akubera/bigdecimal-rs/issues/67 + Infinity, + + /// Floating point negative infinity. + /// + /// This is represented as its own enumeration member instead of as + /// a [`BigDecimal`] because the `bigdecimal` library does not + /// support infinity, see [here][0]. + /// + /// [0]: https://github.com/akubera/bigdecimal-rs/issues/67 + MinusInfinity, + + /// Floating point negative zero. + /// + /// This is represented as its own enumeration member instead of as + /// a [`BigDecimal`] because the `bigdecimal` library does not + /// support negative zero. + MinusZero, + + /// Floating point NaN. + /// + /// This is represented as its own enumeration member instead of as + /// a [`BigDecimal`] because the `bigdecimal` library does not + /// support NaN, see [here][0]. + /// + /// [0]: https://github.com/akubera/bigdecimal-rs/issues/67 + Nan, +} + +/// The smallest integer greater than or equal to this number. +fn ceil(x: BigDecimal) -> BigInt { + if x.is_integer() { + // Unwrapping the Option because it always returns Some + x.to_bigint().unwrap() + } else { + (x + BigDecimal::one().half()).round(0).to_bigint().unwrap() + } +} + +/// The largest integer less than or equal to this number. +fn floor(x: BigDecimal) -> BigInt { + if x.is_integer() { + // Unwrapping the Option because it always returns Some + x.to_bigint().unwrap() + } else { + (x - BigDecimal::one().half()).round(0).to_bigint().unwrap() + } +} + +impl ExtendedBigDecimal { + /// The smallest integer greater than or equal to this number. + pub fn ceil(self) -> ExtendedBigInt { + match self { + ExtendedBigDecimal::BigDecimal(x) => ExtendedBigInt::BigInt(ceil(x)), + other => From::from(other), + } + } + + /// The largest integer less than or equal to this number. + pub fn floor(self) -> ExtendedBigInt { + match self { + ExtendedBigDecimal::BigDecimal(x) => ExtendedBigInt::BigInt(floor(x)), + other => From::from(other), + } + } +} + +impl From for ExtendedBigDecimal { + fn from(big_int: ExtendedBigInt) -> Self { + match big_int { + ExtendedBigInt::BigInt(n) => Self::BigDecimal(BigDecimal::from(n)), + ExtendedBigInt::Infinity => ExtendedBigDecimal::Infinity, + ExtendedBigInt::MinusInfinity => ExtendedBigDecimal::MinusInfinity, + ExtendedBigInt::MinusZero => ExtendedBigDecimal::MinusZero, + ExtendedBigInt::Nan => ExtendedBigDecimal::Nan, + } + } +} + +impl Display for ExtendedBigDecimal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExtendedBigDecimal::BigDecimal(x) => { + let (n, p) = x.as_bigint_and_exponent(); + match p { + 0 => ExtendedBigDecimal::BigDecimal(BigDecimal::new(n * 10, 1)).fmt(f), + _ => x.fmt(f), + } + } + ExtendedBigDecimal::Infinity => f32::INFINITY.fmt(f), + ExtendedBigDecimal::MinusInfinity => f32::NEG_INFINITY.fmt(f), + ExtendedBigDecimal::MinusZero => { + // FIXME In Rust version 1.53.0 and later, the display + // of floats was updated to allow displaying negative + // zero. See + // https://github.com/rust-lang/rust/pull/78618. Currently, + // this just formats "0.0". + (0.0f32).fmt(f) + } + ExtendedBigDecimal::Nan => "nan".fmt(f), + } + } +} + +impl Zero for ExtendedBigDecimal { + fn zero() -> Self { + ExtendedBigDecimal::BigDecimal(BigDecimal::zero()) + } + fn is_zero(&self) -> bool { + match self { + Self::BigDecimal(n) => n.is_zero(), + Self::MinusZero => true, + _ => false, + } + } +} + +impl Add for ExtendedBigDecimal { + type Output = Self; + + fn add(self, other: Self) -> Self { + match (self, other) { + (Self::BigDecimal(m), Self::BigDecimal(n)) => Self::BigDecimal(m.add(n)), + (Self::BigDecimal(_), Self::MinusInfinity) => Self::MinusInfinity, + (Self::BigDecimal(_), Self::Infinity) => Self::Infinity, + (Self::BigDecimal(_), Self::Nan) => Self::Nan, + (Self::BigDecimal(m), Self::MinusZero) => Self::BigDecimal(m), + (Self::Infinity, Self::BigDecimal(_)) => Self::Infinity, + (Self::Infinity, Self::Infinity) => Self::Infinity, + (Self::Infinity, Self::MinusZero) => Self::Infinity, + (Self::Infinity, Self::MinusInfinity) => Self::Nan, + (Self::Infinity, Self::Nan) => Self::Nan, + (Self::MinusInfinity, Self::BigDecimal(_)) => Self::MinusInfinity, + (Self::MinusInfinity, Self::MinusInfinity) => Self::MinusInfinity, + (Self::MinusInfinity, Self::MinusZero) => Self::MinusInfinity, + (Self::MinusInfinity, Self::Infinity) => Self::Nan, + (Self::MinusInfinity, Self::Nan) => Self::Nan, + (Self::Nan, _) => Self::Nan, + (Self::MinusZero, other) => other, + } + } +} + +impl PartialEq for ExtendedBigDecimal { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::BigDecimal(m), Self::BigDecimal(n)) => m.eq(n), + (Self::BigDecimal(_), Self::MinusInfinity) => false, + (Self::BigDecimal(_), Self::Infinity) => false, + (Self::BigDecimal(_), Self::Nan) => false, + (Self::BigDecimal(_), Self::MinusZero) => false, + (Self::Infinity, Self::BigDecimal(_)) => false, + (Self::Infinity, Self::Infinity) => true, + (Self::Infinity, Self::MinusZero) => false, + (Self::Infinity, Self::MinusInfinity) => false, + (Self::Infinity, Self::Nan) => false, + (Self::MinusInfinity, Self::BigDecimal(_)) => false, + (Self::MinusInfinity, Self::Infinity) => false, + (Self::MinusInfinity, Self::MinusZero) => false, + (Self::MinusInfinity, Self::MinusInfinity) => true, + (Self::MinusInfinity, Self::Nan) => false, + (Self::Nan, _) => false, + (Self::MinusZero, Self::BigDecimal(_)) => false, + (Self::MinusZero, Self::Infinity) => false, + (Self::MinusZero, Self::MinusZero) => true, + (Self::MinusZero, Self::MinusInfinity) => false, + (Self::MinusZero, Self::Nan) => false, + } + } +} + +impl PartialOrd for ExtendedBigDecimal { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Self::BigDecimal(m), Self::BigDecimal(n)) => m.partial_cmp(n), + (Self::BigDecimal(_), Self::MinusInfinity) => Some(Ordering::Greater), + (Self::BigDecimal(_), Self::Infinity) => Some(Ordering::Less), + (Self::BigDecimal(_), Self::Nan) => None, + (Self::BigDecimal(m), Self::MinusZero) => m.partial_cmp(&BigDecimal::zero()), + (Self::Infinity, Self::BigDecimal(_)) => Some(Ordering::Greater), + (Self::Infinity, Self::Infinity) => Some(Ordering::Equal), + (Self::Infinity, Self::MinusZero) => Some(Ordering::Greater), + (Self::Infinity, Self::MinusInfinity) => Some(Ordering::Greater), + (Self::Infinity, Self::Nan) => None, + (Self::MinusInfinity, Self::BigDecimal(_)) => Some(Ordering::Less), + (Self::MinusInfinity, Self::Infinity) => Some(Ordering::Less), + (Self::MinusInfinity, Self::MinusZero) => Some(Ordering::Less), + (Self::MinusInfinity, Self::MinusInfinity) => Some(Ordering::Equal), + (Self::MinusInfinity, Self::Nan) => None, + (Self::Nan, _) => None, + (Self::MinusZero, Self::BigDecimal(n)) => BigDecimal::zero().partial_cmp(n), + (Self::MinusZero, Self::Infinity) => Some(Ordering::Less), + (Self::MinusZero, Self::MinusZero) => Some(Ordering::Equal), + (Self::MinusZero, Self::MinusInfinity) => Some(Ordering::Greater), + (Self::MinusZero, Self::Nan) => None, + } + } +} + +#[cfg(test)] +mod tests { + + use bigdecimal::BigDecimal; + use num_traits::Zero; + + use crate::extendedbigdecimal::ExtendedBigDecimal; + + #[test] + fn test_addition_infinity() { + let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero()); + let summand2 = ExtendedBigDecimal::Infinity; + assert_eq!(summand1 + summand2, ExtendedBigDecimal::Infinity); + } + + #[test] + fn test_addition_minus_infinity() { + let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero()); + let summand2 = ExtendedBigDecimal::MinusInfinity; + assert_eq!(summand1 + summand2, ExtendedBigDecimal::MinusInfinity); + } + + #[test] + fn test_addition_nan() { + let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero()); + let summand2 = ExtendedBigDecimal::Nan; + let sum = summand1 + summand2; + match sum { + ExtendedBigDecimal::Nan => (), + _ => unreachable!(), + } + } + + #[test] + fn test_display() { + assert_eq!( + format!("{}", ExtendedBigDecimal::BigDecimal(BigDecimal::zero())), + "0.0" + ); + assert_eq!(format!("{}", ExtendedBigDecimal::Infinity), "inf"); + assert_eq!(format!("{}", ExtendedBigDecimal::MinusInfinity), "-inf"); + assert_eq!(format!("{}", ExtendedBigDecimal::Nan), "nan"); + // FIXME In Rust version 1.53.0 and later, the display of floats + // was updated to allow displaying negative zero. Until then, we + // just display `MinusZero` as "0.0". + // + // assert_eq!(format!("{}", ExtendedBigDecimal::MinusZero), "-0.0"); + // + } +} diff --git a/src/uu/seq/src/extendedbigint.rs b/src/uu/seq/src/extendedbigint.rs new file mode 100644 index 00000000000..4a33fa6174f --- /dev/null +++ b/src/uu/seq/src/extendedbigint.rs @@ -0,0 +1,218 @@ +// spell-checker:ignore bigint extendedbigint extendedbigdecimal +//! An arbitrary precision integer that can also represent infinity, NaN, etc. +//! +//! Usually infinity, NaN, and negative zero are only represented for +//! floating point numbers. The [`ExtendedBigInt`] enumeration provides +//! a representation of those things with the set of integers. The +//! finite values are stored as [`BigInt`] instances. +//! +//! # Examples +//! +//! Addition works for [`ExtendedBigInt`] as it does for floats. For +//! example, adding infinity to any finite value results in infinity: +//! +//! ```rust,ignore +//! let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); +//! let summand2 = ExtendedBigInt::Infinity; +//! assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity); +//! ``` +use std::cmp::Ordering; +use std::fmt::Display; +use std::ops::Add; + +use num_bigint::BigInt; +use num_bigint::ToBigInt; +use num_traits::One; +use num_traits::Zero; + +use crate::extendedbigdecimal::ExtendedBigDecimal; + +#[derive(Debug, Clone)] +pub enum ExtendedBigInt { + BigInt(BigInt), + Infinity, + MinusInfinity, + MinusZero, + Nan, +} + +impl ExtendedBigInt { + /// The integer number one. + pub fn one() -> Self { + // We would like to implement `num_traits::One`, but it requires + // a multiplication implementation, and we don't want to + // implement that here. + ExtendedBigInt::BigInt(BigInt::one()) + } +} + +impl From for ExtendedBigInt { + fn from(big_decimal: ExtendedBigDecimal) -> Self { + match big_decimal { + // TODO When can this fail? + ExtendedBigDecimal::BigDecimal(x) => Self::BigInt(x.to_bigint().unwrap()), + ExtendedBigDecimal::Infinity => ExtendedBigInt::Infinity, + ExtendedBigDecimal::MinusInfinity => ExtendedBigInt::MinusInfinity, + ExtendedBigDecimal::MinusZero => ExtendedBigInt::MinusZero, + ExtendedBigDecimal::Nan => ExtendedBigInt::Nan, + } + } +} + +impl Display for ExtendedBigInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExtendedBigInt::BigInt(n) => n.fmt(f), + ExtendedBigInt::Infinity => f32::INFINITY.fmt(f), + ExtendedBigInt::MinusInfinity => f32::NEG_INFINITY.fmt(f), + ExtendedBigInt::MinusZero => { + // FIXME Come up with a way of formatting this with a + // "-" prefix. + 0.fmt(f) + } + ExtendedBigInt::Nan => "nan".fmt(f), + } + } +} + +impl Zero for ExtendedBigInt { + fn zero() -> Self { + ExtendedBigInt::BigInt(BigInt::zero()) + } + fn is_zero(&self) -> bool { + match self { + Self::BigInt(n) => n.is_zero(), + Self::MinusZero => true, + _ => false, + } + } +} + +impl Add for ExtendedBigInt { + type Output = Self; + + fn add(self, other: Self) -> Self { + match (self, other) { + (Self::BigInt(m), Self::BigInt(n)) => Self::BigInt(m.add(n)), + (Self::BigInt(_), Self::MinusInfinity) => Self::MinusInfinity, + (Self::BigInt(_), Self::Infinity) => Self::Infinity, + (Self::BigInt(_), Self::Nan) => Self::Nan, + (Self::BigInt(m), Self::MinusZero) => Self::BigInt(m), + (Self::Infinity, Self::BigInt(_)) => Self::Infinity, + (Self::Infinity, Self::Infinity) => Self::Infinity, + (Self::Infinity, Self::MinusZero) => Self::Infinity, + (Self::Infinity, Self::MinusInfinity) => Self::Nan, + (Self::Infinity, Self::Nan) => Self::Nan, + (Self::MinusInfinity, Self::BigInt(_)) => Self::MinusInfinity, + (Self::MinusInfinity, Self::MinusInfinity) => Self::MinusInfinity, + (Self::MinusInfinity, Self::MinusZero) => Self::MinusInfinity, + (Self::MinusInfinity, Self::Infinity) => Self::Nan, + (Self::MinusInfinity, Self::Nan) => Self::Nan, + (Self::Nan, _) => Self::Nan, + (Self::MinusZero, other) => other, + } + } +} + +impl PartialEq for ExtendedBigInt { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::BigInt(m), Self::BigInt(n)) => m.eq(n), + (Self::BigInt(_), Self::MinusInfinity) => false, + (Self::BigInt(_), Self::Infinity) => false, + (Self::BigInt(_), Self::Nan) => false, + (Self::BigInt(_), Self::MinusZero) => false, + (Self::Infinity, Self::BigInt(_)) => false, + (Self::Infinity, Self::Infinity) => true, + (Self::Infinity, Self::MinusZero) => false, + (Self::Infinity, Self::MinusInfinity) => false, + (Self::Infinity, Self::Nan) => false, + (Self::MinusInfinity, Self::BigInt(_)) => false, + (Self::MinusInfinity, Self::Infinity) => false, + (Self::MinusInfinity, Self::MinusZero) => false, + (Self::MinusInfinity, Self::MinusInfinity) => true, + (Self::MinusInfinity, Self::Nan) => false, + (Self::Nan, _) => false, + (Self::MinusZero, Self::BigInt(_)) => false, + (Self::MinusZero, Self::Infinity) => false, + (Self::MinusZero, Self::MinusZero) => true, + (Self::MinusZero, Self::MinusInfinity) => false, + (Self::MinusZero, Self::Nan) => false, + } + } +} + +impl PartialOrd for ExtendedBigInt { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Self::BigInt(m), Self::BigInt(n)) => m.partial_cmp(n), + (Self::BigInt(_), Self::MinusInfinity) => Some(Ordering::Greater), + (Self::BigInt(_), Self::Infinity) => Some(Ordering::Less), + (Self::BigInt(_), Self::Nan) => None, + (Self::BigInt(m), Self::MinusZero) => m.partial_cmp(&BigInt::zero()), + (Self::Infinity, Self::BigInt(_)) => Some(Ordering::Greater), + (Self::Infinity, Self::Infinity) => Some(Ordering::Equal), + (Self::Infinity, Self::MinusZero) => Some(Ordering::Greater), + (Self::Infinity, Self::MinusInfinity) => Some(Ordering::Greater), + (Self::Infinity, Self::Nan) => None, + (Self::MinusInfinity, Self::BigInt(_)) => Some(Ordering::Less), + (Self::MinusInfinity, Self::Infinity) => Some(Ordering::Less), + (Self::MinusInfinity, Self::MinusZero) => Some(Ordering::Less), + (Self::MinusInfinity, Self::MinusInfinity) => Some(Ordering::Equal), + (Self::MinusInfinity, Self::Nan) => None, + (Self::Nan, _) => None, + (Self::MinusZero, Self::BigInt(n)) => BigInt::zero().partial_cmp(n), + (Self::MinusZero, Self::Infinity) => Some(Ordering::Less), + (Self::MinusZero, Self::MinusZero) => Some(Ordering::Equal), + (Self::MinusZero, Self::MinusInfinity) => Some(Ordering::Greater), + (Self::MinusZero, Self::Nan) => None, + } + } +} + +#[cfg(test)] +mod tests { + + use num_bigint::BigInt; + use num_traits::Zero; + + use crate::extendedbigint::ExtendedBigInt; + + #[test] + fn test_addition_infinity() { + let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); + let summand2 = ExtendedBigInt::Infinity; + assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity); + } + + #[test] + fn test_addition_minus_infinity() { + let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); + let summand2 = ExtendedBigInt::MinusInfinity; + assert_eq!(summand1 + summand2, ExtendedBigInt::MinusInfinity); + } + + #[test] + fn test_addition_nan() { + let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); + let summand2 = ExtendedBigInt::Nan; + let sum = summand1 + summand2; + match sum { + ExtendedBigInt::Nan => (), + _ => unreachable!(), + } + } + + #[test] + fn test_display() { + assert_eq!(format!("{}", ExtendedBigInt::BigInt(BigInt::zero())), "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/number.rs b/src/uu/seq/src/number.rs new file mode 100644 index 00000000000..9062fa1a167 --- /dev/null +++ b/src/uu/seq/src/number.rs @@ -0,0 +1,119 @@ +// spell-checker:ignore extendedbigdecimal extendedbigint +//! A type to represent the possible start, increment, and end values for seq. +//! +//! The [`Number`] enumeration represents the possible values for the +//! start, increment, and end values for `seq`. These may be integers, +//! floating point numbers, negative zero, etc. A [`Number`] can be +//! parsed from a string by calling [`parse`]. +use num_traits::Zero; + +use crate::extendedbigdecimal::ExtendedBigDecimal; +use crate::extendedbigint::ExtendedBigInt; + +/// An integral or floating point number. +#[derive(Debug, PartialEq)] +pub enum Number { + Int(ExtendedBigInt), + Float(ExtendedBigDecimal), +} + +impl Number { + /// Decide whether this number is zero (either positive or negative). + pub fn is_zero(&self) -> bool { + // We would like to implement `num_traits::Zero`, but it + // requires an addition implementation, and we don't want to + // implement that here. + match self { + Number::Int(n) => n.is_zero(), + Number::Float(x) => x.is_zero(), + } + } + + /// Convert this number into an `ExtendedBigDecimal`. + pub fn into_extended_big_decimal(self) -> ExtendedBigDecimal { + match self { + Number::Int(n) => ExtendedBigDecimal::from(n), + Number::Float(x) => x, + } + } + + /// The integer number one. + pub fn one() -> Self { + // We would like to implement `num_traits::One`, but it requires + // a multiplication implementation, and we don't want to + // implement that here. + Number::Int(ExtendedBigInt::one()) + } + + /// Round this number towards the given other number. + /// + /// If `other` is greater, then round up. If `other` is smaller, + /// then round down. + pub fn round_towards(self, other: &ExtendedBigInt) -> ExtendedBigInt { + match self { + // If this number is already an integer, it is already + // rounded to the nearest integer in the direction of + // `other`. + Number::Int(num) => num, + // Otherwise, if this number is a float, we need to decide + // whether `other` is larger or smaller than it, and thus + // whether to round up or round down, respectively. + Number::Float(num) => { + let other: ExtendedBigDecimal = From::from(other.clone()); + if other > num { + num.ceil() + } else { + // If they are equal, then `self` is already an + // integer, so calling `floor()` does no harm and + // will just return that integer anyway. + num.floor() + } + } + } + } +} + +/// A number with a specified number of integer and fractional digits. +/// +/// This struct can be used to represent a number along with information +/// on how many significant digits to use when displaying the number. +/// The [`num_integral_digits`] field also includes the width needed to +/// display the "-" character for a negative number. +/// +/// You can get an instance of this struct by calling [`str::parse`]. +#[derive(Debug)] +pub struct PreciseNumber { + pub number: Number, + pub num_integral_digits: usize, + pub num_fractional_digits: usize, +} + +impl PreciseNumber { + pub fn new( + number: Number, + num_integral_digits: usize, + num_fractional_digits: usize, + ) -> PreciseNumber { + PreciseNumber { + number, + num_integral_digits, + num_fractional_digits, + } + } + + /// The integer number one. + pub fn one() -> Self { + // We would like to implement `num_traits::One`, but it requires + // a multiplication implementation, and we don't want to + // implement that here. + PreciseNumber::new(Number::one(), 1, 0) + } + + /// Decide whether this number is zero (either positive or negative). + pub fn is_zero(&self) -> bool { + // We would like to implement `num_traits::Zero`, but it + // requires an addition implementation, and we don't want to + // implement that here. + self.number.is_zero() + } +} diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs new file mode 100644 index 00000000000..06f5534780f --- /dev/null +++ b/src/uu/seq/src/numberparse.rs @@ -0,0 +1,618 @@ +// spell-checker:ignore extendedbigdecimal extendedbigint bigdecimal numberparse +//! Parsing numbers for use in `seq`. +//! +//! This module provides an implementation of [`FromStr`] for the +//! [`PreciseNumber`] struct. +use std::convert::TryInto; +use std::str::FromStr; + +use bigdecimal::BigDecimal; +use num_bigint::BigInt; +use num_bigint::Sign; +use num_traits::Num; +use num_traits::Zero; + +use crate::extendedbigdecimal::ExtendedBigDecimal; +use crate::extendedbigint::ExtendedBigInt; +use crate::number::Number; +use crate::number::PreciseNumber; + +/// An error returned when parsing a number fails. +#[derive(Debug, PartialEq)] +pub enum ParseNumberError { + Float, + Nan, + Hex, +} + +/// Decide whether a given string and its parsed `BigInt` is negative zero. +fn is_minus_zero_int(s: &str, n: &BigInt) -> bool { + s.starts_with('-') && n == &BigInt::zero() +} + +/// Decide whether a given string and its parsed `BigDecimal` is negative zero. +fn is_minus_zero_float(s: &str, x: &BigDecimal) -> bool { + s.starts_with('-') && x == &BigDecimal::zero() +} + +/// Parse a number with neither a decimal point nor an exponent. +/// +/// # Errors +/// +/// This function returns an error if the input string is a variant of +/// "NaN" or if no [`BigInt`] could be parsed from the string. +/// +/// # Examples +/// +/// ```rust,ignore +/// let actual = "0".parse::().unwrap().number; +/// let expected = Number::BigInt(BigInt::zero()); +/// assert_eq!(actual, expected); +/// ``` +fn parse_no_decimal_no_exponent(s: &str) -> Result { + match s.parse::() { + Ok(n) => { + // If `s` is '-0', then `parse()` returns `BigInt::zero()`, + // but we need to return `Number::MinusZeroInt` instead. + if is_minus_zero_int(s, &n) { + Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::MinusZero), + s.len(), + 0, + )) + } else { + Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::BigInt(n)), + s.len(), + 0, + )) + } + } + Err(_) => { + // Possibly "NaN" or "inf". + // + // TODO In Rust v1.53.0, this change + // https://github.com/rust-lang/rust/pull/78618 improves the + // parsing of floats to include being able to parse "NaN" + // and "inf". So when the minimum version of this crate is + // increased to 1.53.0, we should just use the built-in + // `f32` parsing instead. + if s.eq_ignore_ascii_case("inf") { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::Infinity), + 0, + 0, + )) + } else if s.eq_ignore_ascii_case("-inf") { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::MinusInfinity), + 0, + 0, + )) + } else if s.eq_ignore_ascii_case("nan") || s.eq_ignore_ascii_case("-nan") { + Err(ParseNumberError::Nan) + } else { + Err(ParseNumberError::Float) + } + } + } +} + +/// Parse a number with an exponent but no decimal point. +/// +/// # Errors +/// +/// This function returns an error if `s` is not a valid number. +/// +/// # Examples +/// +/// ```rust,ignore +/// let actual = "1e2".parse::().unwrap().number; +/// let expected = "100".parse::().unwrap(); +/// assert_eq!(actual, expected); +/// ``` +fn parse_exponent_no_decimal(s: &str, j: usize) -> Result { + let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?; + // If the exponent is strictly less than zero, then the number + // should be treated as a floating point number that will be + // displayed in decimal notation. For example, "1e-2" will be + // displayed as "0.01", but "1e2" will be displayed as "100", + // without a decimal point. + let x: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?; + let num_integral_digits = if is_minus_zero_float(s, &x) { + 2 + } else { + let total = j as i64 + exponent; + let result = if total < 1 { + 1 + } else { + total.try_into().unwrap() + }; + if x.sign() == Sign::Minus { + result + 1 + } else { + result + } + }; + let num_fractional_digits = if exponent < 0 { -exponent as usize } else { 0 }; + + if exponent < 0 { + if is_minus_zero_float(s, &x) { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::MinusZero), + num_integral_digits, + num_fractional_digits, + )) + } else { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::BigDecimal(x)), + num_integral_digits, + num_fractional_digits, + )) + } + } else { + let zeros = "0".repeat(exponent.try_into().unwrap()); + let expanded = [&s[0..j], &zeros].concat(); + parse_no_decimal_no_exponent(&expanded) + } +} + +/// Parse a number with a decimal point but no exponent. +/// +/// # Errors +/// +/// This function returns an error if `s` is not a valid number. +/// +/// # Examples +/// +/// ```rust,ignore +/// let actual = "1.2".parse::().unwrap().number; +/// let expected = "1.2".parse::().unwrap(); +/// assert_eq!(actual, expected); +/// ``` +fn parse_decimal_no_exponent(s: &str, i: usize) -> Result { + let x: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?; + + // The number of integral digits is the number of chars until the period. + // + // This includes the negative sign if there is one. Also, it is + // possible that a number is expressed as "-.123" instead of + // "-0.123", but when we display the number we want it to include + // the leading 0. + let num_integral_digits = if s.starts_with("-.") { i + 1 } else { i }; + let num_fractional_digits = s.len() - (i + 1); + if is_minus_zero_float(s, &x) { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::MinusZero), + num_integral_digits, + num_fractional_digits, + )) + } else { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::BigDecimal(x)), + num_integral_digits, + num_fractional_digits, + )) + } +} + +/// Parse a number with both a decimal point and an exponent. +/// +/// # Errors +/// +/// This function returns an error if `s` is not a valid number. +/// +/// # Examples +/// +/// ```rust,ignore +/// let actual = "1.2e3".parse::().unwrap().number; +/// let expected = "1200".parse::().unwrap(); +/// assert_eq!(actual, expected); +/// ``` +fn parse_decimal_and_exponent( + s: &str, + i: usize, + j: usize, +) -> Result { + // Because of the match guard, this subtraction will not underflow. + let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64; + let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?; + let val: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?; + + let num_integral_digits = { + let minimum: usize = { + let integral_part: f64 = s[..j].parse().map_err(|_| ParseNumberError::Float)?; + if integral_part.is_sign_negative() { + 2 + } else { + 1 + } + }; + // Special case: if the string is "-.1e2", we need to treat it + // as if it were "-0.1e2". + let total = if s.starts_with("-.") { + i as i64 + exponent + 1 + } else { + i as i64 + exponent + }; + if total < minimum as i64 { + minimum + } else { + total.try_into().unwrap() + } + }; + + let num_fractional_digits = if num_digits_between_decimal_point_and_e < exponent { + 0 + } else { + (num_digits_between_decimal_point_and_e - exponent) + .try_into() + .unwrap() + }; + + if num_digits_between_decimal_point_and_e <= exponent { + if is_minus_zero_float(s, &val) { + Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::MinusZero), + num_integral_digits, + num_fractional_digits, + )) + } else { + let zeros: String = "0".repeat( + (exponent - num_digits_between_decimal_point_and_e) + .try_into() + .unwrap(), + ); + let expanded = [&s[0..i], &s[i + 1..j], &zeros].concat(); + let n = expanded + .parse::() + .map_err(|_| ParseNumberError::Float)?; + Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::BigInt(n)), + num_integral_digits, + num_fractional_digits, + )) + } + } else if is_minus_zero_float(s, &val) { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::MinusZero), + num_integral_digits, + num_fractional_digits, + )) + } else { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::BigDecimal(val)), + num_integral_digits, + num_fractional_digits, + )) + } +} + +/// Parse a hexadecimal integer from a string. +/// +/// # Errors +/// +/// This function returns an error if no [`BigInt`] could be parsed from +/// the string. +/// +/// # Examples +/// +/// ```rust,ignore +/// let actual = "0x0".parse::().unwrap().number; +/// let expected = Number::BigInt(BigInt::zero()); +/// assert_eq!(actual, expected); +/// ``` +fn parse_hexadecimal(s: &str) -> Result { + let (is_neg, s) = if s.starts_with('-') { + (true, &s[3..]) + } else { + (false, &s[2..]) + }; + + if s.starts_with('-') || s.starts_with('+') { + // Even though this is more like an invalid hexadecimal number, + // GNU reports this as an invalid floating point number, so we + // use `ParseNumberError::Float` to match that behavior. + return Err(ParseNumberError::Float); + } + + let num = BigInt::from_str_radix(s, 16).map_err(|_| ParseNumberError::Hex)?; + + match (is_neg, num == BigInt::zero()) { + (true, true) => Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::MinusZero), + 2, + 0, + )), + (true, false) => Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::BigInt(-num)), + 0, + 0, + )), + (false, _) => Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::BigInt(num)), + 0, + 0, + )), + } +} + +impl FromStr for PreciseNumber { + type Err = ParseNumberError; + fn from_str(mut s: &str) -> Result { + // Trim leading whitespace. + s = s.trim_start(); + + // Trim a single leading "+" character. + if s.starts_with('+') { + s = &s[1..]; + } + + // Check if the string seems to be in hexadecimal format. + // + // May be 0x123 or -0x123, so the index `i` may be either 0 or 1. + if let Some(i) = s.to_lowercase().find("0x") { + if i <= 1 { + return parse_hexadecimal(s); + } + } + + // Find the decimal point and the exponent symbol. Parse the + // number differently depending on its form. This is important + // because the form of the input dictates how the output will be + // presented. + match (s.find('.'), s.find('e')) { + // For example, "123456" or "inf". + (None, None) => parse_no_decimal_no_exponent(s), + // For example, "123e456" or "1e-2". + (None, Some(j)) => parse_exponent_no_decimal(s, j), + // For example, "123.456". + (Some(i), None) => parse_decimal_no_exponent(s, i), + // For example, "123.456e789". + (Some(i), Some(j)) if i < j => parse_decimal_and_exponent(s, i, j), + // For example, "1e2.3" or "1.2.3". + _ => Err(ParseNumberError::Float), + } + } +} + +#[cfg(test)] +mod tests { + + use bigdecimal::BigDecimal; + use num_bigint::BigInt; + use num_traits::Zero; + + use crate::extendedbigdecimal::ExtendedBigDecimal; + use crate::extendedbigint::ExtendedBigInt; + use crate::number::Number; + use crate::number::PreciseNumber; + use crate::numberparse::ParseNumberError; + + /// Convenience function for parsing a [`Number`] and unwrapping. + fn parse(s: &str) -> Number { + s.parse::().unwrap().number + } + + /// Convenience function for getting the number of integral digits. + fn num_integral_digits(s: &str) -> usize { + s.parse::().unwrap().num_integral_digits + } + + /// Convenience function for getting the number of fractional digits. + fn num_fractional_digits(s: &str) -> usize { + s.parse::().unwrap().num_fractional_digits + } + + #[test] + fn test_parse_minus_zero_int() { + assert_eq!(parse("-0e0"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0e-0"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0e1"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0e+1"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0.0e1"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0x0"), Number::Int(ExtendedBigInt::MinusZero)); + } + + #[test] + fn test_parse_minus_zero_float() { + assert_eq!(parse("-0.0"), Number::Float(ExtendedBigDecimal::MinusZero)); + assert_eq!(parse("-0e-1"), Number::Float(ExtendedBigDecimal::MinusZero)); + assert_eq!( + parse("-0.0e-1"), + Number::Float(ExtendedBigDecimal::MinusZero) + ); + } + + #[test] + fn test_parse_big_int() { + assert_eq!(parse("0"), Number::Int(ExtendedBigInt::zero())); + assert_eq!(parse("0.1e1"), Number::Int(ExtendedBigInt::one())); + assert_eq!( + parse("1.0e1"), + Number::Int(ExtendedBigInt::BigInt("10".parse::().unwrap())) + ); + } + + #[test] + fn test_parse_hexadecimal_big_int() { + assert_eq!(parse("0x0"), Number::Int(ExtendedBigInt::zero())); + assert_eq!( + parse("0x10"), + Number::Int(ExtendedBigInt::BigInt("16".parse::().unwrap())) + ); + } + + #[test] + fn test_parse_big_decimal() { + assert_eq!( + parse("0.0"), + Number::Float(ExtendedBigDecimal::BigDecimal( + "0.0".parse::().unwrap() + )) + ); + assert_eq!( + parse(".0"), + Number::Float(ExtendedBigDecimal::BigDecimal( + "0.0".parse::().unwrap() + )) + ); + assert_eq!( + parse("1.0"), + Number::Float(ExtendedBigDecimal::BigDecimal( + "1.0".parse::().unwrap() + )) + ); + assert_eq!( + parse("10e-1"), + Number::Float(ExtendedBigDecimal::BigDecimal( + "1.0".parse::().unwrap() + )) + ); + assert_eq!( + parse("-1e-3"), + Number::Float(ExtendedBigDecimal::BigDecimal( + "-0.001".parse::().unwrap() + )) + ); + } + + #[test] + fn test_parse_inf() { + assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity)); + assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity)); + assert_eq!( + parse("-inf"), + Number::Float(ExtendedBigDecimal::MinusInfinity) + ); + } + + #[test] + fn test_parse_invalid_float() { + assert_eq!( + "1.2.3".parse::().unwrap_err(), + ParseNumberError::Float + ); + assert_eq!( + "1e2e3".parse::().unwrap_err(), + ParseNumberError::Float + ); + assert_eq!( + "1e2.3".parse::().unwrap_err(), + ParseNumberError::Float + ); + assert_eq!( + "-+-1".parse::().unwrap_err(), + ParseNumberError::Float + ); + } + + #[test] + fn test_parse_invalid_hex() { + assert_eq!( + "0xg".parse::().unwrap_err(), + ParseNumberError::Hex + ); + } + + #[test] + fn test_parse_invalid_nan() { + assert_eq!( + "nan".parse::().unwrap_err(), + ParseNumberError::Nan + ); + assert_eq!( + "NAN".parse::().unwrap_err(), + ParseNumberError::Nan + ); + assert_eq!( + "NaN".parse::().unwrap_err(), + ParseNumberError::Nan + ); + assert_eq!( + "nAn".parse::().unwrap_err(), + ParseNumberError::Nan + ); + assert_eq!( + "-nan".parse::().unwrap_err(), + ParseNumberError::Nan + ); + } + + #[test] + fn test_num_integral_digits() { + // no decimal, no exponent + assert_eq!(num_integral_digits("123"), 3); + // decimal, no exponent + assert_eq!(num_integral_digits("123.45"), 3); + assert_eq!(num_integral_digits("-0.1"), 2); + assert_eq!(num_integral_digits("-.1"), 2); + // exponent, no decimal + assert_eq!(num_integral_digits("123e4"), 3 + 4); + assert_eq!(num_integral_digits("123e-4"), 1); + assert_eq!(num_integral_digits("-1e-3"), 2); + // decimal and exponent + assert_eq!(num_integral_digits("123.45e6"), 3 + 6); + assert_eq!(num_integral_digits("123.45e-6"), 1); + assert_eq!(num_integral_digits("123.45e-1"), 2); + assert_eq!(num_integral_digits("-0.1e0"), 2); + assert_eq!(num_integral_digits("-0.1e2"), 4); + assert_eq!(num_integral_digits("-.1e0"), 2); + assert_eq!(num_integral_digits("-.1e2"), 4); + assert_eq!(num_integral_digits("-1.e-3"), 2); + assert_eq!(num_integral_digits("-1.0e-4"), 2); + // minus zero int + assert_eq!(num_integral_digits("-0e0"), 2); + assert_eq!(num_integral_digits("-0e-0"), 2); + assert_eq!(num_integral_digits("-0e1"), 3); + assert_eq!(num_integral_digits("-0e+1"), 3); + assert_eq!(num_integral_digits("-0.0e1"), 3); + // minus zero float + assert_eq!(num_integral_digits("-0.0"), 2); + assert_eq!(num_integral_digits("-0e-1"), 2); + assert_eq!(num_integral_digits("-0.0e-1"), 2); + + // TODO In GNU `seq`, the `-w` option does not seem to work with + // hexadecimal arguments. In order to match that behavior, we + // report the number of integral digits as zero for hexadecimal + // inputs. + assert_eq!(num_integral_digits("0xff"), 0); + } + + #[test] + fn test_num_fractional_digits() { + // no decimal, no exponent + assert_eq!(num_fractional_digits("123"), 0); + assert_eq!(num_fractional_digits("0xff"), 0); + // decimal, no exponent + assert_eq!(num_fractional_digits("123.45"), 2); + assert_eq!(num_fractional_digits("-0.1"), 1); + assert_eq!(num_fractional_digits("-.1"), 1); + // exponent, no decimal + assert_eq!(num_fractional_digits("123e4"), 0); + assert_eq!(num_fractional_digits("123e-4"), 4); + assert_eq!(num_fractional_digits("123e-1"), 1); + assert_eq!(num_fractional_digits("-1e-3"), 3); + // decimal and exponent + assert_eq!(num_fractional_digits("123.45e6"), 0); + assert_eq!(num_fractional_digits("123.45e1"), 1); + assert_eq!(num_fractional_digits("123.45e-6"), 8); + assert_eq!(num_fractional_digits("123.45e-1"), 3); + assert_eq!(num_fractional_digits("-0.1e0"), 1); + assert_eq!(num_fractional_digits("-0.1e2"), 0); + assert_eq!(num_fractional_digits("-.1e0"), 1); + assert_eq!(num_fractional_digits("-.1e2"), 0); + assert_eq!(num_fractional_digits("-1.e-3"), 3); + assert_eq!(num_fractional_digits("-1.0e-4"), 5); + // minus zero int + assert_eq!(num_fractional_digits("-0e0"), 0); + assert_eq!(num_fractional_digits("-0e-0"), 0); + assert_eq!(num_fractional_digits("-0e1"), 0); + assert_eq!(num_fractional_digits("-0e+1"), 0); + assert_eq!(num_fractional_digits("-0.0e1"), 0); + // minus zero float + assert_eq!(num_fractional_digits("-0.0"), 1); + assert_eq!(num_fractional_digits("-0e-1"), 1); + assert_eq!(num_fractional_digits("-0.0e-1"), 2); + } +} diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index a76a23c4e7b..556ef9a6d01 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -1,25 +1,27 @@ -// TODO: Make -w flag work with decimals +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. // TODO: Support -f flag - -// spell-checker:ignore (ToDO) istr chiter argptr ilen - -#[macro_use] -extern crate uucore; +// spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse +use std::io::{stdout, ErrorKind, Write}; use clap::{crate_version, App, AppSettings, Arg}; -use num_bigint::BigInt; -use num_traits::One; use num_traits::Zero; -use num_traits::{Num, ToPrimitive}; -use std::cmp; -use std::io::{stdout, ErrorKind, Write}; -use std::str::FromStr; -mod digits; -use crate::digits::num_fractional_digits; -use crate::digits::num_integral_digits; +use uucore::error::FromIo; +use uucore::error::UResult; -use uucore::display::Quotable; +mod error; +mod extendedbigdecimal; +mod extendedbigint; +mod number; +mod numberparse; +use crate::error::SeqError; +use crate::extendedbigdecimal::ExtendedBigDecimal; +use crate::extendedbigint::ExtendedBigInt; +use crate::number::Number; +use crate::number::PreciseNumber; static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT."; static OPT_SEPARATOR: &str = "separator"; @@ -43,126 +45,18 @@ struct SeqOptions { widths: bool, } -enum Number { - /// Negative zero, as if it were an integer. - MinusZero, - BigInt(BigInt), - F64(f64), -} - -impl Number { - fn is_zero(&self) -> bool { - match self { - Number::MinusZero => true, - Number::BigInt(n) => n.is_zero(), - Number::F64(n) => n.is_zero(), - } - } - - fn into_f64(self) -> f64 { - match self { - Number::MinusZero => -0., - // BigInt::to_f64() can not return None. - Number::BigInt(n) => n.to_f64().unwrap(), - Number::F64(n) => n, - } - } - - /// Convert this number into a bigint, consuming it. - /// - /// For floats, this returns the [`BigInt`] corresponding to the - /// floor of the number. - fn into_bigint(self) -> BigInt { - match self { - Number::MinusZero => BigInt::zero(), - Number::F64(x) => BigInt::from(x.floor() as i64), - Number::BigInt(n) => n, - } - } -} - -impl FromStr for Number { - type Err = String; - fn from_str(mut s: &str) -> Result { - s = s.trim_start(); - if s.starts_with('+') { - s = &s[1..]; - } - let is_neg = s.starts_with('-'); - - match s.to_lowercase().find("0x") { - Some(i) if i <= 1 => match &s.as_bytes()[i + 2] { - b'-' | b'+' => Err(format!( - "invalid hexadecimal argument: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - )), - // TODO: hexadecimal floating point parsing (see #2660) - b'.' => Err(format!( - "NotImplemented: hexadecimal floating point numbers: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - )), - _ => { - let num = BigInt::from_str_radix(&s[i + 2..], 16) - .map_err(|_| format!( - "invalid hexadecimal argument: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - ))?; - match (is_neg, num == BigInt::zero()) { - (true, true) => Ok(Number::MinusZero), - (true, false) => Ok(Number::BigInt(-num)), - (false, _) => Ok(Number::BigInt(num)), - } - } - }, - Some(_) => Err(format!( - "invalid hexadecimal argument: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - )), - - None => match s.parse::() { - Ok(n) => { - // If `s` is '-0', then `parse()` returns - // `BigInt::zero()`, but we need to return - // `Number::MinusZero` instead. - if n == BigInt::zero() && is_neg { - Ok(Number::MinusZero) - } else { - Ok(Number::BigInt(n)) - } - } - Err(_) => match s.parse::() { - Ok(value) if value.is_nan() => Err(format!( - "invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - )), - Ok(value) => Ok(Number::F64(value)), - Err(_) => Err(format!( - "invalid floating point argument: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - )), - }, - }, - } - } -} - /// A range of integers. /// /// The elements are (first, increment, last). -type RangeInt = (BigInt, BigInt, BigInt); +type RangeInt = (ExtendedBigInt, ExtendedBigInt, ExtendedBigInt); -/// A range of f64. +/// A range of floats. /// /// The elements are (first, increment, last). -type RangeF64 = (f64, f64, f64); +type RangeFloat = (ExtendedBigDecimal, ExtendedBigDecimal, ExtendedBigDecimal); -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -174,110 +68,61 @@ pub fn uumain(args: impl uucore::Args) -> i32 { widths: matches.is_present(OPT_WIDTHS), }; - let mut largest_dec = 0; - let mut padding = 0; let first = if numbers.len() > 1 { - let slice = numbers[0]; - largest_dec = num_fractional_digits(slice).unwrap_or_else(|_| { - crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - slice.quote(), - uucore::execution_phrase() - ) - }); - padding = num_integral_digits(slice).unwrap_or_else(|_| { - crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - slice.quote(), - uucore::execution_phrase() - ) - }); - crash_if_err!(1, slice.parse()) + match numbers[0].parse() { + Ok(num) => num, + Err(e) => return Err(SeqError::ParseError(numbers[0].to_string(), e).into()), + } } else { - Number::BigInt(BigInt::one()) + PreciseNumber::one() }; let increment = if numbers.len() > 2 { - let slice = numbers[1]; - let dec = num_fractional_digits(slice).unwrap_or_else(|_| { - crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - slice.quote(), - uucore::execution_phrase() - ) - }); - let int_digits = num_integral_digits(slice).unwrap_or_else(|_| { - crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - slice.quote(), - uucore::execution_phrase() - ) - }); - largest_dec = cmp::max(largest_dec, dec); - padding = cmp::max(padding, int_digits); - crash_if_err!(1, slice.parse()) + match numbers[1].parse() { + Ok(num) => num, + Err(e) => return Err(SeqError::ParseError(numbers[1].to_string(), e).into()), + } } else { - Number::BigInt(BigInt::one()) + PreciseNumber::one() }; if increment.is_zero() { - show_error!( - "invalid Zero increment value: '{}'\nTry '{} --help' for more information.", - numbers[1], - uucore::execution_phrase() - ); - return 1; + return Err(SeqError::ZeroIncrement(numbers[1].to_string()).into()); } - let last: Number = { - let slice = numbers[numbers.len() - 1]; - let int_digits = num_integral_digits(slice).unwrap_or_else(|_| { - crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - slice.quote(), - uucore::execution_phrase() - ) - }); - padding = cmp::max(padding, int_digits); - crash_if_err!(1, slice.parse()) + let last: PreciseNumber = { + // We are guaranteed that `numbers.len()` is greater than zero + // and at most three because of the argument specification in + // `uu_app()`. + let n: usize = numbers.len(); + match numbers[n - 1].parse() { + Ok(num) => num, + Err(e) => return Err(SeqError::ParseError(numbers[n - 1].to_string(), e).into()), + } }; - let is_negative_zero_f64 = |x: f64| x == -0.0 && x.is_sign_negative() && largest_dec == 0; - let result = match (first, last, increment) { - // For example, `seq -0 1 2` or `seq -0 1 2.0`. - (Number::MinusZero, last, Number::BigInt(increment)) => print_seq_integers( - (BigInt::zero(), increment, last.into_bigint()), - options.separator, - options.terminator, - options.widths, - padding, - true, - ), - // For example, `seq -0e0 1 2` or `seq -0e0 1 2.0`. - (Number::F64(x), last, Number::BigInt(increment)) if is_negative_zero_f64(x) => { + let padding = first + .num_integral_digits + .max(increment.num_integral_digits) + .max(last.num_integral_digits); + let largest_dec = first + .num_fractional_digits + .max(increment.num_fractional_digits); + + let result = match (first.number, increment.number, last.number) { + (Number::Int(first), Number::Int(increment), last) => { + let last = last.round_towards(&first); print_seq_integers( - (BigInt::zero(), increment, last.into_bigint()), + (first, increment, last), options.separator, options.terminator, options.widths, padding, - true, ) } - // For example, `seq 0 1 2` or `seq 0 1 2.0`. - (Number::BigInt(first), last, Number::BigInt(increment)) => print_seq_integers( - (first, increment, last.into_bigint()), - options.separator, - options.terminator, - options.widths, - padding, - false, - ), - // For example, `seq 0 0.5 1` or `seq 0.0 0.5 1` or `seq 0.0 0.5 1.0`. - (first, last, increment) => print_seq( - (first.into_f64(), increment.into_f64(), last.into_f64()), + (first, increment, last) => print_seq( + ( + first.into_extended_big_decimal(), + increment.into_extended_big_decimal(), + last.into_extended_big_decimal(), + ), largest_dec, options.separator, options.terminator, @@ -286,9 +131,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ), }; match result { - Ok(_) => 0, - Err(err) if err.kind() == ErrorKind::BrokenPipe => 0, - Err(_) => 1, + Ok(_) => Ok(()), + Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()), + Err(e) => Err(e.map_err_context(|| "write error".into())), } } @@ -329,7 +174,7 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn done_printing(next: &T, increment: &T, last: &T) -> bool { +fn done_printing(next: &T, increment: &T, last: &T) -> bool { if increment >= &T::zero() { next > last } else { @@ -337,9 +182,73 @@ fn done_printing(next: &T, increment: &T, last: &T) -> bool } } +/// Write a big decimal formatted according to the given parameters. +/// +/// This method is an adapter to support displaying negative zero on +/// Rust versions earlier than 1.53.0. After that version, we should be +/// able to display negative zero using the default formatting provided +/// by `-0.0f32`, for example. +fn write_value_float( + writer: &mut impl Write, + value: &ExtendedBigDecimal, + width: usize, + precision: usize, + is_first_iteration: bool, +) -> std::io::Result<()> { + let value_as_str = if *value == ExtendedBigDecimal::MinusZero && is_first_iteration { + format!( + "-{value:>0width$.precision$}", + value = value, + width = if width > 0 { width - 1 } else { width }, + precision = precision, + ) + } else if *value == ExtendedBigDecimal::Infinity || *value == ExtendedBigDecimal::MinusInfinity + { + format!( + "{value:>width$.precision$}", + value = value, + width = width, + precision = precision, + ) + } else { + format!( + "{value:>0width$.precision$}", + value = value, + width = width, + precision = precision, + ) + }; + write!(writer, "{}", value_as_str) +} + +/// Write a big int formatted according to the given parameters. +fn write_value_int( + writer: &mut impl Write, + 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$}", value = value, width = width - 1,) + } else { + format!("{value:>0width$}", value = value, width = width,) + } + } else if *value == ExtendedBigInt::MinusZero && is_first_iteration { + format!("-{}", value) + } else { + format!("{}", value) + }; + write!(writer, "{}", value_as_str) +} + +// TODO `print_seq()` and `print_seq_integers()` are nearly identical, +// they could be refactored into a single more general function. + /// Floating point based code path fn print_seq( - range: RangeF64, + range: RangeFloat, largest_dec: usize, separator: String, terminator: String, @@ -349,30 +258,23 @@ fn print_seq( let stdout = stdout(); let mut stdout = stdout.lock(); let (first, increment, last) = range; - let mut i = 0isize; - let is_first_minus_zero = first == -0.0 && first.is_sign_negative(); - let mut value = first + i as f64 * increment; + let mut value = first; let padding = if pad { padding + 1 + largest_dec } else { 0 }; let mut is_first_iteration = true; while !done_printing(&value, &increment, &last) { if !is_first_iteration { write!(stdout, "{}", separator)?; } - let mut width = padding; - if is_first_iteration && is_first_minus_zero { - write!(stdout, "-")?; - width -= 1; - } - is_first_iteration = false; - write!( - stdout, - "{value:>0width$.precision$}", - value = value, - width = width, - precision = largest_dec, + write_value_float( + &mut stdout, + &value, + padding, + largest_dec, + is_first_iteration, )?; - i += 1; - value = first + i as f64 * increment; + // TODO Implement augmenting addition. + value = value + increment.clone(); + is_first_iteration = false; } if !is_first_iteration { write!(stdout, "{}", terminator)?; @@ -401,7 +303,6 @@ fn print_seq_integers( terminator: String, pad: bool, padding: usize, - is_first_minus_zero: bool, ) -> std::io::Result<()> { let stdout = stdout(); let mut stdout = stdout.lock(); @@ -412,18 +313,10 @@ fn print_seq_integers( if !is_first_iteration { write!(stdout, "{}", separator)?; } - let mut width = padding; - if is_first_iteration && is_first_minus_zero { - write!(stdout, "-")?; - width -= 1; - } + write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?; + // TODO Implement augmenting addition. + value = value + increment.clone(); is_first_iteration = false; - if pad { - write!(stdout, "{number:>0width$}", number = value, width = width)?; - } else { - write!(stdout, "{}", value)?; - } - value += &increment; } if !is_first_iteration { diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index 00356d70135..d647495915b 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shred" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "shred ~ (uutils) hide former FILE contents with repeated overwrites" @@ -18,13 +18,12 @@ path = "src/shred.rs" clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" rand = "0.7" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "shred" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index fa455f0279a..f745c3bf6ff 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -19,6 +19,7 @@ use std::io::prelude::*; use std::io::SeekFrom; use std::path::{Path, PathBuf}; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::{util_name, InvalidEncodingHandling}; #[macro_use] @@ -266,7 +267,8 @@ pub mod options { pub const ZERO: &str = "zero"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -277,20 +279,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = app.get_matches_from(args); - let mut errs: Vec = vec![]; - if !matches.is_present(options::FILE) { - show_error!("Missing an argument"); - show_error!("For help, try '{} --help'", uucore::execution_phrase()); - return 0; + return Err(UUsageError::new(1, "missing file operand")); } let iterations = match matches.value_of(options::ITERATIONS) { Some(s) => match s.parse::() { Ok(u) => u, Err(_) => { - errs.push(format!("invalid number of passes: {}", s.quote())); - 0 + return Err(USimpleError::new( + 1, + format!("invalid number of passes: {}", s.quote()), + )) } }, None => unreachable!(), @@ -313,21 +313,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let zero = matches.is_present(options::ZERO); let verbose = matches.is_present(options::VERBOSE); - if !errs.is_empty() { - show_error!("Invalid arguments supplied."); - for message in errs { - show_error!("{}", message); - } - return 1; - } - for path_str in matches.values_of(options::FILE).unwrap() { - wipe_file( + show_if_err!(wipe_file( path_str, iterations, remove, size, exact, zero, verbose, force, - ); + )); } - - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -419,7 +410,7 @@ fn get_size(size_str_opt: Option) -> Option { util_name(), size_str_opt.unwrap().maybe_quote() ); - exit!(1); + std::process::exit(1); } }; @@ -452,34 +443,28 @@ fn wipe_file( zero: bool, verbose: bool, force: bool, -) { +) -> UResult<()> { // Get these potential errors out of the way first let path: &Path = Path::new(path_str); if !path.exists() { - show_error!("{}: No such file or directory", path.maybe_quote()); - return; + return Err(USimpleError::new( + 1, + format!("{}: No such file or directory", path.maybe_quote()), + )); } if !path.is_file() { - show_error!("{}: Not a file", path.maybe_quote()); - return; + return Err(USimpleError::new( + 1, + format!("{}: Not a file", path.maybe_quote()), + )); } // If force is true, set file permissions to not-readonly. if force { - let metadata = match fs::metadata(path) { - Ok(m) => m, - Err(e) => { - show_error!("{}", e); - return; - } - }; - + let metadata = fs::metadata(path).map_err_context(String::new)?; let mut perms = metadata.permissions(); perms.set_readonly(false); - if let Err(e) = fs::set_permissions(path, perms) { - show_error!("{}", e); - return; - } + fs::set_permissions(path, perms).map_err_context(String::new)?; } // Fill up our pass sequence @@ -521,13 +506,11 @@ fn wipe_file( { let total_passes: usize = pass_sequence.len(); - let mut file: File = match OpenOptions::new().write(true).truncate(false).open(path) { - Ok(f) => f, - Err(e) => { - show_error!("{}: failed to open for writing: {}", path.maybe_quote(), e); - return; - } - }; + let mut file: File = OpenOptions::new() + .write(true) + .truncate(false) + .open(path) + .map_err_context(|| format!("{}: failed to open for writing", path.maybe_quote()))?; // NOTE: it does not really matter what we set for total_bytes and gen_type here, so just // use bogus values @@ -557,24 +540,17 @@ fn wipe_file( } } // size is an optional argument for exactly how many bytes we want to shred - match do_pass(&mut file, path, &mut generator, *pass_type, size) { - Ok(_) => {} - Err(e) => { - show_error!("{}: File write pass failed: {}", path.maybe_quote(), e); - } - } + show_if_err!(do_pass(&mut file, path, &mut generator, *pass_type, size) + .map_err_context(|| format!("{}: File write pass failed", path.maybe_quote()))); // Ignore failed writes; just keep trying } } if remove { - match do_remove(path, path_str, verbose) { - Ok(_) => {} - Err(e) => { - show_error!("{}: failed to remove file: {}", path.maybe_quote(), e); - } - } + do_remove(path, path_str, verbose) + .map_err_context(|| format!("{}: failed to remove file", path.maybe_quote()))?; } + Ok(()) } fn do_pass<'a>( diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index 35da4114580..42b03c0359e 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shuf" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "shuf ~ (uutils) display random permutations of input lines" @@ -17,13 +17,12 @@ path = "src/shuf.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } rand = "0.5" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "shuf" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 9a899d7460d..ce0af5ec299 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -7,14 +7,12 @@ // spell-checker:ignore (ToDO) cmdline evec seps rvec fdata -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use rand::Rng; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError}; use uucore::InvalidEncodingHandling; enum Mode { @@ -52,7 +50,8 @@ mod options { pub static FILE: &str = "file"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -65,7 +64,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match parse_range(range) { Ok(m) => Mode::InputRange(m), Err(msg) => { - crash!(1, "{}", msg); + return Err(USimpleError::new(1, msg)); } } } else { @@ -77,8 +76,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(count) => match count.parse::() { Ok(val) => val, Err(_) => { - show_error!("invalid line count: {}", count.quote()); - return 1; + return Err(USimpleError::new( + 1, + format!("invalid line count: {}", count.quote()), + )); } }, None => std::usize::MAX, @@ -97,22 +98,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Mode::Echo(args) => { let mut evec = args.iter().map(String::as_bytes).collect::>(); find_seps(&mut evec, options.sep); - shuf_bytes(&mut evec, options); + shuf_bytes(&mut evec, options)?; } Mode::InputRange((b, e)) => { let rvec = (b..e).map(|x| format!("{}", x)).collect::>(); let mut rvec = rvec.iter().map(String::as_bytes).collect::>(); - shuf_bytes(&mut rvec, options); + shuf_bytes(&mut rvec, options)?; } Mode::Default(filename) => { - let fdata = read_input_file(&filename); + let fdata = read_input_file(&filename)?; let mut fdata = vec![&fdata[..]]; find_seps(&mut fdata, options.sep); - shuf_bytes(&mut fdata, options); + shuf_bytes(&mut fdata, options)?; } } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -180,22 +181,20 @@ pub fn uu_app() -> App<'static, 'static> { .arg(Arg::with_name(options::FILE).takes_value(true)) } -fn read_input_file(filename: &str) -> Vec { +fn read_input_file(filename: &str) -> UResult> { let mut file = BufReader::new(if filename == "-" { Box::new(stdin()) as Box } else { - match File::open(filename) { - Ok(f) => Box::new(f) as Box, - Err(e) => crash!(1, "failed to open {}: {}", filename.quote(), e), - } + let file = File::open(filename) + .map_err_context(|| format!("failed to open {}", filename.quote()))?; + Box::new(file) as Box }); let mut data = Vec::new(); - if let Err(e) = file.read_to_end(&mut data) { - crash!(1, "failed reading {}: {}", filename.quote(), e) - }; + file.read_to_end(&mut data) + .map_err_context(|| format!("failed reading {}", filename.quote()))?; - data + Ok(data) } fn find_seps(data: &mut Vec<&[u8]>, sep: u8) { @@ -231,22 +230,22 @@ fn find_seps(data: &mut Vec<&[u8]>, sep: u8) { } } -fn shuf_bytes(input: &mut Vec<&[u8]>, opts: Options) { +fn shuf_bytes(input: &mut Vec<&[u8]>, opts: Options) -> UResult<()> { let mut output = BufWriter::new(match opts.output { None => Box::new(stdout()) as Box, - Some(s) => match File::create(&s[..]) { - Ok(f) => Box::new(f) as Box, - Err(e) => crash!(1, "failed to open {} for writing: {}", s.quote(), e), - }, + Some(s) => { + let file = File::create(&s[..]) + .map_err_context(|| format!("failed to open {} for writing", s.quote()))?; + Box::new(file) as Box + } }); let mut rng = match opts.random_source { - Some(r) => WrappedRng::RngFile(rand::rngs::adapter::ReadRng::new( - match File::open(&r[..]) { - Ok(f) => f, - Err(e) => crash!(1, "failed to open random source {}: {}", r.quote(), e), - }, - )), + Some(r) => { + let file = File::open(&r[..]) + .map_err_context(|| format!("failed to open random source {}", r.quote()))?; + WrappedRng::RngFile(rand::rngs::adapter::ReadRng::new(file)) + } None => WrappedRng::RngDefault(rand::thread_rng()), }; @@ -268,10 +267,10 @@ fn shuf_bytes(input: &mut Vec<&[u8]>, opts: Options) { // write the randomly chosen value and the separator output .write_all(input[r]) - .unwrap_or_else(|e| crash!(1, "write failed: {}", e)); + .map_err_context(|| "write failed".to_string())?; output .write_all(&[opts.sep]) - .unwrap_or_else(|e| crash!(1, "write failed: {}", e)); + .map_err_context(|| "write failed".to_string())?; // if we do not allow repeats, remove the chosen value from the input vector if !opts.repeat { @@ -284,6 +283,7 @@ fn shuf_bytes(input: &mut Vec<&[u8]>, opts: Options) { count -= 1; } + Ok(()) } fn parse_range(input_range: &str) -> Result<(usize, usize), String> { diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index af6f22b9f74..00f6cf0fac4 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sleep" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "sleep ~ (uutils) pause for DURATION" @@ -16,8 +16,8 @@ path = "src/sleep.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "sleep" diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index b3d4fe0eaae..14d7aef308f 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sort" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "sort ~ (uutils) sort input lines" @@ -27,8 +27,8 @@ rand = "0.7" rayon = "1.5" tempfile = "3" unicode-width = "0.1.8" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "sort" diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index 80d6060d4ca..bfe6aa73ba8 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -193,16 +193,13 @@ pub fn read( /// Split `read` into `Line`s, and add them to `lines`. fn parse_lines<'a>( - mut read: &'a str, + read: &'a str, lines: &mut Vec>, line_data: &mut LineData<'a>, separator: u8, settings: &GlobalSettings, ) { - // Strip a trailing separator. TODO: Once our MinRustV is 1.45 or above, use strip_suffix() instead. - if read.ends_with(separator as char) { - read = &read[..read.len() - 1]; - } + let read = read.strip_suffix(separator as char).unwrap_or(read); assert!(lines.is_empty()); assert!(line_data.selections.is_empty()); diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index bd79a681132..ae1bcfa4c19 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -171,10 +171,6 @@ impl UError for SortError { _ => 2, } } - - fn usage(&self) -> bool { - false - } } impl Display for SortError { @@ -663,18 +659,14 @@ impl<'a> Line<'a> { " ".repeat(UnicodeWidthStr::width(&line[..selection.start])) )?; - // TODO: Once our minimum supported rust version is at least 1.47, use selection.is_empty() instead. - #[allow(clippy::len_zero)] - { - if selection.len() == 0 { - writeln!(writer, "^ no match for key")?; - } else { - writeln!( - writer, - "{}", - "_".repeat(UnicodeWidthStr::width(&line[selection])) - )?; - } + if selection.is_empty() { + writeln!(writer, "^ no match for key")?; + } else { + writeln!( + writer, + "{}", + "_".repeat(UnicodeWidthStr::width(&line[selection])) + )?; } } if settings.mode != SortMode::Random diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index 44d0350b29b..54569844a16 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_split" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "split ~ (uutils) split input into output files" @@ -16,13 +16,12 @@ path = "src/split.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "split" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs new file mode 100644 index 00000000000..da72e090e36 --- /dev/null +++ b/src/uu/split/src/filenames.rs @@ -0,0 +1,529 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +// spell-checker:ignore zaaa zaab zzaaaa zzzaaaaa +//! Compute filenames from a given index. +//! +//! The [`FilenameFactory`] can be used to convert a chunk index given +//! as a [`usize`] to a filename for that chunk. +//! +//! # Examples +//! +//! Create filenames of the form `chunk_??.txt`: +//! +//! ```rust,ignore +//! use crate::filenames::FilenameFactory; +//! +//! let prefix = "chunk_".to_string(); +//! let suffix = ".txt".to_string(); +//! let width = 2; +//! let use_numeric_suffix = false; +//! let factory = FilenameFactory::new(prefix, suffix, width, use_numeric_suffix); +//! +//! assert_eq!(factory.make(0).unwrap(), "chunk_aa.txt"); +//! assert_eq!(factory.make(10).unwrap(), "chunk_ak.txt"); +//! assert_eq!(factory.make(28).unwrap(), "chunk_bc.txt"); +//! ``` + +/// Base 10 logarithm. +fn log10(n: usize) -> usize { + (n as f64).log10() as usize +} + +/// Base 26 logarithm. +fn log26(n: usize) -> usize { + (n as f64).log(26.0) as usize +} + +/// Convert a radix 10 number to a radix 26 number of the given width. +/// +/// `n` is the radix 10 (that is, decimal) number to transform. This +/// function returns a [`Vec`] of unsigned integers representing the +/// digits, with the most significant digit first and the least +/// significant digit last. The returned `Vec` is always of length +/// `width`. +/// +/// If the number `n` is too large to represent within `width` digits, +/// then this function returns `None`. +/// +/// # Examples +/// +/// ```rust,ignore +/// use crate::filenames::to_radix_26; +/// +/// assert_eq!(to_radix_26(20, 2), Some(vec![0, 20])); +/// assert_eq!(to_radix_26(26, 2), Some(vec![1, 0])); +/// assert_eq!(to_radix_26(30, 2), Some(vec![1, 4])); +/// ``` +fn to_radix_26(mut n: usize, width: usize) -> Option> { + if width == 0 { + return None; + } + // Use the division algorithm to repeatedly compute the quotient + // and remainder of the number after division by the radix 26. The + // successive quotients are the digits in radix 26, from most + // significant to least significant. + let mut result = vec![]; + for w in (0..width).rev() { + let divisor = 26_usize.pow(w as u32); + let (quotient, remainder) = (n / divisor, n % divisor); + n = remainder; + // If the quotient is equal to or greater than the radix, that + // means the number `n` requires a greater width to be able to + // represent it in radix 26. + if quotient >= 26 { + return None; + } + result.push(quotient as u8); + } + Some(result) +} + +/// Convert a number between 0 and 25 into a lowercase ASCII character. +/// +/// # Examples +/// +/// ```rust,ignore +/// use crate::filenames::to_ascii_char; +/// +/// assert_eq!(to_ascii_char(&0), Some('a')); +/// assert_eq!(to_ascii_char(&25), Some('z')); +/// assert_eq!(to_ascii_char(&26), None); +/// ``` +fn to_ascii_char(n: &u8) -> Option { + // TODO In Rust v1.52.0 or later, use `char::from_digit`: + // https://doc.rust-lang.org/std/primitive.char.html#method.from_digit + // + // char::from_digit(*n as u32 + 10, 36) + // + // In that call, radix 36 is used because the characters in radix + // 36 are [0-9a-z]. We want to exclude the the first ten of those + // characters, so we add 10 to the number before conversion. + // + // Until that function is available, just add `n` to `b'a'` and + // cast to `char`. + if *n < 26 { + Some((b'a' + n) as char) + } else { + None + } +} + +/// Fixed width alphabetic string representation of index `i`. +/// +/// If `i` is greater than or equal to the number of lowercase ASCII +/// strings that can be represented in the given `width`, then this +/// function returns `None`. +/// +/// # Examples +/// +/// ```rust,ignore +/// use crate::filenames::str_prefix_fixed_width; +/// +/// assert_eq!(str_prefix_fixed_width(0, 2).as_deref(), "aa"); +/// assert_eq!(str_prefix_fixed_width(675, 2).as_deref(), "zz"); +/// assert_eq!(str_prefix_fixed_width(676, 2), None); +/// ``` +fn str_prefix_fixed_width(i: usize, width: usize) -> Option { + to_radix_26(i, width)?.iter().map(to_ascii_char).collect() +} + +/// Dynamically sized alphabetic string representation of index `i`. +/// +/// The size of the returned string starts at two then grows by 2 if +/// `i` is sufficiently large. +/// +/// # Examples +/// +/// ```rust,ignore +/// use crate::filenames::str_prefix; +/// +/// assert_eq!(str_prefix(0), "aa"); +/// assert_eq!(str_prefix(649), "yz"); +/// assert_eq!(str_prefix(650), "zaaa"); +/// assert_eq!(str_prefix(651), "zaab"); +/// ``` +fn str_prefix(i: usize) -> Option { + // This number tells us the order of magnitude of `i`, with a + // slight adjustment. + // + // We shift by 26 so that + // + // * if `i` is in the interval [0, 26^2 - 26), then `d` is 1, + // * if `i` is in the interval [26^2 - 26, 26^3 - 26), then `d` is 2, + // * if `i` is in the interval [26^3 - 26, 26^4 - 26), then `d` is 3, + // + // and so on. This will allow us to compute how many leading "z" + // characters need to appear in the string and how many characters + // to format to the right of those. + let d = log26(i + 26); + + // This is the number of leading "z" characters. + // + // For values of `i` less than 26^2 - 26, the returned string is + // just the radix 26 representation of that number with a width of + // two (using the lowercase ASCII characters as the digits). + // + // * if `i` is 26^2 - 26, then the returned string is "zaa", + // * if `i` is 26^3 - 26, then the returned string is "zzaaaa", + // * if `i` is 26^4 - 26, then the returned string is "zzzaaaaa", + // + // and so on. As you can see, the number of leading "z"s there is + // linearly increasing by 1 for each order of magnitude. + let num_fill_chars = d - 1; + + // This is the number of characters after the leading "z" characters. + let width = d + 1; + + // This is the radix 10 number to render in radix 26, to the right + // of the leading "z"s. + let number = (i + 26) - 26_usize.pow(d as u32); + + // This is the radix 26 number to render after the leading "z"s, + // collected in a `String`. + // + // For example, if `i` is 789, then `number` is 789 + 26 - 676, + // which equals 139. In radix 26 and assuming a `width` of 3, this + // number is + // + // [0, 5, 9] + // + // with the most significant digit on the left and the least + // significant digit on the right. After translating to ASCII + // lowercase letters, this becomes "afj". + let digits = str_prefix_fixed_width(number, width)?; + + // `empty` is just the empty string, to be displayed with a width + // of `num_fill_chars` and with blank spaces filled with the + // character "z". + // + // `digits` is as described in the previous comment. + Some(format!( + "{empty:z Option { + let max = 10_usize.pow(width as u32); + if i >= max { + None + } else { + Some(format!("{i:0width$}", i = i, width = width)) + } +} + +/// Dynamically sized numeric string representation of index `i`. +/// +/// The size of the returned string starts at two then grows by 2 if +/// `i` is sufficiently large. +/// +/// # Examples +/// +/// ```rust,ignore +/// use crate::filenames::num_prefix; +/// +/// assert_eq!(num_prefix(89), "89"); +/// assert_eq!(num_prefix(90), "9000"); +/// assert_eq!(num_prefix(91), "9001"); +/// ``` +fn num_prefix(i: usize) -> String { + // This number tells us the order of magnitude of `i`, with a + // slight adjustment. + // + // We shift by 10 so that + // + // * if `i` is in the interval [0, 90), then `d` is 1, + // * if `i` is in the interval [90, 990), then `d` is 2, + // * if `i` is in the interval [990, 9990), then `d` is 3, + // + // and so on. This will allow us to compute how many leading "9" + // characters need to appear in the string and how many digits to + // format to the right of those. + let d = log10(i + 10); + + // This is the number of leading "9" characters. + // + // For values of `i` less than 90, the returned string is just + // that number padded by a 0 to ensure the width is 2, but + // + // * if `i` is 90, then the returned string is "900", + // * if `i` is 990, then the returned string is "990000", + // * if `i` is 9990, then the returned string is "99900000", + // + // and so on. As you can see, the number of leading 9s there is + // linearly increasing by 1 for each order of magnitude. + let num_fill_chars = d - 1; + + // This is the number of characters after the leading "9" characters. + let width = d + 1; + + // This is the number to render after the leading "9"s. + // + // For example, if `i` is 5732, then the returned string is + // "994742". After the two "9" characters is the number 4742, + // which equals 5732 + 10 - 1000. + let number = (i + 10) - 10_usize.pow(d as u32); + + // `empty` is just the empty string, to be displayed with a width + // of `num_fill_chars` and with blank spaces filled with the + // character "9". + // + // `number` is the next remaining part of the number to render; + // for small numbers we pad with 0 and enforce a minimum width. + format!( + "{empty:9 FilenameFactory { + FilenameFactory { + prefix, + additional_suffix, + suffix_length, + use_numeric_suffix, + } + } + + /// Construct the filename for the specified element of the output collection of files. + /// + /// For an explanation of the parameters, see the struct documentation. + /// + /// If `suffix_length` has been set to a positive integer and `i` + /// is greater than or equal to the number of strings that can be + /// represented within that length, then this returns `None`. For + /// example: + /// + /// ```rust,ignore + /// use crate::filenames::FilenameFactory; + /// + /// let prefix = String::new(); + /// let suffix = String::new(); + /// let width = 1; + /// let use_numeric_suffix = true; + /// let factory = FilenameFactory::new(prefix, suffix, width, use_numeric_suffix); + /// + /// assert_eq!(factory.make(10), None); + /// ``` + pub fn make(&self, i: usize) -> Option { + let prefix = self.prefix.clone(); + let suffix1 = match (self.use_numeric_suffix, self.suffix_length) { + (true, 0) => Some(num_prefix(i)), + (false, 0) => str_prefix(i), + (true, width) => num_prefix_fixed_width(i, width), + (false, width) => str_prefix_fixed_width(i, width), + }?; + let suffix2 = &self.additional_suffix; + Some(prefix + &suffix1 + suffix2) + } +} + +#[cfg(test)] +mod tests { + use crate::filenames::num_prefix; + use crate::filenames::num_prefix_fixed_width; + use crate::filenames::str_prefix; + use crate::filenames::str_prefix_fixed_width; + use crate::filenames::to_ascii_char; + use crate::filenames::to_radix_26; + use crate::filenames::FilenameFactory; + + #[test] + fn test_to_ascii_char() { + assert_eq!(to_ascii_char(&0), Some('a')); + assert_eq!(to_ascii_char(&5), Some('f')); + assert_eq!(to_ascii_char(&25), Some('z')); + assert_eq!(to_ascii_char(&26), None); + } + + #[test] + fn test_to_radix_26_exceed_width() { + assert_eq!(to_radix_26(1, 0), None); + assert_eq!(to_radix_26(26, 1), None); + assert_eq!(to_radix_26(26 * 26, 2), None); + } + + #[test] + fn test_to_radix_26_width_one() { + assert_eq!(to_radix_26(0, 1), Some(vec![0])); + assert_eq!(to_radix_26(10, 1), Some(vec![10])); + assert_eq!(to_radix_26(20, 1), Some(vec![20])); + assert_eq!(to_radix_26(25, 1), Some(vec![25])); + } + + #[test] + fn test_to_radix_26_width_two() { + assert_eq!(to_radix_26(0, 2), Some(vec![0, 0])); + assert_eq!(to_radix_26(10, 2), Some(vec![0, 10])); + assert_eq!(to_radix_26(20, 2), Some(vec![0, 20])); + assert_eq!(to_radix_26(25, 2), Some(vec![0, 25])); + + assert_eq!(to_radix_26(26, 2), Some(vec![1, 0])); + assert_eq!(to_radix_26(30, 2), Some(vec![1, 4])); + + assert_eq!(to_radix_26(26 * 2, 2), Some(vec![2, 0])); + assert_eq!(to_radix_26(26 * 26 - 1, 2), Some(vec![25, 25])); + } + + #[test] + fn test_str_prefix_dynamic_width() { + assert_eq!(str_prefix(0).as_deref(), Some("aa")); + assert_eq!(str_prefix(1).as_deref(), Some("ab")); + assert_eq!(str_prefix(2).as_deref(), Some("ac")); + assert_eq!(str_prefix(25).as_deref(), Some("az")); + + assert_eq!(str_prefix(26).as_deref(), Some("ba")); + assert_eq!(str_prefix(27).as_deref(), Some("bb")); + assert_eq!(str_prefix(28).as_deref(), Some("bc")); + assert_eq!(str_prefix(51).as_deref(), Some("bz")); + + assert_eq!(str_prefix(52).as_deref(), Some("ca")); + + assert_eq!(str_prefix(26 * 25 - 1).as_deref(), Some("yz")); + assert_eq!(str_prefix(26 * 25).as_deref(), Some("zaaa")); + assert_eq!(str_prefix(26 * 25 + 1).as_deref(), Some("zaab")); + } + + #[test] + fn test_num_prefix_dynamic_width() { + assert_eq!(num_prefix(0), "00"); + assert_eq!(num_prefix(9), "09"); + assert_eq!(num_prefix(17), "17"); + assert_eq!(num_prefix(89), "89"); + assert_eq!(num_prefix(90), "9000"); + assert_eq!(num_prefix(91), "9001"); + assert_eq!(num_prefix(989), "9899"); + assert_eq!(num_prefix(990), "990000"); + } + + #[test] + fn test_str_prefix_fixed_width() { + assert_eq!(str_prefix_fixed_width(0, 2).as_deref(), Some("aa")); + assert_eq!(str_prefix_fixed_width(1, 2).as_deref(), Some("ab")); + assert_eq!(str_prefix_fixed_width(26, 2).as_deref(), Some("ba")); + assert_eq!( + str_prefix_fixed_width(26 * 26 - 1, 2).as_deref(), + Some("zz") + ); + assert_eq!(str_prefix_fixed_width(26 * 26, 2).as_deref(), None); + } + + #[test] + fn test_num_prefix_fixed_width() { + assert_eq!(num_prefix_fixed_width(0, 2).as_deref(), Some("00")); + assert_eq!(num_prefix_fixed_width(1, 2).as_deref(), Some("01")); + assert_eq!(num_prefix_fixed_width(99, 2).as_deref(), Some("99")); + assert_eq!(num_prefix_fixed_width(100, 2).as_deref(), None); + } + + #[test] + fn test_alphabetic_suffix() { + let factory = FilenameFactory::new("123".to_string(), "789".to_string(), 3, false); + assert_eq!(factory.make(0).unwrap(), "123aaa789"); + assert_eq!(factory.make(1).unwrap(), "123aab789"); + assert_eq!(factory.make(28).unwrap(), "123abc789"); + } + + #[test] + fn test_numeric_suffix() { + let factory = FilenameFactory::new("abc".to_string(), "xyz".to_string(), 3, true); + assert_eq!(factory.make(0).unwrap(), "abc000xyz"); + assert_eq!(factory.make(1).unwrap(), "abc001xyz"); + assert_eq!(factory.make(123).unwrap(), "abc123xyz"); + } +} diff --git a/src/uu/split/src/platform/unix.rs b/src/uu/split/src/platform/unix.rs index a115d1959c7..448b8b78221 100644 --- a/src/uu/split/src/platform/unix.rs +++ b/src/uu/split/src/platform/unix.rs @@ -2,6 +2,8 @@ use std::env; use std::io::Write; use std::io::{BufWriter, Result}; use std::process::{Child, Command, Stdio}; +use uucore::crash; + /// A writer that writes to a shell_process' stdin /// /// We use a shell process (not directly calling a sub-process) so we can forward the name of the diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 581b632d292..04ee3641cc9 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -7,19 +7,19 @@ // spell-checker:ignore (ToDO) PREFIXaa -#[macro_use] -extern crate uucore; - +mod filenames; mod platform; -use clap::{crate_version, App, Arg}; +use crate::filenames::FilenameFactory; +use clap::{crate_version, App, Arg, ArgMatches}; use std::convert::TryFrom; use std::env; +use std::fs::remove_file; use std::fs::File; use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write}; use std::path::Path; -use std::{char, fs::remove_file}; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::parse_size::parse_size; static OPT_BYTES: &str = "bytes"; @@ -29,7 +29,7 @@ static OPT_ADDITIONAL_SUFFIX: &str = "additional-suffix"; static OPT_FILTER: &str = "filter"; static OPT_NUMERIC_SUFFIXES: &str = "numeric-suffixes"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; -static OPT_DEFAULT_SUFFIX_LENGTH: &str = "2"; +static OPT_DEFAULT_SUFFIX_LENGTH: &str = "0"; static OPT_VERBOSE: &str = "verbose"; static ARG_INPUT: &str = "input"; @@ -53,7 +53,8 @@ size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let long_usage = get_long_usage(); @@ -69,8 +70,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { additional_suffix: "".to_owned(), input: "".to_owned(), filter: None, - strategy: "".to_owned(), - strategy_param: "".to_owned(), + strategy: Strategy::Lines(1000), verbose: false, }; @@ -84,48 +84,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.additional_suffix = matches.value_of(OPT_ADDITIONAL_SUFFIX).unwrap().to_owned(); settings.verbose = matches.occurrences_of("verbose") > 0; - // check that the user is not specifying more than one strategy - // note: right now, this exact behavior cannot be handled by ArgGroup since ArgGroup - // considers a default value Arg as "defined" - let explicit_strategies = - vec![OPT_LINE_BYTES, OPT_LINES, OPT_BYTES] - .into_iter() - .fold(0, |count, strategy| { - if matches.occurrences_of(strategy) > 0 { - count + 1 - } else { - count - } - }); - if explicit_strategies > 1 { - crash!(1, "cannot split in more than one way"); - } - - // default strategy (if no strategy is passed, use this one) - settings.strategy = String::from(OPT_LINES); - settings.strategy_param = matches.value_of(OPT_LINES).unwrap().to_owned(); - // take any (other) defined strategy - for &strategy in &[OPT_LINE_BYTES, OPT_BYTES] { - if matches.occurrences_of(strategy) > 0 { - settings.strategy = String::from(strategy); - settings.strategy_param = matches.value_of(strategy).unwrap().to_owned(); - } - } - + settings.strategy = Strategy::from(&matches)?; settings.input = matches.value_of(ARG_INPUT).unwrap().to_owned(); settings.prefix = matches.value_of(ARG_PREFIX).unwrap().to_owned(); if matches.occurrences_of(OPT_FILTER) > 0 { if cfg!(windows) { // see https://github.com/rust-lang/rust/issues/29494 - show_error!("{} is currently not supported in this platform", OPT_FILTER); - exit!(-1); + return Err(USimpleError::new( + -1, + format!("{} is currently not supported in this platform", OPT_FILTER), + )); } else { settings.filter = Some(matches.value_of(OPT_FILTER).unwrap().to_owned()); } } - split(&settings) + split(settings) } pub fn uu_app() -> App<'static, 'static> { @@ -138,8 +113,7 @@ pub fn uu_app() -> App<'static, 'static> { .short("b") .long(OPT_BYTES) .takes_value(true) - .default_value("2") - .help("use suffixes of length N (default 2)"), + .help("put SIZE bytes per output file"), ) .arg( Arg::with_name(OPT_LINE_BYTES) @@ -155,7 +129,7 @@ pub fn uu_app() -> App<'static, 'static> { .long(OPT_LINES) .takes_value(true) .default_value("1000") - .help("write to shell COMMAND file name is $FILE (Currently not implemented for Windows)"), + .help("put NUMBER lines/records per output file"), ) // rest of the arguments .arg( @@ -206,7 +180,56 @@ pub fn uu_app() -> App<'static, 'static> { ) } -#[allow(dead_code)] +/// The strategy for breaking up the input file into chunks. +enum Strategy { + /// Each chunk has the specified number of lines. + Lines(usize), + + /// Each chunk has the specified number of bytes. + Bytes(usize), + + /// Each chunk has as many lines as possible without exceeding the + /// specified number of bytes. + LineBytes(usize), +} + +impl Strategy { + /// Parse a strategy from the command-line arguments. + fn from(matches: &ArgMatches) -> UResult { + // Check that the user is not specifying more than one strategy. + // + // Note: right now, this exact behavior cannot be handled by + // `ArgGroup` since `ArgGroup` considers a default value `Arg` + // as "defined". + match ( + matches.occurrences_of(OPT_LINES), + matches.occurrences_of(OPT_BYTES), + matches.occurrences_of(OPT_LINE_BYTES), + ) { + (0, 0, 0) => Ok(Strategy::Lines(1000)), + (1, 0, 0) => { + let s = matches.value_of(OPT_LINES).unwrap(); + let n = parse_size(s) + .map_err(|e| USimpleError::new(1, format!("invalid number of lines: {}", e)))?; + Ok(Strategy::Lines(n)) + } + (0, 1, 0) => { + let s = matches.value_of(OPT_BYTES).unwrap(); + let n = parse_size(s) + .map_err(|e| USimpleError::new(1, format!("invalid number of bytes: {}", e)))?; + Ok(Strategy::Bytes(n)) + } + (0, 0, 1) => { + let s = matches.value_of(OPT_LINE_BYTES).unwrap(); + let n = parse_size(s) + .map_err(|e| USimpleError::new(1, format!("invalid number of bytes: {}", e)))?; + Ok(Strategy::LineBytes(n)) + } + _ => Err(UUsageError::new(1, "cannot split in more than one way")), + } + } +} + struct Settings { prefix: String, numeric_suffix: bool, @@ -215,9 +238,8 @@ struct Settings { input: String, /// When supplied, a shell command to output to instead of xaa, xab … filter: Option, - strategy: String, - strategy_param: String, - verbose: bool, // TODO: warning: field is never read: `verbose` + strategy: Strategy, + verbose: bool, } trait Splitter { @@ -228,7 +250,7 @@ trait Splitter { &mut self, reader: &mut BufReader>, writer: &mut BufWriter>, - ) -> u128; + ) -> std::io::Result; } struct LineSplitter { @@ -236,15 +258,9 @@ struct LineSplitter { } impl LineSplitter { - fn new(settings: &Settings) -> LineSplitter { + fn new(chunk_size: usize) -> LineSplitter { LineSplitter { - lines_per_split: settings.strategy_param.parse().unwrap_or_else(|_| { - crash!( - 1, - "invalid number of lines: {}", - settings.strategy_param.quote() - ) - }), + lines_per_split: chunk_size, } } } @@ -254,21 +270,17 @@ impl Splitter for LineSplitter { &mut self, reader: &mut BufReader>, writer: &mut BufWriter>, - ) -> u128 { + ) -> std::io::Result { let mut bytes_consumed = 0u128; let mut buffer = String::with_capacity(1024); for _ in 0..self.lines_per_split { - let bytes_read = reader - .read_line(&mut buffer) - .unwrap_or_else(|_| crash!(1, "error reading bytes from input file")); + let bytes_read = reader.read_line(&mut buffer)?; // If we ever read 0 bytes then we know we've hit EOF. if bytes_read == 0 { - return bytes_consumed; + return Ok(bytes_consumed); } - writer - .write_all(buffer.as_bytes()) - .unwrap_or_else(|_| crash!(1, "error writing bytes to output file")); + writer.write_all(buffer.as_bytes())?; // Empty out the String buffer since `read_line` appends instead of // replaces. buffer.clear(); @@ -276,7 +288,7 @@ impl Splitter for LineSplitter { bytes_consumed += bytes_read as u128; } - bytes_consumed + Ok(bytes_consumed) } } @@ -285,15 +297,9 @@ struct ByteSplitter { } impl ByteSplitter { - fn new(settings: &Settings) -> ByteSplitter { - let size_string = &settings.strategy_param; - let size_num = match parse_size(size_string) { - Ok(n) => n, - Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()), - }; - + fn new(chunk_size: usize) -> ByteSplitter { ByteSplitter { - bytes_per_split: u128::try_from(size_num).unwrap(), + bytes_per_split: u128::try_from(chunk_size).unwrap(), } } } @@ -303,7 +309,7 @@ impl Splitter for ByteSplitter { &mut self, reader: &mut BufReader>, writer: &mut BufWriter>, - ) -> u128 { + ) -> std::io::Result { // We buffer reads and writes. We proceed until `bytes_consumed` is // equal to `self.bytes_per_split` or we reach EOF. let mut bytes_consumed = 0u128; @@ -320,96 +326,62 @@ impl Splitter for ByteSplitter { // than BUFFER_SIZE in this branch. (self.bytes_per_split - bytes_consumed) as usize }; - let bytes_read = reader - .read(&mut buffer[0..bytes_desired]) - .unwrap_or_else(|_| crash!(1, "error reading bytes from input file")); + let bytes_read = reader.read(&mut buffer[0..bytes_desired])?; // If we ever read 0 bytes then we know we've hit EOF. if bytes_read == 0 { - return bytes_consumed; + return Ok(bytes_consumed); } - writer - .write_all(&buffer[0..bytes_read]) - .unwrap_or_else(|_| crash!(1, "error writing bytes to output file")); + writer.write_all(&buffer[0..bytes_read])?; bytes_consumed += bytes_read as u128; } - bytes_consumed - } -} - -// (1, 3) -> "aab" -#[allow(clippy::many_single_char_names)] -fn str_prefix(i: usize, width: usize) -> String { - let mut c = "".to_owned(); - let mut n = i; - let mut w = width; - while w > 0 { - w -= 1; - let div = 26usize.pow(w as u32); - let r = n / div; - n -= r * div; - c.push(char::from_u32((r as u32) + 97).unwrap()); + Ok(bytes_consumed) } - c } -// (1, 3) -> "001" -#[allow(clippy::many_single_char_names)] -fn num_prefix(i: usize, width: usize) -> String { - let mut c = "".to_owned(); - let mut n = i; - let mut w = width; - while w > 0 { - w -= 1; - let div = 10usize.pow(w as u32); - let r = n / div; - n -= r * div; - c.push(char::from_digit(r as u32, 10).unwrap()); - } - c -} - -fn split(settings: &Settings) -> i32 { +fn split(settings: Settings) -> UResult<()> { let mut reader = BufReader::new(if settings.input == "-" { Box::new(stdin()) as Box } else { - let r = File::open(Path::new(&settings.input)).unwrap_or_else(|_| { - crash!( - 1, + let r = File::open(Path::new(&settings.input)).map_err_context(|| { + format!( "cannot open {} for reading: No such file or directory", settings.input.quote() ) - }); + })?; Box::new(r) as Box }); - let mut splitter: Box = match settings.strategy.as_str() { - s if s == OPT_LINES => Box::new(LineSplitter::new(settings)), - s if (s == OPT_BYTES || s == OPT_LINE_BYTES) => Box::new(ByteSplitter::new(settings)), - a => crash!(1, "strategy {} not supported", a.quote()), + let mut splitter: Box = match settings.strategy { + Strategy::Lines(chunk_size) => Box::new(LineSplitter::new(chunk_size)), + Strategy::Bytes(chunk_size) | Strategy::LineBytes(chunk_size) => { + Box::new(ByteSplitter::new(chunk_size)) + } }; + // This object is responsible for creating the filename for each chunk. + let filename_factory = FilenameFactory::new( + settings.prefix, + settings.additional_suffix, + settings.suffix_length, + settings.numeric_suffix, + ); let mut fileno = 0; loop { // Get a new part file set up, and construct `writer` for it. - let mut filename = settings.prefix.clone(); - filename.push_str( - if settings.numeric_suffix { - num_prefix(fileno, settings.suffix_length) - } else { - str_prefix(fileno, settings.suffix_length) - } - .as_ref(), - ); - filename.push_str(settings.additional_suffix.as_ref()); + let filename = filename_factory + .make(fileno) + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; let mut writer = platform::instantiate_current_writer(&settings.filter, filename.as_str()); - let bytes_consumed = splitter.consume(&mut reader, &mut writer); + let bytes_consumed = splitter + .consume(&mut reader, &mut writer) + .map_err_context(|| "input/output error".to_string())?; writer .flush() - .unwrap_or_else(|e| crash!(1, "error flushing to output file: {}", e)); + .map_err_context(|| "error flushing to output file".to_string())?; // If we didn't write anything we should clean up the empty file, and // break from the loop. @@ -418,12 +390,27 @@ fn split(settings: &Settings) -> i32 { // Complicated, I know... if settings.filter.is_none() { remove_file(filename) - .unwrap_or_else(|e| crash!(1, "error removing empty file: {}", e)); + .map_err_context(|| "error removing empty file".to_string())?; } break; } + // TODO It is silly to have the "creating file" message here + // after the file has been already created. However, because + // of the way the main loop has been written, an extra file + // gets created and then deleted in the last iteration of the + // loop. So we need to make sure we are not in that case when + // printing this message. + // + // This is only here temporarily while we make some + // improvements to the architecture of the main loop in this + // function. In the future, it will move to a more appropriate + // place---at the point where the file is actually created. + if settings.verbose { + println!("creating file {}", filename.quote()); + } + fileno += 1; } - 0 + Ok(()) } diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index a1f168b14fe..f642a330f84 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stat" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "stat ~ (uutils) display FILE status" @@ -16,9 +16,12 @@ path = "src/stat.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "libc", "fs", "fsext"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "libc", "fs", "fsext"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "stat" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index fd4a6443d55..916acc041b1 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -9,6 +9,7 @@ extern crate uucore; use uucore::display::Quotable; use uucore::entries; +use uucore::error::{UResult, USimpleError}; use uucore::fs::display_permissions; use uucore::fsext::{ pretty_filetype, pretty_fstype, pretty_time, read_fs_list, statfs, BirthTime, FsMeta, @@ -25,7 +26,10 @@ use std::{cmp, fs, iter}; macro_rules! check_bound { ($str: ident, $bound:expr, $beg: expr, $end: expr) => { if $end >= $bound { - return Err(format!("{}: invalid directive", $str[$beg..$end].quote())); + return Err(USimpleError::new( + 1, + format!("{}: invalid directive", $str[$beg..$end].quote()), + )); } }; } @@ -332,7 +336,7 @@ fn print_it(arg: &str, output_type: OutputType, flag: u8, width: usize, precisio } impl Stater { - pub fn generate_tokens(format_str: &str, use_printf: bool) -> Result, String> { + pub fn generate_tokens(format_str: &str, use_printf: bool) -> UResult> { let mut tokens = Vec::new(); let bound = format_str.len(); let chars = format_str.chars().collect::>(); @@ -457,7 +461,7 @@ impl Stater { Ok(tokens) } - fn new(matches: ArgMatches) -> Result { + fn new(matches: ArgMatches) -> UResult { let files: Vec = matches .values_of(ARG_FILES) .map(|v| v.map(ToString::to_string).collect()) @@ -476,14 +480,12 @@ impl Stater { let show_fs = matches.is_present(options::FILE_SYSTEM); let default_tokens = if format_str.is_empty() { - Stater::generate_tokens(&Stater::default_format(show_fs, terse, false), use_printf) - .unwrap() + Stater::generate_tokens(&Stater::default_format(show_fs, terse, false), use_printf)? } else { Stater::generate_tokens(format_str, use_printf)? }; let default_dev_tokens = - Stater::generate_tokens(&Stater::default_format(show_fs, terse, true), use_printf) - .unwrap(); + Stater::generate_tokens(&Stater::default_format(show_fs, terse, true), use_printf)?; let mount_list = if show_fs { // mount points aren't displayed when showing filesystem information @@ -945,7 +947,8 @@ for details about the options it supports. ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let long_usage = get_long_usage(); @@ -954,12 +957,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .after_help(&long_usage[..]) .get_matches_from(args); - match Stater::new(matches) { - Ok(stater) => stater.exec(), - Err(e) => { - show_error!("{}", e); - 1 - } + let stater = Stater::new(matches)?; + let exit_status = stater.exec(); + if exit_status == 0 { + Ok(()) + } else { + Err(exit_status.into()) } } diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 45863cd0c9e..d8f67abdc6f 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "stdbuf ~ (uutils) run COMMAND with modified standard stream buffering" @@ -17,11 +17,11 @@ path = "src/stdbuf.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } tempfile = "3.1" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [build-dependencies] -libstdbuf = { version="0.0.8", package="uu_stdbuf_libstdbuf", path="src/libstdbuf" } +libstdbuf = { version="0.0.12", 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 b14d503cf79..9d36f20f4f1 100644 --- a/src/uu/stdbuf/build.rs +++ b/src/uu/stdbuf/build.rs @@ -20,17 +20,38 @@ mod platform { } fn main() { - let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("Could not find manifest dir"); - let profile = env::var("PROFILE").expect("Could not determine profile"); - let out_dir = env::var("OUT_DIR").unwrap(); - let libstdbuf = format!( - "{}/../../../{}/{}/deps/liblibstdbuf{}", - manifest_dir, - env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "target".to_string()), - profile, - platform::DYLIB_EXT - ); + let mut target_dir = Path::new(&out_dir); + + // Depending on how this is util is built, the directory structure. This seems to work for now. + // Here are three cases to test when changing this: + // - cargo run + // - cross run + // - cargo install --git + // - cargo publish --dry-run + let mut name = target_dir.file_name().unwrap().to_string_lossy(); + while name != "target" && !name.starts_with("cargo-install") { + target_dir = target_dir.parent().unwrap(); + name = target_dir.file_name().unwrap().to_string_lossy(); + } + let mut dir = target_dir.to_path_buf(); + dir.push(env::var("PROFILE").unwrap()); + dir.push("deps"); + let mut path = None; - fs::copy(libstdbuf, Path::new(&out_dir).join("libstdbuf.so")).unwrap(); + // When running cargo publish, cargo appends hashes to the filenames of the compiled artifacts. + // Therefore, it won't work to just get liblibstdbuf.so. Instead, we look for files with the + // glob pattern "liblibstdbuf*.so" (i.e. starts with liblibstdbuf and ends with the extension). + for entry in fs::read_dir(dir).unwrap().flatten() { + let name = entry.file_name(); + let name = name.to_string_lossy(); + if name.starts_with("liblibstdbuf") && name.ends_with(platform::DYLIB_EXT) { + path = Some(entry.path()); + } + } + fs::copy( + path.expect("liblibstdbuf was not found"), + Path::new(&out_dir).join("libstdbuf.so"), + ) + .unwrap(); } diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index 069a2ae11cb..4e35a9438c1 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.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "stdbuf/libstdbuf ~ (uutils); dynamic library required for stdbuf" @@ -19,8 +19,8 @@ crate-type = ["cdylib", "rlib"] # XXX: note: the rlib is just to prevent Cargo f [dependencies] cpp = "0.5" libc = "0.2" -uucore = { version=">=0.0.10", package="uucore", path="../../../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../../../uucore_procs" } [build-dependencies] cpp_build = "0.4" diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 77d80f777ff..b5093cc01cc 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -19,6 +19,7 @@ use std::path::PathBuf; use std::process::Command; use tempfile::tempdir; use tempfile::TempDir; +use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::parse_size::parse_size; use uucore::InvalidEncodingHandling; @@ -148,7 +149,8 @@ fn get_preload_env(tmp_dir: &mut TempDir) -> io::Result<(String, PathBuf)> { Ok((preload.to_owned(), inject_path)) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -156,37 +158,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = uu_app().usage(&usage[..]).get_matches_from(args); - let options = ProgramOptions::try_from(&matches).unwrap_or_else(|e| { - crash!( - 125, - "{}\nTry '{} --help' for more information.", - e.0, - uucore::execution_phrase() - ) - }); + let options = ProgramOptions::try_from(&matches).map_err(|e| UUsageError::new(125, e.0))?; let mut command_values = matches.values_of::<&str>(options::COMMAND).unwrap(); let mut command = Command::new(command_values.next().unwrap()); let command_params: Vec<&str> = command_values.collect(); let mut tmp_dir = tempdir().unwrap(); - let (preload_env, libstdbuf) = crash_if_err!(1, get_preload_env(&mut tmp_dir)); + let (preload_env, libstdbuf) = get_preload_env(&mut tmp_dir).map_err_context(String::new)?; command.env(preload_env, libstdbuf); set_command_env(&mut command, "_STDBUF_I", options.stdin); set_command_env(&mut command, "_STDBUF_O", options.stdout); set_command_env(&mut command, "_STDBUF_E", options.stderr); command.args(command_params); - let mut process = match command.spawn() { - Ok(p) => p, - Err(e) => crash!(1, "failed to execute process: {}", e), - }; - match process.wait() { - Ok(status) => match status.code() { - Some(i) => i, - None => crash!(1, "process killed by signal {}", status.signal().unwrap()), - }, - Err(e) => crash!(1, "{}", e), + let mut process = command + .spawn() + .map_err_context(|| "failed to execute process".to_string())?; + let status = process.wait().map_err_context(String::new)?; + match status.code() { + Some(i) => { + if i == 0 { + Ok(()) + } else { + Err(i.into()) + } + } + None => Err(USimpleError::new( + 1, + format!("process killed by signal {}", status.signal().unwrap()), + )), } } diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index 273f9dff980..c97db0fb2f2 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sum" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "sum ~ (uutils) display checksum and block counts for input" @@ -16,13 +16,12 @@ path = "src/sum.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "sum" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index c58a1dcdc98..bcc4738e80c 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -12,9 +12,10 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::fs::File; -use std::io::{stdin, Read, Result}; +use std::io::{stdin, Read}; use std::path::Path; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError}; use uucore::InvalidEncodingHandling; static NAME: &str = "sum"; @@ -65,26 +66,25 @@ fn sysv_sum(mut reader: Box) -> (usize, u16) { (blocks_read, ret as u16) } -fn open(name: &str) -> Result> { +fn open(name: &str) -> UResult> { match name { "-" => Ok(Box::new(stdin()) as Box), _ => { let path = &Path::new(name); if path.is_dir() { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Is a directory", + return Err(USimpleError::new( + 2, + format!("{}: Is a directory", name.maybe_quote()), )); }; // Silent the warning as we want to the error message - #[allow(clippy::question_mark)] if path.metadata().is_err() { - return Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - "No such file or directory", + return Err(USimpleError::new( + 2, + format!("{}: No such file or directory", name.maybe_quote()), )); }; - let f = File::open(path)?; + let f = File::open(path).map_err_context(String::new)?; Ok(Box::new(f) as Box) } } @@ -96,7 +96,8 @@ mod options { pub static SYSTEM_V_COMPATIBLE: &str = "sysv"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -116,13 +117,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { files.len() > 1 }; - let mut exit_code = 0; for file in &files { let reader = match open(file) { Ok(f) => f, Err(error) => { - show_error!("{}: {}", file.maybe_quote(), error); - exit_code = 2; + show!(error); continue; } }; @@ -138,8 +137,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { println!("{} {}", sum, blocks); } } - - exit_code + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index 4bf2c60b3f7..61d507826bc 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sync" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "sync ~ (uutils) synchronize cache writes to storage" @@ -17,10 +17,13 @@ path = "src/sync.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["wide"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["wide"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "std", "winbase", "winerror"] } [[bin]] name = "sync" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs", "winapi"] diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index d6a21f2802b..4e6bb7d272b 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -9,14 +9,10 @@ extern crate libc; -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::path::Path; use uucore::display::Quotable; - -static EXIT_ERR: i32 = 1; +use uucore::error::{UResult, USimpleError}; static ABOUT: &str = "Synchronize cached writes to persistent storage"; pub mod options { @@ -72,6 +68,7 @@ mod platform { use std::mem; use std::os::windows::prelude::*; use std::path::Path; + use uucore::crash; use uucore::wide::{FromWide, ToWide}; unsafe fn flush_volume(name: &str) { @@ -164,7 +161,8 @@ fn usage() -> String { format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -176,11 +174,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for f in &files { if !Path::new(&f).exists() { - crash!( - EXIT_ERR, - "cannot stat {}: No such file or directory", - f.quote() - ); + return Err(USimpleError::new( + 1, + format!("cannot stat {}: No such file or directory", f.quote()), + )); } } @@ -194,7 +191,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } else { sync(); } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 6840d6bc300..375a813cdc4 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uu_tac" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "tac ~ (uutils) concatenate and display input lines in reverse order" @@ -21,13 +21,12 @@ memchr = "2" memmap2 = "0.5" regex = "1" clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "tac" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/tac/src/error.rs b/src/uu/tac/src/error.rs new file mode 100644 index 00000000000..92b071cc497 --- /dev/null +++ b/src/uu/tac/src/error.rs @@ -0,0 +1,60 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +//! Errors returned by tac during processing of a file. +use std::error::Error; +use std::fmt::Display; + +use uucore::display::Quotable; +use uucore::error::UError; + +#[derive(Debug)] +pub enum TacError { + /// A regular expression given by the user is invalid. + InvalidRegex(regex::Error), + + /// An argument to tac is invalid. + InvalidArgument(String), + + /// The specified file is not found on the filesystem. + FileNotFound(String), + + /// An error reading the contents of a file or stdin. + /// + /// The parameters are the name of the file and the underlying + /// [`std::io::Error`] that caused this error. + ReadError(String, std::io::Error), + + /// An error writing the (reversed) contents of a file or stdin. + /// + /// The parameter is the underlying [`std::io::Error`] that caused + /// this error. + WriteError(std::io::Error), +} + +impl UError for TacError { + fn code(&self) -> i32 { + 1 + } +} + +impl Error for TacError {} + +impl Display for TacError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TacError::InvalidRegex(e) => write!(f, "invalid regular expression: {}", e), + TacError::InvalidArgument(s) => { + write!(f, "{}: read error: Invalid argument", s.maybe_quote()) + } + TacError::FileNotFound(s) => write!( + f, + "failed to open {} for reading: No such file or directory", + s.quote() + ), + TacError::ReadError(s, e) => write!(f, "failed to read from {}: {}", s, e), + TacError::WriteError(e) => write!(f, "failed to write to stdout: {}", e), + } + } +} diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index cdb2d74e34c..0a6599d7cef 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -6,9 +6,7 @@ // * file that was distributed with this source code. // spell-checker:ignore (ToDO) sbytes slen dlen memmem memmap Mmap mmap SIGBUS - -#[macro_use] -extern crate uucore; +mod error; use clap::{crate_version, App, Arg}; use memchr::memmem; @@ -19,8 +17,13 @@ use std::{ path::Path, }; use uucore::display::Quotable; +use uucore::error::UError; +use uucore::error::UResult; +use uucore::show; use uucore::InvalidEncodingHandling; +use crate::error::TacError; + static NAME: &str = "tac"; static USAGE: &str = "[OPTION]... [FILE]..."; static SUMMARY: &str = "Write each file to standard output, last line first."; @@ -32,7 +35,8 @@ mod options { pub static FILE: &str = "file"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -214,11 +218,13 @@ fn buffer_tac(data: &[u8], before: bool, separator: &str) -> std::io::Result<()> Ok(()) } -fn tac(filenames: Vec<&str>, before: bool, regex: bool, separator: &str) -> i32 { - let mut exit_code = 0; - - let pattern = if regex { - Some(crash_if_err!(1, regex::bytes::Regex::new(separator))) +fn tac(filenames: Vec<&str>, before: bool, regex: bool, separator: &str) -> UResult<()> { + // Compile the regular expression pattern if it is provided. + let maybe_pattern = if regex { + match regex::bytes::Regex::new(separator) { + Ok(p) => Some(p), + Err(e) => return Err(TacError::InvalidRegex(e).into()), + } } else { None }; @@ -234,8 +240,8 @@ fn tac(filenames: Vec<&str>, before: bool, regex: bool, separator: &str) -> i32 } else { let mut buf1 = Vec::new(); if let Err(e) = stdin().read_to_end(&mut buf1) { - show_error!("failed to read from stdin: {}", e); - exit_code = 1; + let e: Box = TacError::ReadError("stdin".to_string(), e).into(); + show!(e); continue; } buf = buf1; @@ -243,16 +249,15 @@ fn tac(filenames: Vec<&str>, before: bool, regex: bool, separator: &str) -> i32 } } else { let path = Path::new(filename); - if path.is_dir() || path.metadata().is_err() { - if path.is_dir() { - show_error!("{}: read error: Invalid argument", filename.maybe_quote()); - } else { - show_error!( - "failed to open {} for reading: No such file or directory", - filename.quote() - ); - } - exit_code = 1; + if path.is_dir() { + let e: Box = TacError::InvalidArgument(String::from(filename)).into(); + show!(e); + continue; + } + + if path.metadata().is_err() { + let e: Box = TacError::FileNotFound(String::from(filename)).into(); + show!(e); continue; } @@ -266,22 +271,28 @@ fn tac(filenames: Vec<&str>, before: bool, regex: bool, separator: &str) -> i32 &buf } Err(e) => { - show_error!("failed to read {}: {}", filename.quote(), e); - exit_code = 1; + let s = format!("{}", filename.quote()); + let e: Box = TacError::ReadError(s.to_string(), e).into(); + show!(e); continue; } } } }; - if let Some(pattern) = &pattern { - buffer_tac_regex(data, pattern, before) - } else { - buffer_tac(data, before, separator) + // Select the appropriate `tac` algorithm based on whether the + // separator is given as a regular expression or a fixed string. + let result = match maybe_pattern { + Some(ref pattern) => buffer_tac_regex(data, pattern, before), + None => buffer_tac(data, before, separator), + }; + + // If there is any error in writing the output, terminate immediately. + if let Err(e) = result { + return Err(TacError::WriteError(e).into()); } - .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); } - exit_code + Ok(()) } fn try_mmap_stdin() -> Option { diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index f288846ea1c..b6df9acfb0a 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tail" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "tail ~ (uutils) display the last lines of input" @@ -17,8 +17,8 @@ path = "src/tail.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["ringbuffer"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["ringbuffer"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(windows)'.dependencies] winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] } @@ -27,13 +27,11 @@ winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", redox_syscall = "0.2" [target.'cfg(unix)'.dependencies] -nix = "0.20" -libc = "0.2" +nix = "0.23.1" [[bin]] name = "tail" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/tail/src/lines.rs b/src/uu/tail/src/lines.rs new file mode 100644 index 00000000000..6e472b32e06 --- /dev/null +++ b/src/uu/tail/src/lines.rs @@ -0,0 +1,83 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +//! Iterate over lines, including the line ending character(s). +//! +//! This module provides the [`lines`] function, similar to the +//! [`BufRead::lines`] method. While the [`BufRead::lines`] method +//! yields [`String`] instances that do not include the line ending +//! characters (`"\n"` or `"\r\n"`), our function yields [`String`] +//! instances that include the line ending characters. This is useful +//! if the input data does not end with a newline character and you +//! want to preserve the exact form of the input data. +use std::io::BufRead; + +/// Returns an iterator over the lines, including line ending characters. +/// +/// This function is just like [`BufRead::lines`], but it includes the +/// line ending characters in each yielded [`String`] if the input +/// data has them. +/// +/// # Examples +/// +/// If the input data does not end with a newline character (`'\n'`), +/// then the last [`String`] yielded by this iterator also does not +/// end with a newline: +/// +/// ```rust,ignore +/// use std::io::BufRead; +/// use std::io::Cursor; +/// +/// let cursor = Cursor::new(b"x\ny\nz"); +/// let mut it = cursor.lines(); +/// +/// assert_eq!(it.next(), Some(String::from("x\n"))); +/// assert_eq!(it.next(), Some(String::from("y\n"))); +/// assert_eq!(it.next(), Some(String::from("z"))); +/// assert_eq!(it.next(), None); +/// ``` +pub(crate) fn lines(reader: B) -> Lines +where + B: BufRead, +{ + Lines { buf: reader } +} + +/// An iterator over the lines of an instance of `BufRead`. +/// +/// This struct is generally created by calling [`lines`] on a `BufRead`. +/// Please see the documentation of [`lines`] for more details. +pub(crate) struct Lines { + buf: B, +} + +impl Iterator for Lines { + type Item = std::io::Result; + + fn next(&mut self) -> Option> { + let mut buf = String::new(); + match self.buf.read_line(&mut buf) { + Ok(0) => None, + Ok(_n) => Some(Ok(buf)), + Err(e) => Some(Err(e)), + } + } +} + +#[cfg(test)] +mod tests { + use crate::lines::lines; + use std::io::Cursor; + + #[test] + fn test_lines() { + let cursor = Cursor::new(b"x\ny\nz"); + let mut it = lines(cursor).map(|l| l.unwrap()); + + assert_eq!(it.next(), Some(String::from("x\n"))); + assert_eq!(it.next(), Some(String::from("y\n"))); + assert_eq!(it.next(), Some(String::from("z"))); + assert_eq!(it.next(), None); + } +} diff --git a/src/uu/tail/src/parse.rs b/src/uu/tail/src/parse.rs new file mode 100644 index 00000000000..92968181190 --- /dev/null +++ b/src/uu/tail/src/parse.rs @@ -0,0 +1,161 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +use std::ffi::OsString; + +#[derive(PartialEq, Debug)] +pub enum ParseError { + Syntax, + Overflow, +} +/// Parses obsolete syntax +/// tail -NUM[kmzv] // spell-checker:disable-line +pub fn parse_obsolete(src: &str) -> Option, ParseError>> { + let mut chars = src.char_indices(); + if let Some((_, '-')) = chars.next() { + let mut num_end = 0usize; + let mut has_num = false; + let mut last_char = 0 as char; + for (n, c) in &mut chars { + if c.is_numeric() { + has_num = true; + num_end = n; + } else { + last_char = c; + break; + } + } + if has_num { + match src[1..=num_end].parse::() { + Ok(num) => { + let mut quiet = false; + let mut verbose = false; + let mut zero_terminated = false; + let mut multiplier = None; + let mut c = last_char; + loop { + // not that here, we only match lower case 'k', 'c', and 'm' + match c { + // we want to preserve order + // this also saves us 1 heap allocation + 'q' => { + quiet = true; + verbose = false + } + 'v' => { + verbose = true; + quiet = false + } + 'z' => zero_terminated = true, + 'c' => multiplier = Some(1), + 'b' => multiplier = Some(512), + 'k' => multiplier = Some(1024), + 'm' => multiplier = Some(1024 * 1024), + '\0' => {} + _ => return Some(Err(ParseError::Syntax)), + } + if let Some((_, next)) = chars.next() { + c = next + } else { + break; + } + } + let mut options = Vec::new(); + if quiet { + options.push(OsString::from("-q")) + } + if verbose { + options.push(OsString::from("-v")) + } + if zero_terminated { + options.push(OsString::from("-z")) + } + if let Some(n) = multiplier { + options.push(OsString::from("-c")); + let num = match num.checked_mul(n) { + Some(n) => n, + None => return Some(Err(ParseError::Overflow)), + }; + options.push(OsString::from(format!("{}", num))); + } else { + options.push(OsString::from("-n")); + options.push(OsString::from(format!("{}", num))); + } + Some(Ok(options.into_iter())) + } + Err(_) => Some(Err(ParseError::Overflow)), + } + } else { + None + } + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + fn obsolete(src: &str) -> Option, ParseError>> { + let r = parse_obsolete(src); + match r { + Some(s) => match s { + Ok(v) => Some(Ok(v.map(|s| s.to_str().unwrap().to_owned()).collect())), + Err(e) => Some(Err(e)), + }, + None => None, + } + } + fn obsolete_result(src: &[&str]) -> Option, ParseError>> { + Some(Ok(src.iter().map(|s| s.to_string()).collect())) + } + #[test] + fn test_parse_numbers_obsolete() { + assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"])); + assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"])); + assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "5242880"])); + assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "1024"])); + assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "1024"])); + assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "1024"])); + assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "1"])); + assert_eq!( + obsolete("-1vzqvq"), // spell-checker:disable-line + obsolete_result(&["-q", "-z", "-n", "1"]) + ); + assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "1"])); + assert_eq!( + obsolete("-105kzm"), + obsolete_result(&["-z", "-c", "110100480"]) + ); + } + #[test] + fn test_parse_errors_obsolete() { + assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax))); + assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax))); + } + #[test] + fn test_parse_obsolete_no_match() { + assert_eq!(obsolete("-k"), None); + assert_eq!(obsolete("asd"), None); + } + #[test] + #[cfg(target_pointer_width = "64")] + fn test_parse_obsolete_overflow_x64() { + assert_eq!( + obsolete("-1000000000000000m"), + Some(Err(ParseError::Overflow)) + ); + assert_eq!( + obsolete("-10000000000000000000000"), + Some(Err(ParseError::Overflow)) + ); + } + #[test] + #[cfg(target_pointer_width = "32")] + fn test_parse_obsolete_overflow_x32() { + assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow))); + assert_eq!(obsolete("-42949672k"), Some(Err(ParseError::Overflow))); + } +} diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index eaf7bf8bf2c..b10e30fb0a8 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -16,17 +16,23 @@ extern crate clap; extern crate uucore; mod chunks; +mod lines; +mod parse; mod platform; use chunks::ReverseChunks; +use lines::lines; use clap::{App, Arg}; use std::collections::VecDeque; +use std::ffi::OsString; use std::fmt; use std::fs::{File, Metadata}; use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write}; use std::path::Path; use std::thread::sleep; use std::time::Duration; +use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError}; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::ringbuffer::RingBuffer; @@ -58,105 +64,122 @@ pub mod options { pub static ARG_FILES: &str = "files"; } +#[derive(Debug)] enum FilterMode { Bytes(usize), Lines(usize, u8), // (number of lines, delimiter) } +impl Default for FilterMode { + fn default() -> Self { + FilterMode::Lines(10, b'\n') + } +} + +#[derive(Debug, Default)] struct Settings { + quiet: bool, + verbose: bool, mode: FilterMode, sleep_msec: u32, beginning: bool, follow: bool, pid: platform::Pid, + files: Vec, } -impl Default for Settings { - fn default() -> Settings { - Settings { - mode: FilterMode::Lines(10, b'\n'), - sleep_msec: 1000, - beginning: false, - follow: false, - pid: 0, - } - } -} - -#[allow(clippy::cognitive_complexity)] -pub fn uumain(args: impl uucore::Args) -> i32 { - let mut settings: Settings = Default::default(); - - let app = uu_app(); - - let matches = app.get_matches_from(args); +impl Settings { + pub fn get_from(args: impl uucore::Args) -> Result { + let matches = uu_app().get_matches_from(arg_iterate(args)?); - settings.follow = matches.is_present(options::FOLLOW); - if settings.follow { - if let Some(n) = matches.value_of(options::SLEEP_INT) { - let parsed: Option = n.parse().ok(); - if let Some(m) = parsed { - settings.sleep_msec = m * 1000 + let mut settings: Settings = Settings { + sleep_msec: 1000, + follow: matches.is_present(options::FOLLOW), + ..Default::default() + }; + + if settings.follow { + if let Some(n) = matches.value_of(options::SLEEP_INT) { + let parsed: Option = n.parse().ok(); + if let Some(m) = parsed { + settings.sleep_msec = m * 1000 + } } } - } - if let Some(pid_str) = matches.value_of(options::PID) { - if let Ok(pid) = pid_str.parse() { - settings.pid = pid; - if pid != 0 { - if !settings.follow { - show_warning!("PID ignored; --pid=PID is useful only when following"); - } + if let Some(pid_str) = matches.value_of(options::PID) { + if let Ok(pid) = pid_str.parse() { + settings.pid = pid; + if pid != 0 { + if !settings.follow { + show_warning!("PID ignored; --pid=PID is useful only when following"); + } - if !platform::supports_pid_checks(pid) { - show_warning!("--pid=PID is not supported on this system"); - settings.pid = 0; + if !platform::supports_pid_checks(pid) { + show_warning!("--pid=PID is not supported on this system"); + settings.pid = 0; + } } } } - } - let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) { - match parse_num(arg) { - Ok((n, beginning)) => (FilterMode::Bytes(n), beginning), - Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()), - } - } else if let Some(arg) = matches.value_of(options::LINES) { - match parse_num(arg) { - Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning), - Err(e) => crash!(1, "invalid number of lines: {}", e.to_string()), + let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) { + match parse_num(arg) { + Ok((n, beginning)) => (FilterMode::Bytes(n), beginning), + Err(e) => return Err(format!("invalid number of bytes: {}", e)), + } + } else if let Some(arg) = matches.value_of(options::LINES) { + match parse_num(arg) { + Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning), + Err(e) => return Err(format!("invalid number of lines: {}", e)), + } + } else { + (FilterMode::Lines(10, b'\n'), false) + }; + settings.mode = mode_and_beginning.0; + settings.beginning = mode_and_beginning.1; + + if matches.is_present(options::ZERO_TERM) { + if let FilterMode::Lines(count, _) = settings.mode { + settings.mode = FilterMode::Lines(count, 0); + } } - } else { - (FilterMode::Lines(10, b'\n'), false) - }; - settings.mode = mode_and_beginning.0; - settings.beginning = mode_and_beginning.1; - if matches.is_present(options::ZERO_TERM) { - if let FilterMode::Lines(count, _) = settings.mode { - settings.mode = FilterMode::Lines(count, 0); - } - } + settings.verbose = matches.is_present(options::verbosity::VERBOSE); + settings.quiet = matches.is_present(options::verbosity::QUIET); + + settings.files = match matches.values_of(options::ARG_FILES) { + Some(v) => v.map(|s| s.to_owned()).collect(), + None => vec!["-".to_owned()], + }; - let verbose = matches.is_present(options::verbosity::VERBOSE); - let quiet = matches.is_present(options::verbosity::QUIET); + Ok(settings) + } +} - let files: Vec = matches - .values_of(options::ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_else(|| vec![String::from("-")]); +#[allow(clippy::cognitive_complexity)] +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let args = match Settings::get_from(args) { + Ok(o) => o, + Err(s) => { + return Err(USimpleError::new(1, s)); + } + }; + uu_tail(&args) +} - let multiple = files.len() > 1; +fn uu_tail(settings: &Settings) -> UResult<()> { + let multiple = settings.files.len() > 1; let mut first_header = true; let mut readers: Vec<(Box, &String)> = Vec::new(); #[cfg(unix)] let stdin_string = String::from("standard input"); - for filename in &files { + for filename in &settings.files { let use_stdin = filename.as_str() == "-"; - if (multiple || verbose) && !quiet { + if (multiple || settings.verbose) && !settings.quiet { if !first_header { println!(); } @@ -170,7 +193,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if use_stdin { let mut reader = BufReader::new(stdin()); - unbounded_tail(&mut reader, &settings); + unbounded_tail(&mut reader, settings)?; // Don't follow stdin since there are no checks for pipes/FIFOs // @@ -199,17 +222,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if path.is_dir() { continue; } - let mut file = File::open(&path).unwrap(); + let mut file = File::open(&path) + .map_err_context(|| format!("cannot open {} for reading", filename.quote()))?; let md = file.metadata().unwrap(); if is_seekable(&mut file) && get_block_size(&md) > 0 { - bounded_tail(&mut file, &settings); + bounded_tail(&mut file, settings); if settings.follow { let reader = BufReader::new(file); readers.push((Box::new(reader), filename)); } } else { let mut reader = BufReader::new(file); - unbounded_tail(&mut reader, &settings); + unbounded_tail(&mut reader, settings)?; if settings.follow { readers.push((Box::new(reader), filename)); } @@ -218,10 +242,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if settings.follow { - follow(&mut readers[..], &settings); + follow(&mut readers[..], settings)?; } - 0 + Ok(()) +} + +fn arg_iterate<'a>( + mut args: impl uucore::Args + 'a, +) -> Result + 'a>, String> { + // argv[0] is always present + let first = args.next().unwrap(); + if let Some(second) = args.next() { + if let Some(s) = second.to_str() { + match parse::parse_obsolete(s) { + Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))), + Some(Err(e)) => match e { + parse::ParseError::Syntax => Err(format!("bad argument format: {}", s.quote())), + parse::ParseError::Overflow => Err(format!( + "invalid argument: {} Value too large for defined datatype", + s.quote() + )), + }, + None => Ok(Box::new(vec![first, second].into_iter().chain(args))), + } + } else { + Err("bad argument encoding".to_owned()) + } + } else { + Ok(Box::new(vec![first].into_iter())) + } } pub fn uu_app() -> App<'static, 'static> { @@ -295,10 +345,9 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn follow(readers: &mut [(T, &String)], settings: &Settings) { - assert!(settings.follow); - if readers.is_empty() { - return; +fn follow(readers: &mut [(T, &String)], settings: &Settings) -> UResult<()> { + if readers.is_empty() || !settings.follow { + return Ok(()); } let mut last = readers.len() - 1; @@ -325,7 +374,7 @@ fn follow(readers: &mut [(T, &String)], settings: &Settings) { } print!("{}", datum); } - Err(err) => panic!("{}", err), + Err(err) => return Err(USimpleError::new(1, err.to_string())), } } } @@ -334,6 +383,7 @@ fn follow(readers: &mut [(T, &String)], settings: &Settings) { break; } } + Ok(()) } /// Iterate over bytes in the file, in reverse, until we find the @@ -428,23 +478,25 @@ where } } -fn unbounded_tail(reader: &mut BufReader, settings: &Settings) { +fn unbounded_tail(reader: &mut BufReader, settings: &Settings) -> UResult<()> { // Read through each line/char and store them in a ringbuffer that always // contains count lines/chars. When reaching the end of file, output the // data in the ringbuf. match settings.mode { FilterMode::Lines(count, _) => { - for line in unbounded_tail_collect(reader.lines(), count, settings.beginning) { - println!("{}", line); + for line in unbounded_tail_collect(lines(reader), count, settings.beginning) { + print!("{}", line); } } FilterMode::Bytes(count) => { for byte in unbounded_tail_collect(reader.bytes(), count, settings.beginning) { - let mut stdout = stdout(); - print_byte(&mut stdout, byte); + if let Err(err) = stdout().write(&[byte]) { + return Err(USimpleError::new(1, err.to_string())); + } } } } + Ok(()) } fn is_seekable(file: &mut T) -> bool { @@ -453,13 +505,6 @@ fn is_seekable(file: &mut T) -> bool { && file.seek(SeekFrom::Start(0)).is_ok() } -#[inline] -fn print_byte(stdout: &mut T, ch: u8) { - if let Err(err) = stdout.write(&[ch]) { - crash!(1, "{}", err); - } -} - fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { let mut size_string = src.trim(); let mut starting_with = false; diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index 00a250565e8..a97bdd30a37 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tee" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "tee ~ (uutils) display input and copy to FILE" @@ -17,14 +17,13 @@ path = "src/tee.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -retain_mut = "0.1.2" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["libc"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +retain_mut = "=0.1.2" # ToDO: [2021-01-01; rivy; maint/MinSRV] ~ v0.1.5 uses const generics which aren't stabilized until rust v1.51.0 +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "tee" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index d2fb015bf1f..9629e711d4a 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -14,6 +14,7 @@ use std::fs::OpenOptions; use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; use std::path::PathBuf; use uucore::display::Quotable; +use uucore::error::UResult; #[cfg(unix)] use uucore::libc; @@ -37,7 +38,8 @@ fn usage() -> String { format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -52,8 +54,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; match tee(options) { - Ok(_) => 0, - Err(_) => 1, + Ok(_) => Ok(()), + Err(_) => Err(1.into()), } } @@ -164,7 +166,7 @@ impl MultiWriter { impl Write for MultiWriter { fn write(&mut self, buf: &[u8]) -> Result { - self.writers.retain_mut(|writer| { + RetainMut::retain_mut(&mut self.writers, |writer| { let result = writer.write_all(buf); match result { Err(f) => { @@ -178,7 +180,7 @@ impl Write for MultiWriter { } fn flush(&mut self) -> Result<()> { - self.writers.retain_mut(|writer| { + RetainMut::retain_mut(&mut self.writers, |writer| { let result = writer.flush(); match result { Err(f) => { diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index 633e0f323f2..ff5c1faa270 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_test" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "test ~ (uutils) evaluate comparison and file type expressions" @@ -17,8 +17,8 @@ path = "src/test.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(target_os = "redox")'.dependencies] redox_syscall = "0.2" @@ -28,5 +28,4 @@ name = "test" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 5ce798bfaee..65316163118 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -13,7 +13,8 @@ mod parser; use clap::{crate_version, App, AppSettings}; use parser::{parse, Operator, Symbol, UnaryOperator}; use std::ffi::{OsStr, OsString}; -use uucore::{display::Quotable, show_error}; +use uucore::display::Quotable; +use uucore::error::{UResult, USimpleError}; const USAGE: &str = "test EXPRESSION or: test @@ -91,7 +92,8 @@ pub fn uu_app() -> App<'static, 'static> { .setting(AppSettings::DisableVersion) } -pub fn uumain(mut args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { let program = args.next().unwrap_or_else(|| OsString::from("test")); let binary_name = uucore::util_name(); let mut args: Vec<_> = args.collect(); @@ -109,13 +111,12 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { .setting(AppSettings::NeedsLongHelp) .setting(AppSettings::NeedsLongVersion) .get_matches_from(std::iter::once(program).chain(args.into_iter())); - return 0; + return Ok(()); } // If invoked via name '[', matching ']' must be in the last arg let last = args.pop(); if last.as_deref() != Some(OsStr::new("]")) { - show_error!("missing ']'"); - return 2; + return Err(USimpleError::new(2, "missing ']'")); } } @@ -124,15 +125,12 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { match result { Ok(result) => { if result { - 0 + Ok(()) } else { - 1 + Err(1.into()) } } - Err(e) => { - show_error!("{}", e); - 2 - } + Err(e) => Err(USimpleError::new(2, e)), } } diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index 13afe8435e1..d79d6b9e7b6 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_timeout" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "timeout ~ (uutils) run COMMAND with a DURATION time limit" @@ -17,11 +17,14 @@ path = "src/timeout.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -nix = "0.20.0" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["process", "signals"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +nix = "0.23.1" +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["process", "signals"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "timeout" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index f686dde3b60..42dd256efbc 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -17,6 +17,7 @@ use std::io::ErrorKind; use std::process::{Command, Stdio}; use std::time::Duration; use uucore::display::Quotable; +use uucore::error::{UResult, USimpleError}; use uucore::process::ChildExt; use uucore::signals::{signal_by_name_or_value, signal_name_by_value}; use uucore::InvalidEncodingHandling; @@ -99,7 +100,8 @@ impl Config { } } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -188,32 +190,36 @@ fn timeout( foreground: bool, preserve_status: bool, verbose: bool, -) -> i32 { +) -> UResult<()> { if !foreground { unsafe { libc::setpgid(0, 0) }; } - let mut process = match Command::new(&cmd[0]) + let mut process = Command::new(&cmd[0]) .args(&cmd[1..]) .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .spawn() - { - Ok(p) => p, - Err(err) => { - show_error!("failed to execute process: {}", err); - if err.kind() == ErrorKind::NotFound { + .map_err(|err| { + let status_code = if err.kind() == ErrorKind::NotFound { // FIXME: not sure which to use - return 127; + 127 } else { // FIXME: this may not be 100% correct... - return 126; - } - } - }; + 126 + }; + USimpleError::new(status_code, format!("failed to execute process: {}", err)) + })?; unblock_sigchld(); match process.wait_or_timeout(duration) { - Ok(Some(status)) => status.code().unwrap_or_else(|| status.signal().unwrap()), + Ok(Some(status)) => { + let status_code = status.code().unwrap_or_else(|| status.signal().unwrap()); + if status_code == 0 { + Ok(()) + } else { + Err(status_code.into()) + } + } Ok(None) => { if verbose { show_error!( @@ -222,38 +228,50 @@ fn timeout( cmd[0].quote() ); } - crash_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); + process + .send_signal(signal) + .map_err(|e| USimpleError::new(ERR_EXIT_STATUS, format!("{}", e)))?; if let Some(kill_after) = kill_after { match process.wait_or_timeout(kill_after) { Ok(Some(status)) => { if preserve_status { - status.code().unwrap_or_else(|| status.signal().unwrap()) + let status_code = + status.code().unwrap_or_else(|| status.signal().unwrap()); + if status_code == 0 { + Ok(()) + } else { + Err(status_code.into()) + } } else { - 124 + Err(124.into()) } } Ok(None) => { if verbose { show_error!("sending signal KILL to command {}", cmd[0].quote()); } - crash_if_err!( - ERR_EXIT_STATUS, - process.send_signal( - uucore::signals::signal_by_name_or_value("KILL").unwrap() - ) - ); - crash_if_err!(ERR_EXIT_STATUS, process.wait()); - 137 + process + .send_signal(uucore::signals::signal_by_name_or_value("KILL").unwrap()) + .map_err(|e| USimpleError::new(ERR_EXIT_STATUS, format!("{}", e)))?; + process + .wait() + .map_err(|e| USimpleError::new(ERR_EXIT_STATUS, format!("{}", e)))?; + Err(137.into()) } - Err(_) => 124, + Err(_) => Err(124.into()), } } else { - 124 + Err(124.into()) } } Err(_) => { - crash_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); - ERR_EXIT_STATUS + // We're going to return ERR_EXIT_STATUS regardless of + // whether `send_signal()` succeeds or fails, so just + // ignore the return value. + process + .send_signal(signal) + .map_err(|e| USimpleError::new(ERR_EXIT_STATUS, format!("{}", e)))?; + Err(ERR_EXIT_STATUS.into()) } } } diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index b21e2dfa390..dcdbeb6d2c9 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_touch" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "touch ~ (uutils) change FILE timestamps" @@ -18,8 +18,8 @@ path = "src/touch.rs" filetime = "0.2.1" clap = { version = "2.33", features = ["wrap_help"] } time = "0.1.40" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["libc"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "touch" diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index 8792938a9a5..3ce093f6d3a 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tr" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "tr ~ (uutils) translate characters within input and display" @@ -18,13 +18,12 @@ path = "src/tr.rs" bit-set = "0.5.0" fnv = "1.0.5" clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "tr" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index fbc4bab9b6e..ffa45ce0e77 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -10,9 +10,6 @@ // spell-checker:ignore (ToDO) allocs bset dflag cflag sflag tflag -#[macro_use] -extern crate uucore; - mod expand; use bit_set::BitSet; @@ -21,6 +18,7 @@ use fnv::FnvHashMap; use std::io::{stdin, stdout, BufRead, BufWriter, Write}; use crate::expand::ExpandSet; +use uucore::error::{UResult, UUsageError}; use uucore::{display::Quotable, InvalidEncodingHandling}; static ABOUT: &str = "translate or delete characters"; @@ -238,7 +236,8 @@ writing to standard output." .to_string() } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -262,20 +261,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap_or_default(); if sets.is_empty() { - show_error!( - "missing operand\nTry '{} --help' for more information.", - uucore::execution_phrase() - ); - return 1; + return Err(UUsageError::new(1, "missing operand")); } if !(delete_flag || squeeze_flag) && sets.len() < 2 { - show_error!( - "missing operand after {}\nTry '{} --help' for more information.", - sets[0].quote(), - uucore::execution_phrase() - ); - return 1; + return Err(UUsageError::new( + 1, + format!("missing operand after {}", sets[0].quote()), + )); } let stdin = stdin(); @@ -307,8 +300,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let op = TranslateOperation::new(set1, &mut set2, truncate_flag, complement_flag); translate_input(&mut locked_stdin, &mut buffered_stdout, op); } - - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index 369bbb51f80..506fd11e292 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_true" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "true ~ (uutils) do nothing and succeed" @@ -16,8 +16,8 @@ path = "src/true.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "true" diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index 811aa6ef680..bbe594a1948 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_truncate" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "truncate ~ (uutils) truncate (or extend) FILE to SIZE" @@ -16,13 +16,12 @@ path = "src/truncate.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "truncate" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 6fb1f06f696..1729e2a2f1a 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -16,6 +16,7 @@ use std::fs::{metadata, OpenOptions}; use std::io::ErrorKind; use std::path::Path; use uucore::display::Quotable; +use uucore::error::{UIoError, UResult, USimpleError, UUsageError}; use uucore::parse_size::{parse_size, ParseSizeError}; #[derive(Debug, Eq, PartialEq)] @@ -90,7 +91,8 @@ fn get_long_usage() -> String { ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let long_usage = get_long_usage(); @@ -105,32 +107,31 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap_or_default(); if files.is_empty() { - show_error!("Missing an argument"); - return 1; + return Err(UUsageError::new(1, "missing file operand")); } else { let io_blocks = matches.is_present(options::IO_BLOCKS); let no_create = matches.is_present(options::NO_CREATE); let reference = matches.value_of(options::REFERENCE).map(String::from); let size = matches.value_of(options::SIZE).map(String::from); - if let Err(e) = truncate(no_create, io_blocks, reference, size, files) { + truncate(no_create, io_blocks, reference, size, files).map_err(|e| { match e.kind() { ErrorKind::NotFound => { // TODO Improve error-handling so that the error // returned by `truncate()` provides the necessary // parameter for formatting the error message. let reference = matches.value_of(options::REFERENCE).map(String::from); - crash!( + USimpleError::new( 1, - "cannot stat {}: No such file or directory", - reference.as_deref().unwrap_or("").quote() - ); // TODO: fix '--no-create' see test_reference and test_truncate_bytes_size + format!( + "cannot stat {}: No such file or directory", + reference.as_deref().unwrap_or("").quote() + ), + ) // TODO: fix '--no-create' see test_reference and test_truncate_bytes_size } - _ => crash!(1, "{}", e.to_string()), + _ => uio_error!(e, ""), } - } + }) } - - 0 } pub fn uu_app() -> App<'static, 'static> { @@ -303,7 +304,7 @@ fn truncate( } (Some(rfilename), None) => truncate_reference_file_only(&rfilename, filenames, create), (None, Some(size_string)) => truncate_size_only(&size_string, filenames, create), - (None, None) => crash!(1, "you must specify either --reference or --size"), // this case cannot happen anymore because it's handled by clap + (None, None) => unreachable!(), // this case cannot happen anymore because it's handled by clap } } diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index bcc2dd75742..e3bb60b870b 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tsort" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "tsort ~ (uutils) topologically sort input (partially ordered) pairs" @@ -16,13 +16,12 @@ path = "src/tsort.rs" [dependencies] clap= "2.33" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "tsort" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 11798db1375..1b4f5bf49c3 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -5,16 +5,13 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. - -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError}; use uucore::InvalidEncodingHandling; static SUMMARY: &str = "Topological sort the strings in FILE. @@ -26,7 +23,8 @@ mod options { pub const FILE: &str = "file"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -43,13 +41,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { stdin_buf = stdin(); &mut stdin_buf as &mut dyn Read } else { - file_buf = match File::open(Path::new(&input)) { - Ok(a) => a, - _ => { - show_error!("{}: No such file or directory", input.maybe_quote()); - return 1; - } - }; + file_buf = File::open(Path::new(&input)).map_err_context(|| input.to_string())?; &mut file_buf as &mut dyn Read }); @@ -69,11 +61,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for ab in tokens.chunks(2) { match ab.len() { 2 => g.add_edge(&ab[0], &ab[1]), - _ => crash!( - 1, - "{}: input contains an odd number of tokens", - input.maybe_quote() - ), + _ => { + return Err(USimpleError::new( + 1, + format!( + "{}: input contains an odd number of tokens", + input.maybe_quote() + ), + )) + } } } } @@ -84,14 +80,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { g.run_tsort(); if !g.is_acyclic() { - crash!(1, "{}, input contains a loop:", input); + return Err(USimpleError::new( + 1, + format!("{}, input contains a loop:", input), + )); } for x in &g.result { println!("{}", x); } - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index 5b2e18cd2ef..b725a24ee5d 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tty" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "tty ~ (uutils) display the name of the terminal connected to standard input" @@ -18,9 +18,12 @@ path = "src/tty.rs" clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" atty = "0.2" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "tty" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 94e2e6b2425..3a02803c07e 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -12,6 +12,7 @@ use clap::{crate_version, App, Arg}; use std::ffi::CStr; use std::io::Write; +use uucore::error::{UResult, UUsageError}; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Print the file name of the terminal connected to standard input."; @@ -24,21 +25,17 @@ fn usage() -> String { format!("{0} [OPTION]...", uucore::execution_phrase()) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = uu_app().usage(&usage[..]).get_matches_from_safe(args); - - let matches = match matches { - Ok(m) => m, - Err(e) => { - eprint!("{}", e); - return 2; - } - }; + let matches = uu_app() + .usage(&usage[..]) + .get_matches_from_safe(args) + .map_err(|e| UUsageError::new(2, format!("{}", e)))?; let silent = matches.is_present(options::SILENT); @@ -68,9 +65,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if atty::is(atty::Stream::Stdin) { - libc::EXIT_SUCCESS + Ok(()) } else { - libc::EXIT_FAILURE + Err(libc::EXIT_FAILURE.into()) } } diff --git a/src/uu/uname/Cargo.toml b/src/uu/uname/Cargo.toml index 90a1030755e..9d4a07d4e17 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uname" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "uname ~ (uutils) display system information" @@ -16,10 +16,13 @@ path = "src/uname.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -platform-info = "0.1" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +platform-info = "0.2" +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "uname" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 2c396081e48..a4801dfc1f8 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -10,11 +10,9 @@ // spell-checker:ignore (ToDO) nodename kernelname kernelrelease kernelversion sysname hwplatform mnrsv -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use platform_info::*; +use uucore::error::{FromIo, UResult}; const ABOUT: &str = "Print certain system information. With no OPTION, same as -s."; @@ -49,11 +47,13 @@ const HOST_OS: &str = "Fuchsia"; #[cfg(target_os = "redox")] const HOST_OS: &str = "Redox"; -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = format!("{} [OPTION]...", uucore::execution_phrase()); let matches = uu_app().usage(&usage[..]).get_matches_from(args); - let uname = crash_if_err!(1, PlatformInfo::new()); + let uname = + PlatformInfo::new().map_err_context(|| "failed to create PlatformInfo".to_string())?; let mut output = String::new(); let all = matches.is_present(options::ALL); @@ -115,7 +115,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } println!("{}", output.trim_end()); - 0 + Ok(()) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index 026a12872f1..fc510111797 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unexpand" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "unexpand ~ (uutils) convert input spaces to tabs" @@ -17,13 +17,12 @@ path = "src/unexpand.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } unicode-width = "0.1.5" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "unexpand" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 95383b89dc3..1b227e4ceb4 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -17,6 +17,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdout, Write} use std::str::from_utf8; use unicode_width::UnicodeWidthChar; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult}; use uucore::InvalidEncodingHandling; static NAME: &str = "unexpand"; @@ -90,16 +91,15 @@ impl Options { } } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); let matches = uu_app().get_matches_from(args); - unexpand(Options::new(matches)); - - 0 + unexpand(Options::new(matches)).map_err_context(String::new) } pub fn uu_app() -> App<'static, 'static> { @@ -242,7 +242,7 @@ fn next_char_info(uflag: bool, buf: &[u8], byte: usize) -> (CharType, usize, usi (ctype, cwidth, nbytes) } -fn unexpand(options: Options) { +fn unexpand(options: Options) -> std::io::Result<()> { let mut output = BufWriter::new(stdout()); let ts = &options.tabstops[..]; let mut buf = Vec::new(); @@ -273,7 +273,7 @@ fn unexpand(options: Options) { init, true, ); - crash_if_err!(1, output.write_all(&buf[byte..])); + output.write_all(&buf[byte..])?; scol = col; break; } @@ -293,7 +293,7 @@ fn unexpand(options: Options) { }; if !tabs_buffered { - crash_if_err!(1, output.write_all(&buf[byte..byte + nbytes])); + output.write_all(&buf[byte..byte + nbytes])?; scol = col; // now printed up to this column } } @@ -318,7 +318,7 @@ fn unexpand(options: Options) { } else { 0 }; - crash_if_err!(1, output.write_all(&buf[byte..byte + nbytes])); + output.write_all(&buf[byte..byte + nbytes])?; scol = col; // we've now printed up to this column } } @@ -337,9 +337,9 @@ fn unexpand(options: Options) { init, true, ); - crash_if_err!(1, output.flush()); + output.flush()?; buf.truncate(0); // clear out the buffer } } - crash_if_err!(1, output.flush()) + output.flush() } diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index a6fcebc9221..4fc3981c1ed 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uniq" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "uniq ~ (uutils) filter identical adjacent lines from input" @@ -18,13 +18,12 @@ path = "src/uniq.rs" clap = { version = "2.33", features = ["wrap_help"] } strum = "0.21" strum_macros = "0.21" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "uniq" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 382118bdbe3..bea64cc53dc 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -5,17 +5,14 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg, ArgMatches}; use std::fs::File; -use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Result, Write}; +use std::io::{self, stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::path::Path; use std::str::FromStr; use strum_macros::{AsRefStr, EnumString}; use uucore::display::Quotable; -use uucore::error::{UResult, USimpleError}; +use uucore::error::{FromIo, UResult, USimpleError}; static ABOUT: &str = "Report or omit repeated lines."; pub mod options { @@ -56,36 +53,45 @@ struct Uniq { zero_terminated: bool, } +macro_rules! write_line_terminator { + ($writer:expr, $line_terminator:expr) => { + $writer + .write_all(&[$line_terminator]) + .map_err_context(|| "Could not write line terminator".to_string()) + }; +} + impl Uniq { pub fn print_uniq( &self, reader: &mut BufReader, writer: &mut BufWriter, - ) { + ) -> UResult<()> { let mut first_line_printed = false; let mut group_count = 1; let line_terminator = self.get_line_terminator(); let mut lines = reader.split(line_terminator).map(get_line_string); let mut line = match lines.next() { - Some(l) => l, - None => return, + Some(l) => l?, + None => return Ok(()), }; // compare current `line` with consecutive lines (`next_line`) of the input // and if needed, print `line` based on the command line options provided for next_line in lines { + let next_line = next_line?; if self.cmp_keys(&line, &next_line) { if (group_count == 1 && !self.repeats_only) || (group_count > 1 && !self.uniques_only) { - self.print_line(writer, &line, group_count, first_line_printed); + self.print_line(writer, &line, group_count, first_line_printed)?; first_line_printed = true; } line = next_line; group_count = 1; } else { if self.all_repeated { - self.print_line(writer, &line, group_count, first_line_printed); + self.print_line(writer, &line, group_count, first_line_printed)?; first_line_printed = true; line = next_line; } @@ -93,14 +99,15 @@ impl Uniq { } } if (group_count == 1 && !self.repeats_only) || (group_count > 1 && !self.uniques_only) { - self.print_line(writer, &line, group_count, first_line_printed); + self.print_line(writer, &line, group_count, first_line_printed)?; first_line_printed = true; } if (self.delimiters == Delimiters::Append || self.delimiters == Delimiters::Both) && first_line_printed { - crash_if_err!(1, writer.write_all(&[line_terminator])); + write_line_terminator!(writer, line_terminator)?; } + Ok(()) } fn skip_fields<'a>(&self, line: &'a str) -> &'a str { @@ -192,41 +199,43 @@ impl Uniq { line: &str, count: usize, first_line_printed: bool, - ) { + ) -> UResult<()> { let line_terminator = self.get_line_terminator(); if self.should_print_delimiter(count, first_line_printed) { - crash_if_err!(1, writer.write_all(&[line_terminator])); + write_line_terminator!(writer, line_terminator)?; } - crash_if_err!( - 1, - if self.show_counts { - writer.write_all(format!("{:7} {}", count, line).as_bytes()) - } else { - writer.write_all(line.as_bytes()) - } - ); - crash_if_err!(1, writer.write_all(&[line_terminator])); + if self.show_counts { + writer.write_all(format!("{:7} {}", count, line).as_bytes()) + } else { + writer.write_all(line.as_bytes()) + } + .map_err_context(|| "Failed to write line".to_string())?; + + write_line_terminator!(writer, line_terminator) } } -fn get_line_string(io_line: Result>) -> String { - let line_bytes = crash_if_err!(1, io_line); - crash_if_err!(1, String::from_utf8(line_bytes)) +fn get_line_string(io_line: io::Result>) -> UResult { + let line_bytes = io_line.map_err_context(|| "failed to split lines".to_string())?; + String::from_utf8(line_bytes) + .map_err(|e| USimpleError::new(1, format!("failed to convert line to utf8: {}", e))) } -fn opt_parsed(opt_name: &str, matches: &ArgMatches) -> Option { - matches.value_of(opt_name).map(|arg_str| { - let opt_val: Option = arg_str.parse().ok(); - opt_val.unwrap_or_else(|| { - crash!( +fn opt_parsed(opt_name: &str, matches: &ArgMatches) -> UResult> { + Ok(match matches.value_of(opt_name) { + Some(arg_str) => Some(arg_str.parse().map_err(|_| { + USimpleError::new( 1, - "Invalid argument for {}: {}", - opt_name, - arg_str.maybe_quote() + format!( + "Invalid argument for {}: {}", + opt_name, + arg_str.maybe_quote() + ), ) - }) + })?), + None => None, }) } @@ -266,8 +275,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { 1 => (files[0].clone(), "-".to_owned()), 2 => (files[0].clone(), files[1].clone()), _ => { - // Cannot happen as clap will fail earlier - return Err(USimpleError::new(1, format!("Extra operand: {}", files[2]))); + unreachable!() // Cannot happen as clap will fail earlier } }; @@ -279,18 +287,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { || matches.is_present(options::GROUP), delimiters: get_delimiter(&matches), show_counts: matches.is_present(options::COUNT), - skip_fields: opt_parsed(options::SKIP_FIELDS, &matches), - slice_start: opt_parsed(options::SKIP_CHARS, &matches), - slice_stop: opt_parsed(options::CHECK_CHARS, &matches), + skip_fields: opt_parsed(options::SKIP_FIELDS, &matches)?, + slice_start: opt_parsed(options::SKIP_CHARS, &matches)?, + slice_stop: opt_parsed(options::CHECK_CHARS, &matches)?, ignore_case: matches.is_present(options::IGNORE_CASE), zero_terminated: matches.is_present(options::ZERO_TERMINATED), }; uniq.print_uniq( - &mut open_input_file(in_file_name), - &mut open_output_file(out_file_name), - ); - - Ok(()) + &mut open_input_file(in_file_name)?, + &mut open_output_file(out_file_name)?, + ) } pub fn uu_app() -> App<'static, 'static> { @@ -390,7 +396,7 @@ fn get_delimiter(matches: &ArgMatches) -> Delimiters { .value_of(options::ALL_REPEATED) .or_else(|| matches.value_of(options::GROUP)); if let Some(delimiter_arg) = value { - crash_if_err!(1, Delimiters::from_str(delimiter_arg)) + Delimiters::from_str(delimiter_arg).unwrap() // All possible values for ALL_REPEATED are Delimiters (of type `&str`) } else if matches.is_present(options::GROUP) { Delimiters::Separate } else { @@ -398,26 +404,26 @@ fn get_delimiter(matches: &ArgMatches) -> Delimiters { } } -fn open_input_file(in_file_name: String) -> BufReader> { +fn open_input_file(in_file_name: String) -> UResult>> { let in_file = if in_file_name == "-" { Box::new(stdin()) as Box } else { let path = Path::new(&in_file_name[..]); - let in_file = File::open(&path); - let r = crash_if_err!(1, in_file); - Box::new(r) as Box + let in_file = File::open(&path) + .map_err_context(|| format!("Could not open {}", in_file_name.maybe_quote()))?; + Box::new(in_file) as Box }; - BufReader::new(in_file) + Ok(BufReader::new(in_file)) } -fn open_output_file(out_file_name: String) -> BufWriter> { +fn open_output_file(out_file_name: String) -> UResult>> { let out_file = if out_file_name == "-" { Box::new(stdout()) as Box } else { let path = Path::new(&out_file_name[..]); - let in_file = File::create(&path); - let w = crash_if_err!(1, in_file); - Box::new(w) as Box + let out_file = File::create(&path) + .map_err_context(|| format!("Could not create {}", out_file_name.maybe_quote()))?; + Box::new(out_file) as Box }; - BufWriter::new(out_file) + Ok(BufWriter::new(out_file)) } diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index c4b7d49cb34..e8ac2f77c4d 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unlink" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "unlink ~ (uutils) remove a (file system) link to FILE" @@ -16,8 +16,8 @@ path = "src/unlink.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "unlink" diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index 785955afcbd..51513c74e81 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uptime" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "uptime ~ (uutils) display dynamic system information" @@ -15,10 +15,10 @@ edition = "2018" path = "src/uptime.rs" [dependencies] -chrono = "0.4" +chrono = "^0.4.11" clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["libc", "utmpx"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc", "utmpx"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "uptime" diff --git a/src/uu/users/Cargo.toml b/src/uu/users/Cargo.toml index 05872e8bf10..0a657e12fa2 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_users" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "users ~ (uutils) display names of currently logged-in users" @@ -16,8 +16,8 @@ path = "src/users.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["utmpx"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["utmpx"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] name = "users" diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index d76e0b1765c..eb0d5b39de7 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_wc" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "wc ~ (uutils) display newline, word, and byte counts for input" @@ -16,14 +16,14 @@ path = "src/wc.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["pipes"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["pipes"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } bytecount = "0.6.2" utf-8 = "0.7.6" unicode-width = "0.1.8" [target.'cfg(unix)'.dependencies] -nix = "0.20" +nix = "0.23.1" libc = "0.2" [[bin]] @@ -31,5 +31,4 @@ name = "wc" path = "src/main.rs" [package.metadata.cargo-udeps.ignore] -# Necessary for "make all" normal = ["uucore_procs"] diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 16522a0a709..0d061cabafb 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -25,6 +25,7 @@ use std::io::{self, Write}; use std::path::{Path, PathBuf}; use uucore::display::{Quotable, Quoted}; +use uucore::error::{UResult, USimpleError}; /// The minimum character width for formatting counts when reading from stdin. const MINIMUM_WIDTH: usize = 7; @@ -132,7 +133,8 @@ impl Input { } } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -157,11 +159,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let settings = Settings::new(&matches); - if wc(inputs, &settings).is_ok() { - 0 - } else { - 1 - } + wc(inputs, &settings) } pub fn uu_app() -> App<'static, 'static> { @@ -326,19 +324,6 @@ fn word_count_from_input(input: &Input, settings: &Settings) -> CountResult { } } -/// Print a message appropriate for the particular error to `stderr`. -/// -/// # Examples -/// -/// This will print `wc: /tmp: Is a directory` to `stderr`. -/// -/// ```rust,ignore -/// show_error(Input::Path("/tmp"), WcError::IsDirectory("/tmp")) -/// ``` -fn show_error(input: &Input, err: io::Error) { - show_error!("{}: {}", input.path_display(), err); -} - /// Compute the number of digits needed to represent any count for this input. /// /// If `input` is [`Input::Stdin`], then this function returns @@ -418,7 +403,7 @@ fn max_width(inputs: &[Input]) -> usize { result } -fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { +fn wc(inputs: Vec, settings: &Settings) -> UResult<()> { // Compute the width, in digits, to use when formatting counts. // // The width is the number of digits needed to print the number of @@ -427,7 +412,6 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { // // If we only need to display a single number, set this to 0 to // prevent leading spaces. - let mut failure = false; let max_width = if settings.number_enabled() <= 1 { 0 } else { @@ -442,44 +426,50 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { let word_count = match word_count_from_input(input, settings) { CountResult::Success(word_count) => word_count, CountResult::Interrupted(word_count, error) => { - show_error(input, error); - failure = true; + show!(USimpleError::new( + 1, + format!("{}: {}", input.path_display(), error) + )); word_count } CountResult::Failure(error) => { - show_error(input, error); - failure = true; + show!(USimpleError::new( + 1, + format!("{}: {}", input.path_display(), error) + )); continue; } }; total_word_count += word_count; let result = word_count.with_title(input.to_title()); if let Err(err) = print_stats(settings, &result, max_width) { - show_warning!( - "failed to print result for {}: {}", - result - .title - .unwrap_or_else(|| "".as_ref()) - .maybe_quote(), - err - ); - failure = true; + show!(USimpleError::new( + 1, + format!( + "failed to print result for {}: {}", + result + .title + .unwrap_or_else(|| "".as_ref()) + .maybe_quote(), + err, + ), + )); } } if num_inputs > 1 { let total_result = total_word_count.with_title(Some("total".as_ref())); if let Err(err) = print_stats(settings, &total_result, max_width) { - show_warning!("failed to print total: {}", err); - failure = true; + show!(USimpleError::new( + 1, + format!("failed to print total: {}", err) + )); } } - if failure { - Err(1) - } else { - Ok(()) - } + // Although this appears to be returning `Ok`, the exit code may + // have been set to a non-zero value by a call to `show!()` above. + Ok(()) } fn print_stats(settings: &Settings, result: &TitledWordCount, min_width: usize) -> io::Result<()> { diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index c660be59d58..1c0193708fd 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_who" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "who ~ (uutils) display information about currently logged-in users" @@ -15,10 +15,13 @@ edition = "2018" path = "src/who.rs" [dependencies] -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["utmpx"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["utmpx"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } clap = { version = "2.33", features = ["wrap_help"] } [[bin]] name = "who" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +normal = ["uucore_procs"] diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index a975c82ba64..14f39536dd8 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -7,8 +7,8 @@ // spell-checker:ignore (ToDO) ttyname hostnames runlevel mesg wtmp statted boottime deadprocs initspawn clockchange curr runlvline pidstr exitstr hoststr -#[macro_use] -extern crate uucore; +use uucore::display::Quotable; +use uucore::error::{FromIo, UResult}; use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP}; use uucore::utmpx::{self, time, Utmpx}; @@ -59,7 +59,8 @@ fn get_long_usage() -> String { ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -157,9 +158,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { args: files, }; - who.exec(); - - 0 + who.exec() } pub fn uu_app() -> App<'static, 'static> { @@ -326,7 +325,7 @@ fn current_tty() -> String { } impl Who { - fn exec(&mut self) { + fn exec(&mut self) -> UResult<()> { let run_level_chk = |_record: i16| { #[cfg(not(target_os = "linux"))] return false; @@ -362,7 +361,7 @@ impl Who { for ut in records { if !self.my_line_only || cur_tty == ut.tty_device() { if self.need_users && ut.is_user_process() { - self.print_user(&ut); + self.print_user(&ut)?; } else if self.need_runlevel && run_level_chk(ut.record_type()) { if cfg!(target_os = "linux") { self.print_runlevel(&ut); @@ -383,6 +382,7 @@ impl Who { if ut.record_type() == utmpx::BOOT_TIME {} } } + Ok(()) } #[inline] @@ -464,7 +464,7 @@ impl Who { self.print_line("", ' ', "system boot", &time_string(ut), "", "", "", ""); } - fn print_user(&self, ut: &Utmpx) { + fn print_user(&self, ut: &Utmpx) -> UResult<()> { let mut p = PathBuf::from("/dev"); p.push(ut.tty_device().as_str()); let mesg; @@ -491,7 +491,13 @@ impl Who { }; let s = if self.do_lookup { - crash_if_err!(1, ut.canon_host()) + ut.canon_host().map_err_context(|| { + let host = ut.host(); + format!( + "failed to canonicalize {}", + host.split(':').next().unwrap_or(&host).quote() + ) + })? } else { ut.host() }; @@ -507,6 +513,8 @@ impl Who { hoststr.as_str(), "", ); + + Ok(()) } #[allow(clippy::too_many_arguments)] diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index 5af93579f58..4f5e6faa08d 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_whoami" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "whoami ~ (uutils) display user name of current effective user ID" @@ -16,8 +16,8 @@ path = "src/whoami.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["lmcons"] } diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index ad8a87b46c4..0d084a598a9 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_yes" -version = "0.0.8" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "yes ~ (uutils) repeatedly display a line with STRING (or 'y')" @@ -16,11 +16,11 @@ path = "src/yes.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["pipes"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["pipes"] } +uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] -nix = "0.20.0" +nix = "0.23.1" [[bin]] name = "yes" diff --git a/src/uu/yes/src/splice.rs b/src/uu/yes/src/splice.rs index 6f025d6a91d..84bd1cc2425 100644 --- a/src/uu/yes/src/splice.rs +++ b/src/uu/yes/src/splice.rs @@ -55,16 +55,13 @@ type Result = std::result::Result; impl From for Error { fn from(error: nix::Error) -> Self { - match error { - nix::Error::Sys(errno) => Error::Io(io::Error::from_raw_os_error(errno as i32)), - _ => Error::Io(io::Error::last_os_error()), - } + Error::Io(io::Error::from_raw_os_error(error as i32)) } } fn maybe_unsupported(error: nix::Error) -> Error { - match error.as_errno() { - Some(Errno::EINVAL) | Some(Errno::ENOSYS) | Some(Errno::EBADF) => Error::Unsupported, + match error { + Errno::EINVAL | Errno::ENOSYS | Errno::EBADF => Error::Unsupported, _ => error.into(), } } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 952eecc288f..708861324b8 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uucore" -version = "0.0.10" +version = "0.0.12" authors = ["uutils developers"] license = "MIT" description = "uutils ~ 'core' uutils code library (cross-platform)" @@ -19,7 +19,6 @@ path="src/lib/lib.rs" clap = "2.33.3" dns-lookup = { version="1.0.5", optional=true } dunce = "1.0.0" -getopts = "<= 0.2.21" wild = "2.0" # * optional thiserror = { version="1.0", optional=true } @@ -30,10 +29,11 @@ data-encoding-macro = { version="0.1.12", optional=true } z85 = { version="3.0.3", optional=true } libc = { version="0.2.15", optional=true } once_cell = "1.8.0" +os_display = "0.1.0" [target.'cfg(unix)'.dependencies] walkdir = { version="2.3.2", optional=true } -nix = { version="0.20", optional=true } +nix = { version="0.23.1", optional=true } [dev-dependencies] clap = "2.33.3" @@ -41,6 +41,7 @@ lazy_static = "1.3" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] } +winapi-util = { version= "0.1.5", optional=true } [target.'cfg(target_os = "redox")'.dependencies] termion = "1.5" @@ -50,7 +51,7 @@ default = [] # * non-default features encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"] entries = ["libc"] -fs = ["libc"] +fs = ["libc", "nix", "winapi-util"] fsext = ["libc", "time"] mode = ["libc"] perms = ["libc", "walkdir"] diff --git a/src/uucore/src/lib/features/encoding.rs b/src/uucore/src/lib/features/encoding.rs index 1008666092a..8eee74c559e 100644 --- a/src/uucore/src/lib/features/encoding.rs +++ b/src/uucore/src/lib/features/encoding.rs @@ -125,11 +125,13 @@ impl Data { } } + #[must_use] pub fn line_wrap(mut self, wrap: usize) -> Self { self.line_wrap = wrap; self } + #[must_use] pub fn ignore_garbage(mut self, ignore: bool) -> Self { self.ignore_garbage = ignore; self diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index f139d687104..df3ab7b0614 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -41,12 +41,15 @@ use libc::{c_char, c_int, gid_t, uid_t}; use libc::{getgrgid, getgrnam, getgroups}; use libc::{getpwnam, getpwuid, group, passwd}; -use std::borrow::Cow; +use std::convert::TryInto; use std::ffi::{CStr, CString}; use std::io::Error as IOError; use std::io::ErrorKind; use std::io::Result as IOResult; use std::ptr; +use std::sync::Mutex; + +use once_cell::sync::Lazy; extern "C" { /// From: https://man7.org/linux/man-pages/man3/getgrouplist.3.html @@ -69,19 +72,31 @@ extern "C" { /// > to be used in a further call to getgroups(). #[cfg(not(target_os = "redox"))] pub fn get_groups() -> IOResult> { - let ngroups = unsafe { getgroups(0, ptr::null_mut()) }; - if ngroups == -1 { - return Err(IOError::last_os_error()); - } - let mut groups = Vec::with_capacity(ngroups as usize); - let ngroups = unsafe { getgroups(ngroups, groups.as_mut_ptr()) }; - if ngroups == -1 { - Err(IOError::last_os_error()) - } else { - unsafe { - groups.set_len(ngroups as usize); + let mut groups = Vec::new(); + loop { + let ngroups = match unsafe { getgroups(0, ptr::null_mut()) } { + -1 => return Err(IOError::last_os_error()), + // Not just optimization; 0 would mess up the next call + 0 => return Ok(Vec::new()), + n => n, + }; + + // This is a small buffer, so we can afford to zero-initialize it and + // use safe Vec operations + groups.resize(ngroups.try_into().unwrap(), 0); + let res = unsafe { getgroups(ngroups, groups.as_mut_ptr()) }; + if res == -1 { + let err = IOError::last_os_error(); + if err.raw_os_error() == Some(libc::EINVAL) { + // Number of groups changed, retry + continue; + } else { + return Err(err); + } + } else { + groups.truncate(ngroups.try_into().unwrap()); + return Ok(groups); } - Ok(groups) } } @@ -124,77 +139,57 @@ fn sort_groups(mut groups: Vec, egid: gid_t) -> Vec { groups } -#[derive(Copy, Clone)] +#[derive(Clone, Debug)] pub struct Passwd { - inner: passwd, -} - -macro_rules! cstr2cow { - ($v:expr) => { - unsafe { CStr::from_ptr($v).to_string_lossy() } - }; -} - -impl Passwd { /// AKA passwd.pw_name - pub fn name(&self) -> Cow { - cstr2cow!(self.inner.pw_name) - } - + pub name: String, /// AKA passwd.pw_uid - pub fn uid(&self) -> uid_t { - self.inner.pw_uid - } - + pub uid: uid_t, /// AKA passwd.pw_gid - pub fn gid(&self) -> gid_t { - self.inner.pw_gid - } - + pub gid: gid_t, /// AKA passwd.pw_gecos - pub fn user_info(&self) -> Cow { - cstr2cow!(self.inner.pw_gecos) - } - + pub user_info: String, /// AKA passwd.pw_shell - pub fn user_shell(&self) -> Cow { - cstr2cow!(self.inner.pw_shell) - } - + pub user_shell: String, /// AKA passwd.pw_dir - pub fn user_dir(&self) -> Cow { - cstr2cow!(self.inner.pw_dir) - } - + pub user_dir: String, /// AKA passwd.pw_passwd - pub fn user_passwd(&self) -> Cow { - cstr2cow!(self.inner.pw_passwd) - } - + pub user_passwd: String, /// AKA passwd.pw_class #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] - pub fn user_access_class(&self) -> Cow { - cstr2cow!(self.inner.pw_class) - } - + pub user_access_class: String, /// AKA passwd.pw_change #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] - pub fn passwd_change_time(&self) -> time_t { - self.inner.pw_change - } - + pub passwd_change_time: time_t, /// AKA passwd.pw_expire #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] - pub fn expiration(&self) -> time_t { - self.inner.pw_expire - } + pub expiration: time_t, +} - pub fn as_inner(&self) -> &passwd { - &self.inner - } +/// SAFETY: ptr must point to a valid C string. +unsafe fn cstr2string(ptr: *const c_char) -> String { + CStr::from_ptr(ptr).to_string_lossy().into_owned() +} - pub fn into_inner(self) -> passwd { - self.inner +impl Passwd { + /// SAFETY: All the pointed-to strings must be valid and not change while + /// the function runs. That means PW_LOCK must be held. + unsafe fn from_raw(raw: passwd) -> Self { + Passwd { + name: cstr2string(raw.pw_name), + uid: raw.pw_uid, + gid: raw.pw_gid, + user_info: cstr2string(raw.pw_gecos), + user_shell: cstr2string(raw.pw_shell), + user_dir: cstr2string(raw.pw_dir), + user_passwd: cstr2string(raw.pw_passwd), + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] + user_access_class: cstr2string(raw.pw_class), + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] + passwd_change_time: raw.pw_change, + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] + expiration: raw.pw_expire, + } } /// This is a wrapper function for `libc::getgrouplist`. @@ -214,49 +209,44 @@ impl Passwd { pub fn belongs_to(&self) -> Vec { let mut ngroups: c_int = 8; let mut ngroups_old: c_int; - let mut groups = Vec::with_capacity(ngroups as usize); - let gid = self.inner.pw_gid; - let name = self.inner.pw_name; + let mut groups = vec![0; ngroups.try_into().unwrap()]; + let name = CString::new(self.name.clone()).unwrap(); loop { ngroups_old = ngroups; - if unsafe { getgrouplist(name, gid, groups.as_mut_ptr(), &mut ngroups) } == -1 { + if unsafe { getgrouplist(name.as_ptr(), self.gid, groups.as_mut_ptr(), &mut ngroups) } + == -1 + { if ngroups == ngroups_old { ngroups *= 2; } - groups.resize(ngroups as usize, 0); + groups.resize(ngroups.try_into().unwrap(), 0); } else { break; } } - unsafe { - groups.set_len(ngroups as usize); - } - groups.truncate(ngroups as usize); + let ngroups = ngroups.try_into().unwrap(); + assert!(ngroups <= groups.len()); + groups.truncate(ngroups); groups } } +#[derive(Clone, Debug)] pub struct Group { - inner: group, -} - -impl Group { /// AKA group.gr_name - pub fn name(&self) -> Cow { - cstr2cow!(self.inner.gr_name) - } - + pub name: String, /// AKA group.gr_gid - pub fn gid(&self) -> gid_t { - self.inner.gr_gid - } - - pub fn as_inner(&self) -> &group { - &self.inner - } + pub gid: gid_t, +} - pub fn into_inner(self) -> group { - self.inner +impl Group { + /// SAFETY: gr_name must be valid and not change while + /// the function runs. That means PW_LOCK must be held. + unsafe fn from_raw(raw: group) -> Self { + Group { + name: cstr2string(raw.gr_name), + gid: raw.gr_gid, + } } } @@ -267,17 +257,32 @@ pub trait Locate { Self: ::std::marker::Sized; } +// These functions are not thread-safe: +// > The return value may point to a static area, and may be +// > overwritten by subsequent calls to getpwent(3), getpwnam(), +// > or getpwuid(). +// This applies not just to the struct but also the strings it points +// to, so we must copy all the data we want before releasing the lock. +// (Technically we must also ensure that the raw functions aren't being called +// anywhere else in the program.) +static PW_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); + macro_rules! f { ($fnam:ident, $fid:ident, $t:ident, $st:ident) => { impl Locate<$t> for $st { fn locate(k: $t) -> IOResult { + let _guard = PW_LOCK.lock(); + // SAFETY: We're holding PW_LOCK. unsafe { let data = $fid(k); if !data.is_null() { - Ok($st { - inner: ptr::read(data as *const _), - }) + Ok($st::from_raw(ptr::read(data as *const _))) } else { + // FIXME: Resource limits, signals and I/O failure may + // cause this too. See getpwnam(3). + // errno must be set to zero before the call. We can + // use libc::__errno_location() on some platforms. + // The same applies for the two cases below. Err(IOError::new( ErrorKind::NotFound, format!("No such id: {}", k), @@ -289,25 +294,26 @@ macro_rules! f { impl<'a> Locate<&'a str> for $st { fn locate(k: &'a str) -> IOResult { + let _guard = PW_LOCK.lock(); if let Ok(id) = k.parse::<$t>() { - let data = unsafe { $fid(id) }; - if !data.is_null() { - Ok($st { - inner: unsafe { ptr::read(data as *const _) }, - }) - } else { - Err(IOError::new( - ErrorKind::NotFound, - format!("No such id: {}", id), - )) + // SAFETY: We're holding PW_LOCK. + unsafe { + let data = $fid(id); + if !data.is_null() { + Ok($st::from_raw(ptr::read(data as *const _))) + } else { + Err(IOError::new( + ErrorKind::NotFound, + format!("No such id: {}", id), + )) + } } } else { + // SAFETY: We're holding PW_LOCK. unsafe { let data = $fnam(CString::new(k).unwrap().as_ptr()); if !data.is_null() { - Ok($st { - inner: ptr::read(data as *const _), - }) + Ok($st::from_raw(ptr::read(data as *const _))) } else { Err(IOError::new( ErrorKind::NotFound, @@ -327,24 +333,24 @@ f!(getgrnam, getgrgid, gid_t, Group); #[inline] pub fn uid2usr(id: uid_t) -> IOResult { - Passwd::locate(id).map(|p| p.name().into_owned()) + Passwd::locate(id).map(|p| p.name) } #[cfg(not(target_os = "redox"))] #[inline] pub fn gid2grp(id: gid_t) -> IOResult { - Group::locate(id).map(|p| p.name().into_owned()) + Group::locate(id).map(|p| p.name) } #[inline] pub fn usr2uid(name: &str) -> IOResult { - Passwd::locate(name).map(|p| p.uid()) + Passwd::locate(name).map(|p| p.uid) } #[cfg(not(target_os = "redox"))] #[inline] pub fn grp2gid(name: &str) -> IOResult { - Group::locate(name).map(|p| p.gid()) + Group::locate(name).map(|p| p.gid) } #[cfg(test)] diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 0b8079a5cc1..ef3dd6adf43 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -17,12 +17,15 @@ use libc::{ use std::borrow::Cow; use std::env; use std::fs; +use std::hash::Hash; use std::io::Error as IOError; use std::io::Result as IOResult; use std::io::{Error, ErrorKind}; -#[cfg(any(unix, target_os = "redox"))] -use std::os::unix::fs::MetadataExt; +#[cfg(unix)] +use std::os::unix::{fs::MetadataExt, io::AsRawFd}; use std::path::{Component, Path, PathBuf}; +#[cfg(target_os = "windows")] +use winapi_util::AsHandleRef; #[cfg(unix)] #[macro_export] @@ -32,6 +35,114 @@ macro_rules! has { }; } +/// Information to uniquely identify a file +pub struct FileInformation( + #[cfg(unix)] nix::sys::stat::FileStat, + #[cfg(windows)] winapi_util::file::Information, +); + +impl FileInformation { + /// Get information from a currently open file + #[cfg(unix)] + pub fn from_file(file: &impl AsRawFd) -> Option { + if let Ok(x) = nix::sys::stat::fstat(file.as_raw_fd()) { + Some(Self(x)) + } else { + None + } + } + + /// Get information from a currently open file + #[cfg(target_os = "windows")] + pub fn from_file(file: &impl AsHandleRef) -> Option { + if let Ok(x) = winapi_util::file::information(file.as_handle_ref()) { + Some(Self(x)) + } else { + None + } + } + + /// Get information for a given path. + /// + /// If `path` points to a symlink and `dereference` is true, information about + /// the link's target will be returned. + pub fn from_path(path: impl AsRef, dereference: bool) -> Option { + #[cfg(unix)] + { + let stat = if dereference { + nix::sys::stat::stat(path.as_ref()) + } else { + nix::sys::stat::lstat(path.as_ref()) + }; + if let Ok(stat) = stat { + Some(Self(stat)) + } else { + None + } + } + #[cfg(target_os = "windows")] + { + use std::fs::OpenOptions; + use std::os::windows::prelude::*; + let mut open_options = OpenOptions::new(); + if !dereference { + open_options.custom_flags(winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT); + } + open_options + .read(true) + .open(path.as_ref()) + .ok() + .and_then(|file| Self::from_file(&file)) + } + } + + pub fn file_size(&self) -> u64 { + #[cfg(unix)] + { + use std::convert::TryInto; + + assert!(self.0.st_size >= 0, "File size is negative"); + self.0.st_size.try_into().unwrap() + } + #[cfg(target_os = "windows")] + { + self.0.file_size() + } + } +} + +#[cfg(unix)] +impl PartialEq for FileInformation { + fn eq(&self, other: &Self) -> bool { + self.0.st_dev == other.0.st_dev && self.0.st_ino == other.0.st_ino + } +} + +#[cfg(target_os = "windows")] +impl PartialEq for FileInformation { + fn eq(&self, other: &Self) -> bool { + self.0.volume_serial_number() == other.0.volume_serial_number() + && self.0.file_index() == other.0.file_index() + } +} + +impl Eq for FileInformation {} + +impl Hash for FileInformation { + fn hash(&self, state: &mut H) { + #[cfg(unix)] + { + self.0.st_dev.hash(state); + self.0.st_ino.hash(state); + } + #[cfg(target_os = "windows")] + { + self.0.volume_serial_number().hash(state); + self.0.file_index().hash(state); + } + } +} + pub fn resolve_relative_path(path: &Path) -> Cow { if path.components().all(|e| e != Component::ParentDir) { return path.into(); diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 97c1da79c55..0461555e779 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -203,10 +203,14 @@ impl MountInfo { // Format: 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue // "man proc" for more details LINUX_MOUNTINFO => { + const FIELDS_OFFSET: usize = 6; + let after_fields = raw[FIELDS_OFFSET..].iter().position(|c| *c == "-").unwrap() + + FIELDS_OFFSET + + 1; let mut m = MountInfo { dev_id: "".to_string(), - dev_name: raw[9].to_string(), - fs_type: raw[8].to_string(), + dev_name: raw[after_fields + 1].to_string(), + fs_type: raw[after_fields].to_string(), mount_root: raw[3].to_string(), mount_dir: raw[4].to_string(), mount_option: raw[5].to_string(), @@ -891,4 +895,46 @@ mod tests { assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234)); // spell-checker:enable } + + #[test] + #[cfg(target_os = "linux")] + fn test_mountinfo() { + // spell-checker:ignore (word) relatime + let info = MountInfo::new( + LINUX_MOUNTINFO, + "106 109 253:6 / /mnt rw,relatime - xfs /dev/fs0 rw" + .split_ascii_whitespace() + .collect(), + ) + .unwrap(); + + assert_eq!(info.mount_root, "/"); + assert_eq!(info.mount_dir, "/mnt"); + assert_eq!(info.mount_option, "rw,relatime"); + assert_eq!(info.fs_type, "xfs"); + assert_eq!(info.dev_name, "/dev/fs0"); + + // Test parsing with different amounts of optional fields. + let info = MountInfo::new( + LINUX_MOUNTINFO, + "106 109 253:6 / /mnt rw,relatime master:1 - xfs /dev/fs0 rw" + .split_ascii_whitespace() + .collect(), + ) + .unwrap(); + + assert_eq!(info.fs_type, "xfs"); + assert_eq!(info.dev_name, "/dev/fs0"); + + let info = MountInfo::new( + LINUX_MOUNTINFO, + "106 109 253:6 / /mnt rw,relatime master:1 shared:2 - xfs /dev/fs0 rw" + .split_ascii_whitespace() + .collect(), + ) + .unwrap(); + + assert_eq!(info.fs_type, "xfs"); + assert_eq!(info.dev_name, "/dev/fs0"); + } } diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index 5e299f9880a..2206177a353 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -163,12 +163,11 @@ pub fn strip_minus_from_mode(args: &mut Vec) -> bool { if arg == "--" { break; } - if arg.starts_with('-') { + if let Some(arg_stripped) = arg.strip_prefix('-') { if let Some(second) = arg.chars().nth(1) { match second { 'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => { - // TODO: use strip_prefix() once minimum rust version reaches 1.45.0 - *arg = arg[1..arg.len()].to_string(); + *arg = arg_stripped.to_string(); return true; } _ => {} diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index a96c3f48c85..a3078b818e8 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -236,17 +236,20 @@ impl Utmpx { flags: AI_CANONNAME, ..AddrInfoHints::default() }; - let sockets = getaddrinfo(Some(hostname), None, Some(hints)) - .unwrap() - .collect::>>()?; - for socket in sockets { - if let Some(ai_canonname) = socket.canonname { - return Ok(if display.is_empty() { - ai_canonname - } else { - format!("{}:{}", ai_canonname, display) - }); + if let Ok(sockets) = getaddrinfo(Some(hostname), None, Some(hints)) { + let sockets = sockets.collect::>>()?; + for socket in sockets { + if let Some(ai_canonname) = socket.canonname { + return Ok(if display.is_empty() { + ai_canonname + } else { + format!("{}:{}", ai_canonname, display) + }); + } } + } else { + // GNU coreutils has this behavior + return Ok(hostname.to_string()); } } diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 3d2d867bd73..2f8ccce130f 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -18,7 +18,6 @@ mod parser; // string parsing modules // * cross-platform modules pub use crate::mods::backup_control; -pub use crate::mods::coreopts; pub use crate::mods::display; pub use crate::mods::error; pub use crate::mods::os; diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index c7b15dba3e5..a3d5b299ebb 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -1,3 +1,35 @@ +//! Macros for the uucore utilities. +//! +//! This module bundles all macros used across the uucore utilities. These +//! include macros for reporting errors in various formats, aborting program +//! execution and more. +//! +//! To make use of all macros in this module, they must be imported like so: +//! +//! ```ignore +//! #[macro_use] +//! extern crate uucore; +//! ``` +//! +//! Alternatively, you can import single macros by importing them through their +//! fully qualified name like this: +//! +//! ```no_run +//! use uucore::{show, crash}; +//! ``` +//! +//! Here's an overview of the macros sorted by purpose +//! +//! - Print errors +//! - From types implementing [`crate::error::UError`]: [`show!`], +//! [`show_if_err!`] +//! - From custom messages: [`show_error!`], [`show_usage_error!`] +//! - Print warnings: [`show_warning!`] +//! - Terminate util execution +//! - Crash program: [`crash!`], [`crash_if_err!`] + +// spell-checker:ignore sourcepath targetpath + use std::sync::atomic::AtomicBool; // This file is part of the uutils coreutils package. @@ -12,6 +44,45 @@ pub static UTILITY_IS_SECOND_ARG: AtomicBool = AtomicBool::new(false); //==== +/// Display a [`crate::error::UError`] and set global exit code. +/// +/// Prints the error message contained in an [`crate::error::UError`] to stderr +/// and sets the exit code through [`crate::error::set_exit_code`]. The printed +/// error message is prepended with the calling utility's name. A call to this +/// macro will not finish program execution. +/// +/// # Examples +/// +/// The following example would print a message "Some error occurred" and set +/// the utility's exit code to 2. +/// +/// ``` +/// # #[macro_use] +/// # extern crate uucore; +/// +/// use uucore::error::{self, USimpleError}; +/// +/// fn main() { +/// let err = USimpleError::new(2, "Some error occurred."); +/// show!(err); +/// assert_eq!(error::get_exit_code(), 2); +/// } +/// ``` +/// +/// If not using [`crate::error::UError`], one may achieve the same behavior +/// like this: +/// +/// ``` +/// # #[macro_use] +/// # extern crate uucore; +/// +/// use uucore::error::set_exit_code; +/// +/// fn main() { +/// set_exit_code(2); +/// show_error!("Some error occurred."); +/// } +/// ``` #[macro_export] macro_rules! show( ($err:expr) => ({ @@ -21,6 +92,36 @@ macro_rules! show( }) ); +/// Display an error and set global exit code in error case. +/// +/// Wraps around [`show!`] and takes a [`crate::error::UResult`] instead of a +/// [`crate::error::UError`] type. This macro invokes [`show!`] if the +/// [`crate::error::UResult`] is an `Err`-variant. This can be invoked directly +/// on the result of a function call, like in the `install` utility: +/// +/// ```ignore +/// show_if_err!(copy(sourcepath, &targetpath, b)); +/// ``` +/// +/// # Examples +/// +/// ```ignore +/// # #[macro_use] +/// # extern crate uucore; +/// # use uucore::error::{UError, UIoError, UResult, USimpleError}; +/// +/// # fn main() { +/// let is_ok = Ok(1); +/// // This does nothing at all +/// show_if_err!(is_ok); +/// +/// let is_err = Err(USimpleError::new(1, "I'm an error").into()); +/// // Calls `show!` on the contained USimpleError +/// show_if_err!(is_err); +/// # } +/// ``` +/// +/// #[macro_export] macro_rules! show_if_err( ($res:expr) => ({ @@ -31,6 +132,19 @@ macro_rules! show_if_err( ); /// Show an error to stderr in a similar style to GNU coreutils. +/// +/// Takes a [`format!`]-like input and prints it to stderr. The output is +/// prepended with the current utility's name. +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] +/// # extern crate uucore; +/// # fn main() { +/// show_error!("Couldn't apply {} to {}", "foo", "bar"); +/// # } +/// ``` #[macro_export] macro_rules! show_error( ($($args:tt)+) => ({ @@ -40,6 +154,17 @@ macro_rules! show_error( ); /// Show a warning to stderr in a similar style to GNU coreutils. +/// +/// Is this really required? Used in the following locations: +/// +/// ./src/uu/head/src/head.rs:12 +/// ./src/uu/head/src/head.rs:424 +/// ./src/uu/head/src/head.rs:427 +/// ./src/uu/head/src/head.rs:430 +/// ./src/uu/head/src/head.rs:453 +/// ./src/uu/du/src/du.rs:339 +/// ./src/uu/wc/src/wc.rs:270 +/// ./src/uu/wc/src/wc.rs:273 #[macro_export] macro_rules! show_error_custom_description ( ($err:expr,$($args:tt)+) => ({ @@ -48,6 +173,21 @@ macro_rules! show_error_custom_description ( }) ); +/// Print a warning message to stderr. +/// +/// Takes [`format!`]-compatible input and prepends it with the current +/// utility's name and "warning: " before printing to stderr. +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] +/// # extern crate uucore; +/// # fn main() { +/// // outputs : warning: Couldn't apply foo to bar +/// show_warning!("Couldn't apply {} to {}", "foo", "bar"); +/// # } +/// ``` #[macro_export] macro_rules! show_warning( ($($args:tt)+) => ({ @@ -57,6 +197,21 @@ macro_rules! show_warning( ); /// Show a bad invocation help message in a similar style to GNU coreutils. +/// +/// Takes a [`format!`]-compatible input and prepends it with the current +/// utility's name before printing to stderr. +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] +/// # extern crate uucore; +/// # fn main() { +/// // outputs : Couldn't apply foo to bar +/// // Try ' --help' for more information. +/// show_usage_error!("Couldn't apply {} to {}", "foo", "bar"); +/// # } +/// ``` #[macro_export] macro_rules! show_usage_error( ($($args:tt)+) => ({ @@ -66,27 +221,50 @@ macro_rules! show_usage_error( }) ); -//==== - -/// Calls `exit()` with the provided exit code. -#[macro_export] -macro_rules! exit( - ($exit_code:expr) => ({ - ::std::process::exit($exit_code) - }) -); - -/// Display the provided error message, then `exit()` with the provided exit code +/// Display an error and [`exit!`] +/// +/// Displays the provided error message using [`show_error!`], then invokes +/// [`std::process::exit`] with the provided exit code. +/// +/// # Examples +/// +/// ```should_panic +/// # #[macro_use] +/// # extern crate uucore; +/// # fn main() { +/// // outputs : Couldn't apply foo to bar +/// // and terminates execution +/// crash!(1, "Couldn't apply {} to {}", "foo", "bar"); +/// # } +/// ``` #[macro_export] macro_rules! crash( ($exit_code:expr, $($args:tt)+) => ({ $crate::show_error!($($args)+); - $crate::exit!($exit_code) + std::process::exit($exit_code); }) ); -/// Unwraps the Result. Instead of panicking, it exists the program with the -/// provided exit code. +/// Unwrap a [`std::result::Result`], crashing instead of panicking. +/// +/// If the result is an `Ok`-variant, returns the value contained inside. If it +/// is an `Err`-variant, invokes [`crash!`] with the formatted error instead. +/// +/// # Examples +/// +/// ```should_panic +/// # #[macro_use] +/// # extern crate uucore; +/// # fn main() { +/// let is_ok: Result = Ok(1); +/// // Does nothing +/// crash_if_err!(1, is_ok); +/// +/// let is_err: Result = Err("This didn't work..."); +/// // Calls `crash!` +/// crash_if_err!(1, is_err); +/// # } +/// ``` #[macro_export] macro_rules! crash_if_err( ($exit_code:expr, $exp:expr) => ( @@ -96,120 +274,3 @@ macro_rules! crash_if_err( } ) ); - -//==== - -#[macro_export] -macro_rules! safe_write( - ($fd:expr, $($args:tt)+) => ( - match write!($fd, $($args)+) { - Ok(_) => {} - Err(f) => panic!("{}", f) - } - ) -); - -#[macro_export] -macro_rules! safe_writeln( - ($fd:expr, $($args:tt)+) => ( - match writeln!($fd, $($args)+) { - Ok(_) => {} - Err(f) => panic!("{}", f) - } - ) -); - -//-- message templates - -//-- message templates : (join utility sub-macros) - -#[macro_export] -macro_rules! snippet_list_join_oxford_comma { - ($conjunction:expr, $valOne:expr, $valTwo:expr) => ( - format!("{}, {} {}", $valOne, $conjunction, $valTwo) - ); - ($conjunction:expr, $valOne:expr, $valTwo:expr $(, $remaining_values:expr)*) => ( - format!("{}, {}", $valOne, $crate::snippet_list_join_oxford_comma!($conjunction, $valTwo $(, $remaining_values)*)) - ); -} - -#[macro_export] -macro_rules! snippet_list_join { - ($conjunction:expr, $valOne:expr, $valTwo:expr) => ( - format!("{} {} {}", $valOne, $conjunction, $valTwo) - ); - ($conjunction:expr, $valOne:expr, $valTwo:expr $(, $remaining_values:expr)*) => ( - format!("{}, {}", $valOne, $crate::snippet_list_join_oxford_comma!($conjunction, $valTwo $(, $remaining_values)*)) - ); -} - -//-- message templates : invalid input - -#[macro_export] -macro_rules! msg_invalid_input { - ($reason: expr) => { - format!("invalid input: {}", $reason) - }; -} - -// -- message templates : invalid input : flag - -#[macro_export] -macro_rules! msg_invalid_opt_use { - ($about:expr, $flag:expr) => { - $crate::msg_invalid_input!(format!("The '{}' option {}", $flag, $about)) - }; - ($about:expr, $long_flag:expr, $short_flag:expr) => { - $crate::msg_invalid_input!(format!( - "The '{}' ('{}') option {}", - $long_flag, $short_flag, $about - )) - }; -} - -#[macro_export] -macro_rules! msg_opt_only_usable_if { - ($clause:expr, $flag:expr) => { - $crate::msg_invalid_opt_use!(format!("only usable if {}", $clause), $flag) - }; - ($clause:expr, $long_flag:expr, $short_flag:expr) => { - $crate::msg_invalid_opt_use!( - format!("only usable if {}", $clause), - $long_flag, - $short_flag - ) - }; -} - -#[macro_export] -macro_rules! msg_opt_invalid_should_be { - ($expects:expr, $received:expr, $flag:expr) => { - $crate::msg_invalid_opt_use!( - format!("expects {}, but was provided {}", $expects, $received), - $flag - ) - }; - ($expects:expr, $received:expr, $long_flag:expr, $short_flag:expr) => { - $crate::msg_invalid_opt_use!( - format!("expects {}, but was provided {}", $expects, $received), - $long_flag, - $short_flag - ) - }; -} - -// -- message templates : invalid input : input combinations - -#[macro_export] -macro_rules! msg_expects_one_of { - ($valOne:expr $(, $remaining_values:expr)*) => ( - $crate::msg_invalid_input!(format!("expects one of {}", $crate::snippet_list_join!("or", $valOne $(, $remaining_values)*))) - ); -} - -#[macro_export] -macro_rules! msg_expects_no_more_than_one_of { - ($valOne:expr $(, $remaining_values:expr)*) => ( - $crate::msg_invalid_input!(format!("expects no more than one of {}", $crate::snippet_list_join!("or", $valOne $(, $remaining_values)*))) ; - ); -} diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index 8f6d149760a..bbde696dc8c 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -1,7 +1,6 @@ // mods ~ cross-platforms modules (core/bundler file) pub mod backup_control; -pub mod coreopts; pub mod display; pub mod error; pub mod os; diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index acb7342b72b..167c513860a 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -56,7 +56,7 @@ //! .get_matches_from(vec![ //! "app", "--backup=t", "--suffix=bak~" //! ]); -//! +//! //! let backup_mode = match backup_control::determine_backup_mode(&matches) { //! Err(e) => { //! show!(e); @@ -321,7 +321,7 @@ pub fn determine_backup_suffix(matches: &ArgMatches) -> String { /// .get_matches_from(vec![ /// "app", "-b", "--backup=n" /// ]); -/// +/// /// let backup_mode = backup_control::determine_backup_mode(&matches); /// /// assert!(backup_mode.is_err()); diff --git a/src/uucore/src/lib/mods/coreopts.rs b/src/uucore/src/lib/mods/coreopts.rs deleted file mode 100644 index b534ff90224..00000000000 --- a/src/uucore/src/lib/mods/coreopts.rs +++ /dev/null @@ -1,141 +0,0 @@ -pub struct HelpText<'a> { - pub name: &'a str, - pub version: &'a str, - pub syntax: &'a str, - pub summary: &'a str, - pub long_help: &'a str, - pub display_usage: bool, -} - -pub struct CoreOptions<'a> { - options: getopts::Options, - help_text: HelpText<'a>, -} - -impl<'a> CoreOptions<'a> { - pub fn new(help_text: HelpText<'a>) -> Self { - let mut ret = CoreOptions { - options: getopts::Options::new(), - help_text, - }; - ret.options - .optflag("", "help", "print usage information") - .optflag("", "version", "print name and version number"); - ret - } - pub fn optflagopt( - &mut self, - short_name: &str, - long_name: &str, - desc: &str, - hint: &str, - ) -> &mut CoreOptions<'a> { - self.options.optflagopt(short_name, long_name, desc, hint); - self - } - pub fn optflag( - &mut self, - short_name: &str, - long_name: &str, - desc: &str, - ) -> &mut CoreOptions<'a> { - self.options.optflag(short_name, long_name, desc); - self - } - pub fn optflagmulti( - &mut self, - short_name: &str, - long_name: &str, - desc: &str, - ) -> &mut CoreOptions<'a> { - self.options.optflagmulti(short_name, long_name, desc); - self - } - pub fn optopt( - &mut self, - short_name: &str, - long_name: &str, - desc: &str, - hint: &str, - ) -> &mut CoreOptions<'a> { - self.options.optopt(short_name, long_name, desc, hint); - self - } - pub fn optmulti( - &mut self, - short_name: &str, - long_name: &str, - desc: &str, - hint: &str, - ) -> &mut CoreOptions<'a> { - self.options.optmulti(short_name, long_name, desc, hint); - self - } - pub fn usage(&self, summary: &str) -> String { - self.options.usage(summary) - } - pub fn parse(&mut self, args: Vec) -> getopts::Matches { - let matches = match self.options.parse(&args[1..]) { - Ok(m) => Some(m), - Err(f) => { - eprint!("{}: error: ", self.help_text.name); - eprintln!("{}", f); - ::std::process::exit(1); - } - } - .unwrap(); - if matches.opt_present("help") { - let usage_str = if self.help_text.display_usage { - format!( - "\n {}\n\n Reference\n", - self.options.usage(self.help_text.summary) - ) - .replace("Options:", " Options:") - } else { - String::new() - }; - print!( - " - {0} {1} - - {0} {2} -{3}{4} -", - self.help_text.name, - self.help_text.version, - self.help_text.syntax, - usage_str, - self.help_text.long_help - ); - crate::exit!(0); - } else if matches.opt_present("version") { - println!("{} {}", self.help_text.name, self.help_text.version); - crate::exit!(0); - } - matches - } -} - -#[macro_export] -macro_rules! app { - ($syntax: expr, $summary: expr, $long_help: expr) => { - uucore::coreopts::CoreOptions::new(uucore::coreopts::HelpText { - name: uucore::util_name(), - version: env!("CARGO_PKG_VERSION"), - syntax: $syntax, - summary: $summary, - long_help: $long_help, - display_usage: true, - }) - }; - ($syntax: expr, $summary: expr, $long_help: expr, $display_usage: expr) => { - uucore::coreopts::CoreOptions::new(uucore::coreopts::HelpText { - name: uucore::util_name(), - version: env!("CARGO_PKG_VERSION"), - syntax: $syntax, - summary: $summary, - long_help: $long_help, - display_usage: $display_usage, - }) - }; -} diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index dfe64184ffc..95288973a28 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -19,378 +19,16 @@ /// println_verbatim(path)?; // Prints "foo/bar.baz" /// # Ok::<(), std::io::Error>(()) /// ``` -// spell-checker:ignore Fbar -use std::borrow::Cow; use std::ffi::OsStr; -#[cfg(any(unix, target_os = "wasi", windows))] -use std::fmt::Write as FmtWrite; -use std::fmt::{self, Display, Formatter}; use std::io::{self, Write as IoWrite}; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::OsStrExt; -#[cfg(any(unix, target_os = "wasi"))] -use std::str::from_utf8; -/// An extension trait for displaying filenames to users. -pub trait Quotable { - /// Returns an object that implements [`Display`] for printing filenames with - /// proper quoting and escaping for the platform. - /// - /// On Unix this corresponds to sh/bash syntax, on Windows Powershell syntax - /// is used. - /// - /// # Examples - /// - /// ``` - /// use std::path::Path; - /// use uucore::display::Quotable; - /// - /// let path = Path::new("foo/bar.baz"); - /// - /// println!("Found file {}", path.quote()); // Prints "Found file 'foo/bar.baz'" - /// ``` - fn quote(&self) -> Quoted<'_>; - - /// Like `quote()`, but don't actually add quotes unless necessary because of - /// whitespace or special characters. - /// - /// # Examples - /// - /// ``` - /// use std::path::Path; - /// use uucore::display::Quotable; - /// use uucore::show_error; - /// - /// let foo = Path::new("foo/bar.baz"); - /// let bar = Path::new("foo bar"); - /// - /// show_error!("{}: Not found", foo.maybe_quote()); // Prints "util: foo/bar.baz: Not found" - /// show_error!("{}: Not found", bar.maybe_quote()); // Prints "util: 'foo bar': Not found" - /// ``` - fn maybe_quote(&self) -> Quoted<'_> { - let mut quoted = self.quote(); - quoted.force_quote = false; - quoted - } -} - -macro_rules! impl_as_ref { - ($type: ty) => { - impl Quotable for $type { - fn quote(&self) -> Quoted<'_> { - Quoted::new(self.as_ref()) - } - } - }; -} - -impl_as_ref!(str); -impl_as_ref!(&'_ str); -impl_as_ref!(String); -impl_as_ref!(std::path::Path); -impl_as_ref!(&'_ std::path::Path); -impl_as_ref!(std::path::PathBuf); -impl_as_ref!(std::path::Component<'_>); -impl_as_ref!(std::path::Components<'_>); -impl_as_ref!(std::path::Iter<'_>); -impl_as_ref!(std::ffi::OsStr); -impl_as_ref!(&'_ std::ffi::OsStr); -impl_as_ref!(std::ffi::OsString); - -// Cow<'_, str> does not implement AsRef and this is unlikely to be fixed -// for backward compatibility reasons. Otherwise we'd use a blanket impl. -impl Quotable for Cow<'_, str> { - fn quote(&self) -> Quoted<'_> { - let text: &str = self.as_ref(); - Quoted::new(text.as_ref()) - } -} - -impl Quotable for Cow<'_, std::path::Path> { - fn quote(&self) -> Quoted<'_> { - let text: &std::path::Path = self.as_ref(); - Quoted::new(text.as_ref()) - } -} - -/// A wrapper around [`OsStr`] for printing paths with quoting and escaping applied. -#[derive(Debug, Copy, Clone)] -pub struct Quoted<'a> { - text: &'a OsStr, - force_quote: bool, -} - -impl<'a> Quoted<'a> { - fn new(text: &'a OsStr) -> Self { - Quoted { - text, - force_quote: true, - } - } -} - -impl Display for Quoted<'_> { - #[cfg(any(windows, unix, target_os = "wasi"))] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // On Unix we emulate sh syntax. On Windows Powershell. - // They're just similar enough to share some code. - - /// Characters with special meaning outside quotes. - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 - // I don't know why % is in there, and GNU doesn't quote it either. - // {} were used in a version elsewhere but seem unnecessary, GNU doesn't - // quote them. They're used in function definitions but not in a way we - // have to worry about. - #[cfg(any(unix, target_os = "wasi"))] - const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\\\"'*?[]="; - // FIXME: I'm not a PowerShell wizard and don't know if this is correct. - // I just copied the Unix version, removed \, and added ,{} based on - // experimentation. - // I have noticed that ~?*[] only get expanded in some contexts, so watch - // out for that if doing your own tests. - // Get-ChildItem seems unwilling to quote anything so it doesn't help. - // There's the additional wrinkle that Windows has stricter requirements - // for filenames: I've been testing using a Linux build of PowerShell, but - // this code doesn't even compile on Linux. - #[cfg(windows)] - const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\"'*?[]=,{}"; - - /// Characters with a special meaning at the beginning of a name. - // ~ expands a home directory. - // # starts a comment. - // ! is a common extension for expanding the shell history. - #[cfg(any(unix, target_os = "wasi"))] - const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '!']; - // Same deal as before, this is possibly incomplete. - // A single stand-alone exclamation mark seems to have some special meaning. - #[cfg(windows)] - const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '@', '!']; - - /// Characters that are interpreted specially in a double-quoted string. - #[cfg(any(unix, target_os = "wasi"))] - const DOUBLE_UNSAFE: &[u8] = &[b'"', b'`', b'$', b'\\']; - #[cfg(windows)] - const DOUBLE_UNSAFE: &[u8] = &[b'"', b'`', b'$']; - - let text = match self.text.to_str() { - None => return write_escaped(f, self.text), - Some(text) => text, - }; - - let mut is_single_safe = true; - let mut is_double_safe = true; - let mut requires_quote = self.force_quote; - - if let Some(first) = text.chars().next() { - if SPECIAL_SHELL_CHARS_START.contains(&first) { - requires_quote = true; - } - // Unlike in Unix, quoting an argument may stop it - // from being recognized as an option. I like that very much. - // But we don't want to quote "-" because that's a common - // special argument and PowerShell doesn't mind it. - #[cfg(windows)] - if first == '-' && text.len() > 1 { - requires_quote = true; - } - } else { - // Empty string - requires_quote = true; - } - - for ch in text.chars() { - if ch.is_ascii() { - let ch = ch as u8; - if ch == b'\'' { - is_single_safe = false; - } - if DOUBLE_UNSAFE.contains(&ch) { - is_double_safe = false; - } - if !requires_quote && SPECIAL_SHELL_CHARS.contains(&ch) { - requires_quote = true; - } - if ch.is_ascii_control() { - return write_escaped(f, self.text); - } - } - if !requires_quote && ch.is_whitespace() { - // This includes unicode whitespace. - // We maybe don't have to escape it, we don't escape other lookalike - // characters either, but it's confusing if it goes unquoted. - requires_quote = true; - } - } - - if !requires_quote { - return f.write_str(text); - } else if is_single_safe { - return write_simple(f, text, '\''); - } else if is_double_safe { - return write_simple(f, text, '\"'); - } else { - return write_single_escaped(f, text); - } - - fn write_simple(f: &mut Formatter<'_>, text: &str, quote: char) -> fmt::Result { - f.write_char(quote)?; - f.write_str(text)?; - f.write_char(quote)?; - Ok(()) - } - - #[cfg(any(unix, target_os = "wasi"))] - fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result { - let mut iter = text.split('\''); - if let Some(chunk) = iter.next() { - if !chunk.is_empty() { - write_simple(f, chunk, '\'')?; - } - } - for chunk in iter { - f.write_str("\\'")?; - if !chunk.is_empty() { - write_simple(f, chunk, '\'')?; - } - } - Ok(()) - } - - /// Write using the syntax described here: - /// https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html - /// - /// Supported by these shells: - /// - bash - /// - zsh - /// - busybox sh - /// - mksh - /// - /// Not supported by these: - /// - fish - /// - dash - /// - tcsh - #[cfg(any(unix, target_os = "wasi"))] - fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result { - f.write_str("$'")?; - for chunk in from_utf8_iter(text.as_bytes()) { - match chunk { - Ok(chunk) => { - for ch in chunk.chars() { - match ch { - '\n' => f.write_str("\\n")?, - '\t' => f.write_str("\\t")?, - '\r' => f.write_str("\\r")?, - // We could do \b, \f, \v, etc., but those are - // rare enough to be confusing. - // \0 doesn't work consistently because of the - // octal \nnn syntax, and null bytes can't appear - // in filenames anyway. - ch if ch.is_ascii_control() => write!(f, "\\x{:02X}", ch as u8)?, - '\\' | '\'' => { - // '?' and '"' can also be escaped this way - // but AFAICT there's no reason to do so - f.write_char('\\')?; - f.write_char(ch)?; - } - ch => { - f.write_char(ch)?; - } - } - } - } - Err(unit) => write!(f, "\\x{:02X}", unit)?, - } - } - f.write_char('\'')?; - Ok(()) - } - - #[cfg(windows)] - fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result { - // Quotes in Powershell can be escaped by doubling them - f.write_char('\'')?; - let mut iter = text.split('\''); - if let Some(chunk) = iter.next() { - f.write_str(chunk)?; - } - for chunk in iter { - f.write_str("''")?; - f.write_str(chunk)?; - } - f.write_char('\'')?; - Ok(()) - } - - #[cfg(windows)] - fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result { - // ` takes the role of \ since \ is already used as the path separator. - // Things are UTF-16-oriented, so we escape code units as "`u{1234}". - use std::char::decode_utf16; - use std::os::windows::ffi::OsStrExt; - - f.write_char('"')?; - for ch in decode_utf16(text.encode_wide()) { - match ch { - Ok(ch) => match ch { - '\0' => f.write_str("`0")?, - '\r' => f.write_str("`r")?, - '\n' => f.write_str("`n")?, - '\t' => f.write_str("`t")?, - ch if ch.is_ascii_control() => write!(f, "`u{{{:04X}}}", ch as u8)?, - '`' => f.write_str("``")?, - '$' => f.write_str("`$")?, - '"' => f.write_str("\"\"")?, - ch => f.write_char(ch)?, - }, - Err(err) => write!(f, "`u{{{:04X}}}", err.unpaired_surrogate())?, - } - } - f.write_char('"')?; - Ok(()) - } - } - - #[cfg(not(any(unix, target_os = "wasi", windows)))] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // As a fallback, we use Rust's own escaping rules. - // This is reasonably sane and very easy to implement. - // We use single quotes because that's hardcoded in a lot of tests. - let text = self.text.to_string_lossy(); - if self.force_quote || !text.chars().all(|ch| ch.is_alphanumeric() || ch == '.') { - write!(f, "'{}'", text.escape_debug()) - } else { - f.write_str(&text) - } - } -} - -#[cfg(any(unix, target_os = "wasi"))] -fn from_utf8_iter(mut bytes: &[u8]) -> impl Iterator> { - std::iter::from_fn(move || { - if bytes.is_empty() { - return None; - } - match from_utf8(bytes) { - Ok(text) => { - bytes = &[]; - Some(Ok(text)) - } - Err(err) if err.valid_up_to() == 0 => { - let res = bytes[0]; - bytes = &bytes[1..]; - Some(Err(res)) - } - Err(err) => { - let (valid, rest) = bytes.split_at(err.valid_up_to()); - bytes = rest; - Some(Ok(from_utf8(valid).unwrap())) - } - } - }) -} +// These used to be defined here, but they live in their own crate now. +pub use os_display::{Quotable, Quoted}; /// Print a path (or `OsStr`-like object) directly to stdout, with a trailing newline, /// without losing any information if its encoding is invalid. @@ -429,129 +67,3 @@ pub fn print_verbatim>(text: S) -> io::Result<()> { write!(stdout, "{}", std::path::Path::new(text.as_ref()).display()) } } - -#[cfg(test)] -mod tests { - use super::*; - - fn verify_quote(cases: &[(impl Quotable, &str)]) { - for (case, expected) in cases { - assert_eq!(case.quote().to_string(), *expected); - } - } - - fn verify_maybe(cases: &[(impl Quotable, &str)]) { - for (case, expected) in cases { - assert_eq!(case.maybe_quote().to_string(), *expected); - } - } - - /// This should hold on any platform, or else other tests fail. - #[test] - fn test_basic() { - verify_quote(&[ - ("foo", "'foo'"), - ("", "''"), - ("foo/bar.baz", "'foo/bar.baz'"), - ]); - verify_maybe(&[ - ("foo", "foo"), - ("", "''"), - ("foo bar", "'foo bar'"), - ("$foo", "'$foo'"), - ("-", "-"), - ]); - } - - #[cfg(any(unix, target_os = "wasi", windows))] - #[test] - fn test_common() { - verify_maybe(&[ - ("a#b", "a#b"), - ("#ab", "'#ab'"), - ("a~b", "a~b"), - ("!", "'!'"), - ]); - } - - #[cfg(any(unix, target_os = "wasi"))] - #[test] - fn test_unix() { - verify_quote(&[ - ("can't", r#""can't""#), - (r#"can'"t"#, r#"'can'\''"t'"#), - (r#"can'$t"#, r#"'can'\''$t'"#), - ("foo\nb\ta\r\\\0`r", r#"$'foo\nb\ta\r\\\x00`r'"#), - ("foo\x02", r#"$'foo\x02'"#), - (r#"'$''"#, r#"\''$'\'\'"#), - ]); - verify_quote(&[(OsStr::from_bytes(b"foo\xFF"), r#"$'foo\xFF'"#)]); - verify_maybe(&[ - ("-x", "-x"), - ("a,b", "a,b"), - ("a\\b", "'a\\b'"), - ("}", ("}")), - ]); - } - - #[cfg(windows)] - #[test] - fn test_windows() { - use std::ffi::OsString; - use std::os::windows::ffi::OsStringExt; - verify_quote(&[ - (r#"foo\bar"#, r#"'foo\bar'"#), - ("can't", r#""can't""#), - (r#"can'"t"#, r#"'can''"t'"#), - (r#"can'$t"#, r#"'can''$t'"#), - ("foo\nb\ta\r\\\0`r", r#""foo`nb`ta`r\`0``r""#), - ("foo\x02", r#""foo`u{0002}""#), - (r#"'$''"#, r#"'''$'''''"#), - ]); - verify_quote(&[( - OsString::from_wide(&[b'x' as u16, 0xD800]), - r#""x`u{D800}""#, - )]); - verify_maybe(&[ - ("-x", "'-x'"), - ("a,b", "'a,b'"), - ("a\\b", "a\\b"), - ("}", "'}'"), - ]); - } - - #[cfg(any(unix, target_os = "wasi"))] - #[test] - fn test_utf8_iter() { - type ByteStr = &'static [u8]; - type Chunk = Result<&'static str, u8>; - const CASES: &[(ByteStr, &[Chunk])] = &[ - (b"", &[]), - (b"hello", &[Ok("hello")]), - // Immediately invalid - (b"\xFF", &[Err(b'\xFF')]), - // Incomplete UTF-8 - (b"\xC2", &[Err(b'\xC2')]), - (b"\xF4\x8F", &[Err(b'\xF4'), Err(b'\x8F')]), - (b"\xFF\xFF", &[Err(b'\xFF'), Err(b'\xFF')]), - (b"hello\xC2", &[Ok("hello"), Err(b'\xC2')]), - (b"\xFFhello", &[Err(b'\xFF'), Ok("hello")]), - (b"\xFF\xC2hello", &[Err(b'\xFF'), Err(b'\xC2'), Ok("hello")]), - (b"foo\xFFbar", &[Ok("foo"), Err(b'\xFF'), Ok("bar")]), - ( - b"foo\xF4\x8Fbar", - &[Ok("foo"), Err(b'\xF4'), Err(b'\x8F'), Ok("bar")], - ), - ( - b"foo\xFF\xC2bar", - &[Ok("foo"), Err(b'\xFF'), Err(b'\xC2'), Ok("bar")], - ), - ]; - for &(case, expected) in CASES { - assert_eq!( - from_utf8_iter(case).collect::>().as_slice(), - expected - ); - } - } -} diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index c04a0f2f168..37231576fbb 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -371,7 +371,7 @@ impl UError for UUsageError { /// ``` #[derive(Debug)] pub struct UIoError { - context: String, + context: Option, inner: std::io::Error, } @@ -379,7 +379,7 @@ impl UIoError { #[allow(clippy::new_ret_no_self)] pub fn new>(kind: std::io::ErrorKind, context: S) -> Box { Box::new(Self { - context: context.into(), + context: Some(context.into()), inner: kind.into(), }) } @@ -435,7 +435,11 @@ impl Display for UIoError { capitalize(&mut message); &message }; - write!(f, "{}: {}", self.context, message) + if let Some(ctx) = &self.context { + write!(f, "{}: {}", ctx, message) + } else { + write!(f, "{}", message) + } } } @@ -464,7 +468,7 @@ pub trait FromIo { impl FromIo> for std::io::Error { fn map_err_context(self, context: impl FnOnce() -> String) -> Box { Box::new(UIoError { - context: (context)(), + context: Some((context)()), inner: self, }) } @@ -479,12 +483,28 @@ impl FromIo> for std::io::Result { impl FromIo> for std::io::ErrorKind { fn map_err_context(self, context: impl FnOnce() -> String) -> Box { Box::new(UIoError { - context: (context)(), + context: Some((context)()), inner: std::io::Error::new(self, ""), }) } } +impl From for UIoError { + fn from(f: std::io::Error) -> UIoError { + UIoError { + context: None, + inner: f, + } + } +} + +impl From for Box { + fn from(f: std::io::Error) -> Box { + let u_error: UIoError = f.into(); + Box::new(u_error) as Box + } +} + /// Shorthand to construct [`UIoError`]-instances. /// /// This macro serves as a convenience call to quickly construct instances of diff --git a/src/uucore_procs/Cargo.toml b/src/uucore_procs/Cargo.toml index 3efe80e00d8..04019806385 100644 --- a/src/uucore_procs/Cargo.toml +++ b/src/uucore_procs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uucore_procs" -version = "0.0.7" +version = "0.0.12" authors = ["Roy Ivy III "] license = "MIT" description = "uutils ~ 'uucore' proc-macros" diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index ffe2cf74ca0..4d244704dfe 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -113,18 +113,12 @@ fn test_wrap_bad_arg() { #[test] fn test_base32_extra_operand() { - let ts = TestScenario::new(util_name!()); - // Expect a failure when multiple files are specified. - ts.ucmd() + new_ucmd!() .arg("a.txt") .arg("b.txt") .fails() - .stderr_only(format!( - "{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + .usage_error("extra operand 'b.txt'"); } #[test] diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index 87aa0db4465..9a7d525bba1 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -95,18 +95,12 @@ fn test_wrap_bad_arg() { #[test] fn test_base64_extra_operand() { - let ts = TestScenario::new(util_name!()); - // Expect a failure when multiple files are specified. - ts.ucmd() + new_ucmd!() .arg("a.txt") .arg("b.txt") .fails() - .stderr_only(format!( - "{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + .usage_error("extra operand 'b.txt'"); } #[test] diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 141745ac35b..962d7373da0 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -114,12 +114,7 @@ fn test_no_args() { #[test] fn test_no_args_output() { - let ts = TestScenario::new(util_name!()); - ts.ucmd().fails().stderr_is(&format!( - "{0}: missing operand\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + new_ucmd!().fails().usage_error("missing operand"); } #[test] @@ -129,12 +124,10 @@ fn test_too_many_args() { #[test] fn test_too_many_args_output() { - let ts = TestScenario::new(util_name!()); - ts.ucmd().args(&["a", "b", "c"]).fails().stderr_is(format!( - "{0}: extra operand 'c'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + new_ucmd!() + .args(&["a", "b", "c"]) + .fails() + .usage_error("extra operand 'c'"); } #[cfg(any(unix, target_os = "redox"))] diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index c8348d49141..1b0b7131b75 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -541,7 +541,7 @@ fn test_no_operands() { .arg("777") .fails() .code_is(1) - .stderr_is("chmod: missing operand"); + .usage_error("missing operand"); } #[test] diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index bf31ceb1862..66bdba9e362 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -74,9 +74,8 @@ fn test_invalid_file() { at.mkdir(folder_name); ts.ucmd() .arg(folder_name) - .fails() - .no_stdout() - .stderr_contains("cksum: asdf: Is a directory"); + .succeeds() + .stdout_only("4294967295 0 asdf\n"); } // Make sure crc is correct for files larger than 32 bytes diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index 01cdcc98517..d5b72b1e908 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -170,3 +170,11 @@ fn no_arguments() { fn one_argument() { new_ucmd!().arg("a").fails().no_stdout().no_stderr(); } + +#[test] +fn test_no_such_file() { + new_ucmd!() + .args(&["bogus_file_1", "bogus_file_2"]) + .fails() + .stderr_only("comm: bogus_file_1: No such file or directory"); +} diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index e86f35833a2..b2a6eede587 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -563,17 +563,13 @@ fn test_cp_backup_off() { #[test] fn test_cp_backup_no_clobber_conflicting_options() { - let ts = TestScenario::new(util_name!()); - ts.ucmd() + new_ucmd!() .arg("--backup") .arg("--no-clobber") .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE) - .fails().stderr_is(&format!( - "{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + .fails() + .usage_error("options --backup and --no-clobber are mutually exclusive"); } #[test] @@ -729,6 +725,21 @@ fn test_cp_parents_dest_not_directory() { .stderr_contains("with --parents, the destination must be a directory"); } +#[test] +#[cfg(unix)] +fn test_cp_writable_special_file_permissions() { + new_ucmd!().arg("/dev/null").arg("/dev/zero").succeeds(); +} + +#[test] +#[cfg(unix)] +fn test_cp_issue_1665() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.arg("/dev/null").arg("foo").succeeds(); + assert!(at.file_exists("foo")); + assert_eq!(at.read("foo"), ""); +} + #[test] fn test_cp_preserve_no_args() { new_ucmd!() @@ -1368,3 +1379,42 @@ fn test_canonicalize_symlink() { .no_stderr() .no_stdout(); } + +#[test] +fn test_copy_through_just_created_symlink() { + for &create_t in &[true, false] { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("a"); + at.mkdir("b"); + at.mkdir("c"); + #[cfg(unix)] + fs::symlink("../t", at.plus("a/1")).unwrap(); + #[cfg(target_os = "windows")] + symlink_file("../t", at.plus("a/1")).unwrap(); + at.touch("b/1"); + if create_t { + at.touch("t"); + } + ucmd.arg("--no-dereference") + .arg("a/1") + .arg("b/1") + .arg("c") + .fails() + .stderr_only(if cfg!(not(target_os = "windows")) { + "cp: will not copy 'b/1' through just-created symlink 'c/1'" + } else { + "cp: will not copy 'b/1' through just-created symlink 'c\\1'" + }); + } +} + +#[test] +fn test_copy_through_dangling_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file"); + at.symlink_file("nonexistent", "target"); + ucmd.arg("file") + .arg("target") + .fails() + .stderr_only("cp: not writing through dangling symlink 'target'"); +} diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 8340c7059eb..dd4204e2efe 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -262,7 +262,9 @@ fn test_nocreat_causes_failure_when_outfile_not_present() { ucmd.args(&["conv=nocreat", of!(&fname)]) .pipe_in("") .fails() - .stderr_is("dd Error: No such file or directory (os error 2)"); + .stderr_only( + "dd: failed to open 'this-file-does-not-exist.txt': No such file or directory", + ); assert!(!fix.file_exists(fname)); } @@ -357,7 +359,7 @@ fn test_fullblock() { of!(&tmp_fn), "bs=128M", // Note: In order for this test to actually test iflag=fullblock, the bs=VALUE - // must be big enough to 'overwhelm' urandom's store of bytes. + // must be big enough to 'overwhelm' the urandom store of bytes. // Try executing 'dd if=/dev/urandom bs=128M count=1' (i.e without iflag=fullblock). // The stats should contain the line: '0+1 records in' indicating a partial read. // Since my system only copies 32 MiB without fullblock, I expect 128 MiB to be diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 1d76c433d20..135ee72efb5 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -1,7 +1,4 @@ -// spell-checker:ignore (words) bamf chdir - -#[cfg(not(windows))] -use std::fs; +// spell-checker:ignore (words) bamf chdir rlimit prlimit COMSPEC use crate::common::util::*; use std::env; @@ -80,6 +77,20 @@ fn test_combined_file_set_unset() { ); } +#[test] +fn test_unset_invalid_variables() { + use uucore::display::Quotable; + + // Cannot test input with \0 in it, since output will also contain \0. rlimit::prlimit fails + // with this error: Error { kind: InvalidInput, message: "nul byte found in provided data" } + for var in &["", "a=b"] { + new_ucmd!().arg("-u").arg(var).run().stderr_only(format!( + "env: cannot unset {}: Invalid argument", + var.quote() + )); + } +} + #[test] fn test_single_name_value_pair() { let out = new_ucmd!().arg("FOO=bar").run(); @@ -108,6 +119,15 @@ fn test_ignore_environment() { scene.ucmd().arg("-").run().no_stdout(); } +#[test] +fn test_empty_name() { + new_ucmd!() + .arg("-i") + .arg("=xyz") + .run() + .stderr_only("env: warning: no name specified for value 'xyz'"); +} + #[test] fn test_null_delimiter() { let out = new_ucmd!() @@ -154,7 +174,7 @@ fn test_fail_null_with_program() { fn test_change_directory() { let scene = TestScenario::new(util_name!()); let temporary_directory = tempdir().unwrap(); - let temporary_path = fs::canonicalize(temporary_directory.path()).unwrap(); + let temporary_path = std::fs::canonicalize(temporary_directory.path()).unwrap(); assert_ne!(env::current_dir().unwrap(), temporary_path); // command to print out current working directory @@ -170,27 +190,36 @@ fn test_change_directory() { assert_eq!(out.trim(), temporary_path.as_os_str()) } -// no way to consistently get "current working directory", `cd` doesn't work @ CI -// instead, we test that the unique temporary directory appears somewhere in the printed variables #[cfg(windows)] #[test] fn test_change_directory() { let scene = TestScenario::new(util_name!()); let temporary_directory = tempdir().unwrap(); + let temporary_path = temporary_directory.path(); + let temporary_path = temporary_path + .strip_prefix(r"\\?\") + .unwrap_or(temporary_path); - assert_ne!(env::current_dir().unwrap(), temporary_path); + let env_cd = env::current_dir().unwrap(); + let env_cd = env_cd.strip_prefix(r"\\?\").unwrap_or(&env_cd); + + assert_ne!(env_cd, temporary_path); + + // COMSPEC is a variable that contains the full path to cmd.exe + let cmd_path = env::var("COMSPEC").unwrap(); + + // command to print out current working directory + let pwd = [&*cmd_path, "/C", "cd"]; let out = scene .ucmd() .arg("--chdir") .arg(&temporary_path) + .args(&pwd) .succeeds() .stdout_move_str(); - - assert!(!out - .lines() - .any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap()))); + assert_eq!(out.trim(), temporary_path.as_os_str()) } #[test] diff --git a/tests/by-util/test_join.rs b/tests/by-util/test_join.rs index 1d92bf8e724..4b2d1bbbaee 100644 --- a/tests/by-util/test_join.rs +++ b/tests/by-util/test_join.rs @@ -328,3 +328,31 @@ fn single_file_with_header() { .succeeds() .stdout_is("A 1\n"); } + +#[test] +fn non_line_feeds() { + new_ucmd!() + .arg("non-line_feeds_1.txt") + .arg("non-line_feeds_2.txt") + .succeeds() + .stdout_only_fixture("non-line_feeds.expected"); +} + +#[test] +fn non_unicode() { + new_ucmd!() + .arg("non-unicode_1.bin") + .arg("non-unicode_2.bin") + .succeeds() + .stdout_only_fixture("non-unicode.expected"); +} + +#[test] +fn null_line_endings() { + new_ucmd!() + .arg("-z") + .arg("non-unicode_1.bin") + .arg("non-unicode_2.bin") + .succeeds() + .stdout_only_fixture("z.expected"); +} diff --git a/tests/by-util/test_link.rs b/tests/by-util/test_link.rs index 6ac3f35cc6c..3219a6591ba 100644 --- a/tests/by-util/test_link.rs +++ b/tests/by-util/test_link.rs @@ -23,7 +23,7 @@ fn test_link_no_circular() { ucmd.args(&[link, link]) .fails() - .stderr_is("link: No such file or directory (os error 2)\n"); + .stderr_is("link: cannot create link 'test_link_no_circular' to 'test_link_no_circular': No such file or directory"); assert!(!at.file_exists(link)); } @@ -35,7 +35,7 @@ fn test_link_nonexistent_file() { ucmd.args(&[file, link]) .fails() - .stderr_is("link: No such file or directory (os error 2)\n"); + .stderr_only("link: cannot create link 'test_link_nonexistent_file_link' to 'test_link_nonexistent_file': No such file or directory"); assert!(!at.file_exists(file)); assert!(!at.file_exists(link)); } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 56711d6e0c8..b5d49337ded 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -11,7 +11,6 @@ use std::collections::HashMap; use std::path::Path; use std::thread::sleep; use std::time::Duration; - #[cfg(not(windows))] extern crate libc; #[cfg(not(windows))] @@ -40,6 +39,198 @@ fn test_ls_i() { } #[test] +fn test_ls_ordering() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("some-dir1"); + at.mkdir("some-dir2"); + at.mkdir("some-dir3"); + at.mkdir("some-dir4"); + at.mkdir("some-dir5"); + at.mkdir("some-dir6"); + + scene + .ucmd() + .arg("-Rl") + .succeeds() + .stdout_matches(&Regex::new("some-dir1:\\ntotal 0").unwrap()); +} + +//#[cfg(all(feature = "mknod"))] +#[test] +fn test_ls_devices() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("some-dir1"); + + // Regex tests correct device ID and correct (no pad) spacing for a single file + #[cfg(any(target_os = "macos", target_os = "ios"))] + { + scene + .ucmd() + .arg("-al") + .arg("/dev/null") + .succeeds() + .stdout_matches(&Regex::new("[^ ] 3, 2 [^ ]").unwrap()); + } + + #[cfg(target_os = "linux")] + { + scene + .ucmd() + .arg("-al") + .arg("/dev/null") + .succeeds() + .stdout_matches(&Regex::new("[^ ] 1, 3 [^ ]").unwrap()); + } + + // Regex tests alignment against a file (stdout is a link to a tty) + #[cfg(unix)] + { + let res = scene + .ucmd() + .arg("-alL") + .arg("/dev/null") + .arg("/dev/stdout") + .succeeds(); + + let null_len = String::from_utf8(res.stdout().to_owned()) + .ok() + .unwrap() + .lines() + .next() + .unwrap() + .strip_suffix("/dev/null") + .unwrap() + .len(); + + let stdout_len = String::from_utf8(res.stdout().to_owned()) + .ok() + .unwrap() + .lines() + .nth(1) + .unwrap() + .strip_suffix("/dev/stdout") + .unwrap() + .len(); + + assert_eq!(stdout_len, null_len); + } +} + +#[cfg(all(feature = "chmod"))] +#[test] +fn test_ls_io_errors() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("some-dir1"); + at.mkdir("some-dir2"); + at.symlink_file("does_not_exist", "some-dir2/dangle"); + at.mkdir("some-dir3"); + at.mkdir("some-dir3/some-dir4"); + at.mkdir("some-dir3/some-dir5"); + at.mkdir("some-dir3/some-dir6"); + at.mkdir("some-dir3/some-dir7"); + at.mkdir("some-dir3/some-dir8"); + + scene.ccmd("chmod").arg("000").arg("some-dir1").succeeds(); + + scene + .ucmd() + .arg("-1") + .arg("some-dir1") + .fails() + .stderr_contains("cannot open directory") + .stderr_contains("Permission denied"); + + scene + .ucmd() + .arg("-Li") + .arg("some-dir2") + .fails() + .stderr_contains("cannot access") + .stderr_contains("No such file or directory") + .stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" }); + + scene + .ccmd("chmod") + .arg("000") + .arg("some-dir3/some-dir4") + .succeeds(); + + scene + .ucmd() + .arg("-laR") + .arg("some-dir3") + .fails() + .stderr_contains("some-dir4") + .stderr_contains("cannot open directory") + .stderr_contains("Permission denied") + .stdout_contains("some-dir4"); + + // test we don't double print on dangling link metadata errors + scene + .ucmd() + .arg("-iRL") + .arg("some-dir2") + .fails() + .stderr_does_not_contain( + "ls: cannot access 'some-dir2/dangle': No such file or directory\nls: cannot access 'some-dir2/dangle': No such file or directory" + ); +} + +#[test] +fn test_ls_only_dirs_formatting() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("some-dir1"); + at.mkdir("some-dir2"); + at.mkdir("some-dir3"); + + #[cfg(unix)] + { + scene.ucmd().arg("-1").arg("-R").succeeds().stdout_only( + ".:\nsome-dir1\nsome-dir2\nsome-dir3\n\n./some-dir1:\n\n./some-dir2:\n\n./some-dir3:\n", + ); + } + #[cfg(windows)] + { + scene.ucmd().arg("-1").arg("-R").succeeds().stdout_only( + ".:\nsome-dir1\nsome-dir2\nsome-dir3\n\n.\\some-dir1:\n\n.\\some-dir2:\n\n.\\some-dir3:\n", + ); + } +} + +#[test] +fn test_ls_walk_glob() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch(".test-1"); + at.mkdir("some-dir"); + at.touch( + Path::new("some-dir") + .join("test-2~") + .as_os_str() + .to_str() + .unwrap(), + ); + + #[allow(clippy::trivial_regex)] + let re_pwd = Regex::new(r"^\.\n").unwrap(); + + scene + .ucmd() + .arg("-1") + .arg("--ignore-backups") + .arg("some-dir") + .succeeds() + .stdout_does_not_contain("test-2~") + .stdout_does_not_contain("..") + .stdout_does_not_match(&re_pwd); +} + +#[test] +#[cfg(unix)] fn test_ls_a() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -1523,6 +1714,41 @@ fn test_ls_hidden_windows() { scene.ucmd().arg("-a").succeeds().stdout_contains(file); } +#[cfg(windows)] +#[test] +fn test_ls_hidden_link_windows() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file = "visibleWindowsFileNoDot"; + at.touch(file); + + let link = "hiddenWindowsLinkNoDot"; + at.symlink_dir(file, link); + // hide the link + scene.cmd("attrib").arg("/l").arg("+h").arg(link).succeeds(); + + scene + .ucmd() + .succeeds() + .stdout_contains(file) + .stdout_does_not_contain(link); + + scene + .ucmd() + .arg("-a") + .succeeds() + .stdout_contains(file) + .stdout_contains(link); +} + +#[cfg(windows)] +#[test] +fn test_ls_success_on_c_drv_root_windows() { + let scene = TestScenario::new(util_name!()); + scene.ucmd().arg("C:\\").succeeds(); +} + #[test] fn test_ls_version_sort() { let scene = TestScenario::new(util_name!()); @@ -1903,6 +2129,7 @@ fn test_ls_ignore_hide() { } #[test] +#[cfg(unix)] fn test_ls_ignore_backups() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -2269,12 +2496,59 @@ fn test_ls_dangling_symlinks() { .succeeds() .stdout_contains("dangle"); + #[cfg(not(windows))] scene .ucmd() .arg("-Li") .arg("temp_dir") - .succeeds() // this should fail, though at the moment, ls lacks a way to propagate errors encountered during display - .stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" }); + .fails() + .stderr_contains("cannot access") + .stdout_contains("? dangle"); + + #[cfg(windows)] + scene + .ucmd() + .arg("-Li") + .arg("temp_dir") + .succeeds() + .stdout_contains("dangle"); + + scene + .ucmd() + .arg("-Ll") + .arg("temp_dir") + .fails() + .stdout_contains("l?????????"); + + #[cfg(unix)] + { + // Check padding is the same for real files and dangling links, in non-long formats + at.touch("temp_dir/real_file"); + + let real_file_res = scene.ucmd().arg("-Li1").arg("temp_dir").fails(); + let real_file_stdout_len = String::from_utf8(real_file_res.stdout().to_owned()) + .ok() + .unwrap() + .lines() + .nth(1) + .unwrap() + .strip_suffix("real_file") + .unwrap() + .len(); + + let dangle_file_res = scene.ucmd().arg("-Li1").arg("temp_dir").fails(); + let dangle_stdout_len = String::from_utf8(dangle_file_res.stdout().to_owned()) + .ok() + .unwrap() + .lines() + .next() + .unwrap() + .strip_suffix("dangle") + .unwrap() + .len(); + + assert_eq!(real_file_stdout_len, dangle_stdout_len); + } } #[test] diff --git a/tests/by-util/test_more.rs b/tests/by-util/test_more.rs index 4b2719d8fce..5c2b3c0f6b3 100644 --- a/tests/by-util/test_more.rs +++ b/tests/by-util/test_more.rs @@ -15,15 +15,10 @@ fn test_more_dir_arg() { // Maybe we could capture the error, i.e. "Device not found" in that case // but I am leaving this for later if atty::is(atty::Stream::Stdout) { - let ts = TestScenario::new(util_name!()); - let result = ts.ucmd().arg(".").run(); - result.failure(); - let expected_error_message = &format!( - "{0}: '.' is a directory.\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - ); - assert_eq!(result.stderr_str().trim(), expected_error_message); + new_ucmd!() + .arg(".") + .fails() + .usage_error("'.' is a directory."); } else { } } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 8d9b00664d2..9fccc90a201 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -232,6 +232,40 @@ fn test_mv_force_replace_file() { assert!(at.file_exists(file_b)); } +#[test] +fn test_mv_same_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_same_file_a"; + + at.touch(file_a); + ucmd.arg(file_a).arg(file_a).fails().stderr_is(format!( + "mv: '{f}' and '{f}' are the same file\n", + f = file_a, + )); +} + +#[test] +fn test_mv_same_file_not_dot_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_mv_errors_dir"; + + at.mkdir(dir); + ucmd.arg(dir).arg(dir).fails().stderr_is(format!( + "mv: cannot move '{d}' to a subdirectory of itself, '{d}/{d}'", + d = dir, + )); +} + +#[test] +fn test_mv_same_file_dot_dir() { + let (_at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg(".") + .arg(".") + .fails() + .stderr_is("mv: '.' and '.' are the same file\n".to_string()); +} + #[test] fn test_mv_simple_backup() { let (at, mut ucmd) = at_and_ucmd!(); @@ -522,17 +556,13 @@ fn test_mv_backup_off() { #[test] fn test_mv_backup_no_clobber_conflicting_options() { - let ts = TestScenario::new(util_name!()); - - ts.ucmd().arg("--backup") + new_ucmd!() + .arg("--backup") .arg("--no-clobber") .arg("file1") .arg("file2") .fails() - .stderr_is(&format!("{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + .usage_error("options --backup and --no-clobber are mutually exclusive"); } #[test] diff --git a/tests/by-util/test_nice.rs b/tests/by-util/test_nice.rs index 7a99a333dcc..4a77ae24eaf 100644 --- a/tests/by-util/test_nice.rs +++ b/tests/by-util/test_nice.rs @@ -22,15 +22,10 @@ fn test_negative_adjustment() { #[test] fn test_adjustment_with_no_command_should_error() { - let ts = TestScenario::new(util_name!()); - - ts.ucmd() - .args(&["-n", "19"]) - .run() - .stderr_is(&format!("{0}: A command must be given with an adjustment.\nTry '{1} {0} --help' for more information.\n", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + new_ucmd!() + .args(&["-n", "19"]) + .fails() + .usage_error("A command must be given with an adjustment."); } #[test] diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index 336b0f7cd4a..596aab6ba6b 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -505,3 +505,66 @@ fn test_round() { .stdout_only(exp.join("\n") + "\n"); } } + +#[test] +fn test_suffix_is_added_if_not_supplied() { + new_ucmd!() + .args(&["--suffix=TEST"]) + .pipe_in("1000") + .succeeds() + .stdout_only("1000TEST\n"); +} + +#[test] +fn test_suffix_is_preserved() { + new_ucmd!() + .args(&["--suffix=TEST"]) + .pipe_in("1000TEST") + .succeeds() + .stdout_only("1000TEST\n"); +} + +#[test] +fn test_suffix_is_only_applied_to_selected_field() { + new_ucmd!() + .args(&["--suffix=TEST", "--field=2"]) + .pipe_in("1000 2000 3000") + .succeeds() + .stdout_only("1000 2000TEST 3000\n"); +} + +#[test] +fn test_transform_with_suffix_on_input() { + new_ucmd!() + .args(&["--suffix=b", "--to=si"]) + .pipe_in("2000b") + .succeeds() + .stdout_only("2.0Kb\n"); +} + +#[test] +fn test_transform_without_suffix_on_input() { + new_ucmd!() + .args(&["--suffix=b", "--to=si"]) + .pipe_in("2000") + .succeeds() + .stdout_only("2.0Kb\n"); +} + +#[test] +fn test_transform_with_suffix_and_delimiter() { + new_ucmd!() + .args(&["--suffix=b", "--to=si", "-d=|"]) + .pipe_in("1000b|2000|3000") + .succeeds() + .stdout_only("1.0Kb|2000|3000\n"); +} + +#[test] +fn test_suffix_with_padding() { + new_ucmd!() + .args(&["--suffix=pad", "--padding=12"]) + .pipe_in("1000 2000 3000") + .succeeds() + .stdout_only(" 1000pad 2000 3000\n"); +} diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index 5394dfde949..17cec1b4bad 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -24,14 +24,11 @@ fn test_capitalize() { fn test_long_format() { let login = "root"; let pw: Passwd = Passwd::locate(login).unwrap(); - let real_name = pw.user_info().replace("&", &pw.name().capitalize()); + let real_name = pw.user_info.replace("&", &pw.name.capitalize()); let ts = TestScenario::new(util_name!()); ts.ucmd().arg("-l").arg(login).succeeds().stdout_is(format!( "Login name: {:<28}In real life: {}\nDirectory: {:<29}Shell: {}\n\n", - login, - real_name, - pw.user_dir(), - pw.user_shell() + login, real_name, pw.user_dir, pw.user_shell )); ts.ucmd() diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 32bc438376f..f846e064b72 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -169,6 +169,29 @@ fn test_rm_recursive() { assert!(!at.file_exists(file_b)); } +#[test] +fn test_rm_recursive_multiple() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rm_recursive_directory"; + let file_a = "test_rm_recursive_directory/test_rm_recursive_file_a"; + let file_b = "test_rm_recursive_directory/test_rm_recursive_file_b"; + + at.mkdir(dir); + at.touch(file_a); + at.touch(file_b); + + ucmd.arg("-r") + .arg("-r") + .arg("-r") + .arg(dir) + .succeeds() + .no_stderr(); + + assert!(!at.dir_exists(dir)); + assert!(!at.file_exists(file_a)); + assert!(!at.file_exists(file_b)); +} + #[test] fn test_rm_directory_without_flag() { let (at, mut ucmd) = at_and_ucmd!(); @@ -257,7 +280,7 @@ fn test_rm_force_no_operand() { fn test_rm_no_operand() { let ts = TestScenario::new(util_name!()); ts.ucmd().fails().stderr_is(&format!( - "{0}: missing an argument\n{0}: for help, try '{1} {0} --help'\n", + "{0}: missing operand\nTry '{1} {0} --help' for more information.\n", ts.util_name, ts.bin_path.to_string_lossy() )); @@ -291,3 +314,39 @@ fn test_rm_verbose_slash() { assert!(!at.dir_exists(dir)); assert!(!at.file_exists(file_a)); } + +#[test] +fn test_rm_silently_accepts_presume_input_tty1() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_1 = "test_rm_silently_accepts_presume_input_tty1"; + + at.touch(file_1); + + ucmd.arg("--presume-input-tty").arg(file_1).succeeds(); + + assert!(!at.file_exists(file_1)); +} + +#[test] +fn test_rm_silently_accepts_presume_input_tty2() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_2 = "test_rm_silently_accepts_presume_input_tty2"; + + at.touch(file_2); + + ucmd.arg("---presume-input-tty").arg(file_2).succeeds(); + + assert!(!at.file_exists(file_2)); +} + +#[test] +fn test_rm_silently_accepts_presume_input_tty3() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_3 = "test_rm_silently_accepts_presume_input_tty3"; + + at.touch(file_3); + + ucmd.arg("----presume-input-tty").arg(file_3).succeeds(); + + assert!(!at.file_exists(file_3)); +} diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 6ed3cb67de3..e6f4bce0bbb 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -1,3 +1,4 @@ +// spell-checker:ignore lmnop xlmnop use crate::common::util::*; use std::io::Read; @@ -7,25 +8,25 @@ fn test_hex_rejects_sign_after_identifier() { .args(&["0x-123ABC"]) .fails() .no_stdout() - .stderr_contains("invalid hexadecimal argument: '0x-123ABC'") + .stderr_contains("invalid floating point argument: '0x-123ABC'") .stderr_contains("for more information."); new_ucmd!() .args(&["0x+123ABC"]) .fails() .no_stdout() - .stderr_contains("invalid hexadecimal argument: '0x+123ABC'") + .stderr_contains("invalid floating point argument: '0x+123ABC'") .stderr_contains("for more information."); new_ucmd!() .args(&["-0x-123ABC"]) .fails() .no_stdout() - .stderr_contains("invalid hexadecimal argument: '-0x-123ABC'") + .stderr_contains("invalid floating point argument: '-0x-123ABC'") .stderr_contains("for more information."); new_ucmd!() .args(&["-0x+123ABC"]) .fails() .no_stdout() - .stderr_contains("invalid hexadecimal argument: '-0x+123ABC'") + .stderr_contains("invalid floating point argument: '-0x+123ABC'") .stderr_contains("for more information."); } @@ -60,30 +61,24 @@ fn test_hex_identifier_in_wrong_place() { .args(&["1234ABCD0x"]) .fails() .no_stdout() - .stderr_contains("invalid hexadecimal argument: '1234ABCD0x'") + .stderr_contains("invalid floating point argument: '1234ABCD0x'") .stderr_contains("for more information."); } #[test] fn test_rejects_nan() { - let ts = TestScenario::new(util_name!()); - - ts.ucmd().args(&["NaN"]).fails().stderr_only(format!( - "{0}: invalid 'not-a-number' argument: 'NaN'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + new_ucmd!() + .arg("NaN") + .fails() + .usage_error("invalid 'not-a-number' argument: 'NaN'"); } #[test] fn test_rejects_non_floats() { - let ts = TestScenario::new(util_name!()); - - ts.ucmd().args(&["foo"]).fails().stderr_only(&format!( - "{0}: invalid floating point argument: 'foo'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + new_ucmd!() + .arg("foo") + .fails() + .usage_error("invalid floating point argument: 'foo'"); } #[test] @@ -479,6 +474,72 @@ fn test_width_decimal_scientific_notation_trailing_zeros_increment() { .no_stderr(); } +#[test] +fn test_width_negative_decimal_notation() { + new_ucmd!() + .args(&["-w", "-.1", ".1", ".11"]) + .succeeds() + .stdout_is("-0.1\n00.0\n00.1\n") + .no_stderr(); +} + +#[test] +fn test_width_negative_scientific_notation() { + new_ucmd!() + .args(&["-w", "-1e-3", "1"]) + .succeeds() + .stdout_is("-0.001\n00.999\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-1.e-3", "1"]) + .succeeds() + .stdout_is("-0.001\n00.999\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-1.0e-4", "1"]) + .succeeds() + .stdout_is("-0.00010\n00.99990\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-.1e2", "10", "100"]) + .succeeds() + .stdout_is( + "-010 +0000 +0010 +0020 +0030 +0040 +0050 +0060 +0070 +0080 +0090 +0100 +", + ) + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.1e2", "10", "100"]) + .succeeds() + .stdout_is( + "-010 +0000 +0010 +0020 +0030 +0040 +0050 +0060 +0070 +0080 +0090 +0100 +", + ) + .no_stderr(); +} + /// Test that trailing zeros in the end argument do not contribute to width. #[test] fn test_width_decimal_scientific_notation_trailing_zeros_end() { @@ -521,6 +582,22 @@ fn test_inf() { run(&["inf"], b"1\n2\n3\n"); } +#[test] +fn test_inf_width() { + run( + &["-w", "1.000", "inf", "inf"], + b"1.000\n inf\n inf\n inf\n", + ); +} + +#[test] +fn test_neg_inf_width() { + run( + &["-w", "1.000", "-inf", "-inf"], + b"1.000\n -inf\n -inf\n -inf\n", + ); +} + #[test] fn test_ignore_leading_whitespace() { new_ucmd!() @@ -538,9 +615,81 @@ fn test_trailing_whitespace_error() { new_ucmd!() .arg("1 ") .fails() - .no_stdout() - .stderr_contains("seq: invalid floating point argument: '1 '") - // FIXME The second line of the error message is "Try 'seq - // --help' for more information." - .stderr_contains("for more information."); + .usage_error("invalid floating point argument: '1 '"); +} + +#[test] +fn test_negative_zero_int_start_float_increment() { + new_ucmd!() + .args(&["-0", "0.1", "0.1"]) + .succeeds() + .stdout_is("-0.0\n0.1\n") + .no_stderr(); +} + +#[test] +fn test_float_precision_increment() { + new_ucmd!() + .args(&["999", "0.1", "1000.1"]) + .succeeds() + .stdout_is( + "999.0 +999.1 +999.2 +999.3 +999.4 +999.5 +999.6 +999.7 +999.8 +999.9 +1000.0 +1000.1 +", + ) + .no_stderr(); +} + +/// Test for floating point precision issues. +#[test] +fn test_negative_increment_decimal() { + new_ucmd!() + .args(&["0.1", "-0.1", "-0.2"]) + .succeeds() + .stdout_is("0.1\n0.0\n-0.1\n-0.2\n") + .no_stderr(); +} + +#[test] +fn test_zero_not_first() { + new_ucmd!() + .args(&["-w", "-0.1", "0.1", "0.1"]) + .succeeds() + .stdout_is("-0.1\n00.0\n00.1\n") + .no_stderr(); +} + +#[test] +fn test_rounding_end() { + new_ucmd!() + .args(&["1", "-1", "0.1"]) + .succeeds() + .stdout_is("1\n") + .no_stderr(); +} + +#[test] +fn test_parse_error_float() { + new_ucmd!() + .arg("lmnop") + .fails() + .usage_error("invalid floating point argument: 'lmnop'"); +} + +#[test] +fn test_parse_error_hex() { + new_ucmd!() + .arg("0xlmnop") + .fails() + .usage_error("invalid hexadecimal argument: '0xlmnop'"); } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 2aa26ad2440..76095ff76e8 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -340,10 +340,10 @@ fn test_dictionary_order() { fn test_dictionary_order2() { for non_dictionary_order2_param in &["-d"] { new_ucmd!() - .pipe_in("a👦🏻aa b\naaaa b") // spell-checker:disable-line + .pipe_in("a👦🏻aa\tb\naaaa\tb") // spell-checker:disable-line .arg(non_dictionary_order2_param) // spell-checker:disable-line .succeeds() - .stdout_only("a👦🏻aa b\naaaa b\n"); // spell-checker:disable-line + .stdout_only("a👦🏻aa\tb\naaaa\tb\n"); // spell-checker:disable-line } } @@ -918,6 +918,7 @@ fn test_compress_merge() { #[test] fn test_compress_fail() { + #[cfg(not(windows))] TestScenario::new(util_name!()) .ucmd_keepenv() .args(&[ @@ -930,6 +931,21 @@ fn test_compress_fail() { ]) .fails() .stderr_only("sort: couldn't execute compress program: errno 2"); + // With coverage, it fails with a different error: + // "thread 'main' panicked at 'called `Option::unwrap()` on ... + // So, don't check the output + #[cfg(windows)] + TestScenario::new(util_name!()) + .ucmd_keepenv() + .args(&[ + "ext_sort.txt", + "-n", + "--compress-program", + "nonexistent-program", + "-S", + "10", + ]) + .fails(); } #[test] diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 229925a1c81..ebcc0926dc5 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.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 xzaaa sixhundredfiftyonebytes ninetyonebytes asciilowercase extern crate rand; extern crate regex; @@ -16,7 +16,7 @@ use std::io::Write; use std::path::Path; use std::{ fs::{read_dir, File}, - io::BufWriter, + io::{BufWriter, Read}, }; fn random_chars(n: usize) -> String { @@ -340,3 +340,87 @@ fn test_split_invalid_bytes_size() { } } } + +fn file_read(at: &AtPath, filename: &str) -> String { + let mut s = String::new(); + at.open(filename).read_to_string(&mut s).unwrap(); + s +} + +// TODO Use char::from_digit() in Rust v1.51.0 or later. +fn char_from_digit(n: usize) -> char { + (b'a' + n as u8) as char +} + +/// Test for the default suffix length behavior: dynamically increasing size. +#[test] +fn test_alphabetic_dynamic_suffix_length() { + let (at, mut ucmd) = at_and_ucmd!(); + // Split into chunks of one byte each. + // + // The input file has (26^2) - 26 + 1 = 651 bytes. This is just + // enough to force `split` to dynamically increase the length of + // the filename for the very last chunk. + // + // We expect the output files to be named + // + // xaa, xab, xac, ..., xyx, xyy, xyz, xzaaa + // + ucmd.args(&["-b", "1", "sixhundredfiftyonebytes.txt"]) + .succeeds(); + for i in 0..25 { + for j in 0..26 { + let filename = format!("x{}{}", char_from_digit(i), char_from_digit(j),); + let contents = file_read(&at, &filename); + assert_eq!(contents, "a"); + } + } + assert_eq!(file_read(&at, "xzaaa"), "a"); +} + +/// Test for the default suffix length behavior: dynamically increasing size. +#[test] +fn test_numeric_dynamic_suffix_length() { + let (at, mut ucmd) = at_and_ucmd!(); + // Split into chunks of one byte each, use numbers instead of + // letters as file suffixes. + // + // The input file has (10^2) - 10 + 1 = 91 bytes. This is just + // enough to force `split` to dynamically increase the length of + // the filename for the very last chunk. + // + // x00, x01, x02, ..., x87, x88, x89, x9000 + // + ucmd.args(&["-d", "-b", "1", "ninetyonebytes.txt"]) + .succeeds(); + for i in 0..90 { + let filename = format!("x{:02}", i); + let contents = file_read(&at, &filename); + assert_eq!(contents, "a"); + } + assert_eq!(file_read(&at, "x9000"), "a"); +} + +#[test] +fn test_suffixes_exhausted() { + new_ucmd!() + .args(&["-b", "1", "-a", "1", "asciilowercase.txt"]) + .fails() + .stderr_only("split: output file suffixes exhausted"); +} + +#[test] +fn test_verbose() { + new_ucmd!() + .args(&["-b", "5", "--verbose", "asciilowercase.txt"]) + .succeeds() + .stdout_only( + "creating file 'xaa' +creating file 'xab' +creating file 'xac' +creating file 'xad' +creating file 'xae' +creating file 'xaf' +", + ); +} diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index c05b65d70e5..3b03a1d4c28 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -53,16 +53,10 @@ fn test_stdbuf_trailing_var_arg() { #[cfg(not(target_os = "windows"))] #[test] fn test_stdbuf_line_buffering_stdin_fails() { - let ts = TestScenario::new(util_name!()); - - ts.ucmd() + new_ucmd!() .args(&["-i", "L", "head"]) .fails() - .stderr_is(&format!( - "{0}: line buffering stdin is meaningless\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + .usage_error("line buffering stdin is meaningless"); } #[cfg(not(target_os = "windows"))] diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 26d8106f0e4..e863e34b747 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.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) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile +// spell-checker:ignore (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile bogusfile extern crate tail; @@ -358,6 +358,36 @@ fn test_positive_lines() { .stdout_is("c\nd\ne\n"); } +/// Test for reading all but the first NUM lines: `tail -3`. +#[test] +fn test_obsolete_syntax_positive_lines() { + new_ucmd!() + .args(&["-3"]) + .pipe_in("a\nb\nc\nd\ne\n") + .succeeds() + .stdout_is("c\nd\ne\n"); +} + +/// Test for reading all but the first NUM lines: `tail -n -10`. +#[test] +fn test_small_file() { + new_ucmd!() + .args(&["-n -10"]) + .pipe_in("a\nb\nc\nd\ne\n") + .succeeds() + .stdout_is("a\nb\nc\nd\ne\n"); +} + +/// Test for reading all but the first NUM lines: `tail -10`. +#[test] +fn test_obsolete_syntax_small_file() { + new_ucmd!() + .args(&["-10"]) + .pipe_in("a\nb\nc\nd\ne\n") + .succeeds() + .stdout_is("a\nb\nc\nd\ne\n"); +} + /// Test for reading all lines, specified by `tail -n +0`. #[test] fn test_positive_zero_lines() { @@ -445,3 +475,17 @@ fn test_tail_bytes_for_funny_files() { .code_is(exp_result.code()); } } + +#[test] +fn test_no_such_file() { + new_ucmd!() + .arg("bogusfile") + .fails() + .no_stdout() + .stderr_contains("cannot open 'bogusfile' for reading: No such file or directory"); +} + +#[test] +fn test_no_trailing_newline() { + new_ucmd!().pipe_in("x").succeeds().stdout_only("x"); +} diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index f2587a11fc9..0f41d7db700 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -63,9 +63,7 @@ fn test_tee_append() { fn test_tee_no_more_writeable_1() { // equals to 'tee /dev/full out2 (); + let content = (1..=10).map(|x| format!("{}\n", x)).collect::(); let file_out = "tee_file_out"; ucmd.arg("/dev/full") @@ -85,9 +83,7 @@ fn test_tee_no_more_writeable_2() { // but currently there is no way to redirect stdout to /dev/full // so this test is disabled let (_at, mut ucmd) = at_and_ucmd!(); - let _content = (1..=10) - .map(|x| format!("{}\n", x.to_string())) - .collect::(); + let _content = (1..=10).map(|x| format!("{}\n", x)).collect::(); let file_out_a = "tee_file_out_a"; let file_out_b = "tee_file_out_b"; diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index bd022b1c213..49dcb813c19 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -290,7 +290,7 @@ fn test_interpret_backslash_at_eol_literally() { #[cfg(not(target_os = "freebsd"))] fn test_more_than_2_sets() { new_ucmd!() - .args(&["'abcdefgh'", "'a", "'b'"]) + .args(&["'abcdef'", "'a'", "'b'"]) .pipe_in("hello world") .fails(); } diff --git a/tests/by-util/test_uniq.rs b/tests/by-util/test_uniq.rs index c191ffcafcd..cd013891b95 100644 --- a/tests/by-util/test_uniq.rs +++ b/tests/by-util/test_uniq.rs @@ -145,7 +145,9 @@ fn test_invalid_utf8() { .arg("not-utf8-sequence.txt") .run() .failure() - .stderr_only("uniq: invalid utf-8 sequence of 1 bytes from index 0"); + .stderr_only( + "uniq: failed to convert line to utf8: invalid utf-8 sequence of 1 bytes from index 0", + ); } #[test] diff --git a/tests/by-util/test_yes.rs b/tests/by-util/test_yes.rs index 7e950e1ea03..38165e92b9e 100644 --- a/tests/by-util/test_yes.rs +++ b/tests/by-util/test_yes.rs @@ -45,7 +45,7 @@ fn test_long_input() { // try something long. #[cfg(windows)] const TIMES: usize = 500; - let arg = "abcdefg".repeat(TIMES) + "\n"; + let arg = "abcdef".repeat(TIMES) + "\n"; let expected_out = arg.repeat(30); run(&[&arg[..arg.len() - 1]], expected_out.as_bytes()); } diff --git a/tests/common/util.rs b/tests/common/util.rs index c7ac816e102..5df0b753ff0 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -62,6 +62,10 @@ fn read_scenario_fixture>(tmpd: &Option>, file_rel_p /// within a struct which has convenience assertion functions about those outputs #[derive(Debug, Clone)] pub struct CmdResult { + /// bin_path provided by `TestScenario` or `UCommand` + bin_path: String, + /// util_name provided by `TestScenario` or `UCommand` + util_name: Option, //tmpd is used for convenience functions for asserts against fixtures tmpd: Option>, /// exit status for command (if there is one) @@ -77,6 +81,8 @@ pub struct CmdResult { impl CmdResult { pub fn new( + bin_path: String, + util_name: Option, tmpd: Option>, code: Option, success: bool, @@ -84,6 +90,8 @@ impl CmdResult { stderr: &[u8], ) -> CmdResult { CmdResult { + bin_path, + util_name, tmpd, code, success, @@ -357,6 +365,23 @@ impl CmdResult { self } + /// asserts that + /// 1. the command resulted in stderr stream output that equals the + /// the following format when both are trimmed of trailing whitespace + /// `"{util_name}: {msg}\nTry '{bin_path} {util_name} --help' for more information."` + /// This the expected format when a UUsageError is returned or when show_error! is called + /// `msg` should be the same as the one provided to UUsageError::new or show_error! + /// + /// 2. the command resulted in empty (zero-length) stdout stream output + pub fn usage_error>(&self, msg: T) -> &CmdResult { + self.stderr_only(format!( + "{0}: {2}\nTry '{1} {0} --help' for more information.", + self.util_name.as_ref().unwrap(), // This shouldn't be called using a normal command + self.bin_path, + msg.as_ref() + )) + } + pub fn stdout_contains>(&self, cmp: T) -> &CmdResult { assert!( self.stdout_str().contains(cmp.as_ref()), @@ -728,20 +753,12 @@ impl AtPath { // Source: // http://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-without-prepended let prefix = "\\\\?\\"; - // FixME: replace ... - #[allow(clippy::manual_strip)] - if s.starts_with(prefix) { - String::from(&s[prefix.len()..]) + + if let Some(stripped) = s.strip_prefix(prefix) { + String::from(stripped) } else { s } - // ... with ... - // if let Some(stripped) = s.strip_prefix(prefix) { - // String::from(stripped) - // } else { - // s - // } - // ... when using MSRV with stabilized `strip_prefix()` } } @@ -788,31 +805,36 @@ impl TestScenario { /// Returns builder for invoking the target uutils binary. Paths given are /// treated relative to the environment's unique temporary test directory. pub fn ucmd(&self) -> UCommand { - let mut cmd = self.cmd(&self.bin_path); - cmd.arg(&self.util_name); - cmd + self.composite_cmd(&self.bin_path, &self.util_name, true) + } + + /// Returns builder for invoking the target uutils binary. Paths given are + /// treated relative to the environment's unique temporary test directory. + pub fn composite_cmd, T: AsRef>( + &self, + bin: S, + util_name: T, + env_clear: bool, + ) -> UCommand { + UCommand::new_from_tmp(bin, Some(util_name), self.tmpd.clone(), env_clear) } /// Returns builder for invoking any system command. Paths given are treated /// relative to the environment's unique temporary test directory. pub fn cmd>(&self, bin: S) -> UCommand { - UCommand::new_from_tmp(bin, self.tmpd.clone(), true) + UCommand::new_from_tmp::(bin, None, self.tmpd.clone(), true) } /// Returns builder for invoking any uutils command. Paths given are treated /// relative to the environment's unique temporary test directory. pub fn ccmd>(&self, bin: S) -> UCommand { - let mut cmd = self.cmd(&self.bin_path); - cmd.arg(bin); - cmd + self.composite_cmd(&self.bin_path, bin, true) } // different names are used rather than an argument // because the need to keep the environment is exceedingly rare. pub fn ucmd_keepenv(&self) -> UCommand { - let mut cmd = self.cmd_keepenv(&self.bin_path); - cmd.arg(&self.util_name); - cmd + self.composite_cmd(&self.bin_path, &self.util_name, false) } /// Returns builder for invoking any system command. Paths given are treated @@ -820,7 +842,7 @@ impl TestScenario { /// Differs from the builder returned by `cmd` in that `cmd_keepenv` does not call /// `Command::env_clear` (Clears the entire environment map for the child process.) pub fn cmd_keepenv>(&self, bin: S) -> UCommand { - UCommand::new_from_tmp(bin, self.tmpd.clone(), false) + UCommand::new_from_tmp::(bin, None, self.tmpd.clone(), false) } } @@ -834,6 +856,8 @@ impl TestScenario { pub struct UCommand { pub raw: Command, comm_string: String, + bin_path: String, + util_name: Option, tmpd: Option>, has_run: bool, ignore_stdin_write_error: bool, @@ -846,12 +870,20 @@ pub struct UCommand { } impl UCommand { - pub fn new, U: AsRef>(arg: T, curdir: U, env_clear: bool) -> UCommand { - UCommand { + pub fn new, S: AsRef, U: AsRef>( + bin_path: T, + util_name: Option, + curdir: U, + env_clear: bool, + ) -> UCommand { + let bin_path = bin_path.as_ref(); + let util_name = util_name.as_ref().map(|un| un.as_ref()); + + let mut ucmd = UCommand { tmpd: None, has_run: false, raw: { - let mut cmd = Command::new(arg.as_ref()); + let mut cmd = Command::new(bin_path); cmd.current_dir(curdir.as_ref()); if env_clear { if cfg!(windows) { @@ -871,7 +903,9 @@ impl UCommand { } cmd }, - comm_string: String::from(arg.as_ref().to_str().unwrap()), + comm_string: String::from(bin_path.to_str().unwrap()), + bin_path: bin_path.to_str().unwrap().to_string(), + util_name: util_name.map(|un| un.to_str().unwrap().to_string()), ignore_stdin_write_error: false, bytes_into_stdin: None, stdin: None, @@ -879,12 +913,23 @@ impl UCommand { stderr: None, #[cfg(target_os = "linux")] limits: vec![], + }; + + if let Some(un) = util_name { + ucmd.arg(un); } + + ucmd } - pub fn new_from_tmp>(arg: T, tmpd: Rc, env_clear: bool) -> UCommand { + pub fn new_from_tmp, S: AsRef>( + bin_path: T, + util_name: Option, + tmpd: Rc, + env_clear: bool, + ) -> UCommand { let tmpd_path_buf = String::from(&(*tmpd.as_ref().path().to_str().unwrap())); - let mut ucmd: UCommand = UCommand::new(arg.as_ref(), tmpd_path_buf, env_clear); + let mut ucmd: UCommand = UCommand::new(bin_path, util_name, tmpd_path_buf, env_clear); ucmd.tmpd = Some(tmpd); ucmd } @@ -1029,6 +1074,8 @@ impl UCommand { let prog = self.run_no_wait().wait_with_output().unwrap(); CmdResult { + bin_path: self.bin_path.clone(), + util_name: self.util_name.clone(), tmpd: self.tmpd.clone(), code: prog.status.code(), success: prog.status.success(), @@ -1119,13 +1166,13 @@ pub fn host_name_for(util_name: &str) -> Cow { { // make call to `host_name_for` idempotent if util_name.starts_with('g') && util_name != "groups" { - return util_name.into(); + util_name.into() } else { - return format!("g{}", util_name).into(); + format!("g{}", util_name).into() } } #[cfg(target_os = "linux")] - return util_name.into(); + util_name.into() } // GNU coreutils version 8.32 is the reference version since it is the latest version and the @@ -1183,14 +1230,7 @@ pub fn check_coreutil_version( .output() { Ok(s) => s, - Err(e) => { - return Err(format!( - "{}: '{}' {}", - UUTILS_WARNING, - util_name, - e.to_string() - )) - } + Err(e) => return Err(format!("{}: '{}' {}", UUTILS_WARNING, util_name, e)), }; std::str::from_utf8(&version_check.stdout).unwrap() .split('\n') @@ -1200,7 +1240,7 @@ pub fn check_coreutil_version( || Err(format!("{}: unexpected output format for reference coreutil: '{} --version'", UUTILS_WARNING, util_name)), |s| { if s.contains(&format!("(GNU coreutils) {}", version_expected)) { - Ok(format!("{}: {}", UUTILS_INFO, s.to_string())) + Ok(format!("{}: {}", UUTILS_INFO, s)) } else if s.contains("(GNU coreutils)") { let version_found = parse_coreutil_version(s); let version_expected = version_expected.parse::().unwrap_or_default(); @@ -1276,6 +1316,8 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result< }; Ok(CmdResult::new( + ts.bin_path.as_os_str().to_str().unwrap().to_string(), + Some(ts.util_name.clone()), Some(result.tmpd()), Some(result.code()), result.succeeded(), @@ -1293,6 +1335,8 @@ mod tests { #[test] fn test_code_is() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: Some(32), success: false, @@ -1306,6 +1350,8 @@ mod tests { #[should_panic] fn test_code_is_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: Some(32), success: false, @@ -1318,6 +1364,8 @@ mod tests { #[test] fn test_failure() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: false, @@ -1331,6 +1379,8 @@ mod tests { #[should_panic] fn test_failure_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1343,6 +1393,8 @@ mod tests { #[test] fn test_success() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1356,6 +1408,8 @@ mod tests { #[should_panic] fn test_success_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: false, @@ -1368,6 +1422,8 @@ mod tests { #[test] fn test_no_stderr_output() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1382,6 +1438,8 @@ mod tests { #[should_panic] fn test_no_stderr_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1396,6 +1454,8 @@ mod tests { #[should_panic] fn test_no_stdout_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1409,6 +1469,8 @@ mod tests { #[test] fn test_std_does_not_contain() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1423,6 +1485,8 @@ mod tests { #[should_panic] fn test_stdout_does_not_contain_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1437,6 +1501,8 @@ mod tests { #[should_panic] fn test_stderr_does_not_contain_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1450,6 +1516,8 @@ mod tests { #[test] fn test_stdout_matches() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1466,6 +1534,8 @@ mod tests { #[should_panic] fn test_stdout_matches_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1481,6 +1551,8 @@ mod tests { #[should_panic] fn test_stdout_not_matches_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1495,6 +1567,8 @@ mod tests { #[test] fn test_normalized_newlines_stdout_is() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1511,6 +1585,8 @@ mod tests { #[should_panic] fn test_normalized_newlines_stdout_is_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, diff --git a/tests/fixtures/join/non-line_feeds.expected b/tests/fixtures/join/non-line_feeds.expected new file mode 100644 index 00000000000..c4cb5b448f5 --- /dev/null +++ b/tests/fixtures/join/non-line_feeds.expected @@ -0,0 +1,2 @@ + b d +a c f diff --git a/tests/fixtures/join/non-line_feeds_1.txt b/tests/fixtures/join/non-line_feeds_1.txt new file mode 100644 index 00000000000..f3e0c0732f1 --- /dev/null +++ b/tests/fixtures/join/non-line_feeds_1.txt @@ -0,0 +1,2 @@ + b +a c diff --git a/tests/fixtures/join/non-line_feeds_2.txt b/tests/fixtures/join/non-line_feeds_2.txt new file mode 100644 index 00000000000..0a5301e1350 --- /dev/null +++ b/tests/fixtures/join/non-line_feeds_2.txt @@ -0,0 +1,2 @@ + d +a f diff --git a/tests/fixtures/join/non-unicode.expected b/tests/fixtures/join/non-unicode.expected new file mode 100644 index 00000000000..7c2e0d9aff2 Binary files /dev/null and b/tests/fixtures/join/non-unicode.expected differ diff --git a/tests/fixtures/join/non-unicode_1.bin b/tests/fixtures/join/non-unicode_1.bin new file mode 100644 index 00000000000..e264bd505fb Binary files /dev/null and b/tests/fixtures/join/non-unicode_1.bin differ diff --git a/tests/fixtures/join/non-unicode_2.bin b/tests/fixtures/join/non-unicode_2.bin new file mode 100644 index 00000000000..6b336c66950 Binary files /dev/null and b/tests/fixtures/join/non-unicode_2.bin differ diff --git a/tests/fixtures/join/z.expected b/tests/fixtures/join/z.expected new file mode 100644 index 00000000000..ed85bb05393 Binary files /dev/null and b/tests/fixtures/join/z.expected differ diff --git a/tests/fixtures/split/asciilowercase.txt b/tests/fixtures/split/asciilowercase.txt new file mode 100644 index 00000000000..b0883f382e1 --- /dev/null +++ b/tests/fixtures/split/asciilowercase.txt @@ -0,0 +1 @@ +abcdefghijklmnopqrstuvwxyz diff --git a/tests/fixtures/split/ninetyonebytes.txt b/tests/fixtures/split/ninetyonebytes.txt new file mode 100644 index 00000000000..62049a6b3e7 --- /dev/null +++ b/tests/fixtures/split/ninetyonebytes.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/tests/fixtures/split/sixhundredfiftyonebytes.txt b/tests/fixtures/split/sixhundredfiftyonebytes.txt new file mode 100644 index 00000000000..f16b32e853d --- /dev/null +++ b/tests/fixtures/split/sixhundredfiftyonebytes.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index b0a78a2e85c..76b6f47284e 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -1,3 +1,4 @@ +#![allow(unused_imports)] mod common; use common::util::TestScenario; @@ -25,6 +26,7 @@ fn execution_phrase_double() { #[test] #[cfg(feature = "ls")] +#[cfg(any(unix, windows))] fn execution_phrase_single() { use std::process::Command; @@ -63,6 +65,7 @@ fn util_name_double() { #[test] #[cfg(feature = "sort")] +#[cfg(any(unix, windows))] fn util_name_single() { use std::{ io::Write, diff --git a/util/build-gnu.sh b/util/build-gnu.sh index eb8293fbe60..aab09f4b44e 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -5,7 +5,7 @@ set -e if test ! -d ../gnu; then echo "Could not find ../gnu" - echo "git clone https://github.com:coreutils/coreutils.git gnu" + echo "git clone https://github.com/coreutils/coreutils.git gnu" exit 1 fi if test ! -d ../gnulib; then @@ -40,7 +40,7 @@ done ./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR" ./configure --quiet --disable-gcc-warnings #Add timeout to to protect against hangs -sed -i 's|"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver +sed -i 's|^"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh @@ -95,11 +95,11 @@ sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh # Add specific timeout to tests that currently hang to limit time spent waiting -sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh +sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh # Remove dup of /usr/bin/ when executed several times -grep -rl '/usr/bin//usr/bin/' tests/* | xargs --no-run-if-empty sed -i 's|/usr/bin//usr/bin/|/usr/bin/|g' +grep -rlE '/usr/bin/\s?/usr/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/bin/\s?/usr/bin/|/usr/bin/|g' #### Adjust tests to make them work with Rust/coreutils diff --git a/util/test-repo-whitespace.BAT b/util/test-repo-whitespace.BAT index c63415295bf..6e2fce5574e 100644 --- a/util/test-repo-whitespace.BAT +++ b/util/test-repo-whitespace.BAT @@ -1,19 +1,21 @@ @setLocal @echo off -:: `test-repo-whitespace [DIR]...` +:: `test-repo-whitespace [DIR]...` v2022.01.01 :: style inspector ~ whitespace: find nonconforming files in repository -:: Copyright (C) 2016-2020 ~ Roy Ivy III +:: Copyright (C) 2016-2022 ~ Roy Ivy III :: License: MIT/Apache-2.0 (see https://opensource.org/licenses/Apache-2.0 , https://opensource.org/licenses/MIT) :: * this software is provided for free, WITHOUT ANY EXPRESS OR IMPLIED WARRANTY (see the license for details) -:: spell-checker:ignore (ignore) CTYPE POSIX RETval RETvar akefile makefile makefiles multiline :: spell-checker:ignore (shell/cmd) COMSPEC ERRORLEVEL +:: spell-checker:ignore () CTYPE POSIX Tval Tvar akefile makefile makefiles multiline testdata :config -set "_exclude_dir=(?i)[_.#]build|[.]git|[.]gpg|[.]vs|fixtures|vendor" -set "_exclude=(?i)[.](cache|dll|exe|gif|gz|zip)$" +set "_exclude_dirs_rx=(?i)[_.#]build|[.]git|[.]gpg|[.]obj|[.]vs|fixtures|node_modules|target|testdata|test-resources|vendor" +set "_exclude_files_rx=(?i)[.](cache|dll|exe|gif|gz|ico|png|zip)$" +set "_crlf_files_rx=(?i)[.](bat|cmd|csv)$" +set "_tabbed_files_rx=(?i)^^(.*[.]go|go[.](mod|sum)|(GNU)?[Mm]akefile([.].*)?)$" :config_done set _dp0=%~dp0. @@ -34,36 +36,36 @@ if /i "%LC_CTYPE%"=="posix" (set "LC_CTYPE=C") &:: `pcregrep` doesn't understand set "ERRORLEVEL=" set "ERROR=" :: 1. Test for TABs within leading whitespace (except go files makefiles) -"%PCREGREP%" -I --exclude-dir "%_exclude_dir%" --exclude "%_exclude%" --exclude "(?i)[.]go$" --exclude "go[.](mod|sum)" --exclude "(GNU)?[Mm]akefile([.].*)?" --count --files-with-matches --recursive "^\s*\t" %dirs% +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --exclude "%_tabbed_files_rx%" --count --files-with-matches --recursive "^\s*\t" %dirs% if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with TABs within leading whitespace [file:#lines_matched]) :: 2. Test for lines with internal TABs -"%PCREGREP%" -I --exclude-dir "%_exclude_dir%" --exclude "%_exclude%" --count --files-with-matches --recursive "^.*[^\t]\t" %dirs% +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --count --files-with-matches --recursive "^.*[^\t]\t" %dirs% if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with lines containing internal TABs [file:#lines_matched]) :: 3. Test that makefiles have ONLY initial-TAB leading whitespace -"%PCREGREP%" -I --exclude-dir "%_exclude_dir%" --include "(GNU)?[Mm]akefile([.].*)?" --exclude "[.](to|y)ml$" --recursive --line-number --invert-match "^([\t]\s*\S|\S|$)" %dirs% +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --include "(GNU)?[Mm]akefile([.].*)?" --exclude "[.](to|y)ml$" --recursive --line-number --invert-match "^([\t]\s*\S|\S|$)" %dirs% if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: Makefiles found with lines having non-TAB leading whitespace [file:line_number]) :: 4. Test for non-LF line endings set "HAVE_NonLF_ERROR=" -"%PCREGREP%" --buffer-size=1M -I --exclude-dir "%_exclude_dir%" --exclude "%_exclude%" -NLF --files-with-matches --multiline --recursive "\r[^\n]" %dirs% +"%PCREGREP%" --buffer-size=1M -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" -NLF --files-with-matches --multiline --recursive "\r[^\n]" %dirs% if NOT "%ERRORLEVEL%" == "1" ( set HAVE_NonLF_ERROR=1 & echo ## files found with CR line endings) -"%PCREGREP%" --buffer-size=1M -I --exclude-dir "%_exclude_dir%" --exclude "%_exclude%" --exclude "(?i)\.bat$|\.cmd$" -NLF --files-with-matches --multiline --recursive "\r\n" %dirs% +"%PCREGREP%" --buffer-size=1M -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --exclude "%_crlf_files_rx%" -NLF --files-with-matches --multiline --recursive "\r\n" %dirs% if NOT "%ERRORLEVEL%" == "1" ( set HAVE_NonLF_ERROR=1 & echo ## files found with CRLF line endings) if DEFINED HAVE_NonLF_ERROR ( set ERROR=1 & echo ERROR: files found with non-LF line endings) :: 5. Test for files without trailing newline -:: "%PCREGREP%" -I --exclude-dir "%_exclude_dir%" --exclude "%_exclude%" --files-without-match --multiline --recursive "\r?[\r\n]\z" %dirs% -"%PCREGREP%" -I --exclude-dir "%_exclude_dir%" --exclude "%_exclude%" --files-with-matches --multiline --recursive "\z" %dirs% +:: "%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --files-without-match --multiline --recursive "\r?[\r\n]\z" %dirs% +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --files-with-matches --multiline --recursive "\z" %dirs% if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found without trailing newline) :: 6. Test for files with lines having trailing whitespace -"%PCREGREP%" -I --exclude-dir "%_exclude_dir%" --exclude "%_exclude%" --recursive --line-number "\s$" %dirs% +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --recursive --line-number "\s$" %dirs% if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with lines having trailing whitespace [file:line_number]) :: 7. Test for files with BOM -"%PCREGREP%" -I --exclude-dir "%_exclude_dir%" --exclude "%_exclude%" --files-with-matches --multiline --recursive "\A[\xEF][\xBB][\xBF]" %dirs% +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --files-with-matches --multiline --recursive "\A[\xEF][\xBB][\xBF]" %dirs% if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with leading BOM) :script_done diff --git a/util/update-version.sh b/util/update-version.sh index cbb811300cc..503f65e5231 100755 --- a/util/update-version.sh +++ b/util/update-version.sh @@ -2,15 +2,23 @@ # This is a stupid helper. I will mass replace all versions (including other crates) # So, it should be triple-checked - -FROM="0.0.7" -TO="0.0.8" - -UUCORE_PROCS_FROM="0.0.6" -UUCORE_PROCS_TO="0.0.7" - -UUCORE_FROM="0.0.9" -UUCORE_TO="0.0.10" +# How to ship a new release: +# 1) update this script +# 2) run it: bash 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 + +FROM="0.0.8" +TO="0.0.9" + +UUCORE_PROCS_FROM="0.0.7" +UUCORE_PROCS_TO="0.0.8" + +UUCORE_FROM="0.0.10" +UUCORE_TO="0.0.11" PROGS=$(ls -1d src/uu/*/Cargo.toml src/uu/stdbuf/src/libstdbuf/Cargo.toml Cargo.toml src/uu/base64/Cargo.toml)