diff --git a/.clippy.toml b/.clippy.toml index bee70857b..814e40b69 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,2 +1,2 @@ -msrv = "1.64.0" +msrv = "1.70.0" cognitive-complexity-threshold = 10 diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 589126b6f..6583a0094 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -11,7 +11,7 @@ env: PROJECT_NAME: coreutils PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_AUTH: "uutils" - RUST_MIN_SRV: "1.64.0" + RUST_MIN_SRV: "1.70.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 @@ -30,7 +30,7 @@ jobs: name: Style/cargo-deny runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 style_deps: @@ -47,7 +47,7 @@ jobs: - { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" } - { os: windows-latest , features: feat_os_windows } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly ## note: requires 'nightly' toolchain b/c `cargo-udeps` uses the `rustc` '-Z save-analysis' option ## * ... ref: @@ -82,192 +82,6 @@ jobs: 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 - style_format: - name: Style/format - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - - { os: ubuntu-latest , features: feat_os_unix } - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - components: rustfmt - - uses: Swatinem/rust-cache@v2 - - name: Initialize workflow variables - id: vars - shell: bash - run: | - ## VARs setup - outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } - # 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 - - name: "`cargo fmt` testing" - shell: bash - run: | - ## `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 - - fuzz: - name: Run the fuzzers - runs-on: ubuntu-latest - env: - RUN_FOR: 60 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - - name: Install `cargo-fuzz` - run: cargo install cargo-fuzz - - uses: Swatinem/rust-cache@v2 - - name: Run fuzz_date for XX seconds - shell: bash - run: | - ## Run it - cd fuzz - cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_glob for XX seconds - shell: bash - run: | - ## Run it - cd fuzz - cargo +nightly fuzz run fuzz_parse_glob -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_size for XX seconds - shell: bash - run: | - ## Run it - cd fuzz - cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_time for XX seconds - shell: bash - run: | - ## Run it - cd fuzz - cargo +nightly fuzz run fuzz_parse_time -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - style_lint: - name: Style/lint - runs-on: ${{ matrix.job.os }} - env: - SCCACHE_GHA_ENABLED: "true" - RUSTC_WRAPPER: "sccache" - 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@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - components: clippy - - uses: Swatinem/rust-cache@v2 - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 - - name: Initialize workflow variables - id: vars - shell: bash - run: | - ## VARs setup - outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } - # 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 - 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 -n "-puu_${u} "; done;)" - outputs CARGO_UTILITY_LIST_OPTIONS - - name: Install/setup prerequisites - shell: bash - run: | - ## Install/setup prerequisites - case '${{ matrix.job.os }}' in - macos-latest) brew install coreutils ;; # needed for show-utils.sh - esac - - name: "`cargo clippy` lint testing" - shell: bash - run: | - ## `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 clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- -W clippy::default_trait_access -W clippy::manual_string_new -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } - if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi - - style_spellcheck: - name: Style/spelling - runs-on: ${{ matrix.job.os }} - strategy: - matrix: - job: - - { os: ubuntu-latest , features: feat_os_unix } - steps: - - uses: actions/checkout@v3 - - name: Initialize workflow variables - id: vars - shell: bash - run: | - ## VARs setup - outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } - # 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 - # * 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` - 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 - doc_warnings: name: Documentation/warnings runs-on: ${{ matrix.job.os }} @@ -285,7 +99,7 @@ jobs: # - { os: macos-latest , features: feat_os_macos } # - { os: windows-latest , features: feat_os_windows } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -319,9 +133,9 @@ jobs: shell: bash run: | RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items - - uses: DavidAnson/markdownlint-cli2-action@v11 + - uses: DavidAnson/markdownlint-cli2-action@v13 with: - command: fix + fix: "true" globs: | *.md docs/src/*.md @@ -338,7 +152,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_MIN_SRV }} @@ -406,7 +220,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: "`cargo update` testing" @@ -429,7 +243,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -483,7 +297,7 @@ jobs: - { os: macos-latest , features: feat_os_macos } - { os: windows-latest , features: feat_os_windows } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -510,7 +324,7 @@ jobs: - { os: macos-latest , features: feat_os_macos } - { os: windows-latest , features: feat_os_windows } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -534,7 +348,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache @@ -596,6 +410,12 @@ jobs: check() { # Warn if the size increases by more than 5% threshold='1.05' + + if [[ "$2" -eq 0 || "$3" -eq 0 ]]; then + echo "::warning file=$4::Invalid size for $1. Sizes cannot be 0." + return + fi + ratio=$(jq -n "$2 / $3") echo "$1: size=$2, previous_size=$3, ratio=$ratio, threshold=$threshold" if [[ "$(jq -n "$ratio > $threshold")" == 'true' ]]; then @@ -654,7 +474,7 @@ jobs: - { 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@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_MIN_SRV }} @@ -913,7 +733,7 @@ jobs: run: | ## VARs setup echo "TEST_SUMMARY_FILE=busybox-result.json" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 @@ -993,7 +813,7 @@ jobs: outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } TEST_SUMMARY_FILE="toybox-result.json" outputs TEST_SUMMARY_FILE - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_MIN_SRV }} @@ -1060,15 +880,6 @@ jobs: name: toybox-result.json path: ${{ steps.vars.outputs.TEST_SUMMARY_FILE }} - toml_format: - runs-on: ubuntu-latest - steps: - - name: Clone repository - uses: actions/checkout@v3 - - - name: Check - run: npx --yes @taplo/cli fmt --check - coverage: name: Code Coverage runs-on: ${{ matrix.job.os }} @@ -1084,7 +895,7 @@ jobs: - { os: macos-latest , features: macos, toolchain: nightly } - { os: windows-latest , features: windows, toolchain: nightly-x86_64-pc-windows-gnu } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.job.toolchain }} @@ -1201,4 +1012,3 @@ jobs: flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} name: codecov-umbrella fail_ci_if_error: false - diff --git a/.github/workflows/CheckScripts.yml b/.github/workflows/CheckScripts.yml index e1b3b24d2..c18c4733c 100644 --- a/.github/workflows/CheckScripts.yml +++ b/.github/workflows/CheckScripts.yml @@ -29,7 +29,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run ShellCheck uses: ludeeus/action-shellcheck@master env: @@ -41,34 +41,16 @@ jobs: shell_fmt: name: ShellScript/Format - # no need to run in pr events - # shfmt will be performed on main branch when the PR is merged - if: github.event_name != 'pull_request' runs-on: ubuntu-latest - needs: [ shell_check ] permissions: - contents: write - pull-requests: write + contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup shfmt - uses: mfinelli/setup-shfmt@v2 + uses: mfinelli/setup-shfmt@v3 - name: Run shfmt shell: bash run: | - # show differs first for every files that need to be formatted # fmt options: bash syntax, 4 spaces indent, indent for switch-case echo "## show the differences between formatted and original scripts..." find ${{ env.SCRIPT_DIR }} -name "*.sh" -print0 | xargs -0 shfmt -ln=bash -i 4 -ci -d || true - # perform a shell format - echo "## perform a shell format..." - # ignore the error code because `-d` will always return false when the file has difference - find ${{ env.SCRIPT_DIR }} -name "*.sh" -print0 | xargs -0 shfmt -ln=bash -i 4 -ci -w - - name: Commit any changes - uses: EndBug/add-and-commit@v9 - with: - default_author: github_actions - message: "style: auto format by CI (shfmt)" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml index 97b0be34a..7f5e5234d 100644 --- a/.github/workflows/FixPR.yml +++ b/.github/workflows/FixPR.yml @@ -26,7 +26,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Initialize job variables id: vars shell: bash @@ -85,7 +85,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Initialize job variables id: vars shell: bash diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index eb3e22a80..61f30eba4 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -42,7 +42,7 @@ jobs: outputs path_GNU path_GNU_tests path_reference path_UUTILS # repo_default_branch="${{ github.event.repository.default_branch }}" - repo_GNU_ref="v9.3" + repo_GNU_ref="v9.4" repo_reference_branch="${{ github.event.repository.default_branch }}" outputs repo_default_branch repo_GNU_ref repo_reference_branch # @@ -55,7 +55,7 @@ jobs: TEST_FULL_SUMMARY_FILE='gnu-full-result.json' outputs SUITE_LOG_FILE ROOT_SUITE_LOG_FILE TEST_FILESET_PREFIX TEST_FILESET_SUFFIX TEST_LOGS_GLOB TEST_SUMMARY_FILE TEST_FULL_SUMMARY_FILE - name: Checkout code (uutil) - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: '${{ steps.vars.outputs.path_UUTILS }}' - uses: dtolnay/rust-toolchain@master @@ -66,7 +66,7 @@ jobs: with: workspaces: "./${{ steps.vars.outputs.path_UUTILS }} -> target" - name: Checkout code (GNU coreutils) - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'coreutils/coreutils' path: '${{ steps.vars.outputs.path_GNU }}' @@ -307,15 +307,15 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout code uutil - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: 'uutils' - name: Checkout GNU coreutils - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'coreutils/coreutils' path: 'gnu' - ref: 'v9.3' + ref: 'v9.4' submodules: recursive - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index a6929b171..5834aceff 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -26,7 +26,7 @@ jobs: env: TERMUX: v0.118.0 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Restore AVD cache uses: actions/cache/restore@v3 id: avd-cache diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 000000000..0a619e097 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,180 @@ +name: Code Quality + +# spell-checker:ignore TERMUX reactivecircus Swatinem noaudio pkill swiftshader dtolnay juliangruber + +on: [push, pull_request] + +env: + # * 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 + +permissions: + contents: read # to fetch code (actions/checkout) + +# End the current execution if there is a new changeset in the PR. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + + style_format: + name: Style/format + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } + # 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 + - name: "`cargo fmt` testing" + shell: bash + run: | + ## `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 + + style_lint: + name: Style/lint + runs-on: ${{ matrix.job.os }} + env: + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + 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@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } + # 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 + 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 -n "-puu_${u} "; done;)" + outputs CARGO_UTILITY_LIST_OPTIONS + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for show-utils.sh + esac + - name: "`cargo clippy` lint testing" + shell: bash + run: | + ## `cargo clippy` lint testing + unset fault + CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity" + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # * convert any warnings to GHA UI annotations; ref: + S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi + + style_spellcheck: + name: Style/spelling + runs-on: ${{ matrix.job.os }} + strategy: + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v4 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } + # 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 + # * 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` + 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 + + toml_format: + name: Style/toml + runs-on: ubuntu-latest + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Check + run: npx --yes @taplo/cli fmt --check diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 095ec3230..5af3da320 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -30,7 +30,7 @@ jobs: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 @@ -120,7 +120,7 @@ jobs: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml new file mode 100644 index 000000000..311d6a0d7 --- /dev/null +++ b/.github/workflows/fuzzing.yml @@ -0,0 +1,64 @@ +name: Fuzzing + +# spell-checker:ignore fuzzer + +on: [push, pull_request] + +permissions: + contents: read # to fetch code (actions/checkout) + +# End the current execution if there is a new changeset in the PR. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + fuzz: + name: Run the fuzzers + runs-on: ubuntu-latest + env: + RUN_FOR: 60 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - name: Install `cargo-fuzz` + run: cargo install cargo-fuzz + - uses: Swatinem/rust-cache@v2 + - name: Restore Cached Corpus + uses: actions/cache/restore@v3 + with: + key: corpus-cache + path: | + fuzz/corpus + - name: Run fuzz_date for XX seconds + continue-on-error: true + shell: bash + run: | + cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_test for XX seconds + shell: bash + run: | + cargo +nightly fuzz run fuzz_test -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_expr for XX seconds + continue-on-error: true + shell: bash + run: | + cargo +nightly fuzz run fuzz_expr -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_parse_glob for XX seconds + shell: bash + run: | + cargo +nightly fuzz run fuzz_parse_glob -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_parse_size for XX seconds + shell: bash + run: | + cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_parse_time for XX seconds + shell: bash + run: | + cargo +nightly fuzz run fuzz_parse_time -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Save Corpus Cache + uses: actions/cache/save@v3 + with: + key: corpus-cache + path: | + fuzz/corpus diff --git a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt index 8711913d9..c004ea2f8 100644 --- a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt +++ b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt @@ -58,6 +58,7 @@ MinGW Minix NetBSD Novell +Nushell OpenBSD POSIX PowerPC diff --git a/.vscode/cspell.dictionaries/shell.wordlist.txt b/.vscode/cspell.dictionaries/shell.wordlist.txt index 16d7b25e9..95dea94a7 100644 --- a/.vscode/cspell.dictionaries/shell.wordlist.txt +++ b/.vscode/cspell.dictionaries/shell.wordlist.txt @@ -83,6 +83,8 @@ codespell commitlint dprint dtrace +flamegraph +flamegraphs gcov gmake grcov @@ -90,6 +92,7 @@ grep markdownlint rerast rollup +samply sed selinuxenabled sestatus diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd87e2d05..695e5ad18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,199 +38,15 @@ CI. However, you can use `#[cfg(...)]` attributes to create platform dependent f VirtualBox and Parallels) for development: -## Tools +## Setting up your development environment -We have an extensive CI that will check your code before it can be merged. This -section explains how to run those checks locally to avoid waiting for the CI. +To setup your local development environment for this project please follow [DEVELOPMENT.md guide](DEVELOPMENT.md) -### pre-commit hooks +It covers [installation of necessary tools and prerequisites](DEVELOPMENT.md#tools) as well as using those tools to [test your code changes locally](DEVELOPMENT.md#testing) -A configuration for `pre-commit` is provided in the repository. It allows -automatically checking every git commit you make to ensure it compiles, and -passes `clippy` and `rustfmt` without warnings. +## Improving the GNU compatibility -To use the provided hook: - -1. [Install `pre-commit`](https://pre-commit.com/#install) -1. Run `pre-commit install` while in the repository directory - -Your git commits will then automatically be checked. If a check fails, an error -message will explain why, and your commit will be canceled. You can then make -the suggested changes, and run `git commit ...` again. - -### clippy - -```shell -cargo clippy --all-targets --all-features -``` - -The `msrv` key in the clippy configuration file `clippy.toml` is used to disable -lints pertaining to newer features by specifying the minimum supported Rust -version (MSRV). - -### rustfmt - -```shell -cargo fmt --all -``` - -### cargo-deny - -This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to -detect duplicate dependencies, checks licenses, etc. To run it locally, first -install it and then run with: - -``` -cargo deny --all-features check all -``` - -### Markdown linter - -We use [markdownlint](https://github.com/DavidAnson/markdownlint) to lint the -Markdown files in the repository. - -### Spell checker - -We use `cspell` as spell checker for all files in the project. If you are using -VS Code, you can install the -[code spell checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) -extension to enable spell checking within your editor. Otherwise, you can -install [cspell](https://cspell.org/) separately. - -If you want to make the spell checker ignore a word, you can add - -```rust -// spell-checker:ignore word_to_ignore -``` - -at the top of the file. - -## Testing - -Testing can be done using either Cargo or `make`. - -### Testing with Cargo - -Just like with building, we follow the standard procedure for testing using -Cargo: - -```shell -cargo test -``` - -By default, `cargo test` only runs the common programs. To run also platform -specific tests, run: - -```shell -cargo test --features unix -``` - -If you would prefer to test a select few utilities: - -```shell -cargo test --features "chmod mv tail" --no-default-features -``` - -If you also want to test the core utilities: - -```shell -cargo test -p uucore -p coreutils -``` - -Running the complete test suite might take a while. We use [nextest](https://nexte.st/index.html) in -the CI and you might want to try it out locally. It can speed up the execution time of the whole -test run significantly if the cpu has multiple cores. - -```shell -cargo nextest run --features unix --no-fail-fast -``` - -To debug: - -```shell -gdb --args target/debug/coreutils ls -(gdb) b ls.rs:79 -(gdb) run -``` - -### Testing with GNU Make - -To simply test all available utilities: - -```shell -make test -``` - -To test all but a few of the available utilities: - -```shell -make SKIP_UTILS='UTILITY_1 UTILITY_2' test -``` - -To test only a few of the available utilities: - -```shell -make UTILS='UTILITY_1 UTILITY_2' test -``` - -To include tests for unimplemented behavior: - -```shell -make UTILS='UTILITY_1 UTILITY_2' SPEC=y test -``` - -To run tests with `nextest` just use the nextest target. Note you'll need to -[install](https://nexte.st/book/installation.html) `nextest` first. The `nextest` target accepts the -same arguments like the default `test` target, so it's possible to pass arguments to `nextest run` -via `CARGOFLAGS`: - -```shell -make CARGOFLAGS='--no-fail-fast' UTILS='UTILITY_1 UTILITY_2' nextest -``` - -### Run Busybox Tests - -This testing functionality is only available on *nix operating systems and -requires `make`. - -To run busybox tests for all utilities for which busybox has tests - -```shell -make busytest -``` - -To run busybox tests for a few of the available utilities - -```shell -make UTILS='UTILITY_1 UTILITY_2' busytest -``` - -To pass an argument like "-v" to the busybox test runtime - -```shell -make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest -``` - -### Comparing with GNU - -To run uutils against the GNU test suite locally, run the following commands: - -```shell -bash util/build-gnu.sh -# Build uutils without release optimizations -UU_MAKE_PROFILE=debug bash util/build-gnu.sh -bash util/run-gnu-test.sh -# To run a single test: -bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example -# To run several tests: -bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example -# If this is a perl (.pl) test, to run in debug: -DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl -``` - -Note that it relies on individual utilities (not the multicall binary). - -### Improving the GNU compatibility +Please make sure you have installed [GNU utils and prerequisites](DEVELOPMENT.md#gnu-utils-and-prerequisites) and can execute commands described in [Comparing with GNU](DEVELOPMENT.md#comparing-with-gnu) section of [DEVELOPMENT.md](DEVELOPMENT.md) The Python script `./util/remaining-gnu-error.py` shows the list of failing tests in the CI. @@ -320,30 +136,7 @@ gitignore: add temporary files ## Code coverage - - -Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov). - -### Using Nightly Rust - -To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report - -```shell -export CARGO_INCREMENTAL=0 -export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" -export RUSTDOCFLAGS="-Cpanic=abort" -cargo build # e.g., --features feat_os_unix -cargo test # e.g., --features feat_os_unix test_pathchk -grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/ -# open target/debug/coverage/index.html in browser -``` - -if changes are not reflected in the report then run `cargo clean` and run the above commands. - -### Using Stable Rust - -If you are using stable version of Rust that doesn't enable code coverage instrumentation by default -then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above. +To generate code coverage report locally please follow [Code coverage report](DEVELOPMENT.md#code-coverage-report) section of [DEVELOPMENT.md](DEVELOPMENT.md) ## Other implementations diff --git a/Cargo.lock b/Cargo.lock index 2d316d0ff..164d6d6b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,18 +10,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.19" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" -dependencies = [ - "memchr", -] - -[[package]] -name = "aho-corasick" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] @@ -43,16 +34,15 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] @@ -82,9 +72,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -98,9 +88,9 @@ checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "autocfg" @@ -135,7 +125,7 @@ version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", @@ -158,10 +148,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "blake2b_simd" -version = "1.0.1" +name = "bitflags" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", "arrayvec", @@ -170,16 +166,15 @@ dependencies = [ [[package]] name = "blake3" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729b71f35bd3fa1a4c86b85d32c8b9069ea7fe14f7a53cfabb65f62d4265b888" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "digest", ] [[package]] @@ -193,9 +188,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" dependencies = [ "memchr", "regex-automata", @@ -210,15 +205,15 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytecount" -version = "0.6.3" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" @@ -243,14 +238,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "winapi", + "windows-targets 0.48.0", ] [[package]] @@ -266,33 +261,31 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" +checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.3.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", - "once_cell", "strsim", - "terminal_size", + "terminal_size 0.2.6", ] [[package]] name = "clap_complete" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04ddfaacc3bc9e6ea67d024575fafc2a813027cf374b8f24f7bc233c6b6be12" +checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" dependencies = [ "clap", ] @@ -340,31 +333,29 @@ dependencies = [ [[package]] name = "const-random" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +checksum = "11df32a13d7892ec42d51d3d175faba5211ffe13ed25d4fb348ac9e9ce835593" dependencies = [ "const-random-macro", - "proc-macro-hack", ] [[package]] name = "const-random-macro" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ "getrandom", "once_cell", - "proc-macro-hack", "tiny-keccak", ] [[package]] name = "constant_time_eq" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "conv" @@ -383,7 +374,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "coreutils" -version = "0.0.20" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -393,7 +384,6 @@ dependencies = [ "filetime", "glob", "hex-literal", - "is-terminal", "libc", "nix", "once_cell", @@ -478,7 +468,6 @@ dependencies = [ "uu_pwd", "uu_readlink", "uu_realpath", - "uu_relpath", "uu_rm", "uu_rmdir", "uu_runcon", @@ -533,52 +522,52 @@ dependencies = [ [[package]] name = "cpp" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec5e86d4f6547f0218ad923d9508244a71ef83b763196e6698b4f70f3595185" +checksum = "bfa65869ef853e45c60e9828aa08cdd1398cb6e13f3911d9cb2a079b144fcd64" dependencies = [ "cpp_macros", ] [[package]] name = "cpp_build" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f4d303b8ec35fb3afd7e963e2c898117f1e49930becb703e4a7ac528ad2dd0" +checksum = "0e361fae2caf9758164b24da3eedd7f7d7451be30d90d8e7b5d2be29a2f0cf5b" dependencies = [ "cc", "cpp_common", "lazy_static", "proc-macro2", "regex", - "syn 1.0.109", + "syn 2.0.23", "unicode-xid", ] [[package]] name = "cpp_common" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76071bb9c8c4dd2b5eb209907deab7b031323cf1be3dfdc6ec5d37f4f187d8a1" +checksum = "3e1a2532e4ed4ea13031c13bc7bc0dbca4aae32df48e9d77f0d1e743179f2ea1" dependencies = [ "lazy_static", "proc-macro2", - "syn 1.0.109", + "syn 2.0.23", ] [[package]] name = "cpp_macros" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fdaa01904c12a8989dbfa110b41ef27efc432ac9934f691b9732f01cb64dc01" +checksum = "47ec9cc90633446f779ef481a9ce5a0077107dd5b87016440448d908625a83fd" dependencies = [ - "aho-corasick 0.7.19", + "aho-corasick", "byteorder", "cpp_common", "lazy_static", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.23", ] [[package]] @@ -644,11 +633,11 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.26.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags", + "bitflags 2.4.0", "crossterm_winapi", "libc", "mio", @@ -660,9 +649,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] @@ -685,9 +674,9 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" +checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" dependencies = [ "nix", "windows-sys 0.48.0", @@ -739,7 +728,6 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", - "subtle", ] [[package]] @@ -753,9 +741,9 @@ dependencies = [ [[package]] name = "dns-lookup" -version = "2.0.2" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f332aa79f9e9de741ac013237294ef42ce2e9c6394dc7d766725812f1238812" +checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc" dependencies = [ "cfg-if", "libc", @@ -793,32 +781,21 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys 0.48.0", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "exacl" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfeb22a59deb24c3262c43ffcafd1eb807180f371f9fcc99098d181b5d639be" +checksum = "c695152c1c2777163ea93fff517edc6dd1f8fc226c14b0d60cdcde0beb316d9f" dependencies = [ - "bitflags", + "bitflags 2.4.0", "log", "scopeguard", "uuid", @@ -826,12 +803,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "file_diff" @@ -841,14 +815,14 @@ checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5" [[package]] name = "filetime" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", - "windows-sys 0.45.0", + "redox_syscall 0.3.5", + "windows-sys 0.48.0", ] [[package]] @@ -894,24 +868,24 @@ dependencies = [ [[package]] name = "fundu" -version = "1.2.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34804ed59f10b3a630c79822ebf7370b562b7281028369e9baa40547c17f8bdc" +checksum = "6c04cb831a8dccadfe3774b07cba4574a1ec24974d761510e65d8a543c2d7cb4" dependencies = [ "fundu-core", ] [[package]] name = "fundu-core" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a99190954ca83bade03ba054799b17a158ea948a6855c6bb8121adb6b49d9f" +checksum = "76a889e633afd839fb5b04fe53adfd588cefe518e71ec8d3c929698c6daf2acd" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -924,9 +898,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -934,15 +908,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -951,32 +925,32 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.23", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-timer" @@ -986,9 +960,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -1037,10 +1011,11 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "half" -version = "2.2.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" dependencies = [ + "cfg-if", "crunchy", ] @@ -1120,7 +1095,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ - "bitflags", + "bitflags 1.3.2", "inotify-sys", "libc", ] @@ -1134,15 +1109,6 @@ dependencies = [ "libc", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1154,18 +1120,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "is-terminal" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix 0.37.19", - "windows-sys 0.48.0", -] - [[package]] name = "itertools" version = "0.11.0" @@ -1183,9 +1137,9 @@ checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1215,7 +1169,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", ] @@ -1233,9 +1187,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libloading" @@ -1265,6 +1219,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + [[package]] name = "lock_api" version = "0.4.9" @@ -1301,24 +1261,25 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" [[package]] name = "memmap2" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180d4b35be83d33392d1d1bfbd2ae1eca7ff5de1a94d3fc87faaa99a069e7cbd" +checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" dependencies = [ "libc", ] @@ -1361,14 +1322,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags", + "bitflags 2.4.0", "cfg-if", "libc", - "static_assertions", ] [[package]] @@ -1387,7 +1347,7 @@ version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossbeam-channel", "filetime", "fsevent-sys", @@ -1410,9 +1370,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -1431,23 +1391,13 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_threads" version = "0.1.6" @@ -1475,7 +1425,7 @@ version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "once_cell", "onig_sys", @@ -1522,22 +1472,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.0", ] [[package]] name = "parse_datetime" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecceaede7767a9a98058687a321bc91742eff7670167a34104afb30fc8757df" +checksum = "3bbf4e25b13841080e018a1e666358adfe5e39b6d353f986ca5091c210b586a1" dependencies = [ "chrono", "regex", @@ -1637,12 +1587,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.63" @@ -1658,11 +1602,11 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "byteorder", "hex", "lazy_static", - "rustix 0.36.14", + "rustix 0.36.16", ] [[package]] @@ -1732,9 +1676,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -1742,23 +1686,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", ] [[package]] @@ -1767,7 +1700,16 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded0bce2d41cc3c57aefa284708ced249a64acb01745dbbe72bd78610bfd644c" +dependencies = [ + "bitflags 1.3.2", ] [[package]] @@ -1778,11 +1720,11 @@ checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f" [[package]] name = "regex" -version = "1.9.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ - "aho-corasick 1.0.1", + "aho-corasick", "memchr", "regex-automata", "regex-syntax", @@ -1790,20 +1732,20 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaecc05d5c4b5f7da074b9a0d1a0867e71fd36e7fc0482d8bcfe8e8fc56290" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ - "aho-corasick 1.0.1", + "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "relative-path" @@ -1828,9 +1770,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rstest" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b96577ca10cb3eade7b337eb46520108a67ca2818a24d0b63f41fd62bc9651c" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" dependencies = [ "futures", "futures-timer", @@ -1840,9 +1782,9 @@ dependencies = [ [[package]] name = "rstest_macros" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225e674cf31712b8bb15fdbca3ec0c1b9d825c5a24407ff2b7e005fb6a29ba03" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", "glob", @@ -1882,11 +1824,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.14" +version = "0.36.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e4d67015953998ad0eb82887a0eb0129e18a7e2f3b7b0f6c422fddcd503d62" +checksum = "6da3636faa25820d8648e0e31c5d519bbb01f72fdf57131f0f5f7da5fed36eab" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -1896,11 +1838,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "84f3f8f960ed3b5a59055428714943298bf3fa2d4a1d53135084e0544829d995" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -1908,6 +1850,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustix" +version = "0.38.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.10", + "windows-sys 0.48.0", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1935,7 +1890,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00576725d21b588213fbd4af84cd7e4cc4304e8e9bd6c0f5a1498a3e2ca6a51" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "once_cell", "reference-counted-singleton", @@ -1969,9 +1924,9 @@ checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1980,9 +1935,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -2081,24 +2036,12 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - [[package]] name = "syn" version = "1.0.109" @@ -2123,34 +2066,34 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ - "autocfg", "cfg-if", "fastrand", - "redox_syscall 0.3.5", - "rustix 0.37.19", + "redox_syscall 0.4.0", + "rustix 0.38.21", "windows-sys 0.48.0", ] -[[package]] -name = "term_grid" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230d3e804faaed5a39b08319efb797783df2fd9671b39b7596490cb486d702cf" -dependencies = [ - "unicode-width", -] - [[package]] name = "terminal_size" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ - "rustix 0.37.19", + "rustix 0.37.26", + "windows-sys 0.48.0", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -2161,7 +2104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" dependencies = [ "smawk", - "terminal_size", + "terminal_size 0.2.6", "unicode-linebreak", "unicode-width", ] @@ -2250,9 +2193,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" @@ -2274,7 +2217,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uu_arch" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "platform-info", @@ -2283,7 +2226,7 @@ dependencies = [ [[package]] name = "uu_base32" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2291,7 +2234,7 @@ dependencies = [ [[package]] name = "uu_base64" -version = "0.0.20" +version = "0.0.22" dependencies = [ "uu_base32", "uucore", @@ -2299,7 +2242,7 @@ dependencies = [ [[package]] name = "uu_basename" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2307,7 +2250,7 @@ dependencies = [ [[package]] name = "uu_basenc" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uu_base32", @@ -2316,10 +2259,9 @@ dependencies = [ [[package]] name = "uu_cat" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", - "is-terminal", "nix", "thiserror", "uucore", @@ -2327,7 +2269,7 @@ dependencies = [ [[package]] name = "uu_chcon" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "fts-sys", @@ -2339,7 +2281,7 @@ dependencies = [ [[package]] name = "uu_chgrp" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2347,7 +2289,7 @@ dependencies = [ [[package]] name = "uu_chmod" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2356,7 +2298,7 @@ dependencies = [ [[package]] name = "uu_chown" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2364,7 +2306,7 @@ dependencies = [ [[package]] name = "uu_chroot" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2372,7 +2314,7 @@ dependencies = [ [[package]] name = "uu_cksum" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "hex", @@ -2381,7 +2323,7 @@ dependencies = [ [[package]] name = "uu_comm" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2389,7 +2331,7 @@ dependencies = [ [[package]] name = "uu_cp" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "exacl", @@ -2405,7 +2347,7 @@ dependencies = [ [[package]] name = "uu_csplit" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "regex", @@ -2415,18 +2357,17 @@ dependencies = [ [[package]] name = "uu_cut" -version = "0.0.20" +version = "0.0.22" dependencies = [ "bstr", "clap", - "is-terminal", "memchr", "uucore", ] [[package]] name = "uu_date" -version = "0.0.20" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -2438,7 +2379,7 @@ dependencies = [ [[package]] name = "uu_dd" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "gcd", @@ -2450,7 +2391,7 @@ dependencies = [ [[package]] name = "uu_df" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "tempfile", @@ -2460,7 +2401,7 @@ dependencies = [ [[package]] name = "uu_dir" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uu_ls", @@ -2469,7 +2410,7 @@ dependencies = [ [[package]] name = "uu_dircolors" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2477,7 +2418,7 @@ dependencies = [ [[package]] name = "uu_dirname" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2485,7 +2426,7 @@ dependencies = [ [[package]] name = "uu_du" -version = "0.0.20" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -2496,7 +2437,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2504,7 +2445,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "nix", @@ -2514,7 +2455,7 @@ dependencies = [ [[package]] name = "uu_expand" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "unicode-width", @@ -2523,7 +2464,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "num-bigint", @@ -2534,7 +2475,7 @@ dependencies = [ [[package]] name = "uu_factor" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "coz", @@ -2547,7 +2488,7 @@ dependencies = [ [[package]] name = "uu_false" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2555,7 +2496,7 @@ dependencies = [ [[package]] name = "uu_fmt" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "unicode-width", @@ -2564,7 +2505,7 @@ dependencies = [ [[package]] name = "uu_fold" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2572,7 +2513,7 @@ dependencies = [ [[package]] name = "uu_groups" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2580,7 +2521,7 @@ dependencies = [ [[package]] name = "uu_hashsum" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "hex", @@ -2591,7 +2532,7 @@ dependencies = [ [[package]] name = "uu_head" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "memchr", @@ -2600,7 +2541,7 @@ dependencies = [ [[package]] name = "uu_hostid" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2609,7 +2550,7 @@ dependencies = [ [[package]] name = "uu_hostname" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "hostname", @@ -2619,7 +2560,7 @@ dependencies = [ [[package]] name = "uu_id" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "selinux", @@ -2628,7 +2569,7 @@ dependencies = [ [[package]] name = "uu_install" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "file_diff", @@ -2639,7 +2580,7 @@ dependencies = [ [[package]] name = "uu_join" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "memchr", @@ -2648,7 +2589,7 @@ dependencies = [ [[package]] name = "uu_kill" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "nix", @@ -2657,7 +2598,7 @@ dependencies = [ [[package]] name = "uu_link" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2665,7 +2606,7 @@ dependencies = [ [[package]] name = "uu_ln" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2673,7 +2614,7 @@ dependencies = [ [[package]] name = "uu_logname" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2682,25 +2623,24 @@ dependencies = [ [[package]] name = "uu_ls" -version = "0.0.20" +version = "0.0.22" dependencies = [ "chrono", "clap", "glob", - "is-terminal", "lscolors", "number_prefix", "once_cell", "selinux", - "term_grid", - "terminal_size", + "terminal_size 0.3.0", "unicode-width", "uucore", + "uutils_term_grid", ] [[package]] name = "uu_mkdir" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2708,7 +2648,7 @@ dependencies = [ [[package]] name = "uu_mkfifo" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2717,7 +2657,7 @@ dependencies = [ [[package]] name = "uu_mknod" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2726,7 +2666,7 @@ dependencies = [ [[package]] name = "uu_mktemp" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "rand", @@ -2736,11 +2676,10 @@ dependencies = [ [[package]] name = "uu_more" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "crossterm", - "is-terminal", "nix", "unicode-segmentation", "unicode-width", @@ -2749,7 +2688,7 @@ dependencies = [ [[package]] name = "uu_mv" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "fs_extra", @@ -2759,7 +2698,7 @@ dependencies = [ [[package]] name = "uu_nice" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2769,7 +2708,7 @@ dependencies = [ [[package]] name = "uu_nl" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "regex", @@ -2778,17 +2717,16 @@ dependencies = [ [[package]] name = "uu_nohup" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", - "is-terminal", "libc", "uucore", ] [[package]] name = "uu_nproc" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2797,7 +2735,7 @@ dependencies = [ [[package]] name = "uu_numfmt" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2805,7 +2743,7 @@ dependencies = [ [[package]] name = "uu_od" -version = "0.0.20" +version = "0.0.22" dependencies = [ "byteorder", "clap", @@ -2815,7 +2753,7 @@ dependencies = [ [[package]] name = "uu_paste" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2823,7 +2761,7 @@ dependencies = [ [[package]] name = "uu_pathchk" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2832,7 +2770,7 @@ dependencies = [ [[package]] name = "uu_pinky" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2840,7 +2778,7 @@ dependencies = [ [[package]] name = "uu_pr" -version = "0.0.20" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -2852,7 +2790,7 @@ dependencies = [ [[package]] name = "uu_printenv" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2860,7 +2798,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2868,7 +2806,7 @@ dependencies = [ [[package]] name = "uu_ptx" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "regex", @@ -2877,7 +2815,7 @@ dependencies = [ [[package]] name = "uu_pwd" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2885,7 +2823,7 @@ dependencies = [ [[package]] name = "uu_readlink" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2893,15 +2831,7 @@ dependencies = [ [[package]] name = "uu_realpath" -version = "0.0.20" -dependencies = [ - "clap", - "uucore", -] - -[[package]] -name = "uu_relpath" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2909,7 +2839,7 @@ dependencies = [ [[package]] name = "uu_rm" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2920,7 +2850,7 @@ dependencies = [ [[package]] name = "uu_rmdir" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2929,7 +2859,7 @@ dependencies = [ [[package]] name = "uu_runcon" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2940,7 +2870,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.20" +version = "0.0.22" dependencies = [ "bigdecimal", "clap", @@ -2951,7 +2881,7 @@ dependencies = [ [[package]] name = "uu_shred" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2961,7 +2891,7 @@ dependencies = [ [[package]] name = "uu_shuf" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "memchr", @@ -2972,7 +2902,7 @@ dependencies = [ [[package]] name = "uu_sleep" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "fundu", @@ -2981,7 +2911,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.20" +version = "0.0.22" dependencies = [ "binary-heap-plus", "clap", @@ -3000,7 +2930,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "memchr", @@ -3009,7 +2939,7 @@ dependencies = [ [[package]] name = "uu_stat" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3017,7 +2947,7 @@ dependencies = [ [[package]] name = "uu_stdbuf" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "tempfile", @@ -3027,7 +2957,7 @@ dependencies = [ [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.20" +version = "0.0.22" dependencies = [ "cpp", "cpp_build", @@ -3037,7 +2967,7 @@ dependencies = [ [[package]] name = "uu_stty" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "nix", @@ -3046,7 +2976,7 @@ dependencies = [ [[package]] name = "uu_sum" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3054,7 +2984,7 @@ dependencies = [ [[package]] name = "uu_sync" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -3065,7 +2995,7 @@ dependencies = [ [[package]] name = "uu_tac" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "memchr", @@ -3076,11 +3006,10 @@ dependencies = [ [[package]] name = "uu_tail" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "fundu", - "is-terminal", "libc", "memchr", "notify", @@ -3093,7 +3022,7 @@ dependencies = [ [[package]] name = "uu_tee" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -3102,17 +3031,17 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.4.0", "uucore", ] [[package]] name = "uu_timeout" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -3122,7 +3051,7 @@ dependencies = [ [[package]] name = "uu_touch" -version = "0.0.20" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -3134,7 +3063,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "nom", @@ -3143,7 +3072,7 @@ dependencies = [ [[package]] name = "uu_true" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3151,7 +3080,7 @@ dependencies = [ [[package]] name = "uu_truncate" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3159,7 +3088,7 @@ dependencies = [ [[package]] name = "uu_tsort" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3167,17 +3096,16 @@ dependencies = [ [[package]] name = "uu_tty" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", - "is-terminal", "nix", "uucore", ] [[package]] name = "uu_uname" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "platform-info", @@ -3186,7 +3114,7 @@ dependencies = [ [[package]] name = "uu_unexpand" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "unicode-width", @@ -3195,7 +3123,7 @@ dependencies = [ [[package]] name = "uu_uniq" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3203,7 +3131,7 @@ dependencies = [ [[package]] name = "uu_unlink" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3211,7 +3139,7 @@ dependencies = [ [[package]] name = "uu_uptime" -version = "0.0.20" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -3220,7 +3148,7 @@ dependencies = [ [[package]] name = "uu_users" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3228,7 +3156,7 @@ dependencies = [ [[package]] name = "uu_vdir" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uu_ls", @@ -3237,7 +3165,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.20" +version = "0.0.22" dependencies = [ "bytecount", "clap", @@ -3250,7 +3178,7 @@ dependencies = [ [[package]] name = "uu_who" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3258,7 +3186,7 @@ dependencies = [ [[package]] name = "uu_whoami" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -3268,7 +3196,7 @@ dependencies = [ [[package]] name = "uu_yes" -version = "0.0.20" +version = "0.0.22" dependencies = [ "clap", "itertools", @@ -3278,7 +3206,7 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.20" +version = "0.0.22" dependencies = [ "blake2b_simd", "blake3", @@ -3314,7 +3242,7 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.20" +version = "0.0.22" dependencies = [ "proc-macro2", "quote", @@ -3323,7 +3251,7 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.20" +version = "0.0.22" [[package]] name = "uuid" @@ -3331,6 +3259,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +[[package]] +name = "uutils_term_grid" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b389452a568698688dda38802068378a16c15c4af9b153cdd99b65391292bbc7" +dependencies = [ + "unicode-width", +] + [[package]] name = "version_check" version = "0.9.4" @@ -3339,12 +3276,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -3356,9 +3292,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3366,24 +3302,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.23", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3391,22 +3327,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.23", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "which" @@ -3421,9 +3357,9 @@ dependencies = [ [[package]] name = "wild" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b116685a6be0c52f5a103334cbff26db643826c7b3735fc0a3ba9871310a74" +checksum = "10d01931a94d5a115a53f95292f51d316856b68a035618eb831bbba593a30b67" dependencies = [ "glob", ] @@ -3446,9 +3382,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index accc027ab..afddd4985 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "coreutils" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" @@ -16,7 +16,7 @@ repository = "https://github.com/uutils/coreutils" readme = "README.md" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] -rust-version = "1.64.0" +rust-version = "1.70.0" edition = "2021" build = "build.rs" @@ -100,7 +100,6 @@ feat_common_core = [ "pwd", "readlink", "realpath", - "relpath", "rm", "rmdir", "seq", @@ -152,6 +151,7 @@ feat_os_unix = [ "feat_require_crate_cpp", "feat_require_unix", "feat_require_unix_utmpx", + "feat_require_unix_hostid", ] # "feat_os_windows" == set of utilities which can be built/run on modern/usual windows platforms feat_os_windows = [ @@ -259,87 +259,86 @@ test = ["uu_test"] [workspace.dependencies] bigdecimal = "0.4" binary-heap-plus = "0.5.0" -bstr = "1.6" -bytecount = "0.6.3" -byteorder = "1.4.3" -chrono = { version = "^0.4.26", default-features = false, features = [ +bstr = "1.7" +bytecount = "0.6.7" +byteorder = "1.5.0" +chrono = { version = "^0.4.31", default-features = false, features = [ "std", "alloc", "clock", ] } -clap = { version = "4.3", features = ["wrap_help", "cargo"] } -clap_complete = "4.3" +clap = { version = "4.4", features = ["wrap_help", "cargo"] } +clap_complete = "4.4" clap_mangen = "0.2" compare = "0.1.0" coz = { version = "0.1.3" } -crossterm = ">=0.26.1" +crossterm = ">=0.27.0" ctrlc = { version = "3.4", features = ["termination"] } -exacl = "0.10.0" +exacl = "0.11.0" file_diff = "1.0.0" filetime = "0.2" fnv = "1.0.7" fs_extra = "1.3.0" fts-sys = "0.2" -fundu = "1.2.0" +fundu = "2.0.0" gcd = "2.3" glob = "0.3.1" -half = "2.2" +half = "2.3" indicatif = "0.17" -is-terminal = "0.4.7" itertools = "0.11.0" -libc = "0.2.147" +libc = "0.2.149" lscolors = { version = "0.15.0", default-features = false, features = [ "nu-ansi-term", ] } memchr = "2" -memmap2 = "0.7" -nix = { version = "0.26", default-features = false } +memmap2 = "0.9" +nix = { version = "0.27", default-features = false } nom = "7.1.3" notify = { version = "=6.0.1", features = ["macos_kqueue"] } -num-bigint = "0.4.3" -num-traits = "0.2.16" +num-bigint = "0.4.4" +num-traits = "0.2.17" number_prefix = "0.4" once_cell = "1.18.0" onig = { version = "~6.4", default-features = false } -parse_datetime = "0.4.0" +parse_datetime = "0.5.0" phf = "0.11.2" phf_codegen = "0.11.2" platform-info = "2.0.2" quick-error = "2.0.1" rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" -rayon = "1.7" -redox_syscall = "0.3" -regex = "1.9.1" -rstest = "0.18.1" +rayon = "1.8" +redox_syscall = "0.4" +regex = "1.10.2" +rstest = "0.18.2" rust-ini = "0.19.0" same-file = "1.0.6" self_cell = "1.0.1" selinux = "0.4" signal-hook = "0.3.17" smallvec = { version = "1.11", features = ["union"] } -tempfile = "3.6.0" -term_grid = "0.1.5" -terminal_size = "0.2.6" +tempfile = "3.8.1" +uutils_term_grid = "0.3" +terminal_size = "0.3.0" textwrap = { version = "0.16.0", features = ["terminal_size"] } thiserror = "1.0" time = { version = "0.3" } unicode-segmentation = "1.10.1" -unicode-width = "0.1.10" +unicode-width = "0.1.11" utf-8 = "0.7.6" -walkdir = "2.3" -winapi-util = "0.1.5" +walkdir = "2.4" +winapi-util = "0.1.6" windows-sys = { version = "0.48.0", default-features = false } xattr = "1.0.1" zip = { version = "0.6.6", default_features = false, features = ["deflate"] } hex = "0.4.3" -md-5 = "0.10.5" -sha1 = "0.10.5" -sha2 = "0.10.7" +md-5 = "0.10.6" +sha1 = "0.10.6" +sha2 = "0.10.8" sha3 = "0.10.8" -blake2b_simd = "1.0.1" -blake3 = "1.4.0" +blake2b_simd = "1.0.2" +blake3 = "1.5.0" sm3 = "0.4.2" digest = "0.10.7" @@ -362,110 +361,109 @@ zip = { workspace = true, optional = true } uuhelp_parser = { optional = true, version = ">=0.0.19", path = "src/uuhelp_parser" } # * uutils -uu_test = { optional = true, version = "0.0.20", package = "uu_test", path = "src/uu/test" } +uu_test = { optional = true, version = "0.0.22", package = "uu_test", path = "src/uu/test" } # -arch = { optional = true, version = "0.0.20", package = "uu_arch", path = "src/uu/arch" } -base32 = { optional = true, version = "0.0.20", package = "uu_base32", path = "src/uu/base32" } -base64 = { optional = true, version = "0.0.20", package = "uu_base64", path = "src/uu/base64" } -basename = { optional = true, version = "0.0.20", package = "uu_basename", path = "src/uu/basename" } -basenc = { optional = true, version = "0.0.20", package = "uu_basenc", path = "src/uu/basenc" } -cat = { optional = true, version = "0.0.20", package = "uu_cat", path = "src/uu/cat" } -chcon = { optional = true, version = "0.0.20", package = "uu_chcon", path = "src/uu/chcon" } -chgrp = { optional = true, version = "0.0.20", package = "uu_chgrp", path = "src/uu/chgrp" } -chmod = { optional = true, version = "0.0.20", package = "uu_chmod", path = "src/uu/chmod" } -chown = { optional = true, version = "0.0.20", package = "uu_chown", path = "src/uu/chown" } -chroot = { optional = true, version = "0.0.20", package = "uu_chroot", path = "src/uu/chroot" } -cksum = { optional = true, version = "0.0.20", package = "uu_cksum", path = "src/uu/cksum" } -comm = { optional = true, version = "0.0.20", package = "uu_comm", path = "src/uu/comm" } -cp = { optional = true, version = "0.0.20", package = "uu_cp", path = "src/uu/cp" } -csplit = { optional = true, version = "0.0.20", package = "uu_csplit", path = "src/uu/csplit" } -cut = { optional = true, version = "0.0.20", package = "uu_cut", path = "src/uu/cut" } -date = { optional = true, version = "0.0.20", package = "uu_date", path = "src/uu/date" } -dd = { optional = true, version = "0.0.20", package = "uu_dd", path = "src/uu/dd" } -df = { optional = true, version = "0.0.20", package = "uu_df", path = "src/uu/df" } -dir = { optional = true, version = "0.0.20", package = "uu_dir", path = "src/uu/dir" } -dircolors = { optional = true, version = "0.0.20", package = "uu_dircolors", path = "src/uu/dircolors" } -dirname = { optional = true, version = "0.0.20", package = "uu_dirname", path = "src/uu/dirname" } -du = { optional = true, version = "0.0.20", package = "uu_du", path = "src/uu/du" } -echo = { optional = true, version = "0.0.20", package = "uu_echo", path = "src/uu/echo" } -env = { optional = true, version = "0.0.20", package = "uu_env", path = "src/uu/env" } -expand = { optional = true, version = "0.0.20", package = "uu_expand", path = "src/uu/expand" } -expr = { optional = true, version = "0.0.20", package = "uu_expr", path = "src/uu/expr" } -factor = { optional = true, version = "0.0.20", package = "uu_factor", path = "src/uu/factor" } -false = { optional = true, version = "0.0.20", package = "uu_false", path = "src/uu/false" } -fmt = { optional = true, version = "0.0.20", package = "uu_fmt", path = "src/uu/fmt" } -fold = { optional = true, version = "0.0.20", package = "uu_fold", path = "src/uu/fold" } -groups = { optional = true, version = "0.0.20", package = "uu_groups", path = "src/uu/groups" } -hashsum = { optional = true, version = "0.0.20", package = "uu_hashsum", path = "src/uu/hashsum" } -head = { optional = true, version = "0.0.20", package = "uu_head", path = "src/uu/head" } -hostid = { optional = true, version = "0.0.20", package = "uu_hostid", path = "src/uu/hostid" } -hostname = { optional = true, version = "0.0.20", package = "uu_hostname", path = "src/uu/hostname" } -id = { optional = true, version = "0.0.20", package = "uu_id", path = "src/uu/id" } -install = { optional = true, version = "0.0.20", package = "uu_install", path = "src/uu/install" } -join = { optional = true, version = "0.0.20", package = "uu_join", path = "src/uu/join" } -kill = { optional = true, version = "0.0.20", package = "uu_kill", path = "src/uu/kill" } -link = { optional = true, version = "0.0.20", package = "uu_link", path = "src/uu/link" } -ln = { optional = true, version = "0.0.20", package = "uu_ln", path = "src/uu/ln" } -ls = { optional = true, version = "0.0.20", package = "uu_ls", path = "src/uu/ls" } -logname = { optional = true, version = "0.0.20", package = "uu_logname", path = "src/uu/logname" } -mkdir = { optional = true, version = "0.0.20", package = "uu_mkdir", path = "src/uu/mkdir" } -mkfifo = { optional = true, version = "0.0.20", package = "uu_mkfifo", path = "src/uu/mkfifo" } -mknod = { optional = true, version = "0.0.20", package = "uu_mknod", path = "src/uu/mknod" } -mktemp = { optional = true, version = "0.0.20", package = "uu_mktemp", path = "src/uu/mktemp" } -more = { optional = true, version = "0.0.20", package = "uu_more", path = "src/uu/more" } -mv = { optional = true, version = "0.0.20", package = "uu_mv", path = "src/uu/mv" } -nice = { optional = true, version = "0.0.20", package = "uu_nice", path = "src/uu/nice" } -nl = { optional = true, version = "0.0.20", package = "uu_nl", path = "src/uu/nl" } -nohup = { optional = true, version = "0.0.20", package = "uu_nohup", path = "src/uu/nohup" } -nproc = { optional = true, version = "0.0.20", package = "uu_nproc", path = "src/uu/nproc" } -numfmt = { optional = true, version = "0.0.20", package = "uu_numfmt", path = "src/uu/numfmt" } -od = { optional = true, version = "0.0.20", package = "uu_od", path = "src/uu/od" } -paste = { optional = true, version = "0.0.20", package = "uu_paste", path = "src/uu/paste" } -pathchk = { optional = true, version = "0.0.20", package = "uu_pathchk", path = "src/uu/pathchk" } -pinky = { optional = true, version = "0.0.20", package = "uu_pinky", path = "src/uu/pinky" } -pr = { optional = true, version = "0.0.20", package = "uu_pr", path = "src/uu/pr" } -printenv = { optional = true, version = "0.0.20", package = "uu_printenv", path = "src/uu/printenv" } -printf = { optional = true, version = "0.0.20", package = "uu_printf", path = "src/uu/printf" } -ptx = { optional = true, version = "0.0.20", package = "uu_ptx", path = "src/uu/ptx" } -pwd = { optional = true, version = "0.0.20", package = "uu_pwd", path = "src/uu/pwd" } -readlink = { optional = true, version = "0.0.20", package = "uu_readlink", path = "src/uu/readlink" } -realpath = { optional = true, version = "0.0.20", package = "uu_realpath", path = "src/uu/realpath" } -relpath = { optional = true, version = "0.0.20", package = "uu_relpath", path = "src/uu/relpath" } -rm = { optional = true, version = "0.0.20", package = "uu_rm", path = "src/uu/rm" } -rmdir = { optional = true, version = "0.0.20", package = "uu_rmdir", path = "src/uu/rmdir" } -runcon = { optional = true, version = "0.0.20", package = "uu_runcon", path = "src/uu/runcon" } -seq = { optional = true, version = "0.0.20", package = "uu_seq", path = "src/uu/seq" } -shred = { optional = true, version = "0.0.20", package = "uu_shred", path = "src/uu/shred" } -shuf = { optional = true, version = "0.0.20", package = "uu_shuf", path = "src/uu/shuf" } -sleep = { optional = true, version = "0.0.20", package = "uu_sleep", path = "src/uu/sleep" } -sort = { optional = true, version = "0.0.20", package = "uu_sort", path = "src/uu/sort" } -split = { optional = true, version = "0.0.20", package = "uu_split", path = "src/uu/split" } -stat = { optional = true, version = "0.0.20", package = "uu_stat", path = "src/uu/stat" } -stdbuf = { optional = true, version = "0.0.20", package = "uu_stdbuf", path = "src/uu/stdbuf" } -stty = { optional = true, version = "0.0.20", package = "uu_stty", path = "src/uu/stty" } -sum = { optional = true, version = "0.0.20", package = "uu_sum", path = "src/uu/sum" } -sync = { optional = true, version = "0.0.20", package = "uu_sync", path = "src/uu/sync" } -tac = { optional = true, version = "0.0.20", package = "uu_tac", path = "src/uu/tac" } -tail = { optional = true, version = "0.0.20", package = "uu_tail", path = "src/uu/tail" } -tee = { optional = true, version = "0.0.20", package = "uu_tee", path = "src/uu/tee" } -timeout = { optional = true, version = "0.0.20", package = "uu_timeout", path = "src/uu/timeout" } -touch = { optional = true, version = "0.0.20", package = "uu_touch", path = "src/uu/touch" } -tr = { optional = true, version = "0.0.20", package = "uu_tr", path = "src/uu/tr" } -true = { optional = true, version = "0.0.20", package = "uu_true", path = "src/uu/true" } -truncate = { optional = true, version = "0.0.20", package = "uu_truncate", path = "src/uu/truncate" } -tsort = { optional = true, version = "0.0.20", package = "uu_tsort", path = "src/uu/tsort" } -tty = { optional = true, version = "0.0.20", package = "uu_tty", path = "src/uu/tty" } -uname = { optional = true, version = "0.0.20", package = "uu_uname", path = "src/uu/uname" } -unexpand = { optional = true, version = "0.0.20", package = "uu_unexpand", path = "src/uu/unexpand" } -uniq = { optional = true, version = "0.0.20", package = "uu_uniq", path = "src/uu/uniq" } -unlink = { optional = true, version = "0.0.20", package = "uu_unlink", path = "src/uu/unlink" } -uptime = { optional = true, version = "0.0.20", package = "uu_uptime", path = "src/uu/uptime" } -users = { optional = true, version = "0.0.20", package = "uu_users", path = "src/uu/users" } -vdir = { optional = true, version = "0.0.20", package = "uu_vdir", path = "src/uu/vdir" } -wc = { optional = true, version = "0.0.20", package = "uu_wc", path = "src/uu/wc" } -who = { optional = true, version = "0.0.20", package = "uu_who", path = "src/uu/who" } -whoami = { optional = true, version = "0.0.20", package = "uu_whoami", path = "src/uu/whoami" } -yes = { optional = true, version = "0.0.20", package = "uu_yes", path = "src/uu/yes" } +arch = { optional = true, version = "0.0.22", package = "uu_arch", path = "src/uu/arch" } +base32 = { optional = true, version = "0.0.22", package = "uu_base32", path = "src/uu/base32" } +base64 = { optional = true, version = "0.0.22", package = "uu_base64", path = "src/uu/base64" } +basename = { optional = true, version = "0.0.22", package = "uu_basename", path = "src/uu/basename" } +basenc = { optional = true, version = "0.0.22", package = "uu_basenc", path = "src/uu/basenc" } +cat = { optional = true, version = "0.0.22", package = "uu_cat", path = "src/uu/cat" } +chcon = { optional = true, version = "0.0.22", package = "uu_chcon", path = "src/uu/chcon" } +chgrp = { optional = true, version = "0.0.22", package = "uu_chgrp", path = "src/uu/chgrp" } +chmod = { optional = true, version = "0.0.22", package = "uu_chmod", path = "src/uu/chmod" } +chown = { optional = true, version = "0.0.22", package = "uu_chown", path = "src/uu/chown" } +chroot = { optional = true, version = "0.0.22", package = "uu_chroot", path = "src/uu/chroot" } +cksum = { optional = true, version = "0.0.22", package = "uu_cksum", path = "src/uu/cksum" } +comm = { optional = true, version = "0.0.22", package = "uu_comm", path = "src/uu/comm" } +cp = { optional = true, version = "0.0.22", package = "uu_cp", path = "src/uu/cp" } +csplit = { optional = true, version = "0.0.22", package = "uu_csplit", path = "src/uu/csplit" } +cut = { optional = true, version = "0.0.22", package = "uu_cut", path = "src/uu/cut" } +date = { optional = true, version = "0.0.22", package = "uu_date", path = "src/uu/date" } +dd = { optional = true, version = "0.0.22", package = "uu_dd", path = "src/uu/dd" } +df = { optional = true, version = "0.0.22", package = "uu_df", path = "src/uu/df" } +dir = { optional = true, version = "0.0.22", package = "uu_dir", path = "src/uu/dir" } +dircolors = { optional = true, version = "0.0.22", package = "uu_dircolors", path = "src/uu/dircolors" } +dirname = { optional = true, version = "0.0.22", package = "uu_dirname", path = "src/uu/dirname" } +du = { optional = true, version = "0.0.22", package = "uu_du", path = "src/uu/du" } +echo = { optional = true, version = "0.0.22", package = "uu_echo", path = "src/uu/echo" } +env = { optional = true, version = "0.0.22", package = "uu_env", path = "src/uu/env" } +expand = { optional = true, version = "0.0.22", package = "uu_expand", path = "src/uu/expand" } +expr = { optional = true, version = "0.0.22", package = "uu_expr", path = "src/uu/expr" } +factor = { optional = true, version = "0.0.22", package = "uu_factor", path = "src/uu/factor" } +false = { optional = true, version = "0.0.22", package = "uu_false", path = "src/uu/false" } +fmt = { optional = true, version = "0.0.22", package = "uu_fmt", path = "src/uu/fmt" } +fold = { optional = true, version = "0.0.22", package = "uu_fold", path = "src/uu/fold" } +groups = { optional = true, version = "0.0.22", package = "uu_groups", path = "src/uu/groups" } +hashsum = { optional = true, version = "0.0.22", package = "uu_hashsum", path = "src/uu/hashsum" } +head = { optional = true, version = "0.0.22", package = "uu_head", path = "src/uu/head" } +hostid = { optional = true, version = "0.0.22", package = "uu_hostid", path = "src/uu/hostid" } +hostname = { optional = true, version = "0.0.22", package = "uu_hostname", path = "src/uu/hostname" } +id = { optional = true, version = "0.0.22", package = "uu_id", path = "src/uu/id" } +install = { optional = true, version = "0.0.22", package = "uu_install", path = "src/uu/install" } +join = { optional = true, version = "0.0.22", package = "uu_join", path = "src/uu/join" } +kill = { optional = true, version = "0.0.22", package = "uu_kill", path = "src/uu/kill" } +link = { optional = true, version = "0.0.22", package = "uu_link", path = "src/uu/link" } +ln = { optional = true, version = "0.0.22", package = "uu_ln", path = "src/uu/ln" } +ls = { optional = true, version = "0.0.22", package = "uu_ls", path = "src/uu/ls" } +logname = { optional = true, version = "0.0.22", package = "uu_logname", path = "src/uu/logname" } +mkdir = { optional = true, version = "0.0.22", package = "uu_mkdir", path = "src/uu/mkdir" } +mkfifo = { optional = true, version = "0.0.22", package = "uu_mkfifo", path = "src/uu/mkfifo" } +mknod = { optional = true, version = "0.0.22", package = "uu_mknod", path = "src/uu/mknod" } +mktemp = { optional = true, version = "0.0.22", package = "uu_mktemp", path = "src/uu/mktemp" } +more = { optional = true, version = "0.0.22", package = "uu_more", path = "src/uu/more" } +mv = { optional = true, version = "0.0.22", package = "uu_mv", path = "src/uu/mv" } +nice = { optional = true, version = "0.0.22", package = "uu_nice", path = "src/uu/nice" } +nl = { optional = true, version = "0.0.22", package = "uu_nl", path = "src/uu/nl" } +nohup = { optional = true, version = "0.0.22", package = "uu_nohup", path = "src/uu/nohup" } +nproc = { optional = true, version = "0.0.22", package = "uu_nproc", path = "src/uu/nproc" } +numfmt = { optional = true, version = "0.0.22", package = "uu_numfmt", path = "src/uu/numfmt" } +od = { optional = true, version = "0.0.22", package = "uu_od", path = "src/uu/od" } +paste = { optional = true, version = "0.0.22", package = "uu_paste", path = "src/uu/paste" } +pathchk = { optional = true, version = "0.0.22", package = "uu_pathchk", path = "src/uu/pathchk" } +pinky = { optional = true, version = "0.0.22", package = "uu_pinky", path = "src/uu/pinky" } +pr = { optional = true, version = "0.0.22", package = "uu_pr", path = "src/uu/pr" } +printenv = { optional = true, version = "0.0.22", package = "uu_printenv", path = "src/uu/printenv" } +printf = { optional = true, version = "0.0.22", package = "uu_printf", path = "src/uu/printf" } +ptx = { optional = true, version = "0.0.22", package = "uu_ptx", path = "src/uu/ptx" } +pwd = { optional = true, version = "0.0.22", package = "uu_pwd", path = "src/uu/pwd" } +readlink = { optional = true, version = "0.0.22", package = "uu_readlink", path = "src/uu/readlink" } +realpath = { optional = true, version = "0.0.22", package = "uu_realpath", path = "src/uu/realpath" } +rm = { optional = true, version = "0.0.22", package = "uu_rm", path = "src/uu/rm" } +rmdir = { optional = true, version = "0.0.22", package = "uu_rmdir", path = "src/uu/rmdir" } +runcon = { optional = true, version = "0.0.22", package = "uu_runcon", path = "src/uu/runcon" } +seq = { optional = true, version = "0.0.22", package = "uu_seq", path = "src/uu/seq" } +shred = { optional = true, version = "0.0.22", package = "uu_shred", path = "src/uu/shred" } +shuf = { optional = true, version = "0.0.22", package = "uu_shuf", path = "src/uu/shuf" } +sleep = { optional = true, version = "0.0.22", package = "uu_sleep", path = "src/uu/sleep" } +sort = { optional = true, version = "0.0.22", package = "uu_sort", path = "src/uu/sort" } +split = { optional = true, version = "0.0.22", package = "uu_split", path = "src/uu/split" } +stat = { optional = true, version = "0.0.22", package = "uu_stat", path = "src/uu/stat" } +stdbuf = { optional = true, version = "0.0.22", package = "uu_stdbuf", path = "src/uu/stdbuf" } +stty = { optional = true, version = "0.0.22", package = "uu_stty", path = "src/uu/stty" } +sum = { optional = true, version = "0.0.22", package = "uu_sum", path = "src/uu/sum" } +sync = { optional = true, version = "0.0.22", package = "uu_sync", path = "src/uu/sync" } +tac = { optional = true, version = "0.0.22", package = "uu_tac", path = "src/uu/tac" } +tail = { optional = true, version = "0.0.22", package = "uu_tail", path = "src/uu/tail" } +tee = { optional = true, version = "0.0.22", package = "uu_tee", path = "src/uu/tee" } +timeout = { optional = true, version = "0.0.22", package = "uu_timeout", path = "src/uu/timeout" } +touch = { optional = true, version = "0.0.22", package = "uu_touch", path = "src/uu/touch" } +tr = { optional = true, version = "0.0.22", package = "uu_tr", path = "src/uu/tr" } +true = { optional = true, version = "0.0.22", package = "uu_true", path = "src/uu/true" } +truncate = { optional = true, version = "0.0.22", package = "uu_truncate", path = "src/uu/truncate" } +tsort = { optional = true, version = "0.0.22", package = "uu_tsort", path = "src/uu/tsort" } +tty = { optional = true, version = "0.0.22", package = "uu_tty", path = "src/uu/tty" } +uname = { optional = true, version = "0.0.22", package = "uu_uname", path = "src/uu/uname" } +unexpand = { optional = true, version = "0.0.22", package = "uu_unexpand", path = "src/uu/unexpand" } +uniq = { optional = true, version = "0.0.22", package = "uu_uniq", path = "src/uu/uniq" } +unlink = { optional = true, version = "0.0.22", package = "uu_unlink", path = "src/uu/unlink" } +uptime = { optional = true, version = "0.0.22", package = "uu_uptime", path = "src/uu/uptime" } +users = { optional = true, version = "0.0.22", package = "uu_users", path = "src/uu/users" } +vdir = { optional = true, version = "0.0.22", package = "uu_vdir", path = "src/uu/vdir" } +wc = { optional = true, version = "0.0.22", package = "uu_wc", path = "src/uu/wc" } +who = { optional = true, version = "0.0.22", package = "uu_who", path = "src/uu/who" } +whoami = { optional = true, version = "0.0.22", package = "uu_whoami", path = "src/uu/whoami" } +yes = { optional = true, version = "0.0.22", 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" } @@ -490,7 +488,6 @@ time = { workspace = true, features = ["local-offset"] } unindent = "0.2" uucore = { workspace = true, features = ["entries", "process", "signals"] } walkdir = { workspace = true } -is-terminal = { workspace = true } hex-literal = "0.4.1" rstest = { workspace = true } diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 000000000..67b201e9c --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,331 @@ + + +# Setting up your local development environment + +For contributing rules and best practices please refer to [CONTRIBUTING.md](CONTRIBUTING.md) + +## Before you start + +For this guide we assume that you already have a GitHub account and have `git` and your favorite code editor or IDE installed and configured. +Before you start working on coreutils, please follow these steps: + +1. Fork the [coreutils repository](https://github.com/uutils/coreutils) to your GitHub account. +***Tip:*** See [this GitHub guide](https://docs.github.com/en/get-started/quickstart/fork-a-repo) for more information on this step. +2. Clone that fork to your local development environment: + +```shell +git clone https://github.com/YOUR-GITHUB-ACCOUNT/coreutils +cd coreutils +``` + +## Tools + +You will need the tools mentioned in this section to build and test your code changes locally. +This section will explain how to install and configure these tools. +We also have an extensive CI that uses these tools and will check your code before it can be merged. +The next section [Testing](#testing) will explain how to run those checks locally to avoid waiting for the CI. + +### Rust toolchain + +[Install Rust](https://www.rust-lang.org/tools/install) + +If you're using rustup to install and manage your Rust toolchains, `clippy` and `rustfmt` are usually already installed. If you are using one of the alternative methods, please make sure to install them manually. See following sub-sections for their usage: [clippy](#clippy) [rustfmt](#rustfmt). + +***Tip*** You might also need to add 'llvm-tools' component if you are going to [generate code coverage reports locally](#code-coverage-report): + +```shell +rustup component add llvm-tools-preview +``` + +### GNU utils and prerequisites + +If you are developing on Linux, most likely you already have all/most GNU utilities and prerequisites installed. + +To make sure, please check GNU coreutils [README-prereq](https://github.com/coreutils/coreutils/blob/master/README-prereq). + +You will need these to [run uutils against the GNU test suite locally](#comparing-with-gnu). + +For MacOS and Windows platform specific setup please check [MacOS GNU utils](#macos-gnu-utils) and [Windows GNU utils](#windows-gnu-utils) sections respectfully. + +### pre-commit hooks + +A configuration for `pre-commit` is provided in the repository. It allows +automatically checking every git commit you make to ensure it compiles, and +passes `clippy` and `rustfmt` without warnings. + +To use the provided hook: + +1. [Install `pre-commit`](https://pre-commit.com/#install) +1. Run `pre-commit install` while in the repository directory + +Your git commits will then automatically be checked. If a check fails, an error +message will explain why, and your commit will be canceled. You can then make +the suggested changes, and run `git commit ...` again. + +**NOTE: On MacOS** the pre-commit hooks are currently broken. There are workarounds involving switching to unstable nightly Rust and components. + +### clippy + +```shell +cargo clippy --all-targets --all-features +``` + +The `msrv` key in the clippy configuration file `clippy.toml` is used to disable +lints pertaining to newer features by specifying the minimum supported Rust +version (MSRV). + +### rustfmt + +```shell +cargo fmt --all +``` + +### cargo-deny + +This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to +detect duplicate dependencies, checks licenses, etc. To run it locally, first +install it and then run with: + +```shell +cargo deny --all-features check all +``` + +### Markdown linter + +We use [markdownlint](https://github.com/DavidAnson/markdownlint) to lint the +Markdown files in the repository. + +### Spell checker + +We use `cspell` as spell checker for all files in the project. If you are using +VS Code, you can install the +[code spell checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) +extension to enable spell checking within your editor. Otherwise, you can +install [cspell](https://cspell.org/) separately. + +If you want to make the spell checker ignore a word, you can add + +```rust +// spell-checker:ignore word_to_ignore +``` + +at the top of the file. + +## Testing + +This section explains how to run our CI checks locally. +Testing can be done using either Cargo or `make`. + +### Testing with Cargo + +Just like with building, we follow the standard procedure for testing using +Cargo: + +```shell +cargo test +``` + +By default, `cargo test` only runs the common programs. To run also platform +specific tests, run: + +```shell +cargo test --features unix +``` + +If you would prefer to test a select few utilities: + +```shell +cargo test --features "chmod mv tail" --no-default-features +``` + +If you also want to test the core utilities: + +```shell +cargo test -p uucore -p coreutils +# or +cargo test --all-features -p uucore +``` + +Running the complete test suite might take a while. We use [nextest](https://nexte.st/index.html) in +the CI and you might want to try it out locally. It can speed up the execution time of the whole +test run significantly if the cpu has multiple cores. + +```shell +cargo nextest run --features unix --no-fail-fast +``` + +To debug: + +```shell +rust-gdb --args target/debug/coreutils ls +(gdb) b ls.rs:79 +(gdb) run +``` + +### Testing with GNU Make + +To simply test all available utilities: + +```shell +make test +``` + +To test all but a few of the available utilities: + +```shell +make SKIP_UTILS='UTILITY_1 UTILITY_2' test +``` + +To test only a few of the available utilities: + +```shell +make UTILS='UTILITY_1 UTILITY_2' test +``` + +To include tests for unimplemented behavior: + +```shell +make UTILS='UTILITY_1 UTILITY_2' SPEC=y test +``` + +To run tests with `nextest` just use the nextest target. Note you'll need to +[install](https://nexte.st/book/installation.html) `nextest` first. The `nextest` target accepts the +same arguments like the default `test` target, so it's possible to pass arguments to `nextest run` +via `CARGOFLAGS`: + +```shell +make CARGOFLAGS='--no-fail-fast' UTILS='UTILITY_1 UTILITY_2' nextest +``` + +### Run Busybox Tests + +This testing functionality is only available on *nix operating systems and +requires `make`. + +To run busybox tests for all utilities for which busybox has tests + +```shell +make busytest +``` + +To run busybox tests for a few of the available utilities + +```shell +make UTILS='UTILITY_1 UTILITY_2' busytest +``` + +To pass an argument like "-v" to the busybox test runtime + +```shell +make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest +``` + +### Comparing with GNU + +To run uutils against the GNU test suite locally, run the following commands: + +```shell +bash util/build-gnu.sh +# Build uutils without release optimizations +UU_MAKE_PROFILE=debug bash util/build-gnu.sh +bash util/run-gnu-test.sh +# To run a single test: +bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example +# To run several tests: +bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example +# If this is a perl (.pl) test, to run in debug: +DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl +``` + +***Tip:*** First time you run `bash util/build-gnu.sh` command, it will provide instructions on how to checkout GNU coreutils repository at the correct release tag. Please follow those instructions and when done, run `bash util/build-gnu.sh` command again. + +Note that GNU test suite relies on individual utilities (not the multicall binary). + +## Code coverage report + +Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov). + +### Using Nightly Rust + +To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report + +```shell +export CARGO_INCREMENTAL=0 +export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" +export RUSTDOCFLAGS="-Cpanic=abort" +cargo build # e.g., --features feat_os_unix +cargo test # e.g., --features feat_os_unix test_pathchk +grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/ +# open target/debug/coverage/index.html in browser +``` + +if changes are not reflected in the report then run `cargo clean` and run the above commands. + +### Using Stable Rust + +If you are using stable version of Rust that doesn't enable code coverage instrumentation by default +then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above. + +## Tips for setting up on Mac + +### C Compiler and linker + +On MacOS you'll need to install C compiler & linker: + +```shell +xcode-select --install +``` + +### MacOS GNU utils + +On MacOS you will need to install [Homebrew](https://docs.brew.sh/Installation) and use it to install the following Homebrew formulas: + +```shell +brew install \ + coreutils \ + autoconf \ + gettext \ + wget \ + texinfo \ + xz \ + automake \ + gnu-sed \ + m4 \ + bison \ + pre-commit \ + findutils +``` + +After installing these Homebrew formulas, please make sure to add the following lines to your `zsh` or `bash` rc file, i.e. `~/.profile` or `~/.zshrc` or `~/.bashrc` ... +(assuming Homebrew is installed at default location `/opt/homebrew`): + +```shell +eval "$(/opt/homebrew/bin/brew shellenv)" +export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH" +export PATH="/opt/homebrew/opt/bison/bin:$PATH" +export PATH="/opt/homebrew/opt/findutils/libexec/gnubin:$PATH" +``` + +Last step is to link Homebrew coreutils version of `timeout` to `/usr/local/bin` (as admin user): + +```shell +sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout +``` + +Do not forget to either source updated rc file or restart you terminal session to update environment variables. + +## Tips for setting up on Windows + +### MSVC build tools + +On Windows you'll need the MSVC build tools for Visual Studio 2013 or later. + +If you are using `rustup-init.exe` to install Rust toolchain, it will guide you through the process of downloading and installing these prerequisites. + +Otherwise please follow [this guide](https://learn.microsoft.com/en-us/windows/dev-environment/rust/setup). + +### Windows GNU utils + +If you have used [Git for Windows](https://gitforwindows.org) to install `git` on you Windows system you might already have some GNU core utilities installed as part of "GNU Bash" included in Git for Windows package, but it is not a complete package. [This article](https://gist.github.com/evanwill/0207876c3243bbb6863e65ec5dc3f058) provides instruction on how to add more to it. + +Alternatively you can install [Cygwin](https://www.cygwin.com) and/or use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/compare-versions#whats-new-in-wsl-2) to get access to all GNU core utilities on Windows. diff --git a/GNUmakefile b/GNUmakefile index c672458a1..ad2d38081 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -102,7 +102,6 @@ PROGS := \ pwd \ readlink \ realpath \ - relpath \ rm \ rmdir \ seq \ diff --git a/README.md b/README.md index 4929224d1..4f341638b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils) [![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils) -![MSRV](https://img.shields.io/badge/MSRV-1.64.0-brightgreen) +![MSRV](https://img.shields.io/badge/MSRV-1.70.0-brightgreen) @@ -54,8 +54,8 @@ that scripts can be easily transferred between platforms. uutils has both user and developer documentation available: -- [User Manual](https://uutils.github.io/user/) -- [Developer Documentation](https://uutils.github.io/dev/coreutils/) +- [User Manual](https://uutils.github.io/coreutils/book/) +- [Developer Documentation](https://uutils.github.io/dev/coreutils/) (currently offline, you can use docs.rs in the meantime) Both can also be generated locally, the instructions for that can be found in the [coreutils docs](https://github.com/uutils/uutils.github.io) repository. @@ -71,7 +71,7 @@ the [coreutils docs](https://github.com/uutils/uutils.github.io) repository. ### Rust Version uutils follows Rust's release channels and is tested against stable, beta and -nightly. The current Minimum Supported Rust Version (MSRV) is `1.64.0`. +nightly. The current Minimum Supported Rust Version (MSRV) is `1.70.0`. ## Building @@ -303,7 +303,7 @@ make PREFIX=/my/path uninstall Below is the evolution of how many GNU tests uutils passes. A more detailed breakdown of the GNU test results of the main branch can be found -[in the user manual](https://uutils.github.io/user/test_coverage.html). +[in the user manual](https://uutils.github.io/coreutils/book/test_coverage.html). See for the main meta bugs (many are missing). diff --git a/README.target.md b/README.target.md deleted file mode 100644 index b8190ce3e..000000000 --- a/README.target.md +++ /dev/null @@ -1,28 +0,0 @@ -# Targets that compile - -**Note: this list isn't up to date.** - -This is an auto-generated table showing which binaries compile for each target-triple. Note that this **does not** indicate that they are fully implemented, or that the tests pass. - -|######OS######|###ARCH####|arch|base32|base64|basename|cat|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|df|dircolors|dirname|du|echo|env|expand|expr|factor|false|fmt|fold|groups|hashsum|head|hostid|hostname|id|install|join|kill|link|ln|logname|ls|mkdir|mkfifo|mknod|mktemp|more|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|printenv|printf|ptx|pwd|readlink|realpath|relpath|rm|rmdir|seq|shred|shuf|sleep|sort|split|stat|stdbuf|sum|sync|tac|tail|tee|test|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|uptime|users|wc|who|whoami|yes|chcon|pr|dir|vdir|dd|basenc|runcon| -|--------------|-----------|----|------|------|--------|---|-----|-----|-----|------|-----|----|--|------|---|----|--|---------|-------|--|----|---|------|----|------|-----|---|----|------|-------|----|------|--------|--|-------|----|----|----|--|-------|--|-----|------|-----|------|----|--|----|--|-----|-----|------|--|-----|-------|-----|--------|------|---|---|--------|--------|-------|--|-----|---|-----|----|-----|----|-----|----|------|---|----|---|----|---|----|-------|-----|--|----|--------|-----|---|-----|--------|----|------|------|-----|--|---|------|---|-----|--|---|----|--|------|------| -|linux-gnu|aarch64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| -|linux-gnu|i686|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| -|linux-gnu|powerpc64|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| -|linux-gnu|riscv64gc| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -|linux-gnu|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| -|windows-msvc|aarch64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y| |y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| | |y|y|y|y|y|y|y|y| -|windows-gnu|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|y|y|y|y|y|y|y| -|windows-msvc|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|y|y|y|y|y|y|y| -|windows-gnu|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|y|y|y|y|y|y|y| -|windows-msvc|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|y|y|y|y|y|y|y| -|apple MacOS|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| -|freebsd|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| -|netbsd|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|y|y|y|y|y|y|y| -|android|aarch64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|y|y|y|y|y|y|y| -|android|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|y|y|y|y|y|y|y| -|solaris|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -|wasi|wasm32| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -|redox|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -|fuchsia|aarch64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -|fuchsia|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | diff --git a/build.rs b/build.rs index 09b33fa91..bb4e2b536 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,8 @@ +// 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 (vars) krate use std::env; @@ -40,6 +45,7 @@ pub fn main() { mf.write_all( "type UtilityMap = phf::OrderedMap<&'static str, (fn(T) -> i32, fn() -> Command)>;\n\ \n\ + #[allow(clippy::too_many_lines)] fn util_map() -> UtilityMap {\n" .as_bytes(), ) diff --git a/deny.toml b/deny.toml index 84dae2b98..fa8f77c01 100644 --- a/deny.toml +++ b/deny.toml @@ -59,9 +59,12 @@ highlight = "all" # spell-checker: disable skip = [ # procfs - { name = "rustix", version = "0.36.14" }, + { name = "rustix", version = "0.36.16" }, # rustix { name = "linux-raw-sys", version = "0.1.4" }, + { name = "linux-raw-sys", version = "0.3.8" }, + # terminal_size + { name = "rustix", version = "0.37.26" }, # various crates { name = "windows-sys", version = "0.45.0" }, # windows-sys @@ -80,12 +83,14 @@ skip = [ { name = "windows_x86_64_gnullvm", version = "0.42.2" }, # windows-targets { name = "windows_x86_64_msvc", version = "0.42.2" }, - # tempfile - { name = "redox_syscall", version = "0.3.5" }, - # cpp_macros - { name = "aho-corasick", version = "0.7.19" }, # various crates { name = "syn", version = "1.0.109" }, + # various crates + { name = "bitflags", version = "1.3.2" }, + # various crates + { name = "redox_syscall", version = "0.3.5" }, + # clap_builder, textwrap + { name = "terminal_size", version = "0.2.6" }, ] # spell-checker: enable diff --git a/docs/.gitignore b/docs/.gitignore index c669da8f6..be017dfbe 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,4 +1,5 @@ book src/utils src/SUMMARY.md +src/platform_table.md tldr.zip \ No newline at end of file diff --git a/docs/compiles_table.csv b/docs/compiles_table.csv index d18854e0e..e263067b7 100644 --- a/docs/compiles_table.csv +++ b/docs/compiles_table.csv @@ -1,21 +1,21 @@ -target,arch,base32,base64,basename,cat,chgrp,chmod,chown,chroot,cksum,comm,cp,csplit,cut,date,df,dircolors,dirname,du,echo,env,expand,expr,factor,false,fmt,fold,groups,hashsum,head,hostid,hostname,id,install,join,kill,link,ln,logname,ls,mkdir,mkfifo,mknod,mktemp,more,mv,nice,nl,nohup,nproc,numfmt,od,paste,pathchk,pinky,printenv,printf,ptx,pwd,readlink,realpath,relpath,rm,rmdir,seq,shred,shuf,sleep,sort,split,stat,stdbuf,sum,sync,tac,tail,tee,test,timeout,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,wc,who,whoami,yes,chcon,pr,dir,vdir,dd,basenc,runcon -aarch64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -i686-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -powerpc64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -riscv64gc-unknown-linux-gnu,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 -x86_64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -aarch64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,101,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,101,0,0,0,0,0,0,0,0 -i686-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 -i686-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 -x86_64-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 -x86_64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 -x86_64-apple-darwin,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -x86_64-unknown-freebsd,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -x86_64-unknown-netbsd,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 -aarch64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 -x86_64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 -x86_64-sun-solaris,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 -wasm32-wasi,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 -x86_64-unknown-redox,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 -aarch64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 -x86_64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +target,arch,base32,base64,basename,cat,chgrp,chmod,chown,chroot,cksum,comm,cp,csplit,cut,date,df,dircolors,dirname,du,echo,env,expand,expr,factor,false,fmt,fold,groups,hashsum,head,hostid,hostname,id,install,join,kill,link,ln,logname,ls,mkdir,mkfifo,mknod,mktemp,more,mv,nice,nl,nohup,nproc,numfmt,od,paste,pathchk,pinky,printenv,printf,ptx,pwd,readlink,realpath,rm,rmdir,seq,shred,shuf,sleep,sort,split,stat,stdbuf,sum,sync,tac,tail,tee,test,timeout,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,wc,who,whoami,yes,chcon,pr,dir,vdir,dd,basenc,runcon +aarch64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +i686-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +powerpc64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +riscv64gc-unknown-linux-gnu,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +x86_64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +aarch64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,101,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,101,0,0,0,0,0,0,0,0 +i686-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 +i686-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 +x86_64-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 +x86_64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 +x86_64-apple-darwin,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +x86_64-unknown-freebsd,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +x86_64-unknown-netbsd,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 +aarch64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 +x86_64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 +x86_64-sun-solaris,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +wasm32-wasi,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +x86_64-unknown-redox,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +aarch64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +x86_64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 diff --git a/docs/src/extensions.md b/docs/src/extensions.md index eeb00ff35..79746498f 100644 --- a/docs/src/extensions.md +++ b/docs/src/extensions.md @@ -77,4 +77,5 @@ third way: `--long`. ## `du` -`du` allows `birth` and `creation` as values for the `--time` argument to show the creation time. +`du` allows `birth` and `creation` as values for the `--time` argument to show the creation time. It +also provides a `-v`/`--verbose` flag. diff --git a/docs/src/installation.md b/docs/src/installation.md index e5fcb6220..da124ead9 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -1,4 +1,4 @@ - + # Installation @@ -139,6 +139,16 @@ pkg install rust-coreutils scoop install uutils-coreutils ``` +## Alternative installers + +### Conda + +[Conda package](https://anaconda.org/conda-forge/uutils-coreutils) + +``` +conda install -c conda-forge uutils-coreutils +``` + ## Non-standard packages ### `coreutils-hybrid` (AUR) diff --git a/docs/src/platforms.md b/docs/src/platforms.md new file mode 100644 index 000000000..b84516e3f --- /dev/null +++ b/docs/src/platforms.md @@ -0,0 +1,45 @@ +# Platform support + + + +uutils aims to be as "universal" as possible, meaning that we try to support +many platforms. However, it is infeasible for us to guarantee that every +platform works. Just like Rust itself, we therefore have multiple tiers of +platform support, with different guarantees. We support two tiers of platforms: + + - **Tier 1**: All applicable utils are compiled and tested in CI for these + platforms. + - **Tier 2**: These platforms are supported but not actively tested. We do accept + fixes for these platforms. + +> **Note**: The tiers are dictated by our CI. We would happily accept a job +> in the CI for testing more platforms, bumping those platforms to tier 1. + +## Platforms per tier + +The platforms in tier 1 and the platforms that we test in CI are listed below. + +| Operating system | Tested targets | +| ---------------- | -------------- | +| **Linux** | `x86_64-unknown-linux-gnu`
`x86_64-unknown-linux-musl`
`arm-unknown-linux-gnueabihf`
`i686-unknown-linux-gnu`
`aarch64-unknown-linux-gnu` | +| **macOS** | `x86_64-apple-darwin` | +| **Windows** | `i686-pc-windows-msvc`
`x86_64-pc-windows-gnu`
`x86_64-pc-windows-msvc` | +| **FreeBSD** | `x86_64-unknown-freebsd` | +| **Android** | `i686-linux-android` | + +The platforms in tier 2 are more vague, but include: + + - untested variations of the platforms above, + - Redox OS, + - and BSDs such as OpenBSD, NetBSD & DragonFlyBSD. + +## Utility compatibility per platform + +Not all utils work on every platform. For instance, `chgrp` is not supported on +Windows, because Windows does not have the concept of groups. Below is a full table +detailing which utilities are supported for the tier 1 platforms. + +Note that for some utilities, not all functionality is supported on each +platform. This is documented per utility. + +{{ #include platform_table.md }} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c8565d691..549f9a6b7 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -9,12 +9,14 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" +libc = "0.2" +rand = { version = "0.8", features = ["small_rng"] } -[dependencies.uucore] -path = "../src/uucore/" +uucore = { path = "../src/uucore/" } +uu_date = { path = "../src/uu/date/" } +uu_test = { path = "../src/uu/test/" } +uu_expr = { path = "../src/uu/expr/" } -[dependencies.uu_date] -path = "../src/uu/date/" # Prevent this from interfering with workspaces [workspace] @@ -26,6 +28,18 @@ path = "fuzz_targets/fuzz_date.rs" test = false doc = false +[[bin]] +name = "fuzz_expr" +path = "fuzz_targets/fuzz_expr.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_test" +path = "fuzz_targets/fuzz_test.rs" +test = false +doc = false + [[bin]] name = "fuzz_parse_glob" path = "fuzz_targets/fuzz_parse_glob.rs" diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs new file mode 100644 index 000000000..a94963ef0 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -0,0 +1,107 @@ +// 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 libc::{dup, dup2, STDOUT_FILENO}; +use std::ffi::OsString; +use std::io; +use std::process::Command; +use std::sync::atomic::Ordering; +use std::sync::{atomic::AtomicBool, Once}; + +static CHECK_GNU: Once = Once::new(); +static IS_GNU: AtomicBool = AtomicBool::new(false); + +pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { + CHECK_GNU.call_once(|| { + let version_output = Command::new(cmd_path).arg("--version").output().unwrap(); + + println!("version_output {:#?}", version_output); + + let version_str = String::from_utf8_lossy(&version_output.stdout).to_string(); + if version_str.contains("GNU coreutils") { + IS_GNU.store(true, Ordering::Relaxed); + } + }); + + if IS_GNU.load(Ordering::Relaxed) { + Ok(()) + } else { + panic!("Not the GNU implementation"); + } +} + +pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, i32) +where + F: FnOnce(std::vec::IntoIter) -> i32, +{ + let uumain_exit_status; + + let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; + println!("Running test {:?}", &args[1..]); + let mut pipe_fds = [-1; 2]; + unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; + + { + unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; + uumain_exit_status = uumain_function(args.to_owned().into_iter()); + unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; + unsafe { libc::close(original_stdout_fd) }; + } + unsafe { libc::close(pipe_fds[1]) }; + + let mut captured_output = Vec::new(); + let mut read_buffer = [0; 1024]; + loop { + let bytes_read = unsafe { + libc::read( + pipe_fds[0], + read_buffer.as_mut_ptr() as *mut libc::c_void, + read_buffer.len(), + ) + }; + if bytes_read <= 0 { + break; + } + captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); + } + + unsafe { libc::close(pipe_fds[0]) }; + + let my_output = String::from_utf8_lossy(&captured_output) + .to_string() + .trim() + .to_owned(); + + (my_output, uumain_exit_status) +} + +pub fn run_gnu_cmd( + cmd_path: &str, + args: &[OsString], + check_gnu: bool, +) -> Result<(String, i32), io::Error> { + if check_gnu { + is_gnu_cmd(cmd_path)?; // Check if it's a GNU implementation + } + + let mut command = Command::new(cmd_path); + for arg in args { + command.arg(arg); + } + + let output = command.output()?; + let exit_code = output.status.code().unwrap_or(-1); + if output.status.success() || !check_gnu { + Ok(( + String::from_utf8_lossy(&output.stdout).to_string(), + exit_code, + )) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + format!("GNU command execution failed with exit code {}", exit_code), + )) + } +} diff --git a/fuzz/fuzz_targets/fuzz_date.rs b/fuzz/fuzz_targets/fuzz_date.rs index 96c56cc6b..0f9cb262c 100644 --- a/fuzz/fuzz_targets/fuzz_date.rs +++ b/fuzz/fuzz_targets/fuzz_date.rs @@ -9,6 +9,6 @@ fuzz_target!(|data: &[u8]| { let args = data .split(|b| *b == delim) .filter_map(|e| std::str::from_utf8(e).ok()) - .map(|e| OsString::from(e)); + .map(OsString::from); uumain(args); }); diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs new file mode 100644 index 000000000..c2217c48a --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -0,0 +1,120 @@ +// 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 parens + +#![no_main] +use libfuzzer_sys::fuzz_target; +use uu_expr::uumain; + +use rand::seq::SliceRandom; +use rand::Rng; +use std::{env, ffi::OsString}; + +mod fuzz_common; +use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; + +static CMD_PATH: &str = "expr"; + +fn generate_random_string(max_length: usize) -> String { + let mut rng = rand::thread_rng(); + let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + .chars() + .collect(); + let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence + let mut result = String::new(); + + for _ in 0..rng.gen_range(1..=max_length) { + if rng.gen_bool(0.9) { + let ch = valid_utf8.choose(&mut rng).unwrap(); + result.push(*ch); + } else { + let ch = invalid_utf8.choose(&mut rng).unwrap(); + if let Some(c) = char::from_u32(*ch as u32) { + result.push(c); + } + } + } + + result +} + +fn generate_expr(max_depth: u32) -> String { + let mut rng = rand::thread_rng(); + let ops = ["+", "-", "*", "/", "%", "<", ">", "=", "&", "|"]; + + let mut expr = String::new(); + let mut depth = 0; + let mut last_was_operator = false; + + while depth <= max_depth { + if last_was_operator || depth == 0 { + // Add a number + expr.push_str(&rng.gen_range(1..=100).to_string()); + last_was_operator = false; + } else { + // 90% chance to add an operator followed by a number + if rng.gen_bool(0.9) { + let op = *ops.choose(&mut rng).unwrap(); + expr.push_str(&format!(" {} ", op)); + last_was_operator = true; + } + // 10% chance to add a random string (potentially invalid syntax) + else { + let random_str = generate_random_string(rng.gen_range(1..=10)); + expr.push_str(&random_str); + last_was_operator = false; + } + } + depth += 1; + } + + // Ensure the expression ends with a number if it ended with an operator + if last_was_operator { + expr.push_str(&rng.gen_range(1..=100).to_string()); + } + + expr +} + +fuzz_target!(|_data: &[u8]| { + let mut rng = rand::thread_rng(); + let expr = generate_expr(rng.gen_range(0..=20)); + let mut args = vec![OsString::from("expr")]; + args.extend(expr.split_whitespace().map(OsString::from)); + + let (rust_output, uumain_exit_code) = generate_and_run_uumain(&args, uumain); + + // Use C locale to avoid false positives, like in https://github.com/uutils/coreutils/issues/5378, + // because uutils expr doesn't support localization yet + // TODO remove once uutils expr supports localization + env::set_var("LC_COLLATE", "C"); + + // Run GNU expr with the provided arguments and compare the output + match run_gnu_cmd(CMD_PATH, &args[1..], true) { + Ok((gnu_output, gnu_exit_code)) => { + let gnu_output = gnu_output.trim().to_owned(); + if uumain_exit_code != gnu_exit_code { + println!("Expression: {}", expr); + println!("Rust code: {}", uumain_exit_code); + println!("GNU code: {}", gnu_exit_code); + panic!("Different error codes"); + } + if rust_output == gnu_output { + println!( + "Outputs matched for expression: {} => Result: {}", + expr, rust_output + ); + } else { + println!("Expression: {}", expr); + println!("Rust output: {}", rust_output); + println!("GNU output: {}", gnu_output); + panic!("Different output between Rust & GNU"); + } + } + Err(_) => { + println!("GNU expr execution failed for expression: {}", expr); + } + } +}); diff --git a/fuzz/fuzz_targets/fuzz_parse_glob.rs b/fuzz/fuzz_targets/fuzz_parse_glob.rs index 061569bc4..e235c0c9d 100644 --- a/fuzz/fuzz_targets/fuzz_parse_glob.rs +++ b/fuzz/fuzz_targets/fuzz_parse_glob.rs @@ -5,6 +5,6 @@ use uucore::parse_glob; fuzz_target!(|data: &[u8]| { if let Ok(s) = std::str::from_utf8(data) { - _ = parse_glob::from_str(s) + _ = parse_glob::from_str(s); } }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs new file mode 100644 index 000000000..4805a41af --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -0,0 +1,234 @@ +// 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 STRINGSTRING INTEGERINTEGER FILEFILE + +#![no_main] +use libfuzzer_sys::fuzz_target; +use uu_test::uumain; + +use rand::seq::SliceRandom; +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; + +#[derive(PartialEq, Debug, Clone)] +enum ArgType { + STRING, + STRINGSTRING, + INTEGER, + INTEGERINTEGER, + FILE, + FILEFILE, + // Add any other types as needed +} + +static CMD_PATH: &str = "test"; + +fn generate_random_string(max_length: usize) -> String { + let mut rng = rand::thread_rng(); + let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + .chars() + .collect(); + let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence + let mut result = String::new(); + + for _ in 0..rng.gen_range(1..=max_length) { + if rng.gen_bool(0.9) { + let ch = valid_utf8.choose(&mut rng).unwrap(); + result.push(*ch); + } else { + let ch = invalid_utf8.choose(&mut rng).unwrap(); + if let Some(c) = char::from_u32(*ch as u32) { + result.push(c); + } + } + } + + result +} + +#[derive(Debug, Clone)] +struct TestArg { + arg: String, + arg_type: ArgType, +} + +fn generate_random_path(rng: &mut dyn rand::RngCore) -> &'static str { + match rng.gen_range(0..=3) { + 0 => "/dev/null", + 1 => "/dev/random", + 2 => "/tmp", + _ => "/dev/urandom", + } +} + +fn generate_test_args() -> Vec { + vec![ + TestArg { + arg: "-z".to_string(), + arg_type: ArgType::STRING, + }, + TestArg { + arg: "-n".to_string(), + arg_type: ArgType::STRING, + }, + TestArg { + arg: "=".to_string(), + arg_type: ArgType::STRINGSTRING, + }, + TestArg { + arg: "!=".to_string(), + arg_type: ArgType::STRINGSTRING, + }, + TestArg { + arg: "-eq".to_string(), + arg_type: ArgType::INTEGERINTEGER, + }, + TestArg { + arg: "-ne".to_string(), + arg_type: ArgType::INTEGERINTEGER, + }, + TestArg { + arg: "-gt".to_string(), + arg_type: ArgType::INTEGERINTEGER, + }, + TestArg { + arg: "-ge".to_string(), + arg_type: ArgType::INTEGERINTEGER, + }, + TestArg { + arg: "-lt".to_string(), + arg_type: ArgType::INTEGERINTEGER, + }, + TestArg { + arg: "-le".to_string(), + arg_type: ArgType::INTEGERINTEGER, + }, + TestArg { + arg: "-f".to_string(), + arg_type: ArgType::FILE, + }, + TestArg { + arg: "-d".to_string(), + arg_type: ArgType::FILE, + }, + TestArg { + arg: "-e".to_string(), + arg_type: ArgType::FILE, + }, + TestArg { + arg: "-ef".to_string(), + arg_type: ArgType::FILEFILE, + }, + TestArg { + arg: "-nt".to_string(), + arg_type: ArgType::FILEFILE, + }, + ] +} + +fn generate_test_arg() -> String { + let mut rng = rand::thread_rng(); + let test_args = generate_test_args(); + let mut arg = String::new(); + + let choice = rng.gen_range(0..=5); + + match choice { + 0 => { + arg.push_str(&rng.gen_range(-100..=100).to_string()); + } + 1..=3 => { + let test_arg = test_args + .choose(&mut rng) + .expect("Failed to choose a random test argument"); + if test_arg.arg_type == ArgType::INTEGER { + arg.push_str(&format!( + "{} {} {}", + &rng.gen_range(-100..=100).to_string(), + test_arg.arg, + &rng.gen_range(-100..=100).to_string() + )); + } else if test_arg.arg_type == ArgType::STRINGSTRING { + let random_str = generate_random_string(rng.gen_range(1..=10)); + let random_str2 = generate_random_string(rng.gen_range(1..=10)); + + arg.push_str(&format!( + "{} {} {}", + &random_str, test_arg.arg, &random_str2 + )); + } else if test_arg.arg_type == ArgType::STRING { + let random_str = generate_random_string(rng.gen_range(1..=10)); + arg.push_str(&format!("{} {}", test_arg.arg, &random_str)); + } else if test_arg.arg_type == ArgType::FILEFILE { + let path = generate_random_path(&mut rng); + let path2 = generate_random_path(&mut rng); + arg.push_str(&format!("{} {} {}", path, test_arg.arg, path2)); + } else if test_arg.arg_type == ArgType::FILE { + let path = generate_random_path(&mut rng); + arg.push_str(&format!("{} {}", test_arg.arg, path)); + } + } + 4 => { + let random_str = generate_random_string(rng.gen_range(1..=10)); + arg.push_str(&random_str); + } + _ => { + let path = generate_random_path(&mut rng); + + let file_test_args: Vec = test_args + .iter() + .filter(|ta| ta.arg_type == ArgType::FILE) + .cloned() + .collect(); + + if let Some(test_arg) = file_test_args.choose(&mut rng) { + arg.push_str(&format!("{}{}", test_arg.arg, path)); + } + } + } + + arg +} + +fuzz_target!(|_data: &[u8]| { + let mut rng = rand::thread_rng(); + let max_args = rng.gen_range(1..=6); + let mut args = vec![OsString::from("test")]; + + for _ in 0..max_args { + args.push(OsString::from(generate_test_arg())); + } + + let (rust_output, uumain_exit_status) = generate_and_run_uumain(&args, uumain); + + // Run GNU test with the provided arguments and compare the output + match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok((gnu_output, gnu_exit_status)) => { + let gnu_output = gnu_output.trim().to_owned(); + println!("gnu_exit_status {}", gnu_exit_status); + println!("uumain_exit_status {}", uumain_exit_status); + if rust_output != gnu_output || uumain_exit_status != gnu_exit_status { + println!("Discrepancy detected!"); + println!("Test: {:?}", &args[1..]); + println!("My output: {}", rust_output); + println!("GNU output: {}", gnu_output); + println!("My exit status: {}", uumain_exit_status); + println!("GNU exit status: {}", gnu_exit_status); + panic!(); + } else { + println!( + "Outputs and exit statuses matched for expression {:?}", + &args[1..] + ); + } + } + Err(_) => { + println!("GNU test execution failed for expression {:?}", &args[1..]); + } + } +}); diff --git a/oranda.json b/oranda.json index b0a93c19a..0a6856a88 100644 --- a/oranda.json +++ b/oranda.json @@ -2,8 +2,13 @@ "project": { "name": "uutils coreutils" }, + "build": { + "path_prefix": "coreutils" + }, "components": { - "changelog": true + "changelog": { + "read_changelog_file": false + } }, "styles": { "theme": "light", diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index d6487dc49..fc2cd16ad 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Michael Gehring -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/bin/uudoc.rs b/src/bin/uudoc.rs index 5ac858226..77c7a2fcf 100644 --- a/src/bin/uudoc.rs +++ b/src/bin/uudoc.rs @@ -42,6 +42,7 @@ fn main() -> io::Result<()> { [Introduction](index.md)\n\ * [Installation](installation.md)\n\ * [Build from source](build.md)\n\ + * [Platform support](platforms.md)\n\ * [Contributing](contributing.md)\n\ * [GNU test coverage](test_coverage.md)\n\ * [Extensions](extensions.md)\n\ @@ -53,7 +54,7 @@ fn main() -> io::Result<()> { println!("Gathering utils per platform"); let utils_per_platform = { let mut map = HashMap::new(); - for platform in ["unix", "macos", "windows"] { + for platform in ["unix", "macos", "windows", "unix_android"] { let platform_utils: Vec = String::from_utf8( std::process::Command::new("./util/show-utils.sh") .arg(format!("--features=feat_os_{}", platform)) @@ -61,6 +62,7 @@ fn main() -> io::Result<()> { .stdout, ) .unwrap() + .trim() .split(' ') .map(ToString::to_string) .collect(); @@ -75,6 +77,7 @@ fn main() -> io::Result<()> { .stdout, ) .unwrap() + .trim() .split(' ') .map(ToString::to_string) .collect(); @@ -83,9 +86,47 @@ fn main() -> io::Result<()> { map }; - println!("Writing to utils"); let mut utils = utils.entries().collect::>(); utils.sort(); + + println!("Writing util per platform table"); + { + let mut platform_table_file = File::create("docs/src/platform_table.md").unwrap(); + + // sum, cksum, b2sum, etc. are all available on all platforms, but not in the data structure + // otherwise, we check the map for the util name. + let check_supported = |name: &str, platform: &str| { + if name.ends_with("sum") || utils_per_platform[platform].iter().any(|u| u == name) { + "✓" + } else { + " " + } + }; + writeln!( + platform_table_file, + "| util | Linux | macOS | Windows | FreeBSD | Android |\n\ + | ---------------- | ----- | ----- | ------- | ------- | ------- |" + )?; + for (&name, _) in &utils { + if name == "[" { + continue; + } + // The alignment is not necessary, but makes the output a bit more + // pretty when viewed as plain markdown. + writeln!( + platform_table_file, + "| {:<16} | {:<5} | {:<5} | {:<7} | {:<7} | {:<7} |", + format!("**{name}**"), + check_supported(name, "linux"), + check_supported(name, "macos"), + check_supported(name, "windows"), + check_supported(name, "unix"), + check_supported(name, "unix_android"), + )?; + } + } + + println!("Writing to utils"); for (&name, (_, command)) in utils { if name == "[" { continue; diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index 35e7e3892..9686c3f0a 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_arch" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "arch ~ (uutils) display machine architecture" diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 96eba1ef9..0d71a8183 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -1,8 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Smigle00 -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index 97c853eb6..f3bca4a19 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base32" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 740e2d70b..09250421c 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -1,9 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. use std::io::{stdin, Read}; diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 78698e8b9..4a30705af 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -1,11 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Jordy Dickinson -// (c) Jian Zeng -// (c) Alex Lyon -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. use std::io::{stdout, Read, Write}; diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index ef4a063fe..ddd669b89 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base64" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index e502482e3..6544638bd 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -1,10 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Jordy Dickinson -// (c) Jian Zeng -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. use uu_base32::base_common; pub use uu_base32::uu_app; diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index 4fd2b6f8c..e74ad7616 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basename" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index ed7faee65..6c9baca6f 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jimmy Lu -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -11,6 +9,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::path::{is_separator, PathBuf}; use uucore::display::Quotable; use uucore::error::{UResult, UUsageError}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; static ABOUT: &str = help_about!("basename.md"); @@ -54,9 +53,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(UUsageError::new(1, "missing operand".to_string())); } + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); + let opt_suffix = matches.get_one::(options::SUFFIX).is_some(); let opt_multiple = matches.get_flag(options::MULTIPLE); - let opt_zero = matches.get_flag(options::ZERO); let multiple_paths = opt_suffix || opt_multiple; let name_args_count = matches .get_many::(options::NAME) @@ -105,7 +105,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect() }; - let line_ending = if opt_zero { "\0" } else { "\n" }; for path in paths { print!("{}{}", basename(path, suffix), line_ending); } diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index 4676a6d8f..7f4188440 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basenc" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "basenc ~ (uutils) decode/encode input" diff --git a/src/uu/basenc/src/basenc.rs b/src/uu/basenc/src/basenc.rs index 3ec8cede0..ff512b176 100644 --- a/src/uu/basenc/src/basenc.rs +++ b/src/uu/basenc/src/basenc.rs @@ -1,10 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Jordy Dickinson -// (c) Jian Zeng -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. //spell-checker:ignore (args) lsbf msbf diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index 636e3bfef..33488cd07 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cat" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "cat ~ (uutils) concatenate and display input" @@ -17,7 +17,6 @@ path = "src/cat.rs" [dependencies] clap = { workspace = true } thiserror = { workspace = true } -is-terminal = { workspace = true } uucore = { workspace = true, features = ["fs", "pipes"] } [target.'cfg(unix)'.dependencies] diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 2c4117a32..d49f4aa07 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -1,10 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jordi Boggiano -// (c) Evgeniy Klyuchikov -// (c) Joshua S. Miller -// (c) Árni Dagur -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -12,9 +7,8 @@ // last synced with: cat (GNU coreutils) 8.13 use clap::{crate_version, Arg, ArgAction, Command}; -use is_terminal::IsTerminal; use std::fs::{metadata, File}; -use std::io::{self, Read, Write}; +use std::io::{self, IsTerminal, Read, Write}; use thiserror::Error; use uucore::display::Quotable; use uucore::error::UResult; @@ -192,7 +186,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { NumberingMode::None }; - let show_nonprint = vec![ + let show_nonprint = [ options::SHOW_ALL.to_owned(), options::SHOW_NONPRINTING_ENDS.to_owned(), options::SHOW_NONPRINTING_TABS.to_owned(), @@ -201,7 +195,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .iter() .any(|v| matches.get_flag(v)); - let show_ends = vec![ + let show_ends = [ options::SHOW_ENDS.to_owned(), options::SHOW_ALL.to_owned(), options::SHOW_NONPRINTING_ENDS.to_owned(), @@ -209,7 +203,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .iter() .any(|v| matches.get_flag(v)); - let show_tabs = vec![ + let show_tabs = [ options::SHOW_ALL.to_owned(), options::SHOW_TABS.to_owned(), options::SHOW_NONPRINTING_TABS.to_owned(), diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index 26802c7e6..6c2b6d3da 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -1,3 +1,7 @@ +// 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 super::{CatResult, FdReadable, InputHandle}; use nix::unistd; @@ -17,7 +21,7 @@ const BUF_SIZE: usize = 1024 * 16; /// copying or not. False means we don't have to. #[inline] pub(super) fn write_fast_using_splice( - handle: &mut InputHandle, + handle: &InputHandle, write_fd: &impl AsRawFd, ) -> CatResult { let (pipe_rd, pipe_wr) = pipe()?; diff --git a/src/uu/chcon/Cargo.toml b/src/uu/chcon/Cargo.toml index f621aa012..07bd73f4b 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chcon" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "chcon ~ (uutils) change file security context" diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 63ae3abea..ec111c853 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -1,3 +1,7 @@ +// 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 (vars) RFILE #![allow(clippy::upper_case_acronyms)] diff --git a/src/uu/chcon/src/errors.rs b/src/uu/chcon/src/errors.rs index 0bd61ec4a..10d5735a0 100644 --- a/src/uu/chcon/src/errors.rs +++ b/src/uu/chcon/src/errors.rs @@ -1,3 +1,7 @@ +// 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; use std::fmt::Write; use std::io; diff --git a/src/uu/chcon/src/fts.rs b/src/uu/chcon/src/fts.rs index 89dd6184d..a81cb39b6 100644 --- a/src/uu/chcon/src/fts.rs +++ b/src/uu/chcon/src/fts.rs @@ -1,3 +1,7 @@ +// 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::{CStr, CString, OsStr}; use std::marker::PhantomData; use std::os::raw::{c_int, c_long, c_short}; diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 9ca3dc26a..9591e1365 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chgrp" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index bd01e132b..fba2cef16 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index a788e83ca..d10d0e08d 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chmod" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "chmod ~ (uutils) change mode of FILE" diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index a79886037..31663b1af 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Alex Lyon -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -337,9 +335,7 @@ impl Chmoder { let mut new_mode = fperm; let mut naively_expected_new_mode = new_mode; for mode in cmode_unwrapped.split(',') { - // cmode is guaranteed to be Some in this case - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { + let result = if mode.chars().any(|c| c.is_ascii_digit()) { mode::parse_numeric(new_mode, mode, file.is_dir()).map(|v| (v, v)) } else { mode::parse_symbolic(new_mode, mode, get_umask(), file.is_dir()).map(|m| { @@ -354,20 +350,22 @@ impl Chmoder { (m, naive_mode) }) }; + match result { Ok((mode, naive_mode)) => { new_mode = mode; naively_expected_new_mode = naive_mode; } Err(f) => { - if self.quiet { - return Err(ExitCode::new(1)); + return if self.quiet { + Err(ExitCode::new(1)) } else { - return Err(USimpleError::new(1, f)); - } + Err(USimpleError::new(1, f)) + }; } } } + 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 { @@ -438,25 +436,25 @@ mod tests { fn test_extract_negative_modes() { // "chmod -w -r file" becomes "chmod -w,-r file". clap does not accept "-w,-r" as MODE. // Therefore, "w" is added as pseudo mode to pass clap. - let (c, a) = extract_negative_modes(vec!["-w", "-r", "file"].iter().map(OsString::from)); + let (c, a) = extract_negative_modes(["-w", "-r", "file"].iter().map(OsString::from)); assert_eq!(c, Some("-w,-r".to_string())); - assert_eq!(a, vec!["w", "file"]); + assert_eq!(a, ["w", "file"]); // "chmod -w file -r" becomes "chmod -w,-r file". clap does not accept "-w,-r" as MODE. // Therefore, "w" is added as pseudo mode to pass clap. - let (c, a) = extract_negative_modes(vec!["-w", "file", "-r"].iter().map(OsString::from)); + let (c, a) = extract_negative_modes(["-w", "file", "-r"].iter().map(OsString::from)); assert_eq!(c, Some("-w,-r".to_string())); - assert_eq!(a, vec!["w", "file"]); + assert_eq!(a, ["w", "file"]); // "chmod -w -- -r file" becomes "chmod -w -r file", where "-r" is interpreted as file. // Again, "w" is needed as pseudo mode. - let (c, a) = extract_negative_modes(vec!["-w", "--", "-r", "f"].iter().map(OsString::from)); + let (c, a) = extract_negative_modes(["-w", "--", "-r", "f"].iter().map(OsString::from)); assert_eq!(c, Some("-w".to_string())); - assert_eq!(a, vec!["w", "--", "-r", "f"]); + assert_eq!(a, ["w", "--", "-r", "f"]); // "chmod -- -r file" becomes "chmod -r file". - let (c, a) = extract_negative_modes(vec!["--", "-r", "file"].iter().map(OsString::from)); + let (c, a) = extract_negative_modes(["--", "-r", "file"].iter().map(OsString::from)); assert_eq!(c, None); - assert_eq!(a, vec!["--", "-r", "file"]); + assert_eq!(a, ["--", "-r", "file"]); } } diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index 3b92e288e..bbd75db03 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chown" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 67e71b815..0e9b8b242 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -197,6 +195,53 @@ pub fn uu_app() -> Command { ) } +/// Parses the user string to extract the UID. +fn parse_uid(user: &str, spec: &str, sep: char) -> UResult> { + if user.is_empty() { + return Ok(None); + } + match Passwd::locate(user) { + Ok(u) => Ok(Some(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 + if spec.contains('.') && !spec.contains(':') && sep == ':' { + // but the input contains a '.' but not a ':' + // we might have something like username.groupname + // So, try to parse it this way + parse_spec(spec, '.').map(|(uid, _)| uid) + } else { + // It's possible that the `user` string contains a + // numeric user ID, in which case, we respect that. + match user.parse() { + Ok(uid) => Ok(Some(uid)), + Err(_) => Err(USimpleError::new( + 1, + format!("invalid user: {}", spec.quote()), + )), + } + } + } + } +} + +/// Parses the group string to extract the GID. +fn parse_gid(group: &str, spec: &str) -> UResult> { + if group.is_empty() { + return Ok(None); + } + match Group::locate(group) { + Ok(g) => Ok(Some(g.gid)), + Err(_) => match group.parse() { + Ok(gid) => Ok(Some(gid)), + Err(_) => Err(USimpleError::new( + 1, + format!("invalid group: {}", spec.quote()), + )), + }, + } +} + /// Parse the owner/group specifier string into a user ID and a group ID. /// /// The `spec` can be of the form: @@ -215,52 +260,8 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { let user = args.next().unwrap_or(""); let group = args.next().unwrap_or(""); - let uid = if user.is_empty() { - None - } else { - Some(match Passwd::locate(user) { - 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 - { - if spec.contains('.') && !spec.contains(':') && sep == ':' { - // but the input contains a '.' but not a ':' - // we might have something like username.groupname - // So, try to parse it this way - return parse_spec(spec, '.'); - } else { - // It's possible that the `user` string contains a - // numeric user ID, in which case, we respect that. - match user.parse() { - Ok(uid) => uid, - Err(_) => { - return Err(USimpleError::new( - 1, - format!("invalid user: {}", spec.quote()), - )) - } - } - } - } - }) - }; - let gid = if group.is_empty() { - None - } else { - Some(match Group::locate(group) { - Ok(g) => g.gid, - Err(_) => match group.parse() { - Ok(gid) => gid, - Err(_) => { - return Err(USimpleError::new( - 1, - format!("invalid group: {}", spec.quote()), - )); - } - }, - }) - }; + let uid = parse_uid(user, spec, sep)?; + let gid = parse_gid(group, spec)?; if user.chars().next().map(char::is_numeric).unwrap_or(false) && group.is_empty() diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index d2f58320d..847b18ef5 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chroot" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 2d1c3703e..6366775c3 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -1,8 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Vsevolod Velichko -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uu/chroot/src/error.rs b/src/uu/chroot/src/error.rs index 43ef98595..526f1a75a 100644 --- a/src/uu/chroot/src/error.rs +++ b/src/uu/chroot/src/error.rs @@ -1,7 +1,7 @@ -// * 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. +// 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; diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index 70886df05..e0d54edb9 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cksum" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index a46f69302..629bb457f 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -1,8 +1,6 @@ // This file is part of the uutils coreutils package. // -// (c) Michael Gehring -// -// For the full copyright and license information, please view the LICENSE +// For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore (ToDO) fname, algo @@ -209,8 +207,7 @@ fn digest_read( Ok((digest.result_str(), output_size)) } else { // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) - let mut bytes = Vec::new(); - bytes.resize((output_bits + 7) / 8, 0); + let mut bytes = vec![0; (output_bits + 7) / 8]; digest.hash_finalize(&mut bytes); Ok((encode(bytes), output_size)) } diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index b86aa4902..07430f9d9 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_comm" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "comm ~ (uutils) compare sorted inputs" diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 26e704037..e6977142e 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -1,18 +1,16 @@ // This file is part of the uutils coreutils package. // -// (c) Michael Gehring -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore (ToDO) delim mkdelim use std::cmp::Ordering; -use std::fmt::Display; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; use uucore::error::{FromIo, UResult}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; @@ -32,46 +30,6 @@ mod options { pub const ZERO_TERMINATED: &str = "zero-terminated"; } -fn column_width(col: &str, opts: &ArgMatches) -> usize { - if opts.get_flag(col) { - 0 - } else { - 1 - } -} - -#[repr(u8)] -#[derive(Clone, Copy)] -enum LineEnding { - Newline = b'\n', - Nul = 0, -} - -impl From for u8 { - fn from(line_ending: LineEnding) -> Self { - line_ending as Self - } -} - -impl From for LineEnding { - fn from(is_zero_terminated: bool) -> Self { - if is_zero_terminated { - Self::Nul - } else { - Self::Newline - } - } -} - -impl Display for LineEnding { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Newline => writeln!(f), - Self::Nul => write!(f, "\0"), - } - } -} - enum Input { Stdin(Stdin), FileIn(BufReader), @@ -109,8 +67,8 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) { delim => delim, }; - let width_col_1 = column_width(options::COLUMN_1, opts); - let width_col_2 = column_width(options::COLUMN_2, opts); + let width_col_1 = usize::from(!opts.get_flag(options::COLUMN_1)); + let width_col_2 = usize::from(!opts.get_flag(options::COLUMN_2)); let delim_col_2 = delim.repeat(width_col_1); let delim_col_3 = delim.repeat(width_col_1 + width_col_2); @@ -168,7 +126,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) { } if opts.get_flag(options::TOTAL) { - let line_ending = LineEnding::from(opts.get_flag(options::ZERO_TERMINATED)); + let line_ending = LineEnding::from_zero_flag(opts.get_flag(options::ZERO_TERMINATED)); print!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total{line_ending}"); } } @@ -190,7 +148,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_lossy(); let matches = uu_app().try_get_matches_from(args)?; - let line_ending = LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)); + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); let filename1 = matches.get_one::(options::FILE_1).unwrap(); let filename2 = matches.get_one::(options::FILE_2).unwrap(); let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?; diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index c768bde0c..49ba75ca0 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cp" -version = "0.0.20" +version = "0.0.22" authors = [ "Jordy Dickinson ", "Joshua S. Miller ", @@ -24,7 +24,14 @@ filetime = { workspace = true } libc = { workspace = true } quick-error = { workspace = true } selinux = { workspace = true, optional = true } -uucore = { workspace = true, features = ["entries", "fs", "perms", "mode"] } +uucore = { workspace = true, features = [ + "backup-control", + "entries", + "fs", + "perms", + "mode", + "update-control", +] } walkdir = { workspace = true } indicatif = { workspace = true } diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index aaeb73f5a..a8b941364 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -1,14 +1,14 @@ -// * 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. +// 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 TODO canonicalizes direntry pathbuf symlinked //! Recursively copy the contents of a directory. //! //! See the [`copy_directory`] function for more information. #[cfg(windows)] use std::borrow::Cow; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::env; use std::fs; use std::io; @@ -24,8 +24,8 @@ use uucore::uio_error; use walkdir::{DirEntry, WalkDir}; use crate::{ - aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, preserve_hardlinks, - CopyResult, Error, Options, TargetSlice, + aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, CopyResult, Error, + Options, }; /// Ensure a Windows path starts with a `\\?`. @@ -33,8 +33,8 @@ use crate::{ fn adjust_canonicalization(p: &Path) -> Cow { // In some cases, \\? can be missing on some Windows paths. Add it at the // beginning unless the path is prefixed with a device namespace. - const VERBATIM_PREFIX: &str = r#"\\?"#; - const DEVICE_NS_PREFIX: &str = r#"\\."#; + const VERBATIM_PREFIX: &str = r"\\?"; + const DEVICE_NS_PREFIX: &str = r"\\."; let has_prefix = p .components() @@ -200,7 +200,7 @@ fn copy_direntry( options: &Options, symlinked_files: &mut HashSet, preserve_hard_links: bool, - hard_links: &mut Vec<(String, u64)>, + copied_files: &mut HashMap, ) -> CopyResult<()> { let Entry { source_absolute, @@ -240,30 +240,27 @@ fn copy_direntry( // If the source is not a directory, then we need to copy the file. if !source_absolute.is_dir() { if preserve_hard_links { - let dest = local_to_target.as_path().to_path_buf(); - let found_hard_link = preserve_hardlinks(hard_links, &source_absolute, &dest)?; - if !found_hard_link { - match copy_file( - progress_bar, - &source_absolute, - local_to_target.as_path(), - options, - symlinked_files, - false, - ) { - Ok(_) => Ok(()), - Err(err) => { - if source_absolute.is_symlink() { - // silent the error with a symlink - // In case we do --archive, we might copy the symlink - // before the file itself - Ok(()) - } else { - Err(err) - } + match copy_file( + progress_bar, + &source_absolute, + local_to_target.as_path(), + options, + symlinked_files, + copied_files, + false, + ) { + Ok(_) => Ok(()), + Err(err) => { + if source_absolute.is_symlink() { + // silent the error with a symlink + // In case we do --archive, we might copy the symlink + // before the file itself + Ok(()) + } else { + Err(err) } - }?; - } + } + }?; } else { // At this point, `path` is just a plain old file. // Terminate this function immediately if there is any @@ -277,6 +274,7 @@ fn copy_direntry( local_to_target.as_path(), options, symlinked_files, + copied_files, false, ) { Ok(_) => {} @@ -307,9 +305,10 @@ fn copy_direntry( pub(crate) fn copy_directory( progress_bar: &Option, root: &Path, - target: &TargetSlice, + target: &Path, options: &Options, symlinked_files: &mut HashSet, + copied_files: &mut HashMap, source_in_command_line: bool, ) -> CopyResult<()> { if !options.recursive { @@ -324,6 +323,7 @@ pub(crate) fn copy_directory( target, options, symlinked_files, + copied_files, source_in_command_line, ); } @@ -372,7 +372,6 @@ pub(crate) fn copy_directory( }; let target = tmp.as_path(); - let mut hard_links: Vec<(String, u64)> = vec![]; let preserve_hard_links = options.preserve_hard_links(); // Collect some paths here that are invariant during the traversal @@ -397,7 +396,7 @@ pub(crate) fn copy_directory( options, symlinked_files, preserve_hard_links, - &mut hard_links, + copied_files, )?; } // Print an error message, but continue traversing the directory. @@ -448,6 +447,7 @@ mod tests { use super::ends_with_slash_dot; #[test] + #[allow(clippy::cognitive_complexity)] fn test_ends_with_slash_dot() { assert!(ends_with_slash_dot("/.")); assert!(ends_with_slash_dot("./.")); diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index de9dd1c91..dce35a8b9 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1,19 +1,15 @@ +// 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 (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO #![allow(clippy::missing_safety_doc)] #![allow(clippy::extra_unused_lifetimes)] -// This file is part of the uutils coreutils package. -// -// (c) Jordy Dickinson -// (c) Joshua S. Miller -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. - -// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv - use quick_error::quick_error; use std::borrow::Cow; -use std::collections::HashSet; +use std::cmp::Ordering; +use std::collections::{HashMap, HashSet}; use std::env; #[cfg(not(windows))] use std::ffi::CString; @@ -34,14 +30,16 @@ use libc::mkfifo; use quick_error::ResultExt; use platform::copy_on_write; -use uucore::backup_control::{self, BackupMode}; use uucore::display::Quotable; use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError}; use uucore::fs::{ canonicalize, is_symlink_loop, paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode, }; -use uucore::update_control::{self, UpdateMode}; +use uucore::{backup_control, update_control}; +// These are exposed for projects (e.g. nushell) that want to create an `Options` value, which +// requires these enum. +pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; use uucore::{ crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error, show_warning, util_name, @@ -51,6 +49,7 @@ use crate::copydir::copy_directory; mod copydir; mod platform; + quick_error! { #[derive(Debug)] pub enum Error { @@ -108,12 +107,8 @@ impl UError for Error { } pub type CopyResult = Result; -pub type Source = PathBuf; -pub type SourceSlice = Path; -pub type Target = PathBuf; -pub type TargetSlice = Path; -/// Specifies whether when overwrite files +/// Specifies how to overwrite files. #[derive(Clone, Copy, Eq, PartialEq)] pub enum ClobberMode { Force, @@ -121,7 +116,7 @@ pub enum ClobberMode { Standard, } -/// Specifies whether when overwrite files +/// Specifies whether files should be overwritten. #[derive(Clone, Copy, Eq, PartialEq)] pub enum OverwriteMode { /// [Default] Always overwrite existing files @@ -148,12 +143,13 @@ pub enum SparseMode { Never, } -/// Specifies the expected file type of copy target +/// The expected file type of copy target pub enum TargetType { Directory, File, } +/// Copy action to perform pub enum CopyMode { Link, SymLink, @@ -162,77 +158,130 @@ pub enum CopyMode { AttrOnly, } +/// Preservation settings for various attributes +/// +/// It should be derived from options as follows: +/// +/// - if there is a list of attributes to preserve (i.e. `--preserve=ATTR_LIST`) parse that list with [`Attributes::parse_iter`], +/// - if `-p` or `--preserve` is given without arguments, use [`Attributes::DEFAULT`], +/// - if `-a`/`--archive` is passed, use [`Attributes::ALL`], +/// - if `-d` is passed use [`Attributes::LINKS`], +/// - otherwise, use [`Attributes::NONE`]. +/// +/// For full compatibility with GNU, these options should also combine. We +/// currently only do a best effort imitation of that behavior, because it is +/// difficult to achieve in clap, especially with `--no-preserve`. #[derive(Debug)] pub struct Attributes { #[cfg(unix)] - ownership: Preserve, - mode: Preserve, - timestamps: Preserve, - context: Preserve, - links: Preserve, - xattr: Preserve, + pub ownership: Preserve, + pub mode: Preserve, + pub timestamps: Preserve, + pub context: Preserve, + pub links: Preserve, + pub xattr: Preserve, } -impl Attributes { - pub(crate) fn max(&mut self, other: Self) { - #[cfg(unix)] - { - self.ownership = self.ownership.max(other.ownership); - } - self.mode = self.mode.max(other.mode); - self.timestamps = self.timestamps.max(other.timestamps); - self.context = self.context.max(other.context); - self.links = self.links.max(other.links); - self.xattr = self.xattr.max(other.xattr); - } -} - -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Preserve { - No, + // explicit means whether the --no-preserve flag is used or not to distinguish out the default value. + // e.g. --no-preserve=mode means mode = No { explicit = true } + No { explicit: bool }, Yes { required: bool }, } -impl Preserve { - /// Preservation level should only increase, with no preservation being the lowest option, - /// preserve but don't require - middle, and preserve and require - top. - pub(crate) fn max(&self, other: Self) -> Self { +impl PartialOrd for Preserve { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Preserve { + fn cmp(&self, other: &Self) -> Ordering { match (self, other) { - (Self::Yes { required: true }, _) | (_, Self::Yes { required: true }) => { - Self::Yes { required: true } - } - (Self::Yes { required: false }, _) | (_, Self::Yes { required: false }) => { - Self::Yes { required: false } - } - _ => Self::No, + (Self::No { .. }, Self::No { .. }) => Ordering::Equal, + (Self::Yes { .. }, Self::No { .. }) => Ordering::Greater, + (Self::No { .. }, Self::Yes { .. }) => Ordering::Less, + ( + Self::Yes { required: req_self }, + Self::Yes { + required: req_other, + }, + ) => req_self.cmp(req_other), } } } -/// Re-usable, extensible copy options +/// Options for the `cp` command +/// +/// All options are public so that the options can be programmatically +/// constructed by other crates, such as nushell. That means that this struct +/// is part of our public API. It should therefore not be changed without good +/// reason. +/// +/// The fields are documented with the arguments that determine their value. #[allow(dead_code)] pub struct Options { - attributes_only: bool, - backup: BackupMode, - copy_contents: bool, - cli_dereference: bool, - copy_mode: CopyMode, - dereference: bool, - no_target_dir: bool, - one_file_system: bool, - overwrite: OverwriteMode, - parents: bool, - sparse_mode: SparseMode, - strip_trailing_slashes: bool, - reflink_mode: ReflinkMode, - attributes: Attributes, - recursive: bool, - backup_suffix: String, - target_dir: Option, - update: UpdateMode, - debug: bool, - verbose: bool, - progress_bar: bool, + /// `--attributes-only` + pub attributes_only: bool, + /// `--backup[=CONTROL]`, `-b` + pub backup: BackupMode, + /// `--copy-contents` + pub copy_contents: bool, + /// `-H` + pub cli_dereference: bool, + /// Determines the type of copying that should be done + /// + /// Set by the following arguments: + /// - `-l`, `--link`: [`CopyMode::Link`] + /// - `-s`, `--symbolic-link`: [`CopyMode::SymLink`] + /// - `-u`, `--update[=WHEN]`: [`CopyMode::Update`] + /// - `--attributes-only`: [`CopyMode::AttrOnly`] + /// - otherwise: [`CopyMode::Copy`] + pub copy_mode: CopyMode, + /// `-L`, `--dereference` + pub dereference: bool, + /// `-T`, `--no-target-dir` + pub no_target_dir: bool, + /// `-x`, `--one-file-system` + pub one_file_system: bool, + /// Specifies what to do with an existing destination + /// + /// Set by the following arguments: + /// - `-i`, `--interactive`: [`OverwriteMode::Interactive`] + /// - `-n`, `--no-clobber`: [`OverwriteMode::NoClobber`] + /// - otherwise: [`OverwriteMode::Clobber`] + /// + /// The `Interactive` and `Clobber` variants have a [`ClobberMode`] argument, + /// set by the following arguments: + /// - `-f`, `--force`: [`ClobberMode::Force`] + /// - `--remove-destination`: [`ClobberMode::RemoveDestination`] + /// - otherwise: [`ClobberMode::Standard`] + pub overwrite: OverwriteMode, + /// `--parents` + pub parents: bool, + /// `--sparse[=WHEN]` + pub sparse_mode: SparseMode, + /// `--strip-trailing-slashes` + pub strip_trailing_slashes: bool, + /// `--reflink[=WHEN]` + pub reflink_mode: ReflinkMode, + /// `--preserve=[=ATTRIBUTE_LIST]` and `--no-preserve=ATTRIBUTE_LIST` + pub attributes: Attributes, + /// `-R`, `-r`, `--recursive` + pub recursive: bool, + /// `-S`, `--suffix` + pub backup_suffix: String, + /// `-t`, `--target-directory` + pub target_dir: Option, + /// `--update[=UPDATE]` + pub update: UpdateMode, + /// `--debug` + pub debug: bool, + /// `-v`, `--verbose` + pub verbose: bool, + /// `-g`, `--progress` + pub progress_bar: bool, } /// Enum representing various debug states of the offload and reflink actions. @@ -741,67 +790,90 @@ impl CopyMode { } impl Attributes { + pub const ALL: Self = Self { + #[cfg(unix)] + ownership: Preserve::Yes { required: true }, + mode: Preserve::Yes { required: true }, + timestamps: Preserve::Yes { required: true }, + context: { + #[cfg(feature = "feat_selinux")] + { + Preserve::Yes { required: false } + } + #[cfg(not(feature = "feat_selinux"))] + { + Preserve::No { explicit: false } + } + }, + links: Preserve::Yes { required: true }, + xattr: Preserve::Yes { required: false }, + }; + + pub const NONE: Self = Self { + #[cfg(unix)] + ownership: Preserve::No { explicit: false }, + mode: Preserve::No { explicit: false }, + timestamps: Preserve::No { explicit: false }, + context: Preserve::No { explicit: false }, + links: Preserve::No { explicit: false }, + xattr: Preserve::No { explicit: false }, + }; + // TODO: ownership is required if the user is root, for non-root users it's not required. - // See: https://github.com/coreutils/coreutils/blob/master/src/copy.c#L3181 + pub const DEFAULT: Self = Self { + #[cfg(unix)] + ownership: Preserve::Yes { required: true }, + mode: Preserve::Yes { required: true }, + timestamps: Preserve::Yes { required: true }, + ..Self::NONE + }; - fn all() -> Self { + pub const LINKS: Self = Self { + links: Preserve::Yes { required: true }, + ..Self::NONE + }; + + pub fn union(self, other: &Self) -> Self { Self { #[cfg(unix)] - ownership: Preserve::Yes { required: true }, - mode: Preserve::Yes { required: true }, - timestamps: Preserve::Yes { required: true }, - context: { - #[cfg(feature = "feat_selinux")] - { - Preserve::Yes { required: false } - } - #[cfg(not(feature = "feat_selinux"))] - { - Preserve::No - } - }, - links: Preserve::Yes { required: true }, - xattr: Preserve::Yes { required: false }, + ownership: self.ownership.max(other.ownership), + context: self.context.max(other.context), + timestamps: self.timestamps.max(other.timestamps), + mode: self.mode.max(other.mode), + links: self.links.max(other.links), + xattr: self.xattr.max(other.xattr), } } - fn default() -> Self { - Self { - #[cfg(unix)] - ownership: Preserve::Yes { required: true }, - mode: Preserve::Yes { required: true }, - timestamps: Preserve::Yes { required: true }, - context: Preserve::No, - links: Preserve::No, - xattr: Preserve::No, - } - } - - fn none() -> Self { - Self { - #[cfg(unix)] - ownership: Preserve::No, - mode: Preserve::No, - timestamps: Preserve::No, - context: Preserve::No, - links: Preserve::No, - xattr: Preserve::No, + pub fn parse_iter(values: impl Iterator) -> Result + where + T: AsRef, + { + let mut new = Self::NONE; + for value in values { + new = new.union(&Self::parse_single_string(value.as_ref())?); } + Ok(new) } /// Tries to match string containing a parameter to preserve with the corresponding entry in the /// Attributes struct. - fn try_set_from_string(&mut self, value: &str) -> Result<(), Error> { - let preserve_yes_required = Preserve::Yes { required: true }; + fn parse_single_string(value: &str) -> Result { + let value = value.to_lowercase(); - match &*value.to_lowercase() { - "mode" => self.mode = preserve_yes_required, + if value == "all" { + return Ok(Self::ALL); + } + + let mut new = Self::NONE; + let attribute = match value.as_ref() { + "mode" => &mut new.mode, #[cfg(unix)] - "ownership" => self.ownership = preserve_yes_required, - "timestamps" => self.timestamps = preserve_yes_required, - "context" => self.context = preserve_yes_required, - "link" | "links" => self.links = preserve_yes_required, - "xattr" => self.xattr = preserve_yes_required, + "ownership" => &mut new.ownership, + "timestamps" => &mut new.timestamps, + "context" => &mut new.context, + "link" | "links" => &mut new.links, + "xattr" => &mut new.xattr, _ => { return Err(Error::InvalidArgument(format!( "invalid attribute {}", @@ -809,7 +881,10 @@ impl Attributes { ))); } }; - Ok(()) + + *attribute = Preserve::Yes { required: true }; + + Ok(new) } } @@ -858,40 +933,35 @@ impl Options { }; // Parse attributes to preserve - let attributes: Attributes = if matches.contains_id(options::PRESERVE) { - match matches.get_many::(options::PRESERVE) { - None => Attributes::default(), - Some(attribute_strs) => { - let mut attributes: Attributes = Attributes::none(); - let mut attributes_empty = true; - for attribute_str in attribute_strs { - attributes_empty = false; - if attribute_str == "all" { - attributes.max(Attributes::all()); - } else { - attributes.try_set_from_string(attribute_str)?; - } - } - // `--preserve` case, use the defaults - if attributes_empty { - Attributes::default() - } else { - attributes - } + let mut attributes = + if let Some(attribute_strs) = matches.get_many::(options::PRESERVE) { + if attribute_strs.len() == 0 { + Attributes::DEFAULT + } else { + Attributes::parse_iter(attribute_strs)? + } + } else if matches.get_flag(options::ARCHIVE) { + // --archive is used. Same as --preserve=all + Attributes::ALL + } else if matches.get_flag(options::NO_DEREFERENCE_PRESERVE_LINKS) { + Attributes::LINKS + } else if matches.get_flag(options::PRESERVE_DEFAULT_ATTRIBUTES) { + Attributes::DEFAULT + } else { + Attributes::NONE + }; + + // handling no-preserve options and adjusting the attributes + if let Some(attribute_strs) = matches.get_many::(options::NO_PRESERVE) { + if attribute_strs.len() > 0 { + let no_preserve_attributes = Attributes::parse_iter(attribute_strs)?; + if matches!(no_preserve_attributes.links, Preserve::Yes { .. }) { + attributes.links = Preserve::No { explicit: true }; + } else if matches!(no_preserve_attributes.mode, Preserve::Yes { .. }) { + attributes.mode = Preserve::No { explicit: true }; } } - } else if matches.get_flag(options::ARCHIVE) { - // --archive is used. Same as --preserve=all - Attributes::all() - } else if matches.get_flag(options::NO_DEREFERENCE_PRESERVE_LINKS) { - let mut attributes = Attributes::none(); - attributes.links = Preserve::Yes { required: true }; - attributes - } else if matches.get_flag(options::PRESERVE_DEFAULT_ATTRIBUTES) { - Attributes::default() - } else { - Attributes::none() - }; + } #[cfg(not(feature = "feat_selinux"))] if let Preserve::Yes { required } = attributes.context { @@ -984,11 +1054,22 @@ impl Options { fn preserve_hard_links(&self) -> bool { match self.attributes.links { - Preserve::No => false, + Preserve::No { .. } => false, Preserve::Yes { .. } => true, } } + #[cfg(unix)] + fn preserve_mode(&self) -> (bool, bool) { + match self.attributes.mode { + Preserve::No { explicit } => match explicit { + true => (false, true), + false => (false, false), + }, + Preserve::Yes { .. } => (true, false), + } + } + /// Whether to force overwriting the destination file. fn force(&self) -> bool { matches!(self.overwrite, OverwriteMode::Clobber(ClobberMode::Force)) @@ -1000,7 +1081,7 @@ impl TargetType { /// /// Treat target as a dir if we have multiple sources or the target /// exists and already is a directory - fn determine(sources: &[Source], target: &TargetSlice) -> Self { + fn determine(sources: &[PathBuf], target: &Path) -> Self { if sources.len() > 1 || target.is_dir() { Self::Directory } else { @@ -1010,10 +1091,16 @@ impl TargetType { } /// Returns tuple of (Source paths, Target) -fn parse_path_args(mut paths: Vec, options: &Options) -> CopyResult<(Vec, Target)> { +fn parse_path_args( + mut paths: Vec, + options: &Options, +) -> CopyResult<(Vec, PathBuf)> { if paths.is_empty() { // No files specified return Err("missing file operand".into()); + } else if paths.len() == 1 && options.target_dir.is_none() { + // Only one file specified + return Err(format!("missing destination file operand after {:?}", paths[0]).into()); } // Return an error if the user requested to copy more than one @@ -1044,66 +1131,6 @@ fn parse_path_args(mut paths: Vec, options: &Options) -> CopyResult<(Vec Ok((paths, target)) } -/// Get the inode information for a file. -fn get_inode(file_info: &FileInformation) -> u64 { - #[cfg(unix)] - let result = file_info.inode(); - #[cfg(windows)] - let result = file_info.file_index(); - result -} - -#[cfg(target_os = "redox")] -fn preserve_hardlinks( - hard_links: &mut Vec<(String, u64)>, - source: &std::path::Path, - dest: &std::path::Path, - found_hard_link: &mut bool, -) -> CopyResult<()> { - // Redox does not currently support hard links - Ok(()) -} - -/// Hard link a pair of files if needed _and_ record if this pair is a new hard link. -#[cfg(not(target_os = "redox"))] -fn preserve_hardlinks( - hard_links: &mut Vec<(String, u64)>, - source: &std::path::Path, - dest: &std::path::Path, -) -> CopyResult { - let info = FileInformation::from_path(source, false) - .context(format!("cannot stat {}", source.quote()))?; - let inode = get_inode(&info); - let nlinks = info.number_of_links(); - let mut found_hard_link = false; - for (link, link_inode) in hard_links.iter() { - if *link_inode == inode { - // Consider the following files: - // - // * `src/f` - a regular file - // * `src/link` - a hard link to `src/f` - // * `dest/src/f` - a different regular file - // - // In this scenario, if we do `cp -a src/ dest/`, it is - // possible that the order of traversal causes `src/link` - // to get copied first (to `dest/src/link`). In that case, - // in order to make sure `dest/src/link` is a hard link to - // `dest/src/f` and `dest/src/f` has the contents of - // `src/f`, we delete the existing file to allow the hard - // linking. - if file_or_link_exists(dest) && file_or_link_exists(Path::new(link)) { - std::fs::remove_file(dest)?; - } - std::fs::hard_link(link, dest).unwrap(); - found_hard_link = true; - } - } - if !found_hard_link && nlinks > 1 { - hard_links.push((dest.to_str().unwrap().to_string(), inode)); - } - Ok(found_hard_link) -} - /// When handling errors, we don't always want to show them to the user. This function handles that. fn show_error_if_needed(error: &Error) { match error { @@ -1122,25 +1149,29 @@ fn show_error_if_needed(error: &Error) { } } -/// Copy all `sources` to `target`. Returns an -/// `Err(Error::NotAllFilesCopied)` if at least one non-fatal error was -/// encountered. +/// Copy all `sources` to `target`. /// -/// Behavior depends on path`options`, see [`Options`] for details. +/// Returns an `Err(Error::NotAllFilesCopied)` if at least one non-fatal error +/// was encountered. /// -/// [`Options`]: ./struct.Options.html -fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResult<()> { +/// Behavior is determined by the `options` parameter, see [`Options`] for details. +pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult<()> { let target_type = TargetType::determine(sources, target); verify_target_type(target, &target_type)?; - let preserve_hard_links = options.preserve_hard_links(); - - let mut hard_links: Vec<(String, u64)> = vec![]; - let mut non_fatal_errors = false; let mut seen_sources = HashSet::with_capacity(sources.len()); let mut symlinked_files = HashSet::new(); + // to remember the copied files for further usage. + // the FileInformation implemented the Hash trait by using + // 1. inode number + // 2. device number + // the combination of a file's inode number and device number is unique throughout all the file systems. + // + // key is the source file's information and the value is the destination filepath. + let mut copied_files: HashMap = HashMap::with_capacity(sources.len()); + let progress_bar = if options.progress_bar { let pb = ProgressBar::new(disk_usage(sources, options.recursive)?) .with_style( @@ -1156,33 +1187,29 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu None }; - for source in sources.iter() { + 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 found_hard_link = if preserve_hard_links && !source.is_dir() { - let dest = construct_dest_path(source, target, &target_type, options)?; - preserve_hardlinks(&mut hard_links, source, &dest)? - } else { - false - }; - if !found_hard_link { - if let Err(error) = copy_source( - &progress_bar, - source, - target, - &target_type, - options, - &mut symlinked_files, - ) { - show_error_if_needed(&error); - non_fatal_errors = true; - } - } - seen_sources.insert(source); + } else if let Err(error) = copy_source( + &progress_bar, + source, + target, + &target_type, + options, + &mut symlinked_files, + &mut copied_files, + ) { + show_error_if_needed(&error); + non_fatal_errors = true; } + seen_sources.insert(source); } + + if let Some(pb) = progress_bar { + pb.finish(); + } + if non_fatal_errors { Err(Error::NotAllFilesCopied) } else { @@ -1192,7 +1219,7 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu fn construct_dest_path( source_path: &Path, - target: &TargetSlice, + target: &Path, target_type: &TargetType, options: &Options, ) -> CopyResult { @@ -1223,16 +1250,25 @@ fn construct_dest_path( fn copy_source( progress_bar: &Option, - source: &SourceSlice, - target: &TargetSlice, + source: &Path, + target: &Path, target_type: &TargetType, options: &Options, symlinked_files: &mut HashSet, + copied_files: &mut HashMap, ) -> CopyResult<()> { let source_path = Path::new(&source); if source_path.is_dir() { // Copy as directory - copy_directory(progress_bar, source, target, options, symlinked_files, true) + copy_directory( + progress_bar, + source, + target, + options, + symlinked_files, + copied_files, + true, + ) } else { // Copy as file let dest = construct_dest_path(source_path, target, target_type, options)?; @@ -1242,6 +1278,7 @@ fn copy_source( dest.as_path(), options, symlinked_files, + copied_files, true, ); if options.parents { @@ -1254,23 +1291,16 @@ fn copy_source( } impl OverwriteMode { - fn verify(&self, path: &Path, verbose: bool) -> CopyResult<()> { + fn verify(&self, path: &Path) -> CopyResult<()> { match *self { Self::NoClobber => { - if verbose { - println!("skipped {}", path.quote()); - } else { - eprintln!("{}: not replacing {}", util_name(), path.quote()); - } + eprintln!("{}: not replacing {}", util_name(), path.quote()); Err(Error::NotAllFilesCopied) } Self::Interactive(_) => { if prompt_yes!("overwrite {}?", path.quote()) { Ok(()) } else { - if verbose { - println!("skipped {}", path.quote()); - } Err(Error::Skipped) } } @@ -1284,7 +1314,7 @@ impl OverwriteMode { /// If it's required, then the error is thrown. fn handle_preserve CopyResult<()>>(p: &Preserve, f: F) -> CopyResult<()> { match p { - Preserve::No => {} + Preserve::No { .. } => {} Preserve::Yes { required } => { let result = f(); if *required { @@ -1478,7 +1508,7 @@ fn handle_existing_dest( return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into()); } - options.overwrite.verify(dest, options.verbose)?; + options.overwrite.verify(dest)?; let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); if let Some(backup_path) = backup_path { @@ -1503,6 +1533,24 @@ fn handle_existing_dest( OverwriteMode::Clobber(ClobberMode::RemoveDestination) => { fs::remove_file(dest)?; } + OverwriteMode::Clobber(ClobberMode::Standard) => { + // Consider the following files: + // + // * `src/f` - a regular file + // * `src/link` - a hard link to `src/f` + // * `dest/src/f` - a different regular file + // + // In this scenario, if we do `cp -a src/ dest/`, it is + // possible that the order of traversal causes `src/link` + // to get copied first (to `dest/src/link`). In that case, + // in order to make sure `dest/src/link` is a hard link to + // `dest/src/f` and `dest/src/f` has the contents of + // `src/f`, we delete the existing file to allow the hard + // linking. + if options.preserve_hard_links() { + fs::remove_file(dest)?; + } + } _ => (), }; @@ -1576,6 +1624,7 @@ fn copy_file( dest: &Path, options: &Options, symlinked_files: &mut HashSet, + copied_files: &mut HashMap, source_in_command_line: bool, ) -> CopyResult<()> { if (options.update == UpdateMode::ReplaceIfOlder || options.update == UpdateMode::ReplaceNone) @@ -1613,12 +1662,33 @@ fn copy_file( dest.display() ))); } + if paths_refer_to_same_file(source, dest, true) + && matches!( + options.overwrite, + OverwriteMode::Clobber(ClobberMode::RemoveDestination) + ) + { + fs::remove_file(dest)?; + } } if file_or_link_exists(dest) { handle_existing_dest(source, dest, options, source_in_command_line)?; } + if options.preserve_hard_links() { + // if we encounter a matching device/inode pair in the source tree + // we can arrange to create a hard link between the corresponding names + // in the destination tree. + if let Some(new_source) = copied_files.get( + &FileInformation::from_path(source, options.dereference(source_in_command_line)) + .context(format!("cannot stat {}", source.quote()))?, + ) { + std::fs::hard_link(new_source, dest)?; + return Ok(()); + }; + } + if options.verbose { if let Some(pb) = progress_bar { // Suspend (hide) the progress bar so the println won't overlap with the progress bar. @@ -1681,15 +1751,10 @@ fn copy_file( let mut permissions = source_metadata.permissions(); #[cfg(unix)] { - use uucore::mode::get_umask; - - let mut mode = permissions.mode(); - - // remove sticky bit, suid and gid bit - const SPECIAL_PERMS_MASK: u32 = 0o7000; - mode &= !SPECIAL_PERMS_MASK; + let mut mode = handle_no_preserve_mode(options, permissions.mode()); // apply umask + use uucore::mode::get_umask; mode &= !get_umask(); permissions.set_mode(mode); @@ -1806,6 +1871,11 @@ fn copy_file( copy_attributes(source, dest, &options.attributes)?; + copied_files.insert( + FileInformation::from_path(source, options.dereference(source_in_command_line))?, + dest.to_path_buf(), + ); + if let Some(progress_bar) = progress_bar { progress_bar.inc(fs::metadata(source)?.len()); } @@ -1813,6 +1883,49 @@ fn copy_file( Ok(()) } +#[cfg(unix)] +fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { + let (is_preserve_mode, is_explicit_no_preserve_mode) = options.preserve_mode(); + if !is_preserve_mode { + use libc::{ + S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR, + }; + + #[cfg(not(any( + target_os = "android", + target_os = "macos", + target_os = "macos-12", + target_os = "freebsd", + )))] + { + const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; + match is_explicit_no_preserve_mode { + true => return MODE_RW_UGO, + false => return org_mode & S_IRWXUGO, + }; + } + + #[cfg(any( + target_os = "android", + target_os = "macos", + target_os = "macos-12", + target_os = "freebsd", + ))] + { + const MODE_RW_UGO: u32 = + (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; + const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; + match is_explicit_no_preserve_mode { + true => return MODE_RW_UGO, + false => return org_mode & S_IRWXUGO, + }; + } + } + + org_mode +} + /// 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( @@ -1836,7 +1949,7 @@ fn copy_helper( File::create(dest).context(dest.display().to_string())?; } else if source_is_fifo && options.recursive && !options.copy_contents { #[cfg(unix)] - copy_fifo(dest, options.overwrite, options.verbose)?; + copy_fifo(dest, options.overwrite)?; } else if source_is_symlink { copy_link(source, dest, symlinked_files)?; } else { @@ -1861,9 +1974,9 @@ fn copy_helper( // "Copies" a FIFO by creating a new one. This workaround is because Rust's // built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390). #[cfg(unix)] -fn copy_fifo(dest: &Path, overwrite: OverwriteMode, verbose: bool) -> CopyResult<()> { +fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> { if dest.exists() { - overwrite.verify(dest, verbose)?; + overwrite.verify(dest)?; fs::remove_file(dest)?; } diff --git a/src/uu/cp/src/platform/linux.rs b/src/uu/cp/src/platform/linux.rs index 18f2520a2..674e66ea5 100644 --- a/src/uu/cp/src/platform/linux.rs +++ b/src/uu/cp/src/platform/linux.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 ficlone reflink ftruncate pwrite fiemap use std::fs::{File, OpenOptions}; use std::io::Read; diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs index b173aa959..77bdbbbdb 100644 --- a/src/uu/cp/src/platform/macos.rs +++ b/src/uu/cp/src/platform/macos.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 reflink use std::ffi::CString; use std::fs::{self, File}; @@ -63,8 +63,15 @@ pub(crate) fn copy_on_write( { // clonefile(2) fails if the destination exists. Remove it and try again. Do not // bother to check if removal worked because we're going to try to clone again. - let _ = fs::remove_file(dest); - error = pfn(src.as_ptr(), dst.as_ptr(), 0); + // first lets make sure the dest file is not read only + if fs::metadata(dest).map_or(false, |md| !md.permissions().readonly()) { + // remove and copy again + // TODO: rewrite this to better match linux behavior + // linux first opens the source file and destination file then uses the file + // descriptors to do the clone. + let _ = fs::remove_file(dest); + error = pfn(src.as_ptr(), dst.as_ptr(), 0); + } } } } diff --git a/src/uu/cp/src/platform/mod.rs b/src/uu/cp/src/platform/mod.rs index 9dbcefa80..c79427068 100644 --- a/src/uu/cp/src/platform/mod.rs +++ b/src/uu/cp/src/platform/mod.rs @@ -1,7 +1,7 @@ -// * 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. +// 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. #[cfg(target_os = "macos")] mod macos; #[cfg(target_os = "macos")] diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index f5882f75e..7ca1a5ded 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 reflink use std::fs; use std::path::Path; diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index 051f567b4..a30ebc4f6 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_csplit" -version = "0.0.20" +version = "0.0.22" 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" diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index d1925308e..6e03c2e5c 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -1,3 +1,7 @@ +// 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. #![crate_name = "uu_csplit"] // spell-checker:ignore rustdoc #![allow(rustdoc::private_intra_doc_links)] @@ -660,6 +664,7 @@ mod tests { use super::*; #[test] + #[allow(clippy::cognitive_complexity)] fn input_splitter() { let input = vec![ Ok(String::from("aaa")), @@ -732,6 +737,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn input_splitter_interrupt_rewind() { let input = vec![ Ok(String::from("aaa")), diff --git a/src/uu/csplit/src/csplit_error.rs b/src/uu/csplit/src/csplit_error.rs index b81a331a2..1559a29f8 100644 --- a/src/uu/csplit/src/csplit_error.rs +++ b/src/uu/csplit/src/csplit_error.rs @@ -1,3 +1,7 @@ +// 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::io; use thiserror::Error; diff --git a/src/uu/csplit/src/patterns.rs b/src/uu/csplit/src/patterns.rs index 652b024d8..8e7b76e6b 100644 --- a/src/uu/csplit/src/patterns.rs +++ b/src/uu/csplit/src/patterns.rs @@ -1,3 +1,7 @@ +// 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 (regex) SKIPTO UPTO ; (vars) ntimes use crate::csplit_error::CsplitError; @@ -207,6 +211,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn up_to_match_pattern() { let input: Vec = vec![ "/test1.*end$/", @@ -260,6 +265,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn skip_to_match_pattern() { let input: Vec = vec![ "%test1.*end$%", diff --git a/src/uu/csplit/src/split_name.rs b/src/uu/csplit/src/split_name.rs index 19e3ac9c2..4d94b56a9 100644 --- a/src/uu/csplit/src/split_name.rs +++ b/src/uu/csplit/src/split_name.rs @@ -1,3 +1,7 @@ +// 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 (regex) diuox use regex::Regex; diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 6f37aa336..695777a72 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cut" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" @@ -16,10 +16,9 @@ path = "src/cut.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["ranges"] } memchr = { workspace = true } bstr = { workspace = true } -is-terminal = { workspace = true } [[bin]] name = "cut" diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 68ad566d7..4d3145c05 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Rolf Morel -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -9,12 +7,12 @@ use bstr::io::BufReadExt; use clap::{crate_version, Arg, ArgAction, Command}; -use is_terminal::IsTerminal; use std::fs::File; -use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; +use std::io::{stdin, stdout, BufReader, BufWriter, IsTerminal, Read, Write}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::line_ending::LineEnding; use self::searcher::Searcher; use matcher::{ExactMatcher, Matcher, WhitespaceMatcher}; @@ -30,7 +28,7 @@ const AFTER_HELP: &str = help_section!("after help", "cut.md"); struct Options { out_delim: Option, - zero_terminated: bool, + line_ending: LineEnding, } enum Delimiter { @@ -42,7 +40,7 @@ struct FieldOptions { delimiter: Delimiter, out_delimiter: Option, only_delimited: bool, - zero_terminated: bool, + line_ending: LineEnding, } enum Mode { @@ -68,7 +66,7 @@ fn list_to_ranges(list: &str, complement: bool) -> Result, String> { } fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> { - let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; + let newline_char = opts.line_ending.into(); let mut buf_in = BufReader::new(reader); let mut out = stdout_writer(); let delim = opts @@ -259,7 +257,7 @@ fn cut_fields_implicit_out_delim( } fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> UResult<()> { - let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; + let newline_char = opts.line_ending.into(); match opts.delimiter { Delimiter::String(ref delim) => { let matcher = ExactMatcher::new(delim.as_bytes()); @@ -376,7 +374,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default() .to_owned(), ), - zero_terminated: matches.get_flag(options::ZERO_TERMINATED), + line_ending: LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)), }, ) }), @@ -391,7 +389,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default() .to_owned(), ), - zero_terminated: matches.get_flag(options::ZERO_TERMINATED), + line_ending: LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)), }, ) }), @@ -411,6 +409,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let only_delimited = matches.get_flag(options::ONLY_DELIMITED); let whitespace_delimited = matches.get_flag(options::WHITESPACE_DELIMITED); let zero_terminated = matches.get_flag(options::ZERO_TERMINATED); + let line_ending = LineEnding::from_zero_flag(zero_terminated); match matches.get_one::(options::DELIMITER).map(|s| s.as_str()) { Some(_) if whitespace_delimited => { @@ -441,7 +440,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { delimiter: Delimiter::String(delim), out_delimiter: out_delim, only_delimited, - zero_terminated, + line_ending, }, )) } @@ -455,7 +454,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }, out_delimiter: out_delim, only_delimited, - zero_terminated, + line_ending, }, )), } diff --git a/src/uu/cut/src/searcher.rs b/src/uu/cut/src/searcher.rs index 95d85c020..21424790e 100644 --- a/src/uu/cut/src/searcher.rs +++ b/src/uu/cut/src/searcher.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Rolf Morel -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index de5b5f2a2..cd860ce58 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_date" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "date ~ (uutils) display or set the current time" diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index adfb74128..b5ab8993a 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -1,8 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Anthony Deschamps -// (c) Sylvestre Ledru -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -169,7 +166,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let date_source = if let Some(date) = matches.get_one::(OPT_DATE) { - if let Ok(duration) = parse_datetime::from_str(date.as_str()) { + let ref_time = Local::now(); + if let Ok(new_time) = parse_datetime::parse_datetime_at_date(ref_time, date.as_str()) { + let duration = new_time.signed_duration_since(ref_time); DateSource::Human(duration) } else { DateSource::Custom(date.into()) @@ -227,8 +226,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { DateSource::Human(relative_time) => { // Get the current DateTime for things like "1 year ago" let current_time = DateTime::::from(Local::now()); - let iter = std::iter::once(Ok(current_time + relative_time)); - Box::new(iter) + // double check the result is overflow or not of the current_time + relative_time + // it may cause a panic of chrono::datetime::DateTime add + match current_time.checked_add_signed(relative_time) { + Some(date) => { + let iter = std::iter::once(Ok(date)); + Box::new(iter) + } + None => { + return Err(USimpleError::new( + 1, + format!("invalid date {}", relative_time), + )); + } + } } DateSource::File(ref path) => { if path.is_dir() { diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index aa19dc760..cf187740d 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dd" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "dd ~ (uutils) copy and convert files" diff --git a/src/uu/dd/src/blocks.rs b/src/uu/dd/src/blocks.rs index a8d2c1408..8e5557a2c 100644 --- a/src/uu/dd/src/blocks.rs +++ b/src/uu/dd/src/blocks.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 datastructures rstat rposition cflags ctable diff --git a/src/uu/dd/src/conversion_tables.rs b/src/uu/dd/src/conversion_tables.rs index aca2ef9bc..bdf6398ac 100644 --- a/src/uu/dd/src/conversion_tables.rs +++ b/src/uu/dd/src/conversion_tables.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Tyler Steele -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uu/dd/src/datastructures.rs b/src/uu/dd/src/datastructures.rs index 6830df63e..c0da8c67c 100644 --- a/src/uu/dd/src/datastructures.rs +++ b/src/uu/dd/src/datastructures.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Tyler Steele -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore ctable, outfile, iseek, oseek diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 4e1287939..b79ae22da 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -1,11 +1,9 @@ // This file is part of the uutils coreutils package. // -// (c) Tyler Steele -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE +// spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE mod datastructures; use datastructures::*; @@ -51,6 +49,8 @@ use nix::{ fcntl::{posix_fadvise, PosixFadviseAdvice}, }; use uucore::display::Quotable; +#[cfg(unix)] +use uucore::error::set_exit_code; use uucore::error::{FromIo, UResult}; use uucore::{format_usage, help_about, help_section, help_usage, show_error}; #[cfg(target_os = "linux")] @@ -202,14 +202,25 @@ impl Source { Err(e) => Err(e), }, #[cfg(unix)] - Self::StdinFile(f) => match io::copy(&mut f.take(n), &mut io::sink()) { - Ok(m) if m < n => { - show_error!("'standard input': cannot skip to specified offset"); - Ok(m) + Self::StdinFile(f) => { + if let Ok(Some(len)) = try_get_len_of_block_device(f) { + if len < n { + // GNU compatibility: + // this case prints the stats but sets the exit code to 1 + show_error!("'standard input': cannot skip: Invalid argument"); + set_exit_code(1); + return Ok(len); + } } - Ok(m) => Ok(m), - Err(e) => Err(e), - }, + match io::copy(&mut f.take(n), &mut io::sink()) { + Ok(m) if m < n => { + show_error!("'standard input': cannot skip to specified offset"); + Ok(m) + } + Ok(m) => Ok(m), + Err(e) => Err(e), + } + } Self::File(f) => f.seek(io::SeekFrom::Start(n)), #[cfg(unix)] Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()), @@ -529,7 +540,19 @@ impl Dest { fn seek(&mut self, n: u64) -> io::Result { match self { Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout), - Self::File(f, _) => f.seek(io::SeekFrom::Start(n)), + Self::File(f, _) => { + #[cfg(unix)] + if let Ok(Some(len)) = try_get_len_of_block_device(f) { + if len < n { + // GNU compatibility: + // this case prints the stats but sets the exit code to 1 + show_error!("'standard output': cannot seek: Invalid argument"); + set_exit_code(1); + return Ok(len); + } + } + f.seek(io::SeekFrom::Start(n)) + } #[cfg(unix)] Self::Fifo(f) => { // Seeking in a named pipe means *reading* from the pipe. @@ -1135,6 +1158,20 @@ fn is_stdout_redirected_to_seekable_file() -> bool { } } +/// Try to get the len if it is a block device +#[cfg(unix)] +fn try_get_len_of_block_device(file: &mut File) -> io::Result> { + let ftype = file.metadata()?.file_type(); + if !ftype.is_block_device() { + return Ok(None); + } + + // FIXME: this can be replaced by file.stream_len() when stable. + let len = file.seek(SeekFrom::End(0))?; + file.rewind()?; + Ok(Some(len)) +} + /// Decide whether the named file is a named pipe, also known as a FIFO. #[cfg(unix)] fn is_fifo(filename: &str) -> bool { diff --git a/src/uu/dd/src/numbers.rs b/src/uu/dd/src/numbers.rs index 0cab572b4..8a6fa5a7a 100644 --- a/src/uu/dd/src/numbers.rs +++ b/src/uu/dd/src/numbers.rs @@ -1,7 +1,7 @@ -// * 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. +// 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. /// Functions for formatting a number as a magnitude and a unit suffix. /// The first ten powers of 1024. @@ -115,6 +115,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_to_magnitude_and_suffix_not_powers_of_1024() { assert_eq!(to_magnitude_and_suffix(1, SuffixType::Si), "1.0 B"); assert_eq!(to_magnitude_and_suffix(999, SuffixType::Si), "999 B"); diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 20a8da1ee..0ff6e752c 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Tyler Steele -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore ctty, ctable, iseek, oseek, iconvflags, oconvflags parseargs outfile oconv @@ -247,29 +245,29 @@ impl Parser { None => return Err(ParseError::UnrecognizedOperand(operand.to_string())), Some((k, v)) => match k { "bs" => { - let bs = self.parse_bytes(k, v)?; + let bs = Self::parse_bytes(k, v)?; self.ibs = bs; self.obs = bs; } - "cbs" => self.cbs = Some(self.parse_bytes(k, v)?), + "cbs" => self.cbs = Some(Self::parse_bytes(k, v)?), "conv" => self.parse_conv_flags(v)?, - "count" => self.count = Some(self.parse_n(v)?), - "ibs" => self.ibs = self.parse_bytes(k, v)?, + "count" => self.count = Some(Self::parse_n(v)?), + "ibs" => self.ibs = Self::parse_bytes(k, v)?, "if" => self.infile = Some(v.to_string()), "iflag" => self.parse_input_flags(v)?, - "obs" => self.obs = self.parse_bytes(k, v)?, + "obs" => self.obs = Self::parse_bytes(k, v)?, "of" => self.outfile = Some(v.to_string()), "oflag" => self.parse_output_flags(v)?, - "seek" | "oseek" => self.seek = self.parse_n(v)?, - "skip" | "iseek" => self.skip = self.parse_n(v)?, - "status" => self.status = Some(self.parse_status_level(v)?), + "seek" | "oseek" => self.seek = Self::parse_n(v)?, + "skip" | "iseek" => self.skip = Self::parse_n(v)?, + "status" => self.status = Some(Self::parse_status_level(v)?), _ => return Err(ParseError::UnrecognizedOperand(operand.to_string())), }, } Ok(()) } - fn parse_n(&self, val: &str) -> Result { + fn parse_n(val: &str) -> Result { let n = parse_bytes_with_opt_multiplier(val)?; Ok(if val.ends_with('B') { Num::Bytes(n) @@ -278,13 +276,13 @@ impl Parser { }) } - fn parse_bytes(&self, arg: &str, val: &str) -> Result { + fn parse_bytes(arg: &str, val: &str) -> Result { parse_bytes_with_opt_multiplier(val)? .try_into() .map_err(|_| ParseError::BsOutOfRange(arg.to_string())) } - fn parse_status_level(&self, val: &str) -> Result { + fn parse_status_level(val: &str) -> Result { match val { "none" => Ok(StatusLevel::None), "noxfer" => Ok(StatusLevel::Noxfer), @@ -506,7 +504,7 @@ fn parse_bytes_no_x(full: &str, s: &str) -> Result { ..Default::default() }; let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) { - (None, None, None) => match parser.parse(s) { + (None, None, None) => match parser.parse_u64(s) { Ok(n) => (n, 1), Err(ParseSizeError::InvalidSuffix(_) | ParseSizeError::ParseFailure(_)) => { return Err(ParseError::InvalidNumber(full.to_string())) diff --git a/src/uu/dd/src/parseargs/unit_tests.rs b/src/uu/dd/src/parseargs/unit_tests.rs index 54e17b882..142e49fd0 100644 --- a/src/uu/dd/src/parseargs/unit_tests.rs +++ b/src/uu/dd/src/parseargs/unit_tests.rs @@ -1,3 +1,7 @@ +// 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 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, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat, oconv use super::*; @@ -99,6 +103,7 @@ fn test_status_level_none() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_all_top_level_args_no_leading_dashes() { let args = &[ "if=foo.file", diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index 674d90984..f24726009 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 btotal sigval //! Read and write progress tracking for dd. //! diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index 047fe9be0..2aae7bd55 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_df" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "df ~ (uutils) display file system information" diff --git a/src/uu/df/src/blocks.rs b/src/uu/df/src/blocks.rs index f48d2ffd2..d7a689d8c 100644 --- a/src/uu/df/src/blocks.rs +++ b/src/uu/df/src/blocks.rs @@ -1,7 +1,7 @@ -// * 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. +// 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. //! Types for representing and displaying block sizes. use crate::{OPT_BLOCKSIZE, OPT_PORTABILITY}; use clap::ArgMatches; @@ -9,7 +9,7 @@ use std::{env, fmt}; use uucore::{ display::Quotable, - parse_size::{parse_size, ParseSizeError}, + parse_size::{parse_size_u64, ParseSizeError}, }; /// The first ten powers of 1024. @@ -165,7 +165,7 @@ impl Default for BlockSize { pub(crate) fn read_block_size(matches: &ArgMatches) -> Result { if matches.contains_id(OPT_BLOCKSIZE) { let s = matches.get_one::(OPT_BLOCKSIZE).unwrap(); - let bytes = parse_size(s)?; + let bytes = parse_size_u64(s)?; if bytes > 0 { Ok(BlockSize::Bytes(bytes)) @@ -184,7 +184,7 @@ pub(crate) fn read_block_size(matches: &ArgMatches) -> Result Option { for env_var in ["DF_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { if let Ok(env_size) = env::var(env_var) { - if let Ok(size) = parse_size(&env_size) { + if let Ok(size) = parse_size_u64(&env_size) { return Some(size); } else { return None; @@ -239,6 +239,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_to_magnitude_and_suffix_not_powers_of_1024() { assert_eq!(to_magnitude_and_suffix(1, SuffixType::Si), "1B"); assert_eq!(to_magnitude_and_suffix(999, SuffixType::Si), "999B"); diff --git a/src/uu/df/src/columns.rs b/src/uu/df/src/columns.rs index f9515d791..0659d7f7d 100644 --- a/src/uu/df/src/columns.rs +++ b/src/uu/df/src/columns.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 itotal iused iavail ipcent pcent squashfs use crate::{OPT_INODES, OPT_OUTPUT, OPT_PRINT_TYPE}; use clap::{parser::ValueSource, ArgMatches}; diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index ef1584323..78325f3d2 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -1,10 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Fangxu Hu -// (c) Sylvestre Ledru -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. // spell-checker:ignore itotal iused iavail ipcent pcent tmpfs squashfs lofs mod blocks; mod columns; diff --git a/src/uu/df/src/filesystem.rs b/src/uu/df/src/filesystem.rs index d50822e7f..ef5107958 100644 --- a/src/uu/df/src/filesystem.rs +++ b/src/uu/df/src/filesystem.rs @@ -1,7 +1,7 @@ -// * 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. +// 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. //! Provides a summary representation of a filesystem. //! //! A [`Filesystem`] struct represents a device containing a diff --git a/src/uu/df/src/table.rs b/src/uu/df/src/table.rs index 06bfc3383..f6e094204 100644 --- a/src/uu/df/src/table.rs +++ b/src/uu/df/src/table.rs @@ -1,8 +1,8 @@ -// * 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 tmpfs Pcent Itotal Iused Iavail Ipcent +// 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 tmpfs Pcent Itotal Iused Iavail Ipcent nosuid nodev //! The filesystem usage data table. //! //! A table ([`Table`]) comprises a header row ([`Header`]) and a @@ -152,8 +152,10 @@ impl From for Row { ffree, .. } = fs.usage; - let bused = blocks - bfree; - let fused = files - ffree; + + // On Windows WSL, files can be less than ffree. Protect such cases via saturating_sub. + let bused = blocks.saturating_sub(bfree); + let fused = files.saturating_sub(ffree); Self { file: fs.file, fs_device: dev_name, @@ -815,4 +817,35 @@ mod tests { assert_eq!(get_formatted_values(1000, 1000, 0), vec!("1", "1", "0")); assert_eq!(get_formatted_values(1001, 1000, 1), vec!("2", "1", "1")); } + + #[test] + fn test_row_converter_with_invalid_numbers() { + // copy from wsl linux + let d = crate::Filesystem { + file: None, + mount_info: crate::MountInfo { + dev_id: "28".to_string(), + dev_name: "none".to_string(), + fs_type: "9p".to_string(), + mount_dir: "/usr/lib/wsl/drivers".to_string(), + mount_option: "ro,nosuid,nodev,noatime".to_string(), + mount_root: "/".to_string(), + remote: false, + dummy: false, + }, + usage: crate::table::FsUsage { + blocksize: 4096, + blocks: 244029695, + bfree: 125085030, + bavail: 125085030, + bavail_top_bit_set: false, + files: 999, + ffree: 1000000, + }, + }; + + let row = Row::from(d); + + assert_eq!(row.inodes_used, 0); + } } diff --git a/src/uu/dir/Cargo.toml b/src/uu/dir/Cargo.toml index 7bd144f02..0bca7bb9d 100644 --- a/src/uu/dir/Cargo.toml +++ b/src/uu/dir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dir" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -C -b" @@ -16,7 +16,7 @@ path = "src/dir.rs" [dependencies] clap = { workspace = true, features = ["env"] } -uucore = { workspace = true, features = ["entries", "fs"] } +uucore = { workspace = true, features = ["entries", "fs", "quoting-style"] } uu_ls = { workspace = true } [[bin]] diff --git a/src/uu/dir/src/dir.rs b/src/uu/dir/src/dir.rs index 6caf7bbe8..e25529511 100644 --- a/src/uu/dir/src/dir.rs +++ b/src/uu/dir/src/dir.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) gmnsii -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 clap::Command; use std::ffi::OsString; diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index f490a4728..a4a101326 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dircolors" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "dircolors ~ (uutils) display commands to set LS_COLORS" diff --git a/src/uu/dircolors/src/colors.rs b/src/uu/dircolors/src/colors.rs index d864a23b3..c0a981db8 100644 --- a/src/uu/dircolors/src/colors.rs +++ b/src/uu/dircolors/src/colors.rs @@ -1,3 +1,7 @@ +// 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 (ToDO) EIGHTBIT ETERM MULTIHARDLINK cpio dtterm jfbterm konsole kterm mlterm rmvb rxvt stat'able svgz tmux webm xspf COLORTERM tzst avif tzst mjpg mjpeg webp dpkg rpmnew rpmorig rpmsave pub const INTERNAL_DB: &str = r#"# Configuration file for dircolors, a utility to help you set the diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 19c3f7e31..2e3087d81 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -1,8 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// (c) Mitchell Mebane -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -168,9 +165,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { println!("{s}"); Ok(()) } - Err(s) => { - return Err(USimpleError::new(1, s)); - } + Err(s) => Err(USimpleError::new(1, s)), } } diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index 4ac789937..67806986c 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dirname" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "dirname ~ (uutils) display parent directory of PATHNAME" diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index cecd6aec8..9a56e9f5b 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Derek Chiang -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -9,6 +7,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::path::Path; use uucore::display::print_verbatim; use uucore::error::{UResult, UUsageError}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_section, help_usage}; const ABOUT: &str = help_about!("dirname.md"); @@ -26,11 +25,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; - let separator = if matches.get_flag(options::ZERO) { - "\0" - } else { - "\n" - }; + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); let dirnames: Vec = matches .get_many::(options::DIR) @@ -59,7 +54,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } } - print!("{separator}"); + print!("{line_ending}"); } } diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 041efeaad..158fa9786 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_du" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "du ~ (uutils) display disk usage" diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index db385c720..ad5e87833 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Derek Chiang -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 chrono::prelude::DateTime; use chrono::Local; @@ -34,8 +32,9 @@ use std::{error::Error, fmt::Display}; use uucore::display::{print_verbatim, Quotable}; use uucore::error::FromIo; use uucore::error::{set_exit_code, UError, UResult}; +use uucore::line_ending::LineEnding; use uucore::parse_glob; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, ParseSizeError}; use uucore::{ crash, format_usage, help_about, help_section, help_usage, show, show_error, show_warning, }; @@ -257,12 +256,12 @@ fn get_file_info(path: &Path) -> Option { fn read_block_size(s: Option<&str>) -> u64 { if let Some(s) = s { - parse_size(s) + parse_size_u64(s) .unwrap_or_else(|e| crash!(1, "{}", format_error_message(&e, s, options::BLOCK_SIZE))) } else { for env_var in ["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { if let Ok(env_size) = env::var(env_var) { - if let Ok(v) = parse_size(&env_size) { + if let Ok(v) = parse_size_u64(&env_size) { return v; } } @@ -600,11 +599,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let time_format_str = parse_time_style(matches.get_one::("time-style").map(|s| s.as_str()))?; - let line_separator = if matches.get_flag(options::NULL) { - "\0" - } else { - "\n" - }; + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::NULL)); let excludes = build_exclude_patterns(&matches)?; @@ -656,12 +651,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let time_str = tm.format(time_format_str).to_string(); print!("{}\t{}\t", convert_size(size), time_str); print_verbatim(stat.path).unwrap(); - print!("{line_separator}"); + print!("{line_ending}"); } } else if !summarize || index == len - 1 { print!("{}\t", convert_size(size)); print_verbatim(stat.path).unwrap(); - print!("{line_separator}"); + print!("{line_ending}"); } if options.total && index == (len - 1) { // The last element will be the total size of the the path under @@ -681,7 +676,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if options.total { print!("{}\ttotal", convert_size(grand_total)); - print!("{line_separator}"); + print!("{line_ending}"); } Ok(()) @@ -951,7 +946,7 @@ impl FromStr for Threshold { fn from_str(s: &str) -> std::result::Result { let offset = usize::from(s.starts_with(&['-', '+'][..])); - let size = parse_size(&s[offset..])?; + let size = parse_size_u64(&s[offset..])?; if s.starts_with('-') { // Threshold of '-0' excludes everything besides 0 sized entries diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index a95bcbe82..92d5d4924 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_echo" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "echo ~ (uutils) display TEXT" diff --git a/src/uu/echo/echo.md b/src/uu/echo/echo.md index 4330ecd88..f6f6f37ff 100644 --- a/src/uu/echo/echo.md +++ b/src/uu/echo/echo.md @@ -12,15 +12,15 @@ Echo the STRING(s) to standard output. If -e is in effect, the following sequences are recognized: -\\ backslash -\a alert (BEL) -\b backspace -\c produce no further output -\e escape -\f form feed -\n new line -\r carriage return -\t horizontal tab -\v vertical tab -\0NNN byte with octal value NNN (1 to 3 digits) -\xHH byte with hexadecimal value HH (1 to 2 digits) \ No newline at end of file +- `\` backslash +- `\a` alert (BEL) +- `\b` backspace +- `\c` produce no further output +- `\e` escape +- `\f` form feed +- `\n` new line +- `\r` carriage return +- `\t` horizontal tab +- `\v` vertical tab +- `\0NNN` byte with octal value NNN (1 to 3 digits) +- `\xHH` byte with hexadecimal value HH (1 to 2 digits) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 659dc836c..b3707b6f8 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -1,14 +1,12 @@ // This file is part of the uutils coreutils package. // -// (c) Derek Chiang -// (c) Christopher Brown -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. use clap::{crate_version, Arg, ArgAction, Command}; use std::io::{self, Write}; use std::iter::Peekable; +use std::ops::ControlFlow; use std::str::Chars; use uucore::error::{FromIo, UResult}; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -24,73 +22,98 @@ mod options { pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape"; } -fn parse_code( - input: &mut Peekable, - base: u32, - max_digits: u32, - bits_per_digit: u32, -) -> Option { - let mut ret = 0x8000_0000; - for _ in 0..max_digits { - match input.peek().and_then(|c| c.to_digit(base)) { - Some(n) => ret = (ret << bits_per_digit) | n, - None => break, - } - input.next(); - } - std::char::from_u32(ret) +#[repr(u8)] +#[derive(Clone, Copy)] +enum Base { + Oct = 8, + Hex = 16, } -fn print_escaped(input: &str, mut output: impl Write) -> io::Result { - let mut should_stop = false; +impl Base { + fn max_digits(&self) -> u8 { + match self { + Self::Oct => 3, + Self::Hex => 2, + } + } +} - let mut buffer = ['\\'; 2]; +/// Parse the numeric part of the `\xHHH` and `\0NNN` escape sequences +fn parse_code(input: &mut Peekable, base: Base) -> Option { + // All arithmetic on `ret` needs to be wrapping, because octal input can + // take 3 digits, which is 9 bits, and therefore more than what fits in a + // `u8`. GNU just seems to wrap these values. + // Note that if we instead make `ret` a `u32` and use `char::from_u32` will + // yield incorrect results because it will interpret values larger than + // `u8::MAX` as unicode. + let mut ret = input.peek().and_then(|c| c.to_digit(base as u32))? as u8; - // TODO `cargo +nightly clippy` complains that `.peek()` is never - // called on `iter`. However, `peek()` is called inside the - // `parse_code()` function that borrows `iter`. + // We can safely ignore the None case because we just peeked it. + let _ = input.next(); + + for _ in 1..base.max_digits() { + match input.peek().and_then(|c| c.to_digit(base as u32)) { + Some(n) => ret = ret.wrapping_mul(base as u8).wrapping_add(n as u8), + None => break, + } + // We can safely ignore the None case because we just peeked it. + let _ = input.next(); + } + + Some(ret.into()) +} + +fn print_escaped(input: &str, mut output: impl Write) -> io::Result> { let mut iter = input.chars().peekable(); - while let Some(mut c) = iter.next() { - let mut start = 1; + while let Some(c) = iter.next() { + if c != '\\' { + write!(output, "{c}")?; + continue; + } - if c == '\\' { - if let Some(next) = iter.next() { - c = match next { - '\\' => '\\', - 'a' => '\x07', - 'b' => '\x08', - 'c' => { - should_stop = true; - break; - } - 'e' => '\x1b', - 'f' => '\x0c', - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - 'v' => '\x0b', - 'x' => parse_code(&mut iter, 16, 2, 4).unwrap_or_else(|| { - start = 0; - next - }), - '0' => parse_code(&mut iter, 8, 3, 3).unwrap_or('\0'), - _ => { - start = 0; - next - } - }; + // This is for the \NNN syntax for octal sequences. + // Note that '0' is intentionally omitted because that + // would be the \0NNN syntax. + if let Some('1'..='8') = iter.peek() { + if let Some(parsed) = parse_code(&mut iter, Base::Oct) { + write!(output, "{parsed}")?; + continue; } } - buffer[1] = c; - - // because printing char slices is apparently not available in the standard library - for ch in &buffer[start..] { - write!(output, "{ch}")?; + if let Some(next) = iter.next() { + let unescaped = match next { + '\\' => '\\', + 'a' => '\x07', + 'b' => '\x08', + 'c' => return Ok(ControlFlow::Break(())), + 'e' => '\x1b', + 'f' => '\x0c', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + 'v' => '\x0b', + 'x' => { + if let Some(c) = parse_code(&mut iter, Base::Hex) { + c + } else { + write!(output, "\\")?; + 'x' + } + } + '0' => parse_code(&mut iter, Base::Oct).unwrap_or('\0'), + c => { + write!(output, "\\")?; + c + } + }; + write!(output, "{unescaped}")?; + } else { + write!(output, "\\")?; } } - Ok(should_stop) + Ok(ControlFlow::Continue(())) } #[uucore::main] @@ -151,9 +174,8 @@ fn execute(no_newline: bool, escaped: bool, free: &[String]) -> io::Result<()> { write!(output, " ")?; } if escaped { - let should_stop = print_escaped(input, &mut output)?; - if should_stop { - break; + if print_escaped(input, &mut output)?.is_break() { + return Ok(()); } } else { write!(output, "{input}")?; diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index 551249b77..78b133733 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_env" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND" diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index b293bc9bf..d7c9687de 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -1,8 +1,5 @@ // 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. @@ -23,6 +20,7 @@ use std::os::unix::process::ExitStatusExt; use std::process; use uucore::display::Quotable; use uucore::error::{UClapError, UResult, USimpleError, UUsageError}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_section, help_usage, show_warning}; const ABOUT: &str = help_about!("env.md"); @@ -31,7 +29,7 @@ const AFTER_HELP: &str = help_section!("after help", "env.md"); struct Options<'a> { ignore_env: bool, - null: bool, + line_ending: LineEnding, running_directory: Option<&'a str>, files: Vec<&'a str>, unsets: Vec<&'a str>, @@ -41,11 +39,11 @@ struct Options<'a> { // print name=value env pairs on screen // if null is true, separate pairs with a \0, \n otherwise -fn print_env(null: bool) { +fn print_env(line_ending: LineEnding) { let stdout_raw = io::stdout(); let mut stdout = stdout_raw.lock(); for (n, v) in env::vars() { - write!(stdout, "{}={}{}", n, v, if null { '\0' } else { '\n' }).unwrap(); + write!(stdout, "{}={}{}", n, v, line_ending).unwrap(); } } @@ -64,7 +62,7 @@ fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> UResult(opts: &mut Options<'a>, opt: &'a str) -> UResult<()> { - if opts.null { + if opts.line_ending == LineEnding::Nul { Err(UUsageError::new( 125, "cannot specify --null (-0) with command".to_string(), @@ -103,7 +101,7 @@ fn load_config_file(opts: &mut Options) -> UResult<()> { #[cfg(not(windows))] #[allow(clippy::ptr_arg)] -fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) { +fn build_command<'a, 'b>(args: &'a Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) { let progname = Cow::from(args[0]); (progname, &args[1..]) } @@ -181,7 +179,7 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { let matches = app.try_get_matches_from(args).with_exit_code(125)?; let ignore_env = matches.get_flag("ignore-environment"); - let null = matches.get_flag("null"); + let line_ending = LineEnding::from_zero_flag(matches.get_flag("null")); let running_directory = matches.get_one::("chdir").map(|s| s.as_str()); let files = match matches.get_many::("file") { Some(v) => v.map(|s| s.as_str()).collect(), @@ -194,7 +192,7 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { let mut opts = Options { ignore_env, - null, + line_ending, running_directory, files, unsets, @@ -302,10 +300,13 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { if opts.program.is_empty() { // no program provided, so just dump all env vars to stdout - print_env(opts.null); + print_env(opts.line_ending); } else { // we need to execute a command + #[cfg(windows)] let (prog, args) = build_command(&mut opts.program); + #[cfg(not(windows))] + let (prog, args) = build_command(&opts.program); /* * On Unix-like systems Command::status either ends up calling either fork or posix_spawnp diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index 894a21be0..c329e61b6 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expand" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "expand ~ (uutils) convert input tabs to spaces" diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 98b292771..9294d1a8f 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -1,9 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Virgile Andreani -// (c) kwantam -// * 2015-04-28 ~ updated to work with both UTF-8 and non-UTF-8 encodings -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 3ea796ee0..3f0b5ca76 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expr" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "expr ~ (uutils) display the value of EXPRESSION" diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 97cfa32f3..ea559090c 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -1,9 +1,7 @@ -//* This file is part of the uutils coreutils package. -//* -//* (c) Roman Gafiyatullin -//* -//* For the full copyright and license information, please view the LICENSE -//* file that was distributed with this source code. +// 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 clap::{crate_version, Arg, ArgAction, Command}; use uucore::{ diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 1d2ddbced..c55fb0bdc 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -1,19 +1,17 @@ -//* This file is part of the uutils coreutils package. -//* -//* (c) Roman Gafiyatullin -//* -//* For the full copyright and license information, please view the LICENSE -//* file that was distributed with this source code. +// 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. //! //! Here we employ shunting-yard algorithm for building AST from tokens according to operators' precedence and associative-ness. //! * `` //! -// spell-checker:ignore (ToDO) binop binops ints paren prec multibytes +// spell-checker:ignore (ToDO) ints paren prec multibytes use num_bigint::BigInt; -use num_traits::{One, Zero}; +use num_traits::Zero; use onig::{Regex, RegexOptions, Syntax}; use crate::tokens::Token; @@ -33,10 +31,12 @@ pub enum AstNode { operands: OperandsList, }, } + impl AstNode { fn debug_dump(&self) { self.debug_dump_impl(1); } + fn debug_dump_impl(&self, depth: usize) { for _ in 0..depth { print!("\t",); @@ -54,7 +54,7 @@ impl AstNode { operands, } => { println!( - "Node( {} ) at #{} (evaluate -> {:?})", + "Node( {} ) at #{} ( evaluate -> {:?} )", op_type, token_idx, self.evaluate() @@ -73,12 +73,14 @@ impl AstNode { operands, }) } + fn new_leaf(token_idx: usize, value: &str) -> Box { Box::new(Self::Leaf { token_idx, value: value.into(), }) } + pub fn evaluate(&self) -> Result { match self { Self::Leaf { value, .. } => Ok(value.clone()), @@ -156,9 +158,37 @@ impl AstNode { }, } } + pub fn operand_values(&self) -> Result, String> { - if let Self::Node { operands, .. } = self { + if let Self::Node { + operands, op_type, .. + } = self + { let mut out = Vec::with_capacity(operands.len()); + let mut operands = operands.iter(); + + if let Some(value) = operands.next() { + let value = value.evaluate()?; + out.push(value.clone()); + // short-circuit evaluation for `|` and `&` + // push dummy to pass `assert!(values.len() == 2);` + match op_type.as_ref() { + "|" => { + if value_as_bool(&value) { + out.push(String::from("dummy")); + return Ok(out); + } + } + "&" => { + if !value_as_bool(&value) { + out.push(String::from("dummy")); + return Ok(out); + } + } + _ => {} + } + } + for operand in operands { let value = operand.evaluate()?; out.push(value); @@ -242,6 +272,7 @@ fn ast_from_rpn(rpn: &mut TokenStack) -> Result, String> { } } } + fn maybe_ast_node( token_idx: usize, op_type: &str, @@ -433,12 +464,15 @@ fn infix_operator_or(values: &[String]) -> String { assert!(values.len() == 2); if value_as_bool(&values[0]) { values[0].clone() - } else { + } else if value_as_bool(&values[1]) { values[1].clone() + } else { + 0.to_string() } } fn infix_operator_and(values: &[String]) -> String { + assert!(values.len() == 2); if value_as_bool(&values[0]) && value_as_bool(&values[1]) { values[0].clone() } else { @@ -505,6 +539,7 @@ fn prefix_operator_substr(values: &[String]) -> String { fn bool_as_int(b: bool) -> u8 { u8::from(b) } + fn bool_as_string(b: bool) -> String { if b { "1".to_string() @@ -512,12 +547,13 @@ fn bool_as_string(b: bool) -> String { "0".to_string() } } + fn value_as_bool(s: &str) -> bool { if s.is_empty() { return false; } match s.parse::() { - Ok(n) => n.is_one(), + Ok(n) => n != Zero::zero(), Err(_) => true, } } diff --git a/src/uu/expr/src/tokens.rs b/src/uu/expr/src/tokens.rs index 21220d7dc..f499881c1 100644 --- a/src/uu/expr/src/tokens.rs +++ b/src/uu/expr/src/tokens.rs @@ -1,9 +1,7 @@ -//* This file is part of the uutils coreutils package. -//* -//* (c) Roman Gafiyatullin -//* -//* For the full copyright and license information, please view the LICENSE -//* file that was distributed with this source code. +// 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. //! //! The following tokens are present in the expr grammar: @@ -18,8 +16,6 @@ // spell-checker:ignore (ToDO) paren -use num_bigint::BigInt; - #[derive(Debug, Clone)] pub enum Token { Value { @@ -40,6 +36,7 @@ pub enum Token { value: String, }, } + impl Token { fn new_infix_op(v: &str, left_assoc: bool, precedence: u8) -> Self { Self::InfixOp { @@ -48,6 +45,7 @@ impl Token { value: v.into(), } } + fn new_value(v: &str) -> Self { Self::Value { value: v.into() } } @@ -58,12 +56,11 @@ impl Token { _ => false, } } - fn is_a_number(&self) -> bool { - match self { - Self::Value { value, .. } => value.parse::().is_ok(), - _ => false, - } + + fn is_a_value(&self) -> bool { + matches!(*self, Self::Value { .. }) } + fn is_a_close_paren(&self) -> bool { matches!(*self, Self::ParClose) } @@ -129,14 +126,14 @@ fn maybe_dump_tokens_acc(tokens_acc: &[(usize, Token)]) { } fn push_token_if_not_escaped(acc: &mut Vec<(usize, Token)>, tok_idx: usize, token: Token, s: &str) { - // Smells heuristics... :( + // `+` may be escaped such as `expr + 1` and `expr 1 + + 1` let prev_is_plus = match acc.last() { None => false, Some(t) => t.1.is_infix_plus(), }; let should_use_as_escaped = if prev_is_plus && acc.len() >= 2 { let pre_prev = &acc[acc.len() - 2]; - !(pre_prev.1.is_a_number() || pre_prev.1.is_a_close_paren()) + !(pre_prev.1.is_a_value() || pre_prev.1.is_a_close_paren()) } else { prev_is_plus }; diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index c97dbcba4..8ecee86c5 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_factor" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "factor ~ (uutils) display the prime factors of each NUMBER" diff --git a/src/uu/factor/build.rs b/src/uu/factor/build.rs index bdd132094..8de0605a2 100644 --- a/src/uu/factor/build.rs +++ b/src/uu/factor/build.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) kwantam -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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. //! Generate a table of the multiplicative inverses of p_i mod 2^64 //! for the first 1027 odd primes (all 13 bit and smaller primes). @@ -92,8 +90,6 @@ const MAX_WIDTH: usize = 102; const PREAMBLE: &str = r##"/* * This file is part of the uutils coreutils package. * -* (c) kwantam -* * For the full copyright and license information, please view the LICENSE file * that was distributed with this source code. */ diff --git a/src/uu/factor/sieve.rs b/src/uu/factor/sieve.rs index 2c637fad1..e2211ce05 100644 --- a/src/uu/factor/sieve.rs +++ b/src/uu/factor/sieve.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) kwantam -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 (ToDO) filts, minidx, minkey paridx diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index 714b38e5e..63a0632a3 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -1,13 +1,8 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2014 T. Jameson Little -// * (c) 2020 nicoo -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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::Write as FmtWrite; use std::io::BufRead; use std::io::{self, stdin, stdout, Write}; @@ -15,7 +10,7 @@ mod factor; use clap::{crate_version, Arg, ArgAction, Command}; pub use factor::*; use uucore::display::Quotable; -use uucore::error::UResult; +use uucore::error::{set_exit_code, FromIo, UResult}; use uucore::{format_usage, help_about, help_usage, show_error, show_warning}; mod miller_rabin; @@ -35,26 +30,27 @@ mod options { fn print_factors_str( num_str: &str, w: &mut io::BufWriter, - factors_buffer: &mut String, print_exponents: bool, -) -> Result<(), Box> { - num_str - .trim() - .parse::() - .map_err(|e| e.into()) - .and_then(|x| { - factors_buffer.clear(); - // If print_exponents is true, use the alternate format specifier {:#} from fmt to print the factors - // of x in the form of p^e. - if print_exponents { - writeln!(factors_buffer, "{}:{:#}", x, factor(x))?; - } else { - writeln!(factors_buffer, "{}:{}", x, factor(x))?; - } - w.write_all(factors_buffer.as_bytes())?; - w.flush()?; - Ok(()) - }) +) -> io::Result<()> { + let x = match num_str.trim().parse::() { + Ok(x) => x, + Err(e) => { + // We return Ok() instead of Err(), because it's non-fatal and we should try the next + // number. + show_warning!("{}: {}", num_str.maybe_quote(), e); + set_exit_code(1); + return Ok(()); + } + }; + + // If print_exponents is true, use the alternate format specifier {:#} from fmt to print the factors + // of x in the form of p^e. + if print_exponents { + writeln!(w, "{}:{:#}", x, factor(x))?; + } else { + writeln!(w, "{}:{}", x, factor(x))?; + } + w.flush() } #[uucore::main] @@ -67,25 +63,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let stdout = stdout(); // We use a smaller buffer here to pass a gnu test. 4KiB appears to be the default pipe size for bash. let mut w = io::BufWriter::with_capacity(4 * 1024, stdout.lock()); - let mut factors_buffer = String::new(); if let Some(values) = matches.get_many::(options::NUMBER) { for number in values { - if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer, print_exponents) - { - show_warning!("{}: {}", number.maybe_quote(), e); - } + print_factors_str(number, &mut w, print_exponents) + .map_err_context(|| "write error".into())?; } } else { let stdin = stdin(); let lines = stdin.lock().lines(); for line in lines { for number in line.unwrap().split_whitespace() { - if let Err(e) = - print_factors_str(number, &mut w, &mut factors_buffer, print_exponents) - { - show_warning!("{}: {}", number.maybe_quote(), e); - } + print_factors_str(number, &mut w, print_exponents) + .map_err_context(|| "write error".into())?; } } } diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index a87f4219e..a7e848bb8 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2020 nicoo -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 smallvec::SmallVec; use std::cell::RefCell; @@ -98,7 +96,7 @@ impl fmt::Display for Factors { v.sort_unstable(); let include_exponents = f.alternate(); - for (p, exp) in v.iter() { + for (p, exp) in v { if include_exponents && *exp > 1 { write!(f, " {p}^{exp}")?; } else { @@ -294,6 +292,7 @@ impl std::ops::BitXor for Factors { fn bitxor(self, rhs: Exponent) -> Self { debug_assert_ne!(rhs, 0); let mut r = Self::one(); + #[allow(clippy::explicit_iter_loop)] for (p, e) in self.0.borrow().0.iter() { r.add(*p, rhs * e); } diff --git a/src/uu/factor/src/miller_rabin.rs b/src/uu/factor/src/miller_rabin.rs index de2f8a924..a06b20cd3 100644 --- a/src/uu/factor/src/miller_rabin.rs +++ b/src/uu/factor/src/miller_rabin.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2020 nicoo -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 (URL) appspot @@ -63,7 +61,7 @@ pub(crate) fn test(m: A) -> Result { let one = m.one(); let minus_one = m.minus_one(); - 'witness: for _a in A::BASIS.iter() { + 'witness: for _a in A::BASIS { let _a = _a % n; if _a == 0 { continue; diff --git a/src/uu/factor/src/numeric/gcd.rs b/src/uu/factor/src/numeric/gcd.rs index 05eb20cb0..43c6ce9b7 100644 --- a/src/uu/factor/src/numeric/gcd.rs +++ b/src/uu/factor/src/numeric/gcd.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2015 Wiktor Kuropatwa -// * (c) 2020 nicoo -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 (vars) kgcdab gcdac gcdbc diff --git a/src/uu/factor/src/numeric/mod.rs b/src/uu/factor/src/numeric/mod.rs index d086027b8..d4c0b5dc7 100644 --- a/src/uu/factor/src/numeric/mod.rs +++ b/src/uu/factor/src/numeric/mod.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2020 nicoo -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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. mod gcd; pub use gcd::gcd; diff --git a/src/uu/factor/src/numeric/modular_inverse.rs b/src/uu/factor/src/numeric/modular_inverse.rs index 992253a43..bacf57d9c 100644 --- a/src/uu/factor/src/numeric/modular_inverse.rs +++ b/src/uu/factor/src/numeric/modular_inverse.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2015 Wiktor Kuropatwa -// * (c) 2020 nicoo -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 super::traits::Int; diff --git a/src/uu/factor/src/numeric/montgomery.rs b/src/uu/factor/src/numeric/montgomery.rs index d411d0d41..10c6dd2d9 100644 --- a/src/uu/factor/src/numeric/montgomery.rs +++ b/src/uu/factor/src/numeric/montgomery.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2020 Alex Lyon -// * (c) 2020 nicoo -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 super::*; diff --git a/src/uu/factor/src/numeric/traits.rs b/src/uu/factor/src/numeric/traits.rs index 1dc681976..c3528a4ad 100644 --- a/src/uu/factor/src/numeric/traits.rs +++ b/src/uu/factor/src/numeric/traits.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2020 Alex Lyon -// * (c) 2020 nicoo -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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. pub(crate) use num_traits::{ identities::{One, Zero}, diff --git a/src/uu/factor/src/rho.rs b/src/uu/factor/src/rho.rs index e7aa00b4d..2af0f6855 100644 --- a/src/uu/factor/src/rho.rs +++ b/src/uu/factor/src/rho.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2015 Wiktor Kuropatwa -// * (c) 2020 nicoo -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 rand::distributions::{Distribution, Uniform}; use rand::rngs::SmallRng; diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index 0fb338d9d..e9b525a3a 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2015 kwantam -// * (c) 2020 nicoo -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 (ToDO) INVS diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index 9eb0ccbb1..3b1ca97f9 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_false" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "false ~ (uutils) do nothing and fail" diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index 27b6be291..3ae25e569 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Jordi Boggiano -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 clap::{Arg, ArgAction, Command}; use std::{ffi::OsString, io::Write}; use uucore::error::{set_exit_code, UResult}; diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index cabb144cf..c49f52741 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fmt" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "fmt ~ (uutils) reformat each paragraph of input" diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index d144bdd8a..c30d923b7 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -1,9 +1,7 @@ -// * This file is part of `fmt` from the uutils coreutils package. -// * -// * (c) kwantam -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) PSKIP linebreak ostream parasplit tabwidth xanti xprefix @@ -60,6 +58,29 @@ pub struct FmtOptions { goal: usize, tabwidth: usize, } + +impl Default for FmtOptions { + fn default() -> Self { + Self { + crown: false, + tagged: false, + mail: false, + uniform: false, + quick: false, + split_only: false, + use_prefix: false, + prefix: String::new(), + xprefix: false, + use_anti_prefix: false, + anti_prefix: String::new(), + xanti_prefix: false, + width: 75, + goal: 70, + tabwidth: 8, + } + } +} + /// Parse the command line arguments and return the list of files and formatting options. /// /// # Arguments @@ -70,7 +91,11 @@ pub struct FmtOptions { /// /// A tuple containing a vector of file names and a `FmtOptions` struct. #[allow(clippy::cognitive_complexity)] +#[allow(clippy::field_reassign_with_default)] fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions)> { + // by default, goal is 93% of width + const DEFAULT_GOAL_TO_WIDTH_RATIO: usize = 93; + let matches = uu_app().try_get_matches_from(args)?; let mut files: Vec = matches @@ -78,23 +103,7 @@ fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - let mut fmt_opts = FmtOptions { - crown: false, - tagged: false, - mail: false, - uniform: false, - quick: false, - split_only: false, - use_prefix: false, - prefix: String::new(), - xprefix: false, - use_anti_prefix: false, - anti_prefix: String::new(), - xanti_prefix: false, - width: 79, - goal: 74, - tabwidth: 8, - }; + let mut fmt_opts = FmtOptions::default(); fmt_opts.tagged = matches.get_flag(OPT_TAGGED_PARAGRAPH); if matches.get_flag(OPT_CROWN_MARGIN) { @@ -122,16 +131,8 @@ fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions) fmt_opts.use_anti_prefix = true; }; - if let Some(s) = matches.get_one::(OPT_WIDTH) { - fmt_opts.width = match s.parse::() { - Ok(t) => t, - Err(e) => { - return Err(USimpleError::new( - 1, - format!("Invalid WIDTH specification: {}: {}", s.quote(), e), - )); - } - }; + if let Some(width) = matches.get_one::(OPT_WIDTH) { + fmt_opts.width = *width; if fmt_opts.width > MAX_WIDTH { return Err(USimpleError::new( 1, @@ -141,21 +142,19 @@ fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions) ), )); } - fmt_opts.goal = cmp::min(fmt_opts.width * 94 / 100, fmt_opts.width - 3); + fmt_opts.goal = cmp::min( + fmt_opts.width * DEFAULT_GOAL_TO_WIDTH_RATIO / 100, + fmt_opts.width - 3, + ); }; - if let Some(s) = matches.get_one::(OPT_GOAL) { - fmt_opts.goal = match s.parse::() { - Ok(t) => t, - Err(e) => { - return Err(USimpleError::new( - 1, - format!("Invalid GOAL specification: {}: {}", s.quote(), e), - )); - } - }; - if !matches.get_flag(OPT_WIDTH) { - fmt_opts.width = cmp::max(fmt_opts.goal * 100 / 94, fmt_opts.goal + 3); + if let Some(goal) = matches.get_one::(OPT_GOAL) { + fmt_opts.goal = *goal; + if !matches.contains_id(OPT_WIDTH) { + fmt_opts.width = cmp::max( + fmt_opts.goal * 100 / DEFAULT_GOAL_TO_WIDTH_RATIO, + fmt_opts.goal + 3, + ); } else if fmt_opts.goal > fmt_opts.width { return Err(USimpleError::new(1, "GOAL cannot be greater than WIDTH.")); } @@ -356,15 +355,17 @@ pub fn uu_app() -> Command { Arg::new(OPT_WIDTH) .short('w') .long("width") - .help("Fill output lines up to a maximum of WIDTH columns, default 79.") - .value_name("WIDTH"), + .help("Fill output lines up to a maximum of WIDTH columns, default 75.") + .value_name("WIDTH") + .value_parser(clap::value_parser!(usize)), ) .arg( Arg::new(OPT_GOAL) .short('g') .long("goal") - .help("Goal width, default ~0.94*WIDTH. Must be less than WIDTH.") - .value_name("GOAL"), + .help("Goal width, default of 93% of WIDTH. Must be less than WIDTH.") + .value_name("GOAL") + .value_parser(clap::value_parser!(usize)), ) .arg( Arg::new(OPT_QUICK) diff --git a/src/uu/fmt/src/linebreak.rs b/src/uu/fmt/src/linebreak.rs index e86623c88..fbd990fff 100644 --- a/src/uu/fmt/src/linebreak.rs +++ b/src/uu/fmt/src/linebreak.rs @@ -1,9 +1,7 @@ -// * This file is part of `fmt` from the uutils coreutils package. -// * -// * (c) kwantam -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) INFTY MULT accum breakwords linebreak linebreaking linebreaks linelen maxlength minlength nchars ostream overlen parasplit plass posn powf punct signum slen sstart tabwidth tlen underlen winfo wlen wordlen @@ -275,6 +273,7 @@ fn find_kp_breakpoints<'a, T: Iterator>>( next_active_breaks.clear(); // go through each active break, extending it and possibly adding a new active // break if we are above the minimum required length + #[allow(clippy::explicit_iter_loop)] for &i in active_breaks.iter() { let active = &mut linebreaks[i]; // normalize demerits to avoid overflow, and record if this is the least diff --git a/src/uu/fmt/src/parasplit.rs b/src/uu/fmt/src/parasplit.rs index c94c81974..68c8f78fa 100644 --- a/src/uu/fmt/src/parasplit.rs +++ b/src/uu/fmt/src/parasplit.rs @@ -1,9 +1,7 @@ -// * This file is part of `fmt` from the uutils coreutils package. -// * -// * (c) kwantam -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) INFTY MULT PSKIP accum aftertab beforetab breakwords fmt's formatline linebreak linebreaking linebreaks linelen maxlength minlength nchars noformat noformatline ostream overlen parasplit pfxind plass pmatch poffset posn powf prefixindent punct signum slen sstart tabwidth tlen underlen winfo wlen wordlen wordsplits xanti xprefix diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index a476e0f13..af36f0466 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fold" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "fold ~ (uutils) wrap each line of input" diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index d53573d82..95b6d9a82 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDOs) ncount routput diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 989dc9c11..5f786acfd 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_groups" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "groups ~ (uutils) display group memberships for USERNAME" diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index dd1cd9b04..0f0dfce80 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -1,8 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Alan Andrade -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index cca2dbfe1..23b60a701 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hashsum" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "hashsum ~ (uutils) display or check input digests" diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index cc1b050fd..d27b09b98 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -1,11 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * (c) Vsevolod Velichko -// * (c) Gil Cottle -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) algo, algoname, regexes, nread, nonames @@ -748,7 +744,7 @@ where ) .map_err_context(|| "failed to read input".to_string())?; if options.tag { - println!("{} ({:?}) = {}", options.algoname, filename.display(), sum); + println!("{} ({}) = {}", options.algoname, filename.display(), sum); } else if options.nonames { println!("{sum}"); } else if options.zero { @@ -807,8 +803,7 @@ fn digest_reader( Ok(digest.result_str()) } else { // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) - let mut bytes = Vec::new(); - bytes.resize((output_bits + 7) / 8, 0); + let mut bytes = vec![0; (output_bits + 7) / 8]; digest.hash_finalize(&mut bytes); Ok(encode(bytes)) } diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index 6326fcd8b..7c73f3941 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_head" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "head ~ (uutils) display the first lines of input" diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 2517a98ea..c533f5a5d 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -1,15 +1,16 @@ -// * 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. +// 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 (vars) zlines BUFWRITER seekable +// spell-checker:ignore (vars) BUFWRITER seekable use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::ffi::OsString; use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; +use uucore::line_ending::LineEnding; use uucore::lines::lines; use uucore::{format_usage, help_about, help_usage, show}; @@ -30,6 +31,7 @@ mod options { pub const FILES_NAME: &str = "FILE"; pub const PRESUME_INPUT_PIPE: &str = "-PRESUME-INPUT-PIPE"; } + mod parse; mod take; use take::take_all_but; @@ -184,7 +186,7 @@ fn arg_iterate<'a>( struct HeadOptions { pub quiet: bool, pub verbose: bool, - pub zeroed: bool, + pub line_ending: LineEnding, pub presume_input_pipe: bool, pub mode: Mode, pub files: Vec, @@ -197,7 +199,7 @@ impl HeadOptions { options.quiet = matches.get_flag(options::QUIET_NAME); options.verbose = matches.get_flag(options::VERBOSE_NAME); - options.zeroed = matches.get_flag(options::ZERO_NAME); + options.line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_NAME)); options.presume_input_pipe = matches.get_flag(options::PRESUME_INPUT_PIPE); options.mode = Mode::from(matches)?; @@ -227,9 +229,8 @@ where Ok(()) } -fn read_n_lines(input: &mut impl std::io::BufRead, n: u64, zero: bool) -> std::io::Result<()> { +fn read_n_lines(input: &mut impl std::io::BufRead, n: u64, separator: u8) -> std::io::Result<()> { // Read the first `n` lines from the `input` reader. - let separator = if zero { b'\0' } else { b'\n' }; let mut reader = take_lines(input, n, separator); // Write those bytes to `stdout`. @@ -293,20 +294,12 @@ fn read_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io fn read_but_last_n_lines( input: impl std::io::BufRead, n: usize, - zero: bool, + separator: u8, ) -> std::io::Result<()> { - if zero { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - for bytes in take_all_but(lines(input, b'\0'), n) { - stdout.write_all(&bytes?)?; - } - } else { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - for bytes in take_all_but(lines(input, b'\n'), n) { - stdout.write_all(&bytes?)?; - } + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + for bytes in take_all_but(lines(input, separator), n) { + stdout.write_all(&bytes?)?; } Ok(()) } @@ -350,7 +343,7 @@ fn read_but_last_n_lines( /// assert_eq!(find_nth_line_from_end(&mut input, 4, false).unwrap(), 0); /// assert_eq!(find_nth_line_from_end(&mut input, 1000, false).unwrap(), 0); /// ``` -fn find_nth_line_from_end(input: &mut R, n: u64, zeroed: bool) -> std::io::Result +fn find_nth_line_from_end(input: &mut R, n: u64, separator: u8) -> std::io::Result where R: Read + Seek, { @@ -370,14 +363,8 @@ where ))?; input.read_exact(buffer)?; for byte in buffer.iter().rev() { - match byte { - b'\n' if !zeroed => { - lines += 1; - } - 0u8 if zeroed => { - lines += 1; - } - _ => {} + if byte == &separator { + lines += 1; } // if it were just `n`, if lines == n + 1 { @@ -407,7 +394,7 @@ fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std: } } Mode::AllButLastLines(n) => { - let found = find_nth_line_from_end(input, n, options.zeroed)?; + let found = find_nth_line_from_end(input, n, options.line_ending.into())?; read_n_bytes( &mut std::io::BufReader::with_capacity(BUF_SIZE, input), found, @@ -426,7 +413,7 @@ fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Resul Mode::FirstLines(n) => read_n_lines( &mut std::io::BufReader::with_capacity(BUF_SIZE, input), n, - options.zeroed, + options.line_ending.into(), ), Mode::AllButLastBytes(_) | Mode::AllButLastLines(_) => head_backwards_file(input, options), } @@ -466,11 +453,13 @@ fn uu_head(options: &HeadOptions) -> UResult<()> { Mode::AllButLastBytes(n) => { read_but_last_n_bytes(&mut stdin, n.try_into().unwrap()) } - Mode::FirstLines(n) => read_n_lines(&mut stdin, n, options.zeroed), + Mode::FirstLines(n) => read_n_lines(&mut stdin, n, options.line_ending.into()), // unwrap is guaranteed to succeed because we checked the value of n above - Mode::AllButLastLines(n) => { - read_but_last_n_lines(&mut stdin, n.try_into().unwrap(), options.zeroed) - } + Mode::AllButLastLines(n) => read_but_last_n_lines( + &mut stdin, + n.try_into().unwrap(), + options.line_ending.into(), + ), } } (name, false) => { @@ -531,6 +520,7 @@ mod tests { use std::io::Cursor; use super::*; + fn options(args: &str) -> Result { let combined = "head ".to_owned() + args; let args = combined.split_whitespace().map(OsString::from); @@ -538,13 +528,15 @@ mod tests { .get_matches_from(arg_iterate(args).map_err(|_| String::from("Arg iterate failed"))?); HeadOptions::get_from(&matches) } + #[test] fn test_args_modes() { let args = options("-n -10M -vz").unwrap(); - assert!(args.zeroed); + assert_eq!(args.line_ending, LineEnding::Nul); assert!(args.verbose); assert_eq!(args.mode, Mode::AllButLastLines(10 * 1024 * 1024)); } + #[test] fn test_gnu_compatibility() { let args = options("-n 1 -c 1 -n 5 -c kiB -vqvqv").unwrap(); // spell-checker:disable-line @@ -554,35 +546,43 @@ mod tests { assert_eq!(options("-2b").unwrap().mode, Mode::FirstBytes(1024)); assert_eq!(options("-5 -c 1").unwrap().mode, Mode::FirstBytes(1)); } + #[test] + #[allow(clippy::cognitive_complexity)] fn all_args_test() { assert!(options("--silent").unwrap().quiet); assert!(options("--quiet").unwrap().quiet); assert!(options("-q").unwrap().quiet); assert!(options("--verbose").unwrap().verbose); assert!(options("-v").unwrap().verbose); - assert!(options("--zero-terminated").unwrap().zeroed); - assert!(options("-z").unwrap().zeroed); + assert_eq!( + options("--zero-terminated").unwrap().line_ending, + LineEnding::Nul + ); + assert_eq!(options("-z").unwrap().line_ending, LineEnding::Nul); assert_eq!(options("--lines 15").unwrap().mode, Mode::FirstLines(15)); assert_eq!(options("-n 15").unwrap().mode, Mode::FirstLines(15)); assert_eq!(options("--bytes 15").unwrap().mode, Mode::FirstBytes(15)); assert_eq!(options("-c 15").unwrap().mode, Mode::FirstBytes(15)); } + #[test] fn test_options_errors() { assert!(options("-n IsThisTheRealLife?").is_err()); assert!(options("-c IsThisJustFantasy").is_err()); } + #[test] fn test_options_correct_defaults() { let opts = HeadOptions::default(); assert!(!opts.verbose); assert!(!opts.quiet); - assert!(!opts.zeroed); + assert_eq!(opts.line_ending, LineEnding::Newline); assert_eq!(opts.mode, Mode::FirstLines(10)); assert!(opts.files.is_empty()); } + fn arg_outputs(src: &str) -> Result { let split = src.split_whitespace().map(OsString::from); match arg_iterate(split) { @@ -595,6 +595,7 @@ mod tests { Err(_) => Err(()), } } + #[test] fn test_arg_iterate() { // test that normal args remain unchanged @@ -619,6 +620,7 @@ mod tests { //test that empty args remain unchanged assert_eq!(arg_outputs("head"), Ok("head".to_owned())); } + #[test] #[cfg(target_os = "linux")] fn test_arg_iterate_bad_encoding() { @@ -627,21 +629,22 @@ mod tests { // this arises from a conversion from OsString to &str assert!(arg_iterate(vec![OsString::from("head"), invalid].into_iter()).is_err()); } + #[test] fn read_early_exit() { let mut empty = std::io::BufReader::new(std::io::Cursor::new(Vec::new())); assert!(read_n_bytes(&mut empty, 0).is_ok()); - assert!(read_n_lines(&mut empty, 0, false).is_ok()); + assert!(read_n_lines(&mut empty, 0, b'\n').is_ok()); } #[test] fn test_find_nth_line_from_end() { let mut input = Cursor::new("x\ny\nz\n"); - assert_eq!(find_nth_line_from_end(&mut input, 0, false).unwrap(), 6); - assert_eq!(find_nth_line_from_end(&mut input, 1, false).unwrap(), 4); - assert_eq!(find_nth_line_from_end(&mut input, 2, false).unwrap(), 2); - assert_eq!(find_nth_line_from_end(&mut input, 3, false).unwrap(), 0); - assert_eq!(find_nth_line_from_end(&mut input, 4, false).unwrap(), 0); - assert_eq!(find_nth_line_from_end(&mut input, 1000, false).unwrap(), 0); + assert_eq!(find_nth_line_from_end(&mut input, 0, b'\n').unwrap(), 6); + assert_eq!(find_nth_line_from_end(&mut input, 1, b'\n').unwrap(), 4); + assert_eq!(find_nth_line_from_end(&mut input, 2, b'\n').unwrap(), 2); + assert_eq!(find_nth_line_from_end(&mut input, 3, b'\n').unwrap(), 0); + assert_eq!(find_nth_line_from_end(&mut input, 4, b'\n').unwrap(), 0); + assert_eq!(find_nth_line_from_end(&mut input, 1000, b'\n').unwrap(), 0); } } diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 56c359a0c..dce60bae0 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -1,19 +1,19 @@ -// * 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. +// 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; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, ParseSizeError}; #[derive(PartialEq, Eq, Debug)] pub enum ParseError { Syntax, Overflow, } + /// Parses obsolete syntax /// head -NUM\[kmzv\] // spell-checker:disable-line -#[allow(clippy::cognitive_complexity)] pub fn parse_obsolete(src: &str) -> Option, ParseError>> { let mut chars = src.char_indices(); if let Some((_, '-')) = chars.next() { @@ -30,65 +30,7 @@ pub fn parse_obsolete(src: &str) -> Option } } 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)), - } + process_num_block(&src[1..=num_end], last_char, &mut chars) } else { None } @@ -96,6 +38,74 @@ pub fn parse_obsolete(src: &str) -> Option None } } + +/// Processes the numeric block of the input string to generate the appropriate options. +fn process_num_block( + src: &str, + last_char: char, + chars: &mut std::str::CharIndices, +) -> Option, ParseError>> { + match src.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 { + // note 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)), + } +} + /// Parses an -c or -n argument, /// the bool specifies whether to read from the end pub fn parse_num(src: &str) -> Result<(u64, bool), ParseSizeError> { @@ -119,13 +129,14 @@ pub fn parse_num(src: &str) -> Result<(u64, bool), ParseSizeError> { if trimmed_string.is_empty() { Ok((0, all_but_last)) } else { - parse_size(trimmed_string).map(|n| (n, all_but_last)) + parse_size_u64(trimmed_string).map(|n| (n, all_but_last)) } } #[cfg(test)] mod tests { use super::*; + fn obsolete(src: &str) -> Option, ParseError>> { let r = parse_obsolete(src); match r { @@ -136,10 +147,13 @@ mod tests { None => None, } } + fn obsolete_result(src: &[&str]) -> Option, ParseError>> { Some(Ok(src.iter().map(|s| s.to_string()).collect())) } + #[test] + #[allow(clippy::cognitive_complexity)] fn test_parse_numbers_obsolete() { assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"])); assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"])); @@ -158,16 +172,19 @@ mod tests { 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() { @@ -180,6 +197,7 @@ mod tests { Some(Err(ParseError::Overflow)) ); } + #[test] #[cfg(target_pointer_width = "32")] fn test_parse_obsolete_overflow_x32() { diff --git a/src/uu/head/src/take.rs b/src/uu/head/src/take.rs index 47beba8a4..da48afd6a 100644 --- a/src/uu/head/src/take.rs +++ b/src/uu/head/src/take.rs @@ -1,3 +1,7 @@ +// 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. //! Take all but the last elements of an iterator. use std::io::Read; diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index 298a9da69..2e6dcc339 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostid" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "hostid ~ (uutils) display the numeric identifier of the current host" diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index 3657d137a..a5c18d075 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Maciej Dziardziel -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 (ToDO) gethostid diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index d28f2511f..49cb14bf2 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostname" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "hostname ~ (uutils) display or set the host name of the current host" diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 83a22a82f..6a318cb8c 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alan Andrade -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) MAKEWORD addrs hashset diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index bd80a447c..bf1eaf44c 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_id" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "id ~ (uutils) display user and group information for USER" diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 7a8e40059..8b16ba8b7 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -1,8 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Alan Andrade -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -44,6 +41,7 @@ use uucore::error::UResult; use uucore::error::{set_exit_code, USimpleError}; pub use uucore::libc; use uucore::libc::{getlogin, uid_t}; +use uucore::line_ending::LineEnding; use uucore::process::{getegid, geteuid, getgid, getuid}; use uucore::{format_usage, help_about, help_section, help_usage, show_error}; @@ -111,6 +109,7 @@ struct State { } #[uucore::main] +#[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; @@ -174,13 +173,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { " ".to_string() } }; - let line_ending = { - if state.zflag { - '\0' - } else { - '\n' - } - }; + let line_ending = LineEnding::from_zero_flag(state.zflag); if state.cflag { if state.selinux_supported { @@ -313,7 +306,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } if default_format { - id_print(&mut state, &groups); + id_print(&state, &groups); } print!("{line_ending}"); @@ -555,7 +548,7 @@ fn auditid() { println!("asid={}", auditinfo.ai_asid); } -fn id_print(state: &mut State, groups: &[u32]) { +fn id_print(state: &State, groups: &[u32]) { let uid = state.ids.as_ref().unwrap().uid; let gid = state.ids.as_ref().unwrap().gid; let euid = state.ids.as_ref().unwrap().euid; diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 8228e0d20..b5353eace 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_install" -version = "0.0.20" +version = "0.0.22" authors = ["Ben Eills ", "uutils developers"] license = "MIT" description = "install ~ (uutils) copy files from SOURCE to DESTINATION (with specified attributes)" @@ -19,7 +19,13 @@ clap = { workspace = true } filetime = { workspace = true } file_diff = { workspace = true } libc = { workspace = true } -uucore = { workspace = true, features = ["fs", "mode", "perms", "entries"] } +uucore = { workspace = true, features = [ + "backup-control", + "fs", + "mode", + "perms", + "entries", +] } [[bin]] name = "install" diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index e0307fe34..63ba52b1c 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Ben Eills -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 (ToDO) rwxr sourcepath targetpath Isnt uioerror @@ -528,6 +526,7 @@ fn is_potential_directory_path(path: &Path) -> bool { /// /// Returns a Result type with the Err variant containing the error message. /// +#[allow(clippy::cognitive_complexity)] fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { // first check that paths contains at least one element if paths.is_empty() { @@ -619,7 +618,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR if !target_dir.is_dir() { return Err(InstallError::TargetDirIsntDir(target_dir.to_path_buf()).into()); } - for sourcepath in files.iter() { + for sourcepath in files { if let Err(err) = sourcepath .metadata() .map_err_context(|| format!("cannot stat {}", sourcepath.quote())) diff --git a/src/uu/install/src/mode.rs b/src/uu/install/src/mode.rs index 6c76dd016..ebdec14af 100644 --- a/src/uu/install/src/mode.rs +++ b/src/uu/install/src/mode.rs @@ -1,3 +1,7 @@ +// 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::fs; use std::path::Path; #[cfg(not(windows))] @@ -5,10 +9,7 @@ use uucore::mode; /// Takes a user-supplied string and tries to parse to u16 mode bitmask. pub fn parse(mode_string: &str, considering_dir: bool, umask: u32) -> Result { - let numbers: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - - // Passing 000 as the existing permissions seems to mirror GNU behavior. - if mode_string.contains(numbers) { + if mode_string.chars().any(|c| c.is_ascii_digit()) { mode::parse_numeric(0, mode_string, considering_dir) } else { mode::parse_symbolic(0, mode_string, umask, considering_dir) diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index a06303c24..c52f66de4 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_join" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "join ~ (uutils) merge lines from inputs with matching join fields" diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index de1a9181b..a48ba3657 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Konstantin Pospelov -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) autoformat FILENUM whitespaces pairable unpairable nocheck @@ -22,6 +20,7 @@ use std::num::IntErrorKind; use std::os::unix::ffi::OsStrExt; use uucore::display::Quotable; use uucore::error::{set_exit_code, UError, UResult, USimpleError}; +use uucore::line_ending::LineEnding; use uucore::{crash, crash_if_err, format_usage, help_about, help_usage}; const ABOUT: &str = help_about!("join.md"); @@ -62,13 +61,6 @@ enum FileNum { File2, } -#[repr(u8)] -#[derive(Copy, Clone)] -enum LineEnding { - Nul = 0, - Newline = b'\n', -} - #[derive(Copy, Clone, PartialEq)] enum Sep { Char(u8), @@ -599,20 +591,38 @@ impl<'a> State<'a> { } } -#[uucore::main] -#[allow(clippy::cognitive_complexity)] -pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; +fn parse_separator(value_os: &OsString) -> UResult { + #[cfg(unix)] + let value = value_os.as_bytes(); + #[cfg(not(unix))] + let value = match value_os.to_str() { + Some(value) => value.as_bytes(), + None => { + return Err(USimpleError::new( + 1, + "unprintable field separators are only supported on unix-like platforms", + )); + } + }; + match value.len() { + 0 => Ok(Sep::Line), + 1 => Ok(Sep::Char(value[0])), + 2 if value[0] == b'\\' && value[1] == b'0' => Ok(Sep::Char(0)), + _ => Err(USimpleError::new( + 1, + format!("multi-character tab {}", value_os.to_string_lossy()), + )), + } +} - let keys = parse_field_number_option(matches.get_one::("j").map(|s| s.as_str()))?; - let key1 = parse_field_number_option(matches.get_one::("1").map(|s| s.as_str()))?; - let key2 = parse_field_number_option(matches.get_one::("2").map(|s| s.as_str()))?; - - let mut settings = Settings::default(); +fn parse_print_settings(matches: &clap::ArgMatches) -> UResult<(bool, bool, bool)> { + let mut print_joined = true; + let mut print_unpaired1 = false; + let mut print_unpaired2 = false; let v_values = matches.get_many::("v"); if v_values.is_some() { - settings.print_joined = false; + print_joined = false; } let unpaired = v_values @@ -620,41 +630,43 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .chain(matches.get_many("a").unwrap_or_default()); for file_num in unpaired { match parse_file_number(file_num)? { - FileNum::File1 => settings.print_unpaired1 = true, - FileNum::File2 => settings.print_unpaired2 = true, + FileNum::File1 => print_unpaired1 = true, + FileNum::File2 => print_unpaired2 = true, } } + Ok((print_joined, print_unpaired1, print_unpaired2)) +} + +fn get_and_parse_field_number(matches: &clap::ArgMatches, key: &str) -> UResult> { + let value = matches.get_one::(key).map(|s| s.as_str()); + parse_field_number_option(value) +} + +/// Parses the command-line arguments and constructs a `Settings` struct. +/// +/// This function takes the matches from the command-line arguments, processes them, +/// and returns a `Settings` struct that encapsulates the configuration for the program. +#[allow(clippy::field_reassign_with_default)] +fn parse_settings(matches: &clap::ArgMatches) -> UResult { + let keys = get_and_parse_field_number(matches, "j")?; + let key1 = get_and_parse_field_number(matches, "1")?; + let key2 = get_and_parse_field_number(matches, "2")?; + + let (print_joined, print_unpaired1, print_unpaired2) = parse_print_settings(matches)?; + + let mut settings = Settings::default(); + + settings.print_joined = print_joined; + settings.print_unpaired1 = print_unpaired1; + settings.print_unpaired2 = print_unpaired2; + settings.ignore_case = matches.get_flag("i"); settings.key1 = get_field_number(keys, key1)?; settings.key2 = get_field_number(keys, key2)?; - if let Some(value_os) = matches.get_one::("t") { - #[cfg(unix)] - let value = value_os.as_bytes(); - #[cfg(not(unix))] - let value = match value_os.to_str() { - Some(value) => value.as_bytes(), - None => { - return Err(USimpleError::new( - 1, - "unprintable field separators are only supported on unix-like platforms", - )) - } - }; - settings.separator = match value.len() { - 0 => Sep::Line, - 1 => Sep::Char(value[0]), - 2 if value[0] == b'\\' && value[1] == b'0' => Sep::Char(0), - _ => { - return Err(USimpleError::new( - 1, - format!("multi-character tab {}", value_os.to_string_lossy()), - )) - } - }; + settings.separator = parse_separator(value_os)?; } - if let Some(format) = matches.get_one::("o") { if format == "auto" { settings.autoformat = true; @@ -683,9 +695,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { settings.headers = true; } - if matches.get_flag("z") { - settings.line_ending = LineEnding::Nul; - } + settings.line_ending = LineEnding::from_zero_flag(matches.get_flag("z")); + + Ok(settings) +} + +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches = uu_app().try_get_matches_from(args)?; + + let settings = parse_settings(&matches)?; let file1 = matches.get_one::("file1").unwrap(); let file2 = matches.get_one::("file2").unwrap(); diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index 8db333e4a..a606c81e7 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_kill" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "kill ~ (uutils) send a signal to a process" diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index d18a483fd..b0e18a798 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Maciej Dziardziel -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 (ToDO) signalname pids killpg diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index abac74b3f..c79ca6041 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_link" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "link ~ (uutils) create a hard (file system) link to FILE" diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 6688003a9..806e89828 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Michael Gehring -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 clap::builder::ValueParser; use clap::{crate_version, Arg, Command}; use std::ffi::OsString; diff --git a/src/uu/ln/Cargo.toml b/src/uu/ln/Cargo.toml index 7674129f3..e9f0e6eb7 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ln" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "ln ~ (uutils) create a (file system) link to TARGET" @@ -16,7 +16,7 @@ path = "src/ln.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["fs"] } +uucore = { workspace = true, features = ["backup-control", "fs"] } [[bin]] name = "ln" diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index c2bf25c5c..8b76aa73c 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Joseph Crail -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) srcpath targetpath EEXIST @@ -299,7 +297,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) } let mut all_successful = true; - for srcpath in files.iter() { + for srcpath in files { let targetpath = if settings.no_dereference && matches!(settings.overwrite, OverwriteMode::Force) { // In that case, we don't want to do link resolution diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index 85cfb766a..41cbcf73b 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_logname" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "logname ~ (uutils) display the login name of the current user" diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index b3cd06243..52505d98d 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Benoit Benedetti -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. /* last synced with: logname (GNU coreutils) 8.22 */ diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 6122460fc..64d1cf136 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ls" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "ls ~ (uutils) display directory contents" @@ -19,13 +19,17 @@ clap = { workspace = true, features = ["env"] } chrono = { workspace = true } unicode-width = { workspace = true } number_prefix = { workspace = true } -term_grid = { workspace = true } +uutils_term_grid = { workspace = true } terminal_size = { workspace = true } glob = { workspace = true } lscolors = { workspace = true } -uucore = { workspace = true, features = ["entries", "fs"] } +uucore = { workspace = true, features = [ + "entries", + "fs", + "quoting-style", + "version-cmp", +] } once_cell = { workspace = true } -is-terminal = { workspace = true } selinux = { workspace = true, optional = true } [[bin]] diff --git a/src/uu/ls/src/dired.rs b/src/uu/ls/src/dired.rs new file mode 100644 index 000000000..c73b11ae3 --- /dev/null +++ b/src/uu/ls/src/dired.rs @@ -0,0 +1,343 @@ +// 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 dired subdired + +/// `dired` Module Documentation +/// +/// This module handles the --dired output format, representing file and +/// directory listings. +/// +/// Key Mechanisms: +/// 1. **Position Tracking**: +/// - The module tracks byte positions for each file or directory entry. +/// - `BytePosition`: Represents a byte range with start and end positions. +/// - `DiredOutput`: Contains positions for DIRED and SUBDIRED outputs and +/// maintains a padding value. +/// +/// 2. **Padding**: +/// - Padding is used when dealing with directory names or the "total" line. +/// - The module adjusts byte positions by adding padding for these cases. +/// - This ensures correct offset for subsequent files or directories. +/// +/// 3. **Position Calculation**: +/// - Functions like `calculate_dired`, `calculate_subdired`, and +/// `calculate_and_update_positions` compute byte positions based on output +/// length, previous positions, and padding. +/// +/// 4. **Output**: +/// - The module provides functions to print the DIRED output +/// (`print_dired_output`) based on calculated positions and configuration. +/// - Helpers like `print_positions` print positions with specific prefixes. +/// +/// Overall, the module ensures each entry in the DIRED output has the correct +/// byte position, considering additional lines or padding affecting positions. +/// +use crate::Config; +use std::fmt; +use std::io::{BufWriter, Stdout, Write}; +use uucore::error::UResult; + +#[derive(Debug, Clone, PartialEq)] +pub struct BytePosition { + pub start: usize, + pub end: usize, +} + +/// Represents the output structure for DIRED, containing positions for both DIRED and SUBDIRED. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct DiredOutput { + pub dired_positions: Vec, + pub subdired_positions: Vec, + pub padding: usize, +} + +impl fmt::Display for BytePosition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.start, self.end) + } +} + +// When --dired is used, all lines starts with 2 spaces +static DIRED_TRAILING_OFFSET: usize = 2; + +fn get_offset_from_previous_line(dired_positions: &[BytePosition]) -> usize { + if let Some(last_position) = dired_positions.last() { + last_position.end + 1 + } else { + 0 + } +} + +/// Calculates the byte positions for DIRED +pub fn calculate_dired( + dired_positions: &[BytePosition], + output_display_len: usize, + dfn_len: usize, +) -> (usize, usize) { + let offset_from_previous_line = get_offset_from_previous_line(dired_positions); + + let start = output_display_len + offset_from_previous_line; + let end = start + dfn_len; + (start, end) +} + +pub fn indent(out: &mut BufWriter) -> UResult<()> { + write!(out, " ")?; + Ok(()) +} + +pub fn calculate_subdired(dired: &mut DiredOutput, path_len: usize) { + let offset_from_previous_line = get_offset_from_previous_line(&dired.dired_positions); + + let additional_offset = if dired.subdired_positions.is_empty() { + 0 + } else { + // if we have several directories: \n\n + 2 + }; + + let start = offset_from_previous_line + DIRED_TRAILING_OFFSET + additional_offset; + let end = start + path_len; + dired.subdired_positions.push(BytePosition { start, end }); +} + +/// Prints the dired output based on the given configuration and dired structure. +pub fn print_dired_output( + config: &Config, + dired: &DiredOutput, + out: &mut BufWriter, +) -> UResult<()> { + out.flush()?; + if dired.padding == 0 && !dired.dired_positions.is_empty() { + print_positions("//DIRED//", &dired.dired_positions); + } + if config.recursive { + print_positions("//SUBDIRED//", &dired.subdired_positions); + } + println!("//DIRED-OPTIONS// --quoting-style={}", config.quoting_style); + Ok(()) +} + +/// Helper function to print positions with a given prefix. +fn print_positions(prefix: &str, positions: &Vec) { + print!("{}", prefix); + for c in positions { + print!(" {}", c); + } + println!(); +} + +pub fn add_total(dired: &mut DiredOutput, total_len: usize) { + if dired.padding == 0 { + let offset_from_previous_line = get_offset_from_previous_line(&dired.dired_positions); + // when dealing with " total: xx", it isn't part of the //DIRED// + // so, we just keep the size line to add it to the position of the next file + dired.padding = total_len + offset_from_previous_line + DIRED_TRAILING_OFFSET; + } else { + // += because if we are in -R, we have " dir:\n total X". So, we need to take the + // previous padding too. + // and we already have the previous position in mind + dired.padding += total_len + DIRED_TRAILING_OFFSET; + } +} + +// when using -R, we have the dirname. we need to add it to the padding +pub fn add_dir_name(dired: &mut DiredOutput, dir_len: usize) { + // 1 for the ":" in " dirname:" + dired.padding += dir_len + DIRED_TRAILING_OFFSET + 1; +} + +/// Calculates byte positions and updates the dired structure. +pub fn calculate_and_update_positions( + dired: &mut DiredOutput, + output_display_len: usize, + dfn_len: usize, +) { + let offset = dired + .dired_positions + .last() + .map_or(DIRED_TRAILING_OFFSET, |last_position| { + last_position.start + DIRED_TRAILING_OFFSET + }); + let start = output_display_len + offset + DIRED_TRAILING_OFFSET; + let end = start + dfn_len; + update_positions(dired, start, end); +} + +/// Updates the dired positions based on the given start and end positions. +/// update when it is the first element in the list (to manage "total X") +/// insert when it isn't the about total +pub fn update_positions(dired: &mut DiredOutput, start: usize, end: usize) { + // padding can be 0 but as it doesn't matter + dired.dired_positions.push(BytePosition { + start: start + dired.padding, + end: end + dired.padding, + }); + // Remove the previous padding + dired.padding = 0; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_calculate_dired() { + let output_display = "sample_output".to_string(); + let dfn = "sample_file".to_string(); + let dired_positions = vec![BytePosition { start: 5, end: 10 }]; + let (start, end) = calculate_dired(&dired_positions, output_display.len(), dfn.len()); + + assert_eq!(start, 24); + assert_eq!(end, 35); + } + + #[test] + fn test_get_offset_from_previous_line() { + let positions = vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ]; + assert_eq!(get_offset_from_previous_line(&positions), 12); + } + #[test] + fn test_calculate_subdired() { + let mut dired = DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + padding: 0, + }; + let path_len = 5; + calculate_subdired(&mut dired, path_len); + assert_eq!( + dired.subdired_positions, + vec![BytePosition { start: 14, end: 19 }], + ); + } + + #[test] + fn test_add_dir_name() { + let mut dired = DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + padding: 0, + }; + let dir_len = 5; + add_dir_name(&mut dired, dir_len); + assert_eq!( + dired, + DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + // 8 = 1 for the \n + 5 for dir_len + 2 for " " + 1 for : + padding: 8 + } + ); + } + + #[test] + fn test_add_total() { + let mut dired = DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + padding: 0, + }; + // if we have "total: 2" + let total_len = 8; + add_total(&mut dired, total_len); + // 22 = 8 (len) + 2 (padding) + 11 (previous position) + 1 (\n) + assert_eq!(dired.padding, 22); + } + + #[test] + fn test_add_dir_name_and_total() { + // test when we have + // dirname: + // total 0 + // -rw-r--r-- 1 sylvestre sylvestre 0 Sep 30 09:41 ab + + let mut dired = DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + padding: 0, + }; + let dir_len = 5; + add_dir_name(&mut dired, dir_len); + // 8 = 2 (" ") + 1 (\n) + 5 + 1 (: of dirname) + assert_eq!(dired.padding, 8); + + let total_len = 8; + add_total(&mut dired, total_len); + assert_eq!(dired.padding, 18); + } + + #[test] + fn test_dired_update_positions() { + let mut dired = DiredOutput { + dired_positions: vec![BytePosition { start: 5, end: 10 }], + subdired_positions: vec![], + padding: 10, + }; + + // Test with adjust = true + update_positions(&mut dired, 15, 20); + let last_position = dired.dired_positions.last().unwrap(); + assert_eq!(last_position.start, 25); // 15 + 10 (end of the previous position) + assert_eq!(last_position.end, 30); // 20 + 10 (end of the previous position) + + // Test with adjust = false + update_positions(&mut dired, 30, 35); + let last_position = dired.dired_positions.last().unwrap(); + assert_eq!(last_position.start, 30); + assert_eq!(last_position.end, 35); + } + + #[test] + fn test_calculate_and_update_positions() { + let mut dired = DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + padding: 5, + }; + let output_display_len = 15; + let dfn_len = 5; + calculate_and_update_positions(&mut dired, output_display_len, dfn_len); + assert_eq!( + dired.dired_positions, + vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + BytePosition { start: 32, end: 37 }, + ] + ); + assert_eq!(dired.padding, 0); + } +} diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index cead745cd..327b914dd 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1,23 +1,19 @@ // This file is part of the uutils coreutils package. // -// (c) Jeremiah Peschka -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. -// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf tabsize dired +// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf tabsize dired subdired dtype use clap::{ builder::{NonEmptyStringValueParser, ValueParser}, crate_version, Arg, ArgAction, Command, }; use glob::{MatchOptions, Pattern}; -use is_terminal::IsTerminal; use lscolors::LsColors; use number_prefix::NumberPrefix; -use once_cell::unsync::OnceCell; -use std::collections::HashSet; -use std::num::IntErrorKind; +use std::{cell::OnceCell, num::IntErrorKind}; +use std::{collections::HashSet, io::IsTerminal}; #[cfg(windows)] use std::os::windows::fs::MetadataExt; @@ -54,17 +50,19 @@ use unicode_width::UnicodeWidthStr; use uucore::libc::{dev_t, major, minor}; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; +use uucore::line_ending::LineEnding; use uucore::quoting_style::{escape_name, QuotingStyle}; use uucore::{ display::Quotable, error::{set_exit_code, UError, UResult}, format_usage, fs::display_permissions, - parse_size::parse_size, + parse_size::parse_size_u64, version_cmp::version_cmp, }; use uucore::{help_about, help_section, help_usage, parse_glob, show, show_error, show_warning}; - +mod dired; +use dired::DiredOutput; #[cfg(not(feature = "selinux"))] static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)"; #[cfg(feature = "selinux")] @@ -170,6 +168,8 @@ enum LsError { IOError(std::io::Error), IOErrorContext(std::io::Error, PathBuf, bool), BlockSizeParseError(String), + ConflictingArgumentDired, + DiredAndZeroAreIncompatible, AlreadyListedError(PathBuf), TimeStyleParseError(String, Vec), } @@ -182,8 +182,10 @@ impl UError for LsError { Self::IOErrorContext(_, _, false) => 1, Self::IOErrorContext(_, _, true) => 2, Self::BlockSizeParseError(_) => 1, + Self::ConflictingArgumentDired => 1, + Self::DiredAndZeroAreIncompatible => 2, Self::AlreadyListedError(_) => 2, - Self::TimeStyleParseError(_, _) => 1, + Self::TimeStyleParseError(_, _) => 2, } } } @@ -196,6 +198,12 @@ impl Display for LsError { Self::BlockSizeParseError(s) => { write!(f, "invalid --block-size argument {}", s.quote()) } + Self::ConflictingArgumentDired => { + write!(f, "--dired requires --format=long") + } + Self::DiredAndZeroAreIncompatible => { + write!(f, "--dired and --zero are incompatible") + } Self::TimeStyleParseError(s, possible_time_styles) => { write!( f, @@ -408,7 +416,8 @@ pub struct Config { context: bool, selinux_supported: bool, group_directories_first: bool, - eol: char, + line_ending: LineEnding, + dired: bool, } // Fields that can be removed or added to the long format @@ -613,6 +622,8 @@ fn extract_quoting_style(options: &clap::ArgMatches, show_control: bool) -> Quot QuotingStyle::C { quotes: quoting_style::Quotes::Double, } + } else if options.get_flag(options::DIRED) { + QuotingStyle::Literal { show_control } } else { // TODO: use environment variable if available QuotingStyle::Shell { @@ -774,7 +785,7 @@ impl Config { }; let block_size: Option = if !opt_si && !opt_hr && !raw_bs.is_empty() { - match parse_size(&raw_bs.to_string_lossy()) { + match parse_size_u64(&raw_bs.to_string_lossy()) { Ok(size) => Some(size), Err(_) => { show!(LsError::BlockSizeParseError( @@ -957,6 +968,14 @@ impl Config { None }; + let dired = options.get_flag(options::DIRED); + if dired && format != Format::Long { + return Err(Box::new(LsError::ConflictingArgumentDired)); + } + if dired && format == Format::Long && options.get_flag(options::ZERO) { + return Err(Box::new(LsError::DiredAndZeroAreIncompatible)); + } + let dereference = if options.get_flag(options::dereference::ALL) { Dereference::All } else if options.get_flag(options::dereference::ARGS) { @@ -1005,11 +1024,8 @@ impl Config { } }, group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST), - eol: if options.get_flag(options::ZERO) { - '\0' - } else { - '\n' - }, + line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)), + dired, }) } } @@ -1018,7 +1034,7 @@ impl Config { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let command = uu_app(); - let matches = command.get_matches_from(args); + let matches = command.try_get_matches_from(args)?; let config = Config::from(&matches)?; @@ -1133,7 +1149,6 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::ZERO) .long(options::ZERO) - .conflicts_with(options::DIRED) .overrides_with(options::ZERO) .help("List entries separated by ASCII NUL characters.") .action(ArgAction::SetTrue), @@ -1142,7 +1157,7 @@ pub fn uu_app() -> Command { Arg::new(options::DIRED) .long(options::DIRED) .short('D') - .hide(true) + .help("generate output designed for Emacs' dired (Directory Editor) mode") .action(ArgAction::SetTrue), ) // The next four arguments do not override with the other format @@ -1847,10 +1862,16 @@ impl PathData { } } +fn show_dir_name(dir: &Path, out: &mut BufWriter) { + write!(out, "{}:", dir.display()).unwrap(); +} + +#[allow(clippy::cognitive_complexity)] pub 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 mut dired = DiredOutput::default(); let initial_locs_len = locs.len(); for loc in locs { @@ -1884,7 +1905,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { sort_entries(&mut files, config, &mut out); sort_entries(&mut dirs, config, &mut out); - display_items(&files, config, &mut out)?; + display_items(&files, config, &mut out, &mut dired)?; for (pos, path_data) in dirs.iter().enumerate() { // Do read_dir call here to match GNU semantics by printing @@ -1906,9 +1927,22 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { // Print dir heading - name... 'total' comes after error display if initial_locs_len > 1 || config.recursive { if pos.eq(&0usize) && files.is_empty() { + if config.dired { + dired::indent(&mut out)?; + } writeln!(out, "{}:", path_data.p_buf.display())?; + if config.dired { + // First directory displayed + let dir_len = path_data.display_name.len(); + // add the //SUBDIRED// coordinates + dired::calculate_subdired(&mut dired, dir_len); + // Add the padding for the dir name + dired::add_dir_name(&mut dired, dir_len); + } } else { - writeln!(out, "\n{}:", path_data.p_buf.display())?; + writeln!(out)?; + show_dir_name(&path_data.p_buf, &mut out); + writeln!(out)?; } } let mut listed_ancestors = HashSet::new(); @@ -1916,9 +1950,18 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { &path_data.p_buf, path_data.must_dereference, )?); - enter_directory(path_data, read_dir, config, &mut out, &mut listed_ancestors)?; + enter_directory( + path_data, + read_dir, + config, + &mut out, + &mut listed_ancestors, + &mut dired, + )?; + } + if config.dired { + dired::print_dired_output(config, &dired, &mut out)?; } - Ok(()) } @@ -2029,6 +2072,7 @@ fn enter_directory( config: &Config, out: &mut BufWriter, listed_ancestors: &mut HashSet, + dired: &mut DiredOutput, ) -> UResult<()> { // Create vec of entries with initial dot files let mut entries: Vec = if config.files == Files::All { @@ -2074,10 +2118,14 @@ fn enter_directory( // Print total after any error display if config.format == Format::Long || config.alloc_size { - display_total(&entries, config, out)?; + let total = return_total(&entries, config, out)?; + write!(out, "{}", total.as_str())?; + if config.dired { + dired::add_total(dired, total.len()); + } } - display_items(&entries, config, out)?; + display_items(&entries, config, out, dired)?; if config.recursive { for e in entries @@ -2101,8 +2149,24 @@ fn enter_directory( if listed_ancestors .insert(FileInformation::from_path(&e.p_buf, e.must_dereference)?) { - writeln!(out, "\n{}:", e.p_buf.display())?; - enter_directory(e, rd, config, out, listed_ancestors)?; + // when listing several directories in recursive mode, we show + // "dirname:" at the beginning of the file list + writeln!(out)?; + if config.dired { + // We already injected the first dir + // Continue with the others + // 2 = \n + \n + dired.padding = 2; + dired::indent(out)?; + let dir_name_size = e.p_buf.to_string_lossy().len(); + dired::calculate_subdired(dired, dir_name_size); + // inject dir name + dired::add_dir_name(dired, dir_name_size); + } + + show_dir_name(&e.p_buf, out); + writeln!(out)?; + enter_directory(e, rd, config, out, listed_ancestors, dired)?; listed_ancestors .remove(&FileInformation::from_path(&e.p_buf, e.must_dereference)?); } else { @@ -2161,7 +2225,11 @@ fn pad_right(string: &str, count: usize) -> String { format!("{string:) -> UResult<()> { +fn return_total( + items: &[PathData], + config: &Config, + out: &mut BufWriter, +) -> UResult { let mut total_size = 0; for item in items { total_size += item @@ -2169,13 +2237,14 @@ fn display_total(items: &[PathData], config: &Config, out: &mut BufWriter) -> UResult<()> { +fn display_items( + items: &[PathData], + config: &Config, + out: &mut BufWriter, + dired: &mut DiredOutput, +) -> UResult<()> { // `-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. @@ -2227,6 +2301,7 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter 0 { - write!(out, "{}", config.eol)?; + write!(out, "{}", config.line_ending)?; } } _ => { for name in names { - write!(out, "{}{}", name.contents, config.eol)?; + write!(out, "{}{}", name.contents, config.line_ending)?; } } }; @@ -2353,10 +2428,10 @@ fn display_grid( writeln!(out)?; } } else { - let mut grid = Grid::new(GridOptions { - filling: Filling::Spaces(2), - direction, - }); + // TODO: To match gnu/tests/ls/stat-dtype.sh + // we might want to have Filling::Text("\t".to_string()); + let filling = Filling::Spaces(2); + let mut grid = Grid::new(GridOptions { filling, direction }); for name in names { grid.add(name); @@ -2409,10 +2484,15 @@ fn display_item_long( padding: &PaddingCollection, config: &Config, out: &mut BufWriter, + dired: &mut DiredOutput, ) -> UResult<()> { + let mut output_display: String = String::new(); + if config.dired { + output_display += " "; + } if let Some(md) = item.md(out) { write!( - out, + output_display, "{}{} {}", display_permissions(md, true), if item.security_context.len() > 1 { @@ -2423,49 +2503,54 @@ fn display_item_long( "" }, pad_left(&display_symlink_count(md), padding.link_count) - )?; + ) + .unwrap(); if config.long.owner { write!( - out, + output_display, " {}", pad_right(&display_uname(md, config), padding.uname) - )?; + ) + .unwrap(); } if config.long.group { write!( - out, + output_display, " {}", pad_right(&display_group(md, config), padding.group) - )?; + ) + .unwrap(); } if config.context { write!( - out, + output_display, " {}", pad_right(&item.security_context, padding.context) - )?; + ) + .unwrap(); } // 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 { write!( - out, + output_display, " {}", pad_right(&display_uname(md, config), padding.uname) - )?; + ) + .unwrap(); } match display_len_or_rdev(md, config) { SizeOrDeviceId::Size(size) => { - write!(out, " {}", pad_left(&size, padding.size))?; + write!(output_display, " {}", pad_left(&size, padding.size)).unwrap(); } SizeOrDeviceId::Device(major, minor) => { write!( - out, + output_display, " {}, {}", pad_left( &major, @@ -2485,13 +2570,23 @@ fn display_item_long( #[cfg(unix)] padding.minor, ), - )?; + ) + .unwrap(); } }; - let dfn = display_file_name(item, config, None, String::new(), out).contents; + write!(output_display, " {} ", display_date(md, config)).unwrap(); - write!(out, " {} {}{}", display_date(md, config), dfn, config.eol)?; + let displayed_file = display_file_name(item, config, None, String::new(), out).contents; + if config.dired { + let (start, end) = dired::calculate_dired( + &dired.dired_positions, + output_display.len(), + displayed_file.len(), + ); + dired::update_positions(dired, start, end); + } + write!(output_display, "{}{}", displayed_file, config.line_ending).unwrap(); } else { #[cfg(unix)] let leading_char = { @@ -2527,7 +2622,7 @@ fn display_item_long( }; write!( - out, + output_display, "{}{} {}", format_args!("{leading_char}?????????"), if item.security_context.len() > 1 { @@ -2538,41 +2633,53 @@ fn display_item_long( "" }, pad_left("?", padding.link_count) - )?; + ) + .unwrap(); if config.long.owner { - write!(out, " {}", pad_right("?", padding.uname))?; + write!(output_display, " {}", pad_right("?", padding.uname)).unwrap(); } if config.long.group { - write!(out, " {}", pad_right("?", padding.group))?; + write!(output_display, " {}", pad_right("?", padding.group)).unwrap(); } if config.context { write!( - out, + output_display, " {}", pad_right(&item.security_context, padding.context) - )?; + ) + .unwrap(); } // 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 { - write!(out, " {}", pad_right("?", padding.uname))?; + write!(output_display, " {}", pad_right("?", padding.uname)).unwrap(); } - let dfn = display_file_name(item, config, None, String::new(), out).contents; + let displayed_file = display_file_name(item, config, None, String::new(), out).contents; let date_len = 12; - writeln!( - out, - " {} {} {}", + write!( + output_display, + " {} {} ", pad_left("?", padding.size), pad_left("?", date_len), - dfn, - )?; + ) + .unwrap(); + + if config.dired { + dired::calculate_and_update_positions( + dired, + output_display.len(), + displayed_file.trim().len(), + ); + } + write!(output_display, "{}{}", displayed_file, config.line_ending).unwrap(); } + write!(out, "{}", output_display)?; Ok(()) } @@ -2815,7 +2922,11 @@ fn classify_file(path: &PathData, out: &mut BufWriter) -> Option { Some('=') } else if file_type.is_fifo() { Some('|') - } else if file_type.is_file() && file_is_executable(path.md(out).as_ref().unwrap()) { + } else if file_type.is_file() + // Safe unwrapping if the file was removed between listing and display + // See https://github.com/uutils/coreutils/issues/5371 + && path.md(out).map(file_is_executable).unwrap_or_default() + { Some('*') } else { None @@ -2909,55 +3020,64 @@ fn display_file_name( && path.file_type(out).unwrap().is_symlink() && !path.must_dereference { - if let Ok(target) = path.p_buf.read_link() { - name.push_str(" -> "); + match path.p_buf.read_link() { + Ok(target) => { + name.push_str(" -> "); - // We might as well color the symlink output after the arrow. - // This makes extra system calls, but provides important information that - // people run `ls -l --color` are very interested in. - if let Some(ls_colors) = &config.color { - // We get the absolute path to be able to construct PathData with valid Metadata. - // This is because relative symlinks will fail to get_metadata. - let mut absolute_target = target.clone(); - if target.is_relative() { - if let Some(parent) = path.p_buf.parent() { - absolute_target = parent.join(absolute_target); + // We might as well color the symlink output after the arrow. + // This makes extra system calls, but provides important information that + // people run `ls -l --color` are very interested in. + if let Some(ls_colors) = &config.color { + // We get the absolute path to be able to construct PathData with valid Metadata. + // This is because relative symlinks will fail to get_metadata. + let mut absolute_target = target.clone(); + if target.is_relative() { + if let Some(parent) = path.p_buf.parent() { + absolute_target = parent.join(absolute_target); + } } - } - let target_data = PathData::new(absolute_target, None, None, config, false); + let target_data = PathData::new(absolute_target, None, None, config, false); - // If we have a symlink to a valid file, we use the metadata of said file. - // 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. - 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()); + // If we have a symlink to a valid file, we use the metadata of said file. + // 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. + 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( + escape_name(target.as_os_str(), &config.quoting_style), + &target_data.p_buf, + Some(&target_metadata), + ls_colors, + )); + } } 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( - escape_name(target.as_os_str(), &config.quoting_style), - &target_data.p_buf, - Some(&target_metadata), - ls_colors, - )); + // If no coloring is required, we just use target as is. + // Apply the right quoting + name.push_str(&escape_name(target.as_os_str(), &config.quoting_style)); } - } else { - // If no coloring is required, we just use target as is. - // Apply the right quoting - name.push_str(&escape_name(target.as_os_str(), &config.quoting_style)); + } + Err(err) => { + show!(LsError::IOErrorContext( + err, + path.p_buf.to_path_buf(), + false + )); } } } diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index d971d4eaf..c0d60dc9d 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkdir" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "mkdir ~ (uutils) create DIRECTORY" diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index a94439af5..4121278b6 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Nicholas Juszczak -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) ugoa cmode @@ -40,31 +38,27 @@ fn get_mode(_matches: &ArgMatches, _mode_had_minus_prefix: bool) -> Result Result { - let digits: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - // Translate a ~str in octal form to u16, default to 777 // Not tested on Windows let mut new_mode = DEFAULT_PERM; - match matches.get_one::(options::MODE) { - Some(m) => { - for mode in m.split(',') { - if mode.contains(digits) { - new_mode = mode::parse_numeric(new_mode, m, true)?; + + if let Some(m) = matches.get_one::(options::MODE) { + for mode in m.split(',') { + if mode.chars().any(|c| c.is_ascii_digit()) { + new_mode = mode::parse_numeric(new_mode, m, true)?; + } else { + let cmode = if mode_had_minus_prefix { + // clap parsing is finished, now put prefix back + format!("-{mode}") } else { - let cmode = if mode_had_minus_prefix { - // clap parsing is finished, now put prefix back - format!("-{mode}") - } else { - mode.to_string() - }; - new_mode = mode::parse_symbolic(new_mode, &cmode, mode::get_umask(), true)?; - } + mode.to_string() + }; + new_mode = mode::parse_symbolic(new_mode, &cmode, mode::get_umask(), true)?; } - Ok(new_mode) - } - None => { - // If no mode argument is specified return the mode derived from umask - Ok(!mode::get_umask() & 0o0777) } + Ok(new_mode) + } else { + // If no mode argument is specified return the mode derived from umask + Ok(!mode::get_umask() & 0o0777) } } @@ -143,21 +137,34 @@ pub fn uu_app() -> Command { */ fn exec(dirs: ValuesRef, recursive: bool, mode: u32, verbose: bool) -> UResult<()> { for dir in dirs { - // Special case to match GNU's behavior: - // mkdir -p foo/. should work and just create foo/ - // std::fs::create_dir("foo/."); fails in pure Rust - let path = if recursive { - dir_strip_dot_for_creation(&PathBuf::from(dir)) - } else { - // Normal case - PathBuf::from(dir) - }; - show_if_err!(mkdir(path.as_path(), recursive, mode, verbose)); + let path_buf = PathBuf::from(dir); + let path = path_buf.as_path(); + + show_if_err!(mkdir(path, recursive, mode, verbose)); } Ok(()) } -fn mkdir(path: &Path, recursive: bool, mode: u32, verbose: bool) -> UResult<()> { +/// Create directory at a given `path`. +/// +/// ## Options +/// +/// * `recursive` --- create parent directories for the `path`, if they do not +/// exist. +/// * `mode` --- file mode for the directories (not implemented on windows). +/// * `verbose` --- print a message for each printed directory. +/// +/// ## Trailing dot +/// +/// To match the GNU behavior, a path with the last directory being a single dot +/// (like `some/path/to/.`) is created (with the dot stripped). +pub fn mkdir(path: &Path, recursive: bool, mode: u32, verbose: bool) -> UResult<()> { + // Special case to match GNU's behavior: + // mkdir -p foo/. should work and just create foo/ + // std::fs::create_dir("foo/."); fails in pure Rust + let path_buf = dir_strip_dot_for_creation(path); + let path = path_buf.as_path(); + create_dir(path, recursive, verbose, false)?; chmod(path, mode) } diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 21b00ffe7..285fc2b34 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkfifo" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "mkfifo ~ (uutils) create FIFOs (named pipes)" diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index dc338cf12..dc1f876fc 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Michael Gehring -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 clap::{crate_version, Arg, ArgAction, Command}; use libc::mkfifo; diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 266c23629..2e6601714 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mknod" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "mknod ~ (uutils) create special file NAME of TYPE" diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index ad7618cc8..ceafd235b 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -101,12 +99,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { matches.get_one::("major"), matches.get_one::("minor"), ) { - (_, None) | (None, _) => { - return Err(UUsageError::new( - 1, - "Special files require major and minor device numbers.", - )); - } + (_, None) | (None, _) => Err(UUsageError::new( + 1, + "Special files require major and minor device numbers.", + )), (Some(&major), Some(&minor)) => { let dev = makedev(major, minor); let exit_code = match file_type { diff --git a/src/uu/mknod/src/parsemode.rs b/src/uu/mknod/src/parsemode.rs index 2767cb303..c38800bcb 100644 --- a/src/uu/mknod/src/parsemode.rs +++ b/src/uu/mknod/src/parsemode.rs @@ -1,3 +1,7 @@ +// 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 (path) osrelease use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; @@ -7,8 +11,7 @@ use uucore::mode; pub const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; pub fn parse_mode(mode: &str) -> Result { - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { + let result = if mode.chars().any(|c| c.is_ascii_digit()) { mode::parse_numeric(MODE_RW_UGO as u32, mode) } else { mode::parse_symbolic(MODE_RW_UGO as u32, mode, true) @@ -39,7 +42,7 @@ mod test { assert_eq!(super::parse_mode("u+x").unwrap(), 0o766); assert_eq!( super::parse_mode("+x").unwrap(), - if !is_wsl() { 0o777 } else { 0o776 } + if is_wsl() { 0o776 } else { 0o777 } ); assert_eq!(super::parse_mode("a-w").unwrap(), 0o444); assert_eq!(super::parse_mode("g-r").unwrap(), 0o626); diff --git a/src/uu/mktemp/Cargo.toml b/src/uu/mktemp/Cargo.toml index 9d4825559..8d8a885b1 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mktemp" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "mktemp ~ (uutils) create and display a temporary file or directory from TEMPLATE" diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 9c0860d92..77ef5fcbe 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -1,8 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Sunrin SHIMURA -// Collaborator: Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -454,7 +451,7 @@ pub fn dry_exec(tmpdir: &str, prefix: &str, rand: usize, suffix: &str) -> UResul // Randomize. let bytes = &mut buf[prefix.len()..prefix.len() + rand]; rand::thread_rng().fill(bytes); - for byte in bytes.iter_mut() { + for byte in bytes { *byte = match *byte % 62 { v @ 0..=9 => v + b'0', v @ 10..=35 => v - 10 + b'a', diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 0b7eb0763..5614a0495 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_more" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "more ~ (uutils) input perusal filter" @@ -18,7 +18,6 @@ path = "src/more.rs" clap = { workspace = true } uucore = { workspace = true } crossterm = { workspace = true } -is-terminal = { workspace = true } unicode-width = { workspace = true } unicode-segmentation = { workspace = true } diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index c488ba8af..02ed0feea 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -1,15 +1,13 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Martin Kysel -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 (methods) isnt use std::{ fs::File, - io::{stdin, stdout, BufReader, Read, Stdout, Write}, + io::{stdin, stdout, BufReader, IsTerminal, Read, Stdout, Write}, path::Path, time::Duration, }; @@ -24,7 +22,6 @@ use crossterm::{ terminal::{self, Clear, ClearType}, }; -use is_terminal::IsTerminal; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; use uucore::display::Quotable; diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index ab90e4509..0be31ad72 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mv" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "mv ~ (uutils) move (rename) SOURCE to DESTINATION" @@ -18,7 +18,11 @@ path = "src/mv.rs" clap = { workspace = true } fs_extra = { workspace = true } indicatif = { workspace = true } -uucore = { workspace = true, features = ["fs"] } +uucore = { workspace = true, features = [ + "backup-control", + "fs", + "update-control", +] } [[bin]] name = "mv" diff --git a/src/uu/mv/src/error.rs b/src/uu/mv/src/error.rs index 7810c3a95..f989d4e13 100644 --- a/src/uu/mv/src/error.rs +++ b/src/uu/mv/src/error.rs @@ -1,7 +1,7 @@ // 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. +// 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}; @@ -10,12 +10,15 @@ use uucore::error::UError; #[derive(Debug)] pub enum MvError { NoSuchFile(String), + CannotStatNotADirectory(String), SameFile(String, String), SelfSubdirectory(String), + SelfTargetSubdirectory(String, String), DirectoryToNonDirectory(String), NonDirectoryToDirectory(String, String), NotADirectory(String), TargetNotADirectory(String), + FailedToAccessNotADirectory(String), } impl Error for MvError {} @@ -24,11 +27,16 @@ impl Display for MvError { fn fmt(&self, f: &mut Formatter) -> Result { match self { Self::NoSuchFile(s) => write!(f, "cannot stat {s}: No such file or directory"), + Self::CannotStatNotADirectory(s) => write!(f, "cannot stat {s}: Not a directory"), Self::SameFile(s, t) => write!(f, "{s} and {t} are the same file"), Self::SelfSubdirectory(s) => write!( f, "cannot move '{s}' to a subdirectory of itself, '{s}/{s}'" ), + Self::SelfTargetSubdirectory(s, t) => write!( + f, + "cannot move '{s}' to a subdirectory of itself, '{t}/{s}'" + ), Self::DirectoryToNonDirectory(t) => { write!(f, "cannot overwrite directory {t} with non-directory") } @@ -37,6 +45,10 @@ impl Display for MvError { } Self::NotADirectory(t) => write!(f, "target {t}: Not a directory"), Self::TargetNotADirectory(t) => write!(f, "target directory {t}: Not a directory"), + + Self::FailedToAccessNotADirectory(t) => { + write!(f, "failed to access {t}: Not a directory") + } } } } diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 79245a427..0ceda8e75 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -1,12 +1,9 @@ // This file is part of the uutils coreutils package. // -// (c) Orvar Segerström -// (c) Sokovikov Evgeniy -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. -// spell-checker:ignore (ToDO) sourcepath targetpath +// spell-checker:ignore (ToDO) sourcepath targetpath nushell canonicalized mod error; @@ -22,11 +19,14 @@ use std::os::unix; #[cfg(windows)] use std::os::windows; use std::path::{Path, PathBuf}; -use uucore::backup_control::{self, source_is_target_backup, BackupMode}; +use uucore::backup_control::{self, source_is_target_backup}; use uucore::display::Quotable; use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError, UUsageError}; use uucore::fs::{are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file}; -use uucore::update_control::{self, UpdateMode}; +use uucore::update_control; +// These are exposed for projects (e.g. nushell) that want to create an `Options` value, which +// requires these enums +pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show}; use fs_extra::dir::{ @@ -36,22 +36,56 @@ use fs_extra::dir::{ use crate::error::MvError; -pub struct Behavior { - overwrite: OverwriteMode, - backup: BackupMode, - suffix: String, - update: UpdateMode, - target_dir: Option, - no_target_dir: bool, - verbose: bool, - strip_slashes: bool, - progress_bar: bool, +/// Options contains all the possible behaviors and flags for mv. +/// +/// All options are public so that the options can be programmatically +/// constructed by other crates, such as nushell. That means that this struct is +/// part of our public API. It should therefore not be changed without good reason. +/// +/// The fields are documented with the arguments that determine their value. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Options { + /// specifies overwrite behavior + /// '-n' '--no-clobber' + /// '-i' '--interactive' + /// '-f' '--force' + pub overwrite: OverwriteMode, + + /// `--backup[=CONTROL]`, `-b` + pub backup: BackupMode, + + /// '-S' --suffix' backup suffix + pub suffix: String, + + /// Available update mode "--update-mode=all|none|older" + pub update: UpdateMode, + + /// Specifies target directory + /// '-t, --target-directory=DIRECTORY' + pub target_dir: Option, + + /// Treat destination as a normal file + /// '-T, --no-target-directory + pub no_target_dir: bool, + + /// '-v, --verbose' + pub verbose: bool, + + /// '--strip-trailing-slashes' + pub strip_slashes: bool, + + /// '-g, --progress' + pub progress_bar: bool, } -#[derive(Clone, Eq, PartialEq)] +/// specifies behavior of the overwrite flag +#[derive(Clone, Debug, Eq, PartialEq)] pub enum OverwriteMode { + /// '-n' '--no-clobber' do not overwrite NoClobber, + /// '-i' '--interactive' prompt before overwrite Interactive, + ///'-f' '--force' overwrite without prompt Force, } @@ -69,6 +103,25 @@ static OPT_VERBOSE: &str = "verbose"; static OPT_PROGRESS: &str = "progress"; static ARG_FILES: &str = "files"; +/// Returns true if the passed `path` ends with a path terminator. +#[cfg(unix)] +fn path_ends_with_terminator(path: &Path) -> bool { + use std::os::unix::prelude::OsStrExt; + path.as_os_str() + .as_bytes() + .last() + .map_or(false, |&byte| byte == b'/' || byte == b'\\') +} + +#[cfg(windows)] +fn path_ends_with_terminator(path: &Path) -> bool { + use std::os::windows::prelude::OsStrExt; + path.as_os_str() + .encode_wide() + .last() + .map_or(false, |wide| wide == b'/'.into() || wide == b'\\'.into()) +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut app = uu_app(); @@ -119,7 +172,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - let behavior = Behavior { + let opts = Options { overwrite: overwrite_mode, backup: backup_mode, suffix: backup_suffix, @@ -131,7 +184,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { progress_bar: matches.get_flag(OPT_PROGRESS), }; - exec(&files[..], &behavior) + mv(&files[..], &opts) } pub fn uu_app() -> Command { @@ -238,10 +291,10 @@ fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { } } -fn parse_paths(files: &[OsString], b: &Behavior) -> Vec { +fn parse_paths(files: &[OsString], opts: &Options) -> Vec { let paths = files.iter().map(Path::new); - if b.strip_slashes { + if opts.strip_slashes { paths .map(|p| p.components().as_path().to_owned()) .collect::>() @@ -250,8 +303,10 @@ fn parse_paths(files: &[OsString], b: &Behavior) -> Vec { } } -fn handle_two_paths(source: &Path, target: &Path, b: &Behavior) -> UResult<()> { - if b.backup == BackupMode::SimpleBackup && source_is_target_backup(source, target, &b.suffix) { +fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> { + if opts.backup == BackupMode::SimpleBackup + && source_is_target_backup(source, target, &opts.suffix) + { return Err(io::Error::new( io::ErrorKind::NotFound, format!( @@ -263,13 +318,17 @@ fn handle_two_paths(source: &Path, target: &Path, b: &Behavior) -> UResult<()> { .into()); } if source.symlink_metadata().is_err() { - return Err(MvError::NoSuchFile(source.quote().to_string()).into()); + return Err(if path_ends_with_terminator(source) { + MvError::CannotStatNotADirectory(source.quote().to_string()).into() + } else { + MvError::NoSuchFile(source.quote().to_string()).into() + }); } if (source.eq(target) || are_hardlinks_to_same_file(source, target) || are_hardlinks_or_one_way_symlink_to_same_file(source, target)) - && b.backup == BackupMode::NoBackup + && opts.backup == BackupMode::NoBackup { if source.eq(Path::new(".")) || source.ends_with("/.") || source.is_file() { return Err( @@ -280,20 +339,48 @@ fn handle_two_paths(source: &Path, target: &Path, b: &Behavior) -> UResult<()> { } } - if target.is_dir() { - if b.no_target_dir { + let target_is_dir = target.is_dir(); + + if path_ends_with_terminator(target) && !target_is_dir { + return Err(MvError::FailedToAccessNotADirectory(target.quote().to_string()).into()); + } + + if target_is_dir { + if opts.no_target_dir { if source.is_dir() { - rename(source, target, b, None).map_err_context(|| { + rename(source, target, opts, None).map_err_context(|| { format!("cannot move {} to {}", source.quote(), target.quote()) }) } else { Err(MvError::DirectoryToNonDirectory(target.quote().to_string()).into()) } } else { - move_files_into_dir(&[source.to_path_buf()], target, b) + // Check that source & target do not contain same subdir/dir when both exist + // mkdir dir1/dir2; mv dir1 dir1/dir2 + let target_contains_itself = target + .as_os_str() + .to_str() + .ok_or("not a valid unicode string") + .and_then(|t| { + source + .as_os_str() + .to_str() + .ok_or("not a valid unicode string") + .map(|s| t.contains(s)) + }) + .unwrap(); + + if target_contains_itself { + return Err(MvError::SelfTargetSubdirectory( + source.display().to_string(), + target.display().to_string(), + ) + .into()); + } + move_files_into_dir(&[source.to_path_buf()], target, opts) } } else if target.exists() && source.is_dir() { - match b.overwrite { + match opts.overwrite { OverwriteMode::NoClobber => return Ok(()), OverwriteMode::Interactive => { if !prompt_yes!("overwrite {}? ", target.quote()) { @@ -308,12 +395,12 @@ fn handle_two_paths(source: &Path, target: &Path, b: &Behavior) -> UResult<()> { ) .into()) } else { - rename(source, target, b, None).map_err(|e| USimpleError::new(1, format!("{e}"))) + rename(source, target, opts, None).map_err(|e| USimpleError::new(1, format!("{e}"))) } } -fn handle_multiple_paths(paths: &[PathBuf], b: &Behavior) -> UResult<()> { - if b.no_target_dir { +fn handle_multiple_paths(paths: &[PathBuf], opts: &Options) -> UResult<()> { + if opts.no_target_dir { return Err(UUsageError::new( 1, format!("mv: extra operand {}", paths[2].quote()), @@ -322,33 +409,36 @@ fn handle_multiple_paths(paths: &[PathBuf], b: &Behavior) -> UResult<()> { let target_dir = paths.last().unwrap(); let sources = &paths[..paths.len() - 1]; - move_files_into_dir(sources, target_dir, b) + move_files_into_dir(sources, target_dir, opts) } -fn exec(files: &[OsString], b: &Behavior) -> UResult<()> { - let paths = parse_paths(files, b); +/// Execute the mv command. This moves 'source' to 'target', where +/// 'target' is a directory. If 'target' does not exist, and source is a single +/// file or directory, then 'source' will be renamed to 'target'. +pub fn mv(files: &[OsString], opts: &Options) -> UResult<()> { + let paths = parse_paths(files, opts); - if let Some(ref name) = b.target_dir { - return move_files_into_dir(&paths, &PathBuf::from(name), b); + if let Some(ref name) = opts.target_dir { + return move_files_into_dir(&paths, &PathBuf::from(name), opts); } match paths.len() { - 2 => handle_two_paths(&paths[0], &paths[1], b), - _ => handle_multiple_paths(&paths, b), + 2 => handle_two_paths(&paths[0], &paths[1], opts), + _ => handle_multiple_paths(&paths, opts), } } #[allow(clippy::cognitive_complexity)] -fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UResult<()> { +fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, opts: &Options) -> UResult<()> { if !target_dir.is_dir() { return Err(MvError::NotADirectory(target_dir.quote().to_string()).into()); } - let canonized_target_dir = target_dir + let canonicalized_target_dir = target_dir .canonicalize() .unwrap_or_else(|_| target_dir.to_path_buf()); - let multi_progress = b.progress_bar.then(MultiProgress::new); + let multi_progress = opts.progress_bar.then(MultiProgress::new); let count_progress = if let Some(ref multi_progress) = multi_progress { if files.len() > 1 { @@ -364,7 +454,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR None }; - for sourcepath in files.iter() { + for sourcepath in files { if let Some(ref pb) = count_progress { pb.set_message(sourcepath.to_string_lossy().to_string()); } @@ -379,8 +469,8 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR // Check if we have mv dir1 dir2 dir2 // And generate an error if this is the case - if let Ok(canonized_source) = sourcepath.canonicalize() { - if canonized_source == canonized_target_dir { + if let Ok(canonicalized_source) = sourcepath.canonicalize() { + if canonicalized_source == canonicalized_target_dir { // User tried to move directory to itself, warning is shown // and process of moving files is continued. show!(USimpleError::new( @@ -389,7 +479,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR "cannot move '{}' to a subdirectory of itself, '{}/{}'", sourcepath.display(), target_dir.display(), - canonized_target_dir.components().last().map_or_else( + canonicalized_target_dir.components().last().map_or_else( || target_dir.display().to_string(), |dir| { PathBuf::from(dir.as_os_str()).display().to_string() } ) @@ -399,7 +489,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR } } - match rename(sourcepath, &targetpath, b, multi_progress.as_ref()) { + match rename(sourcepath, &targetpath, opts, multi_progress.as_ref()) { Err(e) if e.to_string().is_empty() => set_exit_code(1), Err(e) => { let e = e.map_err_context(|| { @@ -416,7 +506,6 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR } Ok(()) => (), } - if let Some(ref pb) = count_progress { pb.inc(1); } @@ -427,52 +516,43 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR fn rename( from: &Path, to: &Path, - b: &Behavior, + opts: &Options, multi_progress: Option<&MultiProgress>, ) -> io::Result<()> { let mut backup_path = None; if to.exists() { - if (b.update == UpdateMode::ReplaceIfOlder || b.update == UpdateMode::ReplaceNone) - && b.overwrite == OverwriteMode::Interactive + if opts.update == UpdateMode::ReplaceIfOlder && opts.overwrite == OverwriteMode::Interactive { // `mv -i --update old new` when `new` exists doesn't move anything // and exit with 0 return Ok(()); } - if b.update == UpdateMode::ReplaceNone { + if opts.update == UpdateMode::ReplaceNone { return Ok(()); } - if (b.update == UpdateMode::ReplaceIfOlder) + if (opts.update == UpdateMode::ReplaceIfOlder) && fs::metadata(from)?.modified()? <= fs::metadata(to)?.modified()? { return Ok(()); } - match b.overwrite { + match opts.overwrite { OverwriteMode::NoClobber => { - let err_msg = if b.verbose { - println!("skipped {}", to.quote()); - String::new() - } else { - format!("not replacing {}", to.quote()) - }; + let err_msg = format!("not replacing {}", to.quote()); return Err(io::Error::new(io::ErrorKind::Other, err_msg)); } OverwriteMode::Interactive => { if !prompt_yes!("overwrite {}?", to.quote()) { - if b.verbose { - println!("skipped {}", to.quote()); - } return Err(io::Error::new(io::ErrorKind::Other, "")); } } OverwriteMode::Force => {} }; - backup_path = backup_control::get_backup_path(b.backup, to, &b.suffix); + backup_path = backup_control::get_backup_path(opts.backup, to, &opts.suffix); if let Some(ref backup_path) = backup_path { rename_with_fallback(to, backup_path, multi_progress)?; } @@ -492,7 +572,7 @@ fn rename( rename_with_fallback(from, to, multi_progress)?; - if b.verbose { + if opts.verbose { let message = match backup_path { Some(path) => format!( "renamed {} -> {} (backup: {})", diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index 1f6a560ff..465b7b765 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nice" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "nice ~ (uutils) run PROGRAM with modified scheduling priority" diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index b23608ff6..3eaeba956 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) getpriority execvp setpriority nstr PRIO cstrs ENOENT diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index 376600703..003e17913 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nl" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "nl ~ (uutils) display input with added line numbers" diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index a9dbbad79..ae14a6d59 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -1,26 +1,11 @@ +// 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 (ToDO) conv use crate::options; -// parse_style parses a style string into a NumberingStyle. -fn parse_style(chars: &[char]) -> Result { - if chars.len() == 1 && chars[0] == 'a' { - Ok(crate::NumberingStyle::All) - } else if chars.len() == 1 && chars[0] == 't' { - Ok(crate::NumberingStyle::NonEmpty) - } else if chars.len() == 1 && chars[0] == 'n' { - Ok(crate::NumberingStyle::None) - } else if chars.len() > 1 && chars[0] == 'p' { - let s: String = chars[1..].iter().cloned().collect(); - match regex::Regex::new(&s) { - Ok(re) => Ok(crate::NumberingStyle::Regex(Box::new(re))), - Err(_) => Err(String::from("Illegal regular expression")), - } - } else { - Err(String::from("Illegal style encountered")) - } -} - // parse_options loads the options into the settings, returning an array of // error messages. #[allow(clippy::cognitive_complexity)] @@ -28,6 +13,15 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> // This vector holds error messages encountered. let mut errs: Vec = vec![]; settings.renumber = opts.get_flag(options::NO_RENUMBER); + if let Some(delimiter) = opts.get_one::(options::SECTION_DELIMITER) { + // check whether the delimiter is a single ASCII char (1 byte) + // because GNU nl doesn't add a ':' to single non-ASCII chars + settings.section_delimiter = if delimiter.len() == 1 { + format!("{delimiter}:") + } else { + delimiter.to_owned() + }; + } if let Some(val) = opts.get_one::(options::NUMBER_SEPARATOR) { settings.number_separator = val.to_owned(); } @@ -35,47 +29,32 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> .get_one::(options::NUMBER_FORMAT) .map(Into::into) .unwrap_or_default(); - match opts.get_one::(options::BODY_NUMBERING) { + match opts + .get_one::(options::HEADER_NUMBERING) + .map(String::as_str) + .map(TryInto::try_into) + { None => {} - Some(val) => { - let chars: Vec = val.chars().collect(); - match parse_style(&chars) { - Ok(s) => { - settings.body_numbering = s; - } - Err(message) => { - errs.push(message); - } - } - } + Some(Ok(style)) => settings.header_numbering = style, + Some(Err(message)) => errs.push(message.to_string()), } - match opts.get_one::(options::FOOTER_NUMBERING) { + match opts + .get_one::(options::BODY_NUMBERING) + .map(String::as_str) + .map(TryInto::try_into) + { None => {} - Some(val) => { - let chars: Vec = val.chars().collect(); - match parse_style(&chars) { - Ok(s) => { - settings.footer_numbering = s; - } - Err(message) => { - errs.push(message); - } - } - } + Some(Ok(style)) => settings.body_numbering = style, + Some(Err(message)) => errs.push(message.to_string()), } - match opts.get_one::(options::HEADER_NUMBERING) { + match opts + .get_one::(options::FOOTER_NUMBERING) + .map(String::as_str) + .map(TryInto::try_into) + { None => {} - Some(val) => { - let chars: Vec = val.chars().collect(); - match parse_style(&chars) { - Ok(s) => { - settings.header_numbering = s; - } - Err(message) => { - errs.push(message); - } - } - } + Some(Ok(style)) => settings.footer_numbering = style, + Some(Err(message)) => errs.push(message.to_string()), } match opts.get_one::(options::NUMBER_WIDTH) { None => {} diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 502e7d518..61ca8406f 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Tobias Bohumir Schottdorf -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. -// * +// 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 clap::{crate_version, Arg, ArgAction, Command}; use std::fs::File; @@ -26,7 +23,7 @@ pub struct Settings { body_numbering: NumberingStyle, footer_numbering: NumberingStyle, // The variable corresponding to -d - section_delimiter: [char; 2], + section_delimiter: String, // The variables corresponding to the options -v, -i, -l, -w. starting_line_number: i64, line_increment: i64, @@ -44,9 +41,9 @@ impl Default for Settings { fn default() -> Self { Self { header_numbering: NumberingStyle::None, - body_numbering: NumberingStyle::All, + body_numbering: NumberingStyle::NonEmpty, footer_numbering: NumberingStyle::None, - section_delimiter: ['\\', ':'], + section_delimiter: String::from("\\:"), starting_line_number: 1, line_increment: 1, join_blank_lines: 1, @@ -58,6 +55,20 @@ impl Default for Settings { } } +struct Stats { + line_number: Option, + consecutive_empty_lines: u64, +} + +impl Stats { + fn new(starting_line_number: i64) -> Self { + Self { + line_number: Some(starting_line_number), + consecutive_empty_lines: 0, + } + } +} + // NumberingStyle stores which lines are to be numbered. // The possible options are: // 1. Number all lines @@ -71,6 +82,23 @@ enum NumberingStyle { Regex(Box), } +impl TryFrom<&str> for NumberingStyle { + type Error = String; + + fn try_from(s: &str) -> Result { + match s { + "a" => Ok(Self::All), + "t" => Ok(Self::NonEmpty), + "n" => Ok(Self::None), + _ if s.starts_with('p') => match regex::Regex::new(&s[1..]) { + Ok(re) => Ok(Self::Regex(Box::new(re))), + Err(_) => Err(String::from("invalid regular expression")), + }, + _ => Err(format!("invalid numbering style: '{s}'")), + } + } +} + // NumberFormat specifies how line numbers are output within their allocated // space. They are justified to the left or right, in the latter case with // the option of having all unused space to its left turned into leading zeroes. @@ -106,6 +134,32 @@ impl NumberFormat { } } +enum SectionDelimiter { + Header, + Body, + Footer, +} + +impl SectionDelimiter { + // A valid section delimiter contains the pattern one to three times, + // and nothing else. + fn parse(s: &str, pattern: &str) -> Option { + if s.is_empty() || pattern.is_empty() { + return None; + } + + let pattern_count = s.matches(pattern).count(); + let is_length_ok = pattern_count * pattern.len() == s.len(); + + match (pattern_count, is_length_ok) { + (3, true) => Some(Self::Header), + (2, true) => Some(Self::Body), + (1, true) => Some(Self::Footer), + _ => None, + } + } +} + pub mod options { pub const HELP: &str = "help"; pub const FILE: &str = "file"; @@ -140,29 +194,25 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { )); } - let mut read_stdin = false; let files: Vec = match matches.get_many::(options::FILE) { Some(v) => v.clone().map(|v| v.to_owned()).collect(), None => vec!["-".to_owned()], }; + let mut stats = Stats::new(settings.starting_line_number); + for file in &files { if file == "-" { - // If both file names and '-' are specified, we choose to treat first all - // regular files, and then read from stdin last. - read_stdin = true; - continue; + let mut buffer = BufReader::new(stdin()); + nl(&mut buffer, &mut stats, &settings)?; + } else { + let path = Path::new(file); + let reader = File::open(path).map_err_context(|| file.to_string())?; + let mut buffer = BufReader::new(reader); + nl(&mut buffer, &mut stats, &settings)?; } - let path = Path::new(file); - let reader = File::open(path).map_err_context(|| file.to_string())?; - let mut buffer = BufReader::new(reader); - nl(&mut buffer, &settings)?; } - if read_stdin { - let mut buffer = BufReader::new(stdin()); - nl(&mut buffer, &settings)?; - } Ok(()) } @@ -271,151 +321,80 @@ pub fn uu_app() -> Command { } // nl implements the main functionality for an individual buffer. -#[allow(clippy::cognitive_complexity)] -fn nl(reader: &mut BufReader, settings: &Settings) -> UResult<()> { - let regexp: regex::Regex = regex::Regex::new(r".?").unwrap(); - let mut line_no = settings.starting_line_number; - let mut empty_line_count: u64 = 0; - // Initially, we use the body's line counting settings - let mut regex_filter = match settings.body_numbering { - NumberingStyle::Regex(ref re) => re, - _ => ®exp, - }; - let mut line_filter: fn(&str, ®ex::Regex) -> bool = pass_regex; - for l in reader.lines() { - let mut l = l.map_err_context(|| "could not read line".to_string())?; - // Sanitize the string. We want to print the newline ourselves. - if l.ends_with('\n') { - l.pop(); - } - // Next we iterate through the individual chars to see if this - // is one of the special lines starting a new "section" in the - // document. - let line = l; - let mut odd = false; - // matched_group counts how many copies of section_delimiter - // this string consists of (0 if there's anything else) - let mut matched_groups = 0u8; - for c in line.chars() { - // If this is a newline character, the loop should end. - if c == '\n' { - break; - } - // If we have already seen three groups (corresponding to - // a header) or the current char does not form part of - // a new group, then this line is not a segment indicator. - if matched_groups >= 3 || settings.section_delimiter[usize::from(odd)] != c { - matched_groups = 0; - break; - } - if odd { - // We have seen a new group and count it. - matched_groups += 1; - } - odd = !odd; - } +fn nl(reader: &mut BufReader, stats: &mut Stats, settings: &Settings) -> UResult<()> { + let mut current_numbering_style = &settings.body_numbering; + + for line in reader.lines() { + let line = line.map_err_context(|| "could not read line".to_string())?; - // See how many groups we matched. That will tell us if this is - // a line starting a new segment, and the number of groups - // indicates what type of segment. - if matched_groups > 0 { - // The current line is a section delimiter, so we output - // a blank line. - println!(); - // However the line does not count as a blank line, so we - // reset the counter used for --join-blank-lines. - empty_line_count = 0; - match *match matched_groups { - 3 => { - // This is a header, so we may need to reset the - // line number - if settings.renumber { - line_no = settings.starting_line_number; - } - &settings.header_numbering - } - 1 => &settings.footer_numbering, - // The only option left is 2, but rust wants - // a catch-all here. - _ => &settings.body_numbering, - } { - NumberingStyle::All => { - line_filter = pass_all; - } - NumberingStyle::NonEmpty => { - line_filter = pass_nonempty; - } - NumberingStyle::None => { - line_filter = pass_none; - } - NumberingStyle::Regex(ref re) => { - line_filter = pass_regex; - regex_filter = re; - } - } - continue; - } - // From this point on we format and print a "regular" line. if line.is_empty() { - // The line is empty, which means that we have to care - // about the --join-blank-lines parameter. - empty_line_count += 1; + stats.consecutive_empty_lines += 1; } else { - // This saves us from having to check for an empty string - // in the next selector. - empty_line_count = 0; - } - if !line_filter(&line, regex_filter) - || (empty_line_count > 0 && empty_line_count < settings.join_blank_lines) + stats.consecutive_empty_lines = 0; + }; + + let new_numbering_style = match SectionDelimiter::parse(&line, &settings.section_delimiter) { - // No number is printed for this line. Either we did not - // want to print one in the first place, or it is a blank - // line but we are still collecting more blank lines via - // the option --join-blank-lines. - println!("{line}"); - continue; + Some(SectionDelimiter::Header) => Some(&settings.header_numbering), + Some(SectionDelimiter::Body) => Some(&settings.body_numbering), + Some(SectionDelimiter::Footer) => Some(&settings.footer_numbering), + None => None, + }; + + if let Some(new_style) = new_numbering_style { + current_numbering_style = new_style; + if settings.renumber { + stats.line_number = Some(settings.starting_line_number); + } + println!(); + } else { + let is_line_numbered = match current_numbering_style { + // consider $join_blank_lines consecutive empty lines to be one logical line + // for numbering, and only number the last one + NumberingStyle::All + if line.is_empty() + && stats.consecutive_empty_lines % settings.join_blank_lines != 0 => + { + false + } + NumberingStyle::All => true, + NumberingStyle::NonEmpty => !line.is_empty(), + NumberingStyle::None => false, + NumberingStyle::Regex(re) => re.is_match(&line), + }; + + if is_line_numbered { + let Some(line_number) = stats.line_number else { + return Err(USimpleError::new(1, "line number overflow")); + }; + println!( + "{}{}{}", + settings + .number_format + .format(line_number, settings.number_width), + settings.number_separator, + line + ); + // update line number for the potential next line + match line_number.checked_add(settings.line_increment) { + Some(new_line_number) => stats.line_number = Some(new_line_number), + None => stats.line_number = None, // overflow + } + } else { + let spaces = " ".repeat(settings.number_width + 1); + println!("{spaces}{line}"); + } } - // If we make it here, then either we are printing a non-empty - // line or assigning a line number to an empty line. Either - // way, start counting empties from zero once more. - empty_line_count = 0; - // A line number is to be printed. - println!( - "{}{}{}", - settings - .number_format - .format(line_no, settings.number_width), - settings.number_separator, - line - ); - // Now update the line number for the (potential) next - // line. - line_no += settings.line_increment; } Ok(()) } -fn pass_regex(line: &str, re: ®ex::Regex) -> bool { - re.is_match(line) -} - -fn pass_nonempty(line: &str, _: ®ex::Regex) -> bool { - !line.is_empty() -} - -fn pass_none(_: &str, _: ®ex::Regex) -> bool { - false -} - -fn pass_all(_: &str, _: ®ex::Regex) -> bool { - true -} - #[cfg(test)] mod test { use super::*; #[test] + #[allow(clippy::cognitive_complexity)] fn test_format() { assert_eq!(NumberFormat::Left.format(12, 1), "12"); assert_eq!(NumberFormat::Left.format(-12, 1), "-12"); diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index b7e4c395a..41c98982a 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nohup" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "nohup ~ (uutils) run COMMAND, ignoring hangup signals" @@ -17,7 +17,6 @@ path = "src/nohup.rs" [dependencies] clap = { workspace = true } libc = { workspace = true } -is-terminal = { workspace = true } uucore = { workspace = true, features = ["fs"] } [[bin]] diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 8247cdb3e..c64f7bf71 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -1,21 +1,18 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2014 Vsevolod Velichko -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) execvp SIGHUP cproc vprocmgr cstrs homeout use clap::{crate_version, Arg, ArgAction, Command}; -use is_terminal::IsTerminal; 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::io::{Error, IsTerminal}; use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; use uucore::display::Quotable; diff --git a/src/uu/nproc/Cargo.toml b/src/uu/nproc/Cargo.toml index 7fad14274..0acc0e951 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nproc" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "nproc ~ (uutils) display the number of processing units available" diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 2b6cae04e..d0bd3083d 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Michael Gehring -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) NPROCESSORS nprocs numstr threadstr sysconf diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index 6cf0f4320..cf2f08646 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_numfmt" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "numfmt ~ (uutils) reformat NUMBER" @@ -16,7 +16,7 @@ path = "src/numfmt.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["ranges"] } [[bin]] name = "numfmt" diff --git a/src/uu/numfmt/src/errors.rs b/src/uu/numfmt/src/errors.rs index 22c6962d6..77dd6f0aa 100644 --- a/src/uu/numfmt/src/errors.rs +++ b/src/uu/numfmt/src/errors.rs @@ -1,7 +1,7 @@ -// * 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. +// 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, diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index eb75f7554..034d900e9 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -1,3 +1,7 @@ +// 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 powf use uucore::display::Quotable; @@ -426,6 +430,7 @@ mod tests { use super::*; #[test] + #[allow(clippy::cognitive_complexity)] fn test_round_with_precision() { let rm = RoundMethod::FromZero; assert_eq!(1.0, round_with_precision(0.12345, rm, 0)); diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index b0a5670d4..4afe56555 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Yury Krivopalov -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 crate::errors::*; use crate::format::format_and_print; diff --git a/src/uu/numfmt/src/options.rs b/src/uu/numfmt/src/options.rs index bef4a8ce3..88e64e963 100644 --- a/src/uu/numfmt/src/options.rs +++ b/src/uu/numfmt/src/options.rs @@ -1,3 +1,7 @@ +// 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::str::FromStr; use crate::units::Unit; @@ -262,6 +266,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_parse_format_with_invalid_formats() { assert!("".parse::().is_err()); assert!("hello".parse::().is_err()); diff --git a/src/uu/numfmt/src/units.rs b/src/uu/numfmt/src/units.rs index 08c7e4d3b..585bae461 100644 --- a/src/uu/numfmt/src/units.rs +++ b/src/uu/numfmt/src/units.rs @@ -1,3 +1,7 @@ +// 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::fmt; pub const SI_BASES: [f64; 10] = [1., 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24, 1e27]; diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index 7bb597ab3..b7799e052 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_od" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "od ~ (uutils) display formatted representation of input" diff --git a/src/uu/od/src/byteorder_io.rs b/src/uu/od/src/byteorder_io.rs index c22097da2..545016ff3 100644 --- a/src/uu/od/src/byteorder_io.rs +++ b/src/uu/od/src/byteorder_io.rs @@ -1,3 +1,7 @@ +// 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 (ToDO) byteorder // workaround until https://github.com/BurntSushi/byteorder/issues/41 has been fixed diff --git a/src/uu/od/src/formatteriteminfo.rs b/src/uu/od/src/formatteriteminfo.rs index 00ee2ee20..9e3c2e836 100644 --- a/src/uu/od/src/formatteriteminfo.rs +++ b/src/uu/od/src/formatteriteminfo.rs @@ -1,3 +1,7 @@ +// 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 (ToDO) formatteriteminfo use std::fmt; diff --git a/src/uu/od/src/inputdecoder.rs b/src/uu/od/src/inputdecoder.rs index 318571cf3..e63eaed14 100644 --- a/src/uu/od/src/inputdecoder.rs +++ b/src/uu/od/src/inputdecoder.rs @@ -1,3 +1,7 @@ +// 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 half::f16; use std::io; @@ -162,6 +166,7 @@ mod tests { #[test] #[allow(clippy::float_cmp)] + #[allow(clippy::cognitive_complexity)] fn smoke_test() { let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xff, 0xff]; let mut input = PeekReader::new(Cursor::new(&data)); diff --git a/src/uu/od/src/inputoffset.rs b/src/uu/od/src/inputoffset.rs index ddaf747f1..a19600055 100644 --- a/src/uu/od/src/inputoffset.rs +++ b/src/uu/od/src/inputoffset.rs @@ -1,3 +1,7 @@ +// 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. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Radix { Decimal, diff --git a/src/uu/od/src/mockstream.rs b/src/uu/od/src/mockstream.rs index a1ce0dd68..925d52f7e 100644 --- a/src/uu/od/src/mockstream.rs +++ b/src/uu/od/src/mockstream.rs @@ -1,3 +1,7 @@ +// 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. // https://github.com/lazy-bitfield/rust-mockstream/pull/2 use std::io::{Cursor, Error, ErrorKind, Read, Result}; diff --git a/src/uu/od/src/multifilereader.rs b/src/uu/od/src/multifilereader.rs index 38e192588..f7575e975 100644 --- a/src/uu/od/src/multifilereader.rs +++ b/src/uu/od/src/multifilereader.rs @@ -1,3 +1,7 @@ +// 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 (ToDO) multifile curr fnames fname xfrd fillloop mockstream use std::fs::File; diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 09765ed2b..769dae98e 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Ben Hirsch -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (clap) dont // spell-checker:ignore (ToDO) formatteriteminfo inputdecoder inputoffset mockstream nrofbytes partialreader odfunc multifile exitcode diff --git a/src/uu/od/src/output_info.rs b/src/uu/od/src/output_info.rs index 211574a61..993bba329 100644 --- a/src/uu/od/src/output_info.rs +++ b/src/uu/od/src/output_info.rs @@ -1,3 +1,7 @@ +// 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 formatteriteminfo blocksize thisblock use std::cmp; @@ -204,6 +208,7 @@ impl TypeSizeInfo for TypeInfo { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_calculate_alignment() { // For this example `byte_size_block` is 8 and 'print_width_block' is 23: // 1777777777777777777777 1777777777777777777777 diff --git a/src/uu/od/src/parse_formats.rs b/src/uu/od/src/parse_formats.rs index c414abebe..2bb876d2b 100644 --- a/src/uu/od/src/parse_formats.rs +++ b/src/uu/od/src/parse_formats.rs @@ -1,3 +1,7 @@ +// 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 formatteriteminfo docopt fvox fvoxw vals acdx use uucore::display::Quotable; diff --git a/src/uu/od/src/parse_inputs.rs b/src/uu/od/src/parse_inputs.rs index 3093843d4..241d842af 100644 --- a/src/uu/od/src/parse_inputs.rs +++ b/src/uu/od/src/parse_inputs.rs @@ -1,3 +1,7 @@ +// 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 super::options; use clap::ArgMatches; @@ -209,6 +213,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_parse_inputs_with_offset() { // offset is found without filename, so stdin will be used. assert_eq!( @@ -351,6 +356,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_parse_offset_operand() { assert_eq!(8, parse_offset_operand_str("10").unwrap()); // default octal assert_eq!(0, parse_offset_operand_str("0").unwrap()); diff --git a/src/uu/od/src/parse_nrofbytes.rs b/src/uu/od/src/parse_nrofbytes.rs index 7d3bca03d..1aa69909f 100644 --- a/src/uu/od/src/parse_nrofbytes.rs +++ b/src/uu/od/src/parse_nrofbytes.rs @@ -1,4 +1,8 @@ -use uucore::parse_size::{parse_size, ParseSizeError}; +// 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 uucore::parse_size::{parse_size_u64, ParseSizeError}; pub fn parse_number_of_bytes(s: &str) -> Result { let mut start = 0; @@ -11,7 +15,7 @@ pub fn parse_number_of_bytes(s: &str) -> Result { } else if s.starts_with('0') { radix = 8; } else { - return parse_size(&s[start..]); + return parse_size_u64(&s[start..]); } let mut ends_with = s.chars().rev(); @@ -75,6 +79,7 @@ pub fn parse_number_of_bytes(s: &str) -> Result { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_parse_number_of_bytes() { // octal input assert_eq!(8, parse_number_of_bytes("010").unwrap()); diff --git a/src/uu/od/src/partialreader.rs b/src/uu/od/src/partialreader.rs index 8b51d8dee..24918f5cf 100644 --- a/src/uu/od/src/partialreader.rs +++ b/src/uu/od/src/partialreader.rs @@ -1,3 +1,7 @@ +// 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 mockstream abcdefgh bcdefgh use std::cmp; diff --git a/src/uu/od/src/peekreader.rs b/src/uu/od/src/peekreader.rs index 45cd554d0..82f139c72 100644 --- a/src/uu/od/src/peekreader.rs +++ b/src/uu/od/src/peekreader.rs @@ -1,3 +1,7 @@ +// 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 (ToDO) tempbuffer abcdefgh abcdefghij //! Contains the trait `PeekRead` and type `PeekReader` implementing it. @@ -182,6 +186,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_peek_read_with_smaller_buffer() { let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..])); diff --git a/src/uu/od/src/prn_char.rs b/src/uu/od/src/prn_char.rs index 7ae6f084c..36a00a67b 100644 --- a/src/uu/od/src/prn_char.rs +++ b/src/uu/od/src/prn_char.rs @@ -1,3 +1,7 @@ +// 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::str::from_utf8; use crate::formatteriteminfo::{FormatWriter, FormatterItemInfo}; @@ -82,7 +86,7 @@ pub fn format_ascii_dump(bytes: &[u8]) -> String { let mut result = String::new(); result.push('>'); - for c in bytes.iter() { + for c in bytes { if *c >= 0x20 && *c <= 0x7e { result.push_str(C_CHARS[*c as usize]); } else { @@ -95,6 +99,7 @@ pub fn format_ascii_dump(bytes: &[u8]) -> String { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_format_item_a() { assert_eq!(" nul", format_item_a(0x00)); assert_eq!(" soh", format_item_a(0x01)); @@ -110,6 +115,7 @@ fn test_format_item_a() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_format_item_c() { assert_eq!(" \\0", format_item_c(&[0x00])); assert_eq!(" 001", format_item_c(&[0x01])); diff --git a/src/uu/od/src/prn_float.rs b/src/uu/od/src/prn_float.rs index 496275574..f44abf7c4 100644 --- a/src/uu/od/src/prn_float.rs +++ b/src/uu/od/src/prn_float.rs @@ -1,3 +1,7 @@ +// 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 half::f16; use std::f32; use std::f64; @@ -87,6 +91,7 @@ fn format_float(f: f64, width: usize, precision: usize) -> String { #[test] #[allow(clippy::excessive_precision)] +#[allow(clippy::cognitive_complexity)] fn test_format_flo32() { assert_eq!(format_flo32(1.0), " 1.0000000"); assert_eq!(format_flo32(9.9999990), " 9.9999990"); @@ -163,6 +168,7 @@ fn test_format_flo32() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_format_flo64() { assert_eq!(format_flo64(1.0), " 1.0000000000000000"); assert_eq!(format_flo64(10.0), " 10.000000000000000"); @@ -192,6 +198,7 @@ fn test_format_flo64() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_format_flo16() { assert_eq!(format_flo16(f16::from_bits(0x8400u16)), "-6.104e-5"); assert_eq!(format_flo16(f16::from_bits(0x8401u16)), "-6.109e-5"); diff --git a/src/uu/od/src/prn_int.rs b/src/uu/od/src/prn_int.rs index 0946824c1..f843ff77c 100644 --- a/src/uu/od/src/prn_int.rs +++ b/src/uu/od/src/prn_int.rs @@ -1,3 +1,7 @@ +// 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 crate::formatteriteminfo::*; /// format string to print octal using `int_writer_unsigned` @@ -85,6 +89,7 @@ int_writer_signed!(FORMAT_ITEM_DEC32S, 4, 12, format_item_dec_s32, DEC!()); // m int_writer_signed!(FORMAT_ITEM_DEC64S, 8, 21, format_item_dec_s64, DEC!()); // max: -9223372036854775808 #[test] +#[allow(clippy::cognitive_complexity)] fn test_sign_extend() { assert_eq!( 0xffff_ffff_ffff_ff80u64 as i64, @@ -175,6 +180,7 @@ fn test_format_item_dec_u() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_format_item_dec_s() { assert_eq!(" 0", format_item_dec_s8(0)); assert_eq!(" 127", format_item_dec_s8(0x7f)); diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index 7d6ed229e..dd030478c 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_paste" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "paste ~ (uutils) merge lines from inputs" diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 6e52727f7..89bba034c 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -1,18 +1,16 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) delim use clap::{crate_version, Arg, ArgAction, Command}; -use std::fmt::Display; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, Read, Write}; use std::path::Path; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; const ABOUT: &str = help_about!("paste.md"); @@ -25,22 +23,6 @@ mod options { pub const ZERO_TERMINATED: &str = "zero-terminated"; } -#[repr(u8)] -#[derive(Clone, Copy)] -enum LineEnding { - Newline = b'\n', - Nul = 0, -} - -impl Display for LineEnding { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Newline => writeln!(f), - Self::Nul => write!(f, "\0"), - } - } -} - // Wraps BufReader and stdin fn read_until( reader: Option<&mut BufReader>, @@ -64,11 +46,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap() .map(|s| s.to_owned()) .collect(); - let line_ending = if matches.get_flag(options::ZERO_TERMINATED) { - LineEnding::Nul - } else { - LineEnding::Newline - }; + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); paste(files, serial, delimiters, line_ending) } diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index b8f4f0310..708ae2afb 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pathchk" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "pathchk ~ (uutils) diagnose invalid or non-portable PATHNAME" diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 598b9718b..81c352088 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -1,12 +1,9 @@ +// 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. #![allow(unused_must_use)] // because we of writeln! -// * This file is part of the uutils coreutils package. -// * -// * (c) Inokentiy Babushkin -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. - // spell-checker:ignore (ToDO) lstat use clap::{crate_version, Arg, ArgAction, Command}; use std::fs; @@ -196,6 +193,17 @@ fn check_default(path: &[String]) -> bool { ); return false; } + if total_len == 0 { + // Check whether a file name component is in a directory that is not searchable, + // or has some other serious problem. POSIX does not allow "" as a file name, + // but some non-POSIX hosts do (as an alias for "."), + // so allow "" if `symlink_metadata` (corresponds to `lstat`) does. + if fs::symlink_metadata(&joined_path).is_err() { + writeln!(std::io::stderr(), "pathchk: '': No such file or directory",); + return false; + } + } + // components: length for p in path { let component_len = p.len(); diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index 8566faf68..c4739e7fc 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pinky" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "pinky ~ (uutils) display user information" diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 172a9e138..8ac8f6c84 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index 71e843a84..9a12e10f5 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pr" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "pr ~ (uutils) convert text files for printing" diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 37674bad7..ef178a888 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -1,7 +1,7 @@ // 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. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. // // spell-checker:ignore (ToDO) adFfmprt, kmerge diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index 11666bd61..64435127f 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printenv" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "printenv ~ (uutils) display value of environment VAR" diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index beeb7285b..cab24336f 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Jordi Boggiano -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. /* last synced with: printenv (GNU coreutils) 8.13 */ diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index 9acd2c78c..e58ebbbc8 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printf" -version = "0.0.20" +version = "0.0.22" authors = ["Nathan Ross", "uutils developers"] license = "MIT" description = "printf ~ (uutils) FORMAT and display ARGUMENTS" diff --git a/src/uu/printf/src/mod.rs b/src/uu/printf/src/mod.rs index 26710c101..759b823cc 100644 --- a/src/uu/printf/src/mod.rs +++ b/src/uu/printf/src/mod.rs @@ -1 +1,5 @@ +// 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. mod cli; diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 042e0932e..36b4c3453 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -1,3 +1,7 @@ +// 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. #![allow(dead_code)] // spell-checker:ignore (change!) each's // spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index e9b11223c..ea241600b 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ptx" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "ptx ~ (uutils) display a permuted index of input" diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index ecfd67ce8..1e9532a3a 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Dorota Kapturkiewicz -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDOs) corasick memchr Roff trunc oset iset CHARCLASS @@ -324,7 +322,7 @@ fn create_word_set(config: &Config, filter: &WordFilter, file_map: &FileMap) -> let reg = Regex::new(&filter.word_regex).unwrap(); let ref_reg = Regex::new(&config.context_regex).unwrap(); let mut word_set: BTreeSet = BTreeSet::new(); - for (file, lines) in file_map.iter() { + for (file, lines) in file_map { let mut count: usize = 0; let offs = lines.offset; for line in &lines.lines { @@ -656,7 +654,7 @@ fn write_traditional_output( let context_reg = Regex::new(&config.context_regex).unwrap(); - for word_ref in words.iter() { + for word_ref in words { let file_map_value: &FileContent = file_map .get(&(word_ref.filename)) .expect("Missing file in file map"); diff --git a/src/uu/pwd/Cargo.toml b/src/uu/pwd/Cargo.toml index 09989d079..5f15245b5 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pwd" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "pwd ~ (uutils) display current working directory" diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 9e04dd38b..fde2357e2 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Derek Chiang -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 clap::ArgAction; use clap::{crate_version, Arg, Command}; diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index ba407d9cc..a696abdfa 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_readlink" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "readlink ~ (uutils) display resolved path of PATHNAME" diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index e247c6146..2febe51af 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Haitao Li -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) errno @@ -14,6 +12,7 @@ use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage, show_error}; const ABOUT: &str = help_about!("readlink.md"); @@ -67,6 +66,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { show_error!("ignoring --no-newline with multiple arguments"); no_trailing_delimiter = false; } + let line_ending = if no_trailing_delimiter { + None + } else { + Some(LineEnding::from_zero_flag(use_zero)) + }; for f in &files { let p = PathBuf::from(f); @@ -77,7 +81,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; match path_result { Ok(path) => { - show(&path, no_trailing_delimiter, use_zero).map_err_context(String::new)?; + show(&path, line_ending).map_err_context(String::new)?; } Err(err) => { if verbose { @@ -173,14 +177,11 @@ pub fn uu_app() -> Command { ) } -fn show(path: &Path, no_trailing_delimiter: bool, use_zero: bool) -> std::io::Result<()> { +fn show(path: &Path, line_ending: Option) -> std::io::Result<()> { let path = path.to_str().unwrap(); - if no_trailing_delimiter { - print!("{path}"); - } else if use_zero { - print!("{path}\0"); - } else { - println!("{path}"); + print!("{path}"); + if let Some(line_ending) = line_ending { + print!("{line_ending}"); } stdout().flush() } diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index 802941cd1..6d2072753 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_realpath" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "realpath ~ (uutils) display resolved absolute path of PATHNAME" diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index cb7a09a41..64806fbab 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2014 Vsevolod Velichko -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) retcode @@ -21,6 +19,7 @@ use uucore::{ format_usage, fs::{canonicalize, MissingHandling, ResolveMode}, help_about, help_usage, + line_ending::LineEnding, }; use uucore::{error::UClapError, show, show_if_err}; @@ -52,7 +51,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect(); let strip = matches.get_flag(OPT_STRIP); - let zero = matches.get_flag(OPT_ZERO); + let line_ending = LineEnding::from_zero_flag(matches.get_flag(OPT_ZERO)); let quiet = matches.get_flag(OPT_QUIET); let logical = matches.get_flag(OPT_LOGICAL); let can_mode = if matches.get_flag(OPT_CANONICALIZE_EXISTING) { @@ -73,7 +72,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for path in &paths { let result = resolve_path( path, - zero, + line_ending, resolve_mode, can_mode, relative_to.as_deref(), @@ -249,19 +248,18 @@ fn canonicalize_relative( /// symbolic links. fn resolve_path( p: &Path, - zero: bool, + line_ending: LineEnding, resolve: ResolveMode, can_mode: MissingHandling, relative_to: Option<&Path>, relative_base: Option<&Path>, ) -> std::io::Result<()> { let abs = canonicalize(p, can_mode, resolve)?; - let line_ending = if zero { b'\0' } else { b'\n' }; let abs = process_relative(abs, relative_base, relative_to); print_verbatim(abs)?; - stdout().write_all(&[line_ending])?; + stdout().write_all(&[line_ending.into()])?; Ok(()) } diff --git a/src/uu/relpath/Cargo.toml b/src/uu/relpath/Cargo.toml deleted file mode 100644 index 62d47e83a..000000000 --- a/src/uu/relpath/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "uu_relpath" -version = "0.0.20" -authors = ["uutils developers"] -license = "MIT" -description = "relpath ~ (uutils) display relative path of PATHNAME_TO from PATHNAME_FROM" - -homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/main/src/uu/relpath" -keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] -categories = ["command-line-utilities"] -edition = "2021" - -[lib] -path = "src/relpath.rs" - -[dependencies] -clap = { workspace = true } -uucore = { workspace = true, features = ["fs"] } - -[[bin]] -name = "relpath" -path = "src/main.rs" diff --git a/src/uu/relpath/LICENSE b/src/uu/relpath/LICENSE deleted file mode 120000 index 5853aaea5..000000000 --- a/src/uu/relpath/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../../../LICENSE \ No newline at end of file diff --git a/src/uu/relpath/relpath.md b/src/uu/relpath/relpath.md deleted file mode 100644 index a1ff9bf4a..000000000 --- a/src/uu/relpath/relpath.md +++ /dev/null @@ -1,8 +0,0 @@ -# relpath - -``` -relpath [-d DIR] TO [FROM] -``` - -Convert TO destination to the relative path from the FROM dir. -If FROM path is omitted, current working dir will be used. \ No newline at end of file diff --git a/src/uu/relpath/src/main.rs b/src/uu/relpath/src/main.rs deleted file mode 100644 index b7dba76ce..000000000 --- a/src/uu/relpath/src/main.rs +++ /dev/null @@ -1 +0,0 @@ -uucore::bin!(uu_relpath); diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs deleted file mode 100644 index ef7c43474..000000000 --- a/src/uu/relpath/src/relpath.rs +++ /dev/null @@ -1,89 +0,0 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) 2014 Vsevolod Velichko -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. - -// spell-checker:ignore (ToDO) subpath absto absfrom absbase - -use clap::{crate_version, Arg, Command}; -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::{format_usage, help_about, help_usage}; - -const USAGE: &str = help_usage!("relpath.md"); -const ABOUT: &str = help_about!("relpath.md"); - -mod options { - pub const DIR: &str = "DIR"; - pub const TO: &str = "TO"; - pub const FROM: &str = "FROM"; -} - -#[uucore::main] -pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_lossy(); - - let matches = uu_app().get_matches_from(args); - - let to = Path::new(matches.get_one::(options::TO).unwrap()).to_path_buf(); // required - let from = match matches.get_one::(options::FROM) { - Some(p) => Path::new(p).to_path_buf(), - None => env::current_dir().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.contains_id(options::DIR) { - let base = Path::new(&matches.get_one::(options::DIR).unwrap()).to_path_buf(); - 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()) - { - return println_verbatim(absto).map_err_context(String::new); - } - } - - let mut suffix_pos = 0; - for (f, t) in absfrom.components().zip(absto.components()) { - if f == t { - suffix_pos += 1; - } else { - break; - } - } - - let mut result = PathBuf::new(); - absfrom - .components() - .skip(suffix_pos) - .map(|_| result.push("..")) - .last(); - absto - .components() - .skip(suffix_pos) - .map(|x| result.push(x.as_os_str())) - .last(); - - println_verbatim(result).map_err_context(String::new) -} - -pub fn uu_app() -> Command { - Command::new(uucore::util_name()) - .version(crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .infer_long_args(true) - .arg(Arg::new(options::DIR).short('d').help( - "If any of FROM and TO is not subpath of DIR, output absolute path instead of relative", - )) - .arg(Arg::new(options::TO).value_hint(clap::ValueHint::AnyPath)) - .arg(Arg::new(options::FROM).value_hint(clap::ValueHint::AnyPath)) -} diff --git a/src/uu/rm/BENCHMARKING.md b/src/uu/rm/BENCHMARKING.md new file mode 100644 index 000000000..507906d49 --- /dev/null +++ b/src/uu/rm/BENCHMARKING.md @@ -0,0 +1,61 @@ + +# Benchmarking rm + +Run `cargo build --release` before benchmarking after you make a change! + +## Simple recursive rm + +- Get a large tree, for example linux kernel source tree. +- We'll need to pass a `--prepare` argument, since `rm` deletes the dir each time. +- Benchmark simple recursive rm with hyperfine: `hyperfine --prepare "cp -r tree tree-tmp" "target/release/coreutils rm -r tree-tmp"`. + +## Comparing with GNU rm + +Hyperfine accepts multiple commands to run and will compare them. To compare performance with GNU rm +duplicate the string you passed to hyperfine but remove the `target/release/coreutils` bit from it. + +Example: `hyperfine --prepare "cp -r tree tree-tmp" "target/release/coreutils rm -rf tree-tmp"` becomes +`hyperfine --prepare "cp -r tree tree-tmp" "target/release/coreutils rm -rf tree-tmp" "rm -rf tree-tmp"` +(This assumes GNU rm is installed as `rm`) + +This can also be used to compare with version of rm built before your changes to ensure your change does not regress this. + +Here is a `bash` script for doing this comparison: + +```shell +#!/bin/bash +cargo build --no-default-features --features rm --release +test_dir="$1" +hyperfine --prepare "cp -r $test_dir tmp_d" "rm -rf tmp_d" "target/release/coreutils rm -rf tmp_d" +``` + +## Checking system call count + +- Another thing to look at would be system calls count using strace (on linux) or equivalent on other operating systems. +- Example: `strace -c target/release/coreutils rm -rf tree` + +## Flamegraph + +### samply + +[samply](https://github.com/mstange/samply) is one option for simply creating flamegraphs. It uses the Firefox profiler as a UI. + +To install: +```bash +cargo install samply +``` + +To run: + +```bash +samply record target/release/coreutils rm -rf ../linux +``` + +### Cargo Flamegraph + +With Cargo Flamegraph you can easily make a flamegraph of `rm`: + +```shell +cargo flamegraph --cmd coreutils -- rm [additional parameters] +``` + diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 2807d2d5b..a1b3999d4 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rm" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "rm ~ (uutils) remove PATHNAME" diff --git a/src/uu/rm/benchmark.sh b/src/uu/rm/benchmark.sh new file mode 100755 index 000000000..5feaea4e8 --- /dev/null +++ b/src/uu/rm/benchmark.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Exit on any failures +set +x + +cargo build --no-default-features --features rm --release +test_dir="$1" +hyperfine --prepare "cp -r $test_dir tmp_d" "rm -rf tmp_d" "target/release/coreutils rm -rf tmp_d" diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index f2e2050d9..4fc37a130 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -1,11 +1,9 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (path) eacces +// spell-checker:ignore (path) eacces inacc use clap::{builder::ValueParser, crate_version, parser::ValueSource, Arg, ArgAction, Command}; use std::collections::VecDeque; @@ -20,22 +18,52 @@ use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, sho use walkdir::{DirEntry, WalkDir}; #[derive(Eq, PartialEq, Clone, Copy)] -enum InteractiveMode { +/// Enum, determining when the `rm` will prompt the user about the file deletion +pub enum InteractiveMode { + /// Never prompt Never, + /// Prompt once before removing more than three files, or when removing + /// recursively. Once, + /// Prompt before every removal Always, + /// Prompt only on write-protected files PromptProtected, } -struct Options { - force: bool, - interactive: InteractiveMode, +/// Options for the `rm` command +/// +/// All options are public so that the options can be programmatically +/// constructed by other crates, such as Nushell. That means that this struct +/// is part of our public API. It should therefore not be changed without good +/// reason. +/// +/// The fields are documented with the arguments that determine their value. +pub struct Options { + /// `-f`, `--force` + pub force: bool, + /// Iterative mode, determines when the command will prompt. + /// + /// Set by the following arguments: + /// - `-i`: [`InteractiveMode::Always`] + /// - `-I`: [`InteractiveMode::Once`] + /// - `--interactive`: sets one of the above or [`InteractiveMode::Never`] + /// - `-f`: implicitly sets [`InteractiveMode::Never`] + /// + /// If no other option sets this mode, [`InteractiveMode::PromptProtected`] + /// is used + pub interactive: InteractiveMode, #[allow(dead_code)] - one_fs: bool, - preserve_root: bool, - recursive: bool, - dir: bool, - verbose: bool, + /// `--one-file-system` + pub one_fs: bool, + /// `--preserve-root`/`--no-preserve-root` + pub preserve_root: bool, + /// `-r`, `--recursive` + pub recursive: bool, + /// `-d`, `--dir` + pub dir: bool, + /// `-v`, `--verbose` + pub verbose: bool, } const ABOUT: &str = help_about!("rm.md"); @@ -251,7 +279,13 @@ pub fn uu_app() -> Command { } // TODO: implement one-file-system (this may get partially implemented in walkdir) -fn remove(files: &[&OsStr], options: &Options) -> bool { +/// Remove (or unlink) the given files +/// +/// Returns true if it has encountered an error. +/// +/// Behavior is determined by the `options` parameter, see [`Options`] for +/// details. +pub fn remove(files: &[&OsStr], options: &Options) -> bool { let mut had_err = false; for filename in files { @@ -270,7 +304,7 @@ fn remove(files: &[&OsStr], options: &Options) -> bool { // TODO: actually print out the specific error // TODO: When the error is not about missing files // (e.g., permission), even rm -f should fail with - // outputting the error, but there's no easy eay. + // outputting the error, but there's no easy way. if options.force { false } else { @@ -296,14 +330,20 @@ fn handle_dir(path: &Path, options: &Options) -> bool { if options.recursive && (!is_root || !options.preserve_root) { if options.interactive != InteractiveMode::Always && !options.verbose { if let Err(e) = fs::remove_dir_all(path) { - had_err = true; - if e.kind() == std::io::ErrorKind::PermissionDenied { - // GNU compatibility (rm/fail-eacces.sh) - // here, GNU doesn't use some kind of remove_dir_all - // It will show directory+file - show_error!("cannot remove {}: {}", path.quote(), "Permission denied"); - } else { - show_error!("cannot remove {}: {}", path.quote(), e); + // GNU compatibility (rm/empty-inacc.sh) + // remove_dir_all failed. maybe it is because of the permissions + // but if the directory is empty, remove_dir might work. + // So, let's try that before failing for real + if let Err(_e) = fs::remove_dir(path) { + had_err = true; + if e.kind() == std::io::ErrorKind::PermissionDenied { + // GNU compatibility (rm/fail-eacces.sh) + // here, GNU doesn't use some kind of remove_dir_all + // It will show directory+file + show_error!("cannot remove {}: {}", path.quote(), "Permission denied"); + } else { + show_error!("cannot remove {}: {}", path.quote(), e); + } } } } else { @@ -370,7 +410,7 @@ fn handle_dir(path: &Path, options: &Options) -> bool { } fn remove_dir(path: &Path, options: &Options) -> bool { - if prompt_file(path, options, true) { + if prompt_dir(path, options) { if let Ok(mut read_dir) = fs::read_dir(path) { if options.dir || options.recursive { if read_dir.next().is_none() { @@ -415,7 +455,7 @@ fn remove_dir(path: &Path, options: &Options) -> bool { } fn remove_file(path: &Path, options: &Options) -> bool { - if prompt_file(path, options, false) { + if prompt_file(path, options) { match fs::remove_file(path) { Ok(_) => { if options.verbose { @@ -437,8 +477,22 @@ fn remove_file(path: &Path, options: &Options) -> bool { false } -#[allow(clippy::cognitive_complexity)] -fn prompt_file(path: &Path, options: &Options, is_dir: bool) -> bool { +fn prompt_dir(path: &Path, options: &Options) -> bool { + // If interactive is Never we never want to send prompts + if options.interactive == InteractiveMode::Never { + return true; + } + + // We can't use metadata.permissions.readonly for directories because it only works on files + // So we have to handle whether a directory is writable manually + if let Ok(metadata) = fs::metadata(path) { + handle_writable_directory(path, options, &metadata) + } else { + true + } +} + +fn prompt_file(path: &Path, options: &Options) -> bool { // If interactive is Never we never want to send prompts if options.interactive == InteractiveMode::Never { return true; @@ -451,60 +505,39 @@ fn prompt_file(path: &Path, options: &Options, is_dir: bool) -> bool { } } } - if is_dir { - // We can't use metadata.permissions.readonly for directories because it only works on files - // So we have to handle whether a directory is writable on not manually - if let Ok(metadata) = fs::metadata(path) { - handle_writable_directory(path, options, &metadata) - } else { - true - } - } else { - // File::open(path) doesn't open the file in write mode so we need to use file options to open it in also write mode to check if it can written too - match File::options().read(true).write(true).open(path) { - Ok(file) => { - if let Ok(metadata) = file.metadata() { - if metadata.permissions().readonly() { - if metadata.len() == 0 { - prompt_yes!( - "remove write-protected regular empty file {}?", - path.quote() - ) - } else { - prompt_yes!("remove write-protected regular file {}?", path.quote()) - } - } else if options.interactive == InteractiveMode::Always { - if metadata.len() == 0 { - prompt_yes!("remove regular empty file {}?", path.quote()) - } else { - prompt_yes!("remove file {}?", path.quote()) - } - } else { - true - } + // File::open(path) doesn't open the file in write mode so we need to use file options to open it in also write mode to check if it can written too + match File::options().read(true).write(true).open(path) { + Ok(file) => { + let Ok(metadata) = file.metadata() else { + return true; + }; + + if options.interactive == InteractiveMode::Always && !metadata.permissions().readonly() + { + return if metadata.len() == 0 { + prompt_yes!("remove regular empty file {}?", path.quote()) } else { - true - } - } - Err(err) => { - if err.kind() == ErrorKind::PermissionDenied { - if let Ok(metadata) = fs::metadata(path) { - if metadata.len() == 0 { - prompt_yes!( - "remove write-protected regular empty file {}?", - path.quote() - ) - } else { - prompt_yes!("remove write-protected regular file {}?", path.quote()) - } - } else { - prompt_yes!("remove write-protected regular file {}?", path.quote()) - } - } else { - true - } + prompt_yes!("remove file {}?", path.quote()) + }; } } + Err(err) => { + if err.kind() != ErrorKind::PermissionDenied { + return true; + } + } + } + prompt_file_permission_readonly(path) +} + +fn prompt_file_permission_readonly(path: &Path) -> bool { + match fs::metadata(path) { + Ok(metadata) if !metadata.permissions().readonly() => true, + Ok(metadata) if metadata.len() == 0 => prompt_yes!( + "remove write-protected regular empty file {}?", + path.quote() + ), + _ => prompt_yes!("remove write-protected regular file {}?", path.quote()), } } diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index 4579cce0a..8288309f2 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rmdir" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "rmdir ~ (uutils) remove empty DIRECTORY" diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index d0123186f..ef152f01a 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) ENOTDIR diff --git a/src/uu/runcon/Cargo.toml b/src/uu/runcon/Cargo.toml index 9ea9dc769..04d3aae71 100644 --- a/src/uu/runcon/Cargo.toml +++ b/src/uu/runcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_runcon" -version = "0.0.20" +version = "0.0.22" 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 b96cc8743..382ab3bed 100644 --- a/src/uu/runcon/src/errors.rs +++ b/src/uu/runcon/src/errors.rs @@ -1,3 +1,7 @@ +// 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; use std::fmt::{Display, Formatter, Write}; use std::io; @@ -22,7 +26,7 @@ pub(crate) enum Error { #[error("No command is specified")] MissingCommand, - #[error("SELinux is not enabled")] + #[error("runcon may be used only on a SELinux kernel")] SELinuxNotEnabled, #[error(transparent)] diff --git a/src/uu/runcon/src/runcon.rs b/src/uu/runcon/src/runcon.rs index a22bff470..a802542b2 100644 --- a/src/uu/runcon/src/runcon.rs +++ b/src/uu/runcon/src/runcon.rs @@ -1,3 +1,7 @@ +// 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 (vars) RFILE use clap::builder::ValueParser; diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index 41b42ef83..7de4bc8bc 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore bigdecimal [package] name = "uu_seq" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "seq ~ (uutils) display a sequence of numbers" diff --git a/src/uu/seq/src/error.rs b/src/uu/seq/src/error.rs index fc8452e13..e81c30fe6 100644 --- a/src/uu/seq/src/error.rs +++ b/src/uu/seq/src/error.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 //! Errors returned by seq. use std::error::Error; diff --git a/src/uu/seq/src/extendedbigdecimal.rs b/src/uu/seq/src/extendedbigdecimal.rs index 253fadfd5..388046ba3 100644 --- a/src/uu/seq/src/extendedbigdecimal.rs +++ b/src/uu/seq/src/extendedbigdecimal.rs @@ -1,3 +1,7 @@ +// 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 bigdecimal extendedbigdecimal extendedbigint //! An arbitrary precision float that can also represent infinity, NaN, etc. //! diff --git a/src/uu/seq/src/extendedbigint.rs b/src/uu/seq/src/extendedbigint.rs index bbe64300a..6828fba2d 100644 --- a/src/uu/seq/src/extendedbigint.rs +++ b/src/uu/seq/src/extendedbigint.rs @@ -1,3 +1,7 @@ +// 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 bigint extendedbigint extendedbigdecimal //! An arbitrary precision integer that can also represent infinity, NaN, etc. //! @@ -65,11 +69,7 @@ impl Display for ExtendedBigInt { Self::BigInt(n) => n.fmt(f), Self::Infinity => f32::INFINITY.fmt(f), Self::MinusInfinity => f32::NEG_INFINITY.fmt(f), - Self::MinusZero => { - // FIXME Come up with a way of formatting this with a - // "-" prefix. - 0.fmt(f) - } + Self::MinusZero => "-0".fmt(f), Self::Nan => "nan".fmt(f), } } @@ -206,13 +206,9 @@ mod tests { #[test] fn test_display() { assert_eq!(format!("{}", ExtendedBigInt::BigInt(BigInt::zero())), "0"); + assert_eq!(format!("{}", ExtendedBigInt::MinusZero), "-0"); assert_eq!(format!("{}", ExtendedBigInt::Infinity), "inf"); assert_eq!(format!("{}", ExtendedBigInt::MinusInfinity), "-inf"); assert_eq!(format!("{}", ExtendedBigInt::Nan), "nan"); - // FIXME Come up with a way of displaying negative zero as - // "-0". Currently it displays as just "0". - // - // assert_eq!(format!("{}", ExtendedBigInt::MinusZero), "-0"); - // } } diff --git a/src/uu/seq/src/number.rs b/src/uu/seq/src/number.rs index 1bab62b14..85bc327ff 100644 --- a/src/uu/seq/src/number.rs +++ b/src/uu/seq/src/number.rs @@ -1,3 +1,7 @@ +// 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 extendedbigdecimal extendedbigint //! A type to represent the possible start, increment, and end values for seq. //! diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 23d94ea2b..3f4b21395 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -1,3 +1,7 @@ +// 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 extendedbigdecimal extendedbigint bigdecimal numberparse //! Parsing numbers for use in `seq`. //! @@ -69,30 +73,13 @@ fn parse_no_decimal_no_exponent(s: &str) -> Result { // 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) - } + let float_val = match s.to_ascii_lowercase().as_str() { + "inf" | "infinity" => ExtendedBigDecimal::Infinity, + "-inf" | "-infinity" => ExtendedBigDecimal::MinusInfinity, + "nan" | "-nan" => return Err(ParseNumberError::Nan), + _ => return Err(ParseNumberError::Float), + }; + Ok(PreciseNumber::new(Number::Float(float_val), 0, 0)) } } } @@ -479,11 +466,23 @@ mod tests { #[test] fn test_parse_inf() { assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity)); + assert_eq!( + parse("infinity"), + Number::Float(ExtendedBigDecimal::Infinity) + ); assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity)); + assert_eq!( + parse("+infinity"), + Number::Float(ExtendedBigDecimal::Infinity) + ); assert_eq!( parse("-inf"), Number::Float(ExtendedBigDecimal::MinusInfinity) ); + assert_eq!( + parse("-infinity"), + Number::Float(ExtendedBigDecimal::MinusInfinity) + ); } #[test] @@ -539,6 +538,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_num_integral_digits() { // no decimal, no exponent assert_eq!(num_integral_digits("123"), 3); @@ -579,6 +579,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_num_fractional_digits() { // no decimal, no exponent assert_eq!(num_fractional_digits("123"), 0); diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 0dd65fc3f..f93ced926 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse use std::io::{stdout, ErrorKind, Write}; @@ -203,7 +203,6 @@ fn write_value_float( value: &ExtendedBigDecimal, width: usize, precision: usize, - _is_first_iteration: bool, ) -> std::io::Result<()> { let value_as_str = if *value == ExtendedBigDecimal::Infinity || *value == ExtendedBigDecimal::MinusInfinity { @@ -220,16 +219,13 @@ fn write_value_int( value: &ExtendedBigInt, width: usize, pad: bool, - is_first_iteration: bool, ) -> std::io::Result<()> { let value_as_str = if pad { - if *value == ExtendedBigInt::MinusZero && is_first_iteration { - format!("-{value:>0width$}", width = width - 1) + if *value == ExtendedBigInt::MinusZero { + format!("{value:00width$}") } - } else if *value == ExtendedBigInt::MinusZero && is_first_iteration { - format!("-{value}") } else { format!("{value}") }; @@ -276,13 +272,7 @@ fn print_seq( let s = format!("{value}"); printf(f, &[s])?; } - None => write_value_float( - &mut stdout, - &value, - padding, - largest_dec, - is_first_iteration, - )?, + None => write_value_float(&mut stdout, &value, padding, largest_dec)?, } // TODO Implement augmenting addition. value = value + increment.clone(); @@ -338,7 +328,7 @@ fn print_seq_integers( let s = format!("{value}"); printf(f, &[s])?; } - None => write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?, + None => write_value_int(&mut stdout, &value, padding, pad)?, } // TODO Implement augmenting addition. value = value + increment.clone(); diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index 81e49dc00..0e7ad2ad3 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shred" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "shred ~ (uutils) hide former FILE contents with repeated overwrites" diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index fd14a3245..eb63f0e5f 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Michael Rosenberg <42micro@gmail.com> -// * (c) Fort -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (words) wipesync prefill @@ -19,7 +16,7 @@ use std::os::unix::prelude::PermissionsExt; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::parse_size::parse_size; +use uucore::parse_size::parse_size_u64; use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_if_err}; const ABOUT: &str = help_about!("shred.md"); @@ -322,7 +319,7 @@ pub fn uu_app() -> Command { fn get_size(size_str_opt: Option) -> Option { size_str_opt .as_ref() - .and_then(|size| parse_size(size.as_str()).ok()) + .and_then(|size| parse_size_u64(size.as_str()).ok()) .or_else(|| { if let Some(size) = size_str_opt { show_error!("invalid file size: {}", size.quote()); diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index 281e51d9e..6a86c6d98 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shuf" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "shuf ~ (uutils) display random permutations of input lines" diff --git a/src/uu/shuf/src/rand_read_adapter.rs b/src/uu/shuf/src/rand_read_adapter.rs index 9cf0ee8a4..728bc0cfb 100644 --- a/src/uu/shuf/src/rand_read_adapter.rs +++ b/src/uu/shuf/src/rand_read_adapter.rs @@ -1,3 +1,7 @@ +// 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. // Copyright 2018 Developers of the Rand project. // Copyright 2013 The Rust Project Developers. // diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 2481baf3d..1b21b9532 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) cmdline evec seps rvec fdata diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index f69678770..6ca686c78 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sleep" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "sleep ~ (uutils) pause for DURATION" diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 8acb7724f..b1d6bd899 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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::thread; use std::time::Duration; diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index eb9cb9b59..0cbafc5cf 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sort" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "sort ~ (uutils) sort input lines" @@ -27,7 +27,7 @@ rayon = { workspace = true } self_cell = { workspace = true } tempfile = { workspace = true } unicode-width = { workspace = true } -uucore = { workspace = true, features = ["fs"] } +uucore = { workspace = true, features = ["fs", "version-cmp"] } [[bin]] name = "sort" diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs index 3f02a4c31..763b6deb7 100644 --- a/src/uu/sort/src/check.rs +++ b/src/uu/sort/src/check.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Michael Debertol -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. //! Check if a file is ordered @@ -54,7 +52,7 @@ pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> { let mut prev_chunk: Option = None; let mut line_idx = 0; - for chunk in loaded_receiver.iter() { + for chunk in loaded_receiver { line_idx += 1; if let Some(prev_chunk) = prev_chunk.take() { // Check if the first element of the new chunk is greater than the last @@ -107,7 +105,7 @@ fn reader( settings: &GlobalSettings, ) -> UResult<()> { let mut carry_over = vec![]; - for recycled_chunk in receiver.iter() { + for recycled_chunk in receiver { let should_continue = chunks::read( sender, recycled_chunk, @@ -115,11 +113,7 @@ fn reader( &mut carry_over, &mut file, &mut iter::empty(), - if settings.zero_terminated { - b'\0' - } else { - b'\n' - }, + settings.line_ending.into(), settings, )?; if !should_continue { diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index ffee7e453..525a9f66b 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Michael Debertol -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. //! Utilities for reading files as chunks. diff --git a/src/uu/sort/src/custom_str_cmp.rs b/src/uu/sort/src/custom_str_cmp.rs index 089d33bc4..fb128d9af 100644 --- a/src/uu/sort/src/custom_str_cmp.rs +++ b/src/uu/sort/src/custom_str_cmp.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Michael Debertol -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. //! Custom string comparisons. //! diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 27cb12d0b..183098812 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Michael Debertol -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. //! Sort big files by using auxiliary files for storing intermediate chunks. //! @@ -84,11 +82,7 @@ fn reader_writer< output: Output, tmp_dir: &mut TmpDirWrapper, ) -> UResult<()> { - let separator = if settings.zero_terminated { - b'\0' - } else { - b'\n' - }; + let separator = settings.line_ending.into(); // Heuristically chosen: Dividing by 10 seems to keep our memory usage roughly // around settings.buffer_size as a whole. @@ -230,7 +224,7 @@ fn read_write_loop( let mut sender_option = Some(sender); let mut tmp_files = vec![]; loop { - let mut chunk = match receiver.recv() { + let chunk = match receiver.recv() { Ok(it) => it, _ => { return Ok(ReadResult::WroteChunksToFile { tmp_files }); @@ -238,7 +232,7 @@ fn read_write_loop( }; let tmp_file = write::( - &mut chunk, + &chunk, tmp_dir.next_file()?, settings.compress_prog.as_deref(), separator, @@ -268,7 +262,7 @@ fn read_write_loop( /// Write the lines in `chunk` to `file`, separated by `separator`. /// `compress_prog` is used to optionally compress file contents. fn write( - chunk: &mut Chunk, + chunk: &Chunk, file: (File, PathBuf), compress_prog: Option<&str>, separator: u8, diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index 7c682d88f..c0457ffa4 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -1,3 +1,7 @@ +// 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. //! Merge already sorted files. //! //! We achieve performance by splitting the tasks of sorting and writing, and reading and parsing between two threads. @@ -169,11 +173,7 @@ fn merge_without_limit>>( &request_receiver, &mut reader_files, &settings, - if settings.zero_terminated { - b'\0' - } else { - b'\n' - }, + settings.line_ending.into(), ) } }); @@ -215,7 +215,7 @@ fn reader( settings: &GlobalSettings, separator: u8, ) -> UResult<()> { - for (file_idx, recycled_chunk) in recycled_receiver.iter() { + for (file_idx, recycled_chunk) in recycled_receiver { if let Some(ReaderFile { file, sender, diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index 1a986ea76..661f536a3 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Michael Debertol -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. //! Fast comparison for strings representing a base 10 number without precision loss. //! diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 80c2275e9..4e6e84187 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1,11 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Michael Yin -// * (c) Robert Swinford -// * (c) Michael Debertol -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. // Although these links don't always seem to describe reality, check out the POSIX and GNU specs: // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sort.html @@ -45,6 +41,7 @@ use std::str::Utf8Error; use unicode_width::UnicodeWidthStr; use uucore::display::Quotable; use uucore::error::{set_exit_code, strip_errno, UError, UResult, USimpleError, UUsageError}; +use uucore::line_ending::LineEnding; use uucore::parse_size::{ParseSizeError, Parser}; use uucore::version_cmp::version_cmp; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -306,7 +303,7 @@ pub struct GlobalSettings { selectors: Vec, separator: Option, threads: String, - zero_terminated: bool, + line_ending: LineEnding, buffer_size: usize, compress_prog: Option, merge_batch_size: usize, @@ -383,7 +380,7 @@ impl Default for GlobalSettings { selectors: vec![], separator: None, threads: String::new(), - zero_terminated: false, + line_ending: LineEnding::Newline, buffer_size: DEFAULT_BUF_SIZE, compress_prog: None, merge_batch_size: 32, @@ -526,14 +523,11 @@ impl<'a> Line<'a> { } fn print(&self, writer: &mut impl Write, settings: &GlobalSettings) { - if settings.zero_terminated && !settings.debug { - writer.write_all(self.line.as_bytes()).unwrap(); - writer.write_all(b"\0").unwrap(); - } else if !settings.debug { - writer.write_all(self.line.as_bytes()).unwrap(); - writer.write_all(b"\n").unwrap(); - } else { + if settings.debug { self.print_debug(settings, writer).unwrap(); + } else { + writer.write_all(self.line.as_bytes()).unwrap(); + writer.write_all(&[settings.line_ending.into()]).unwrap(); } } @@ -1169,7 +1163,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { })?; } - settings.zero_terminated = matches.get_flag(options::ZERO_TERMINATED); + settings.line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); settings.merge = matches.get_flag(options::MERGE); settings.check = matches.contains_id(options::check::CHECK); diff --git a/src/uu/sort/src/tmp_dir.rs b/src/uu/sort/src/tmp_dir.rs index ff14442b5..14e17a0d6 100644 --- a/src/uu/sort/src/tmp_dir.rs +++ b/src/uu/sort/src/tmp_dir.rs @@ -1,3 +1,7 @@ +// 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::{ fs::File, path::{Path, PathBuf}, diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index 881e53e26..c5a348473 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_split" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "split ~ (uutils) split input into output files" diff --git a/src/uu/split/split.md b/src/uu/split/split.md index d3a481fd3..836e3a0c6 100644 --- a/src/uu/split/split.md +++ b/src/uu/split/split.md @@ -11,3 +11,16 @@ Create output files containing consecutive or interleaved sections of input ## After Help Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is -, read standard input. + +The SIZE argument is an integer and optional unit (example: 10K is 10*1024). +Units are K,M,G,T,P,E,Z,Y,R,Q (powers of 1024) or KB,MB,... (powers of 1000). +Binary prefixes can be used, too: KiB=K, MiB=M, and so on. + +CHUNKS may be: + +- N split into N files based on size of input +- K/N output Kth of N to stdout +- l/N split into N files without splitting lines/records +- l/K/N output Kth of N to stdout without splitting lines/records +- r/N like 'l' but use round robin distribution +- r/K/N likewise but only output Kth of N to stdout diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index 6bec4105f..e6a9f19b2 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 //! Compute filenames from a given index. //! @@ -121,9 +121,10 @@ impl<'a> FilenameIterator<'a> { suffix_length: usize, suffix_type: SuffixType, suffix_start: usize, + suffix_auto_widening: bool, ) -> UResult> { let radix = suffix_type.radix(); - let number = if suffix_length == 0 { + let number = if suffix_auto_widening { Number::DynamicWidth(DynamicWidthNumber::new(radix, suffix_start)) } else { Number::FixedWidth( @@ -171,36 +172,42 @@ mod tests { #[test] fn test_filename_iterator_alphabetic_fixed_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, false).unwrap(); assert_eq!(it.next().unwrap(), "chunk_aa.txt"); assert_eq!(it.next().unwrap(), "chunk_ab.txt"); assert_eq!(it.next().unwrap(), "chunk_ac.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, false).unwrap(); assert_eq!(it.nth(26 * 26 - 1).unwrap(), "chunk_zz.txt"); assert_eq!(it.next(), None); } #[test] fn test_filename_iterator_numeric_fixed_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, false).unwrap(); assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, false).unwrap(); assert_eq!(it.nth(10 * 10 - 1).unwrap(), "chunk_99.txt"); assert_eq!(it.next(), None); } #[test] fn test_filename_iterator_alphabetic_dynamic_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, true).unwrap(); assert_eq!(it.next().unwrap(), "chunk_aa.txt"); assert_eq!(it.next().unwrap(), "chunk_ab.txt"); assert_eq!(it.next().unwrap(), "chunk_ac.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, true).unwrap(); assert_eq!(it.nth(26 * 25 - 1).unwrap(), "chunk_yz.txt"); assert_eq!(it.next().unwrap(), "chunk_zaaa.txt"); assert_eq!(it.next().unwrap(), "chunk_zaab.txt"); @@ -208,12 +215,14 @@ mod tests { #[test] fn test_filename_iterator_numeric_dynamic_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, true).unwrap(); assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, true).unwrap(); assert_eq!(it.nth(10 * 9 - 1).unwrap(), "chunk_89.txt"); assert_eq!(it.next().unwrap(), "chunk_9000.txt"); assert_eq!(it.next().unwrap(), "chunk_9001.txt"); @@ -221,7 +230,8 @@ mod tests { #[test] fn test_filename_iterator_numeric_suffix_decimal() { - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 5).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 5, true).unwrap(); assert_eq!(it.next().unwrap(), "chunk_05.txt"); assert_eq!(it.next().unwrap(), "chunk_06.txt"); assert_eq!(it.next().unwrap(), "chunk_07.txt"); @@ -230,7 +240,7 @@ mod tests { #[test] fn test_filename_iterator_numeric_suffix_hex() { let mut it = - FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Hexadecimal, 9).unwrap(); + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Hexadecimal, 9, true).unwrap(); assert_eq!(it.next().unwrap(), "chunk_09.txt"); assert_eq!(it.next().unwrap(), "chunk_0a.txt"); assert_eq!(it.next().unwrap(), "chunk_0b.txt"); @@ -238,19 +248,21 @@ mod tests { #[test] fn test_filename_iterator_numeric_suffix_err() { - let mut it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 999).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 999, false).unwrap(); assert_eq!(it.next().unwrap(), "chunk_999.txt"); assert!(it.next().is_none()); - let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 1000); + let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 1000, false); assert!(it.is_err()); let mut it = - FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0xfff).unwrap(); + FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0xfff, false) + .unwrap(); assert_eq!(it.next().unwrap(), "chunk_fff.txt"); assert!(it.next().is_none()); - let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0x1000); + let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0x1000, false); assert!(it.is_err()); } } diff --git a/src/uu/split/src/number.rs b/src/uu/split/src/number.rs index 567526538..a01701c80 100644 --- a/src/uu/split/src/number.rs +++ b/src/uu/split/src/number.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 //! A number in arbitrary radix expressed in a positional notation. //! @@ -398,6 +398,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_dynamic_width_number_display_alphabetic() { fn num(n: usize) -> Number { let mut number = Number::DynamicWidth(DynamicWidthNumber::new(26, 0)); @@ -443,6 +444,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_dynamic_width_number_display_numeric_hexadecimal() { fn num(n: usize) -> Number { let mut number = Number::DynamicWidth(DynamicWidthNumber::new(16, 0)); @@ -467,6 +469,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_fixed_width_number_increment() { let mut n = Number::FixedWidth(FixedWidthNumber::new(3, 2, 0).unwrap()); assert_eq!(n.digits(), vec![0, 0]); @@ -490,6 +493,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_fixed_width_number_display_alphabetic() { fn num(n: usize) -> Result { let mut number = Number::FixedWidth(FixedWidthNumber::new(26, 2, 0).unwrap()); diff --git a/src/uu/split/src/platform/mod.rs b/src/uu/split/src/platform/mod.rs index cf5d8d384..5afc43eeb 100644 --- a/src/uu/split/src/platform/mod.rs +++ b/src/uu/split/src/platform/mod.rs @@ -1,3 +1,7 @@ +// 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. #[cfg(unix)] pub use self::unix::instantiate_current_writer; #[cfg(unix)] diff --git a/src/uu/split/src/platform/unix.rs b/src/uu/split/src/platform/unix.rs index fedd66dc8..f4adb8188 100644 --- a/src/uu/split/src/platform/unix.rs +++ b/src/uu/split/src/platform/unix.rs @@ -1,3 +1,7 @@ +// 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::env; use std::io::Write; use std::io::{BufWriter, Error, ErrorKind, Result}; diff --git a/src/uu/split/src/platform/windows.rs b/src/uu/split/src/platform/windows.rs index a2711fd3a..8b9078989 100644 --- a/src/uu/split/src/platform/windows.rs +++ b/src/uu/split/src/platform/windows.rs @@ -1,3 +1,7 @@ +// 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::io::Write; use std::io::{BufWriter, Error, ErrorKind, Result}; use std::path::Path; diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index f1be0c47d..fff1ccb65 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -1,11 +1,9 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Akira Hayakawa -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 nbbbb ncccc +// spell-checker:ignore nbbbb ncccc hexdigit mod filenames; mod number; @@ -13,17 +11,18 @@ mod platform; use crate::filenames::FilenameIterator; use crate::filenames::SuffixType; -use clap::ArgAction; -use clap::{crate_version, parser::ValueSource, Arg, ArgMatches, Command}; +use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command, ValueHint}; use std::env; +use std::ffi::OsString; use std::fmt; use std::fs::{metadata, File}; use std::io; use std::io::{stdin, BufRead, BufReader, BufWriter, ErrorKind, Read, Write}; use std::path::Path; +use std::u64; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, parse_size_u64_max, ParseSizeError}; use uucore::uio_error; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -34,10 +33,14 @@ static OPT_ADDITIONAL_SUFFIX: &str = "additional-suffix"; static OPT_FILTER: &str = "filter"; static OPT_NUMBER: &str = "number"; static OPT_NUMERIC_SUFFIXES: &str = "numeric-suffixes"; +static OPT_NUMERIC_SUFFIXES_SHORT: &str = "-d"; static OPT_HEX_SUFFIXES: &str = "hex-suffixes"; +static OPT_HEX_SUFFIXES_SHORT: &str = "-x"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; -static OPT_DEFAULT_SUFFIX_LENGTH: &str = "0"; +// If no suffix length is specified, default to "2" characters following GNU split behavior +static OPT_DEFAULT_SUFFIX_LENGTH: &str = "2"; static OPT_VERBOSE: &str = "verbose"; +static OPT_SEPARATOR: &str = "separator"; //The ---io and ---io-blksize parameters are consumed and ignored. //The parameter is included to make GNU coreutils tests pass. static OPT_IO: &str = "-io"; @@ -53,14 +56,180 @@ const AFTER_HELP: &str = help_section!("after help", "split.md"); #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let (args, obs_lines) = handle_obsolete(args); let matches = uu_app().try_get_matches_from(args)?; - match Settings::from(&matches) { + + match Settings::from(&matches, &obs_lines) { Ok(settings) => split(&settings), Err(e) if e.requires_usage() => Err(UUsageError::new(1, format!("{e}"))), Err(e) => Err(USimpleError::new(1, format!("{e}"))), } } +/// Extract obsolete shorthand (if any) for specifying lines in following scenarios (and similar) +/// `split -22 file` would mean `split -l 22 file` +/// `split -2de file` would mean `split -l 2 -d -e file` +/// `split -x300e file` would mean `split -x -l 300 -e file` +/// `split -x300e -22 file` would mean `split -x -e -l 22 file` (last obsolete lines option wins) +/// following GNU `split` behavior +fn handle_obsolete(args: impl uucore::Args) -> (Vec, Option) { + let mut obs_lines = None; + let mut preceding_long_opt_req_value = false; + let mut preceding_short_opt_req_value = false; + + let filtered_args = args + .filter_map(|os_slice| { + filter_args( + os_slice, + &mut obs_lines, + &mut preceding_long_opt_req_value, + &mut preceding_short_opt_req_value, + ) + }) + .collect(); + + (filtered_args, obs_lines) +} + +/// Helper function to [`handle_obsolete`] +/// Filters out obsolete lines option from args +fn filter_args( + os_slice: OsString, + obs_lines: &mut Option, + preceding_long_opt_req_value: &mut bool, + preceding_short_opt_req_value: &mut bool, +) -> Option { + let filter: Option; + if let Some(slice) = os_slice.to_str() { + if should_extract_obs_lines( + slice, + preceding_long_opt_req_value, + preceding_short_opt_req_value, + ) { + // start of the short option string + // that can have obsolete lines option value in it + filter = handle_extract_obs_lines(slice, obs_lines); + } else { + // either not a short option + // or a short option that cannot have obsolete lines value in it + filter = Some(OsString::from(slice)); + } + handle_preceding_options( + slice, + preceding_long_opt_req_value, + preceding_short_opt_req_value, + ); + } else { + // Cannot cleanly convert os_slice to UTF-8 + // Do not process and return as-is + // This will cause failure later on, but we should not handle it here + // and let clap panic on invalid UTF-8 argument + filter = Some(os_slice); + } + filter +} + +/// Helper function to [`filter_args`] +/// Checks if the slice is a true short option (and not hyphen prefixed value of an option) +/// and if so, a short option that can contain obsolete lines value +fn should_extract_obs_lines( + slice: &str, + preceding_long_opt_req_value: &bool, + preceding_short_opt_req_value: &bool, +) -> bool { + slice.starts_with('-') + && !slice.starts_with("--") + && !preceding_long_opt_req_value + && !preceding_short_opt_req_value + && !slice.starts_with("-a") + && !slice.starts_with("-b") + && !slice.starts_with("-C") + && !slice.starts_with("-l") + && !slice.starts_with("-n") + && !slice.starts_with("-t") +} + +/// Helper function to [`filter_args`] +/// Extracts obsolete lines numeric part from argument slice +/// and filters it out +fn handle_extract_obs_lines(slice: &str, obs_lines: &mut Option) -> Option { + let mut obs_lines_extracted: Vec = vec![]; + let mut obs_lines_end_reached = false; + let filtered_slice: Vec = slice + .chars() + .filter(|c| { + // To correctly process scenario like '-x200a4' + // we need to stop extracting digits once alphabetic character is encountered + // after we already have something in obs_lines_extracted + if c.is_ascii_digit() && !obs_lines_end_reached { + obs_lines_extracted.push(*c); + false + } else { + if !obs_lines_extracted.is_empty() { + obs_lines_end_reached = true; + } + true + } + }) + .collect(); + + if obs_lines_extracted.is_empty() { + // no obsolete lines value found/extracted + Some(OsString::from(slice)) + } else { + // obsolete lines value was extracted + let extracted: String = obs_lines_extracted.iter().collect(); + *obs_lines = Some(extracted); + if filtered_slice.get(1).is_some() { + // there were some short options in front of or after obsolete lines value + // i.e. '-xd100' or '-100de' or similar, which after extraction of obsolete lines value + // would look like '-xd' or '-de' or similar + let filtered_slice: String = filtered_slice.iter().collect(); + Some(OsString::from(filtered_slice)) + } else { + None + } + } +} + +/// Helper function to [`handle_extract_obs_lines`] +/// Captures if current slice is a preceding option +/// that requires value +fn handle_preceding_options( + slice: &str, + preceding_long_opt_req_value: &mut bool, + preceding_short_opt_req_value: &mut bool, +) { + // capture if current slice is a preceding long option that requires value and does not use '=' to assign that value + // following slice should be treaded as value for this option + // even if it starts with '-' (which would be treated as hyphen prefixed value) + if slice.starts_with("--") { + *preceding_long_opt_req_value = &slice[2..] == OPT_BYTES + || &slice[2..] == OPT_LINE_BYTES + || &slice[2..] == OPT_LINES + || &slice[2..] == OPT_ADDITIONAL_SUFFIX + || &slice[2..] == OPT_FILTER + || &slice[2..] == OPT_NUMBER + || &slice[2..] == OPT_SUFFIX_LENGTH + || &slice[2..] == OPT_SEPARATOR; + } + // capture if current slice is a preceding short option that requires value and does not have value in the same slice (value separated by whitespace) + // following slice should be treaded as value for this option + // even if it starts with '-' (which would be treated as hyphen prefixed value) + *preceding_short_opt_req_value = slice == "-b" + || slice == "-C" + || slice == "-l" + || slice == "-n" + || slice == "-a" + || slice == "-t"; + // slice is a value + // reset preceding option flags + if !slice.starts_with('-') { + *preceding_short_opt_req_value = false; + *preceding_long_opt_req_value = false; + } +} + pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) @@ -73,6 +242,7 @@ pub fn uu_app() -> Command { Arg::new(OPT_BYTES) .short('b') .long(OPT_BYTES) + .allow_hyphen_values(true) .value_name("SIZE") .help("put SIZE bytes per output file"), ) @@ -80,14 +250,15 @@ pub fn uu_app() -> Command { Arg::new(OPT_LINE_BYTES) .short('C') .long(OPT_LINE_BYTES) + .allow_hyphen_values(true) .value_name("SIZE") - .default_value("2") .help("put at most SIZE bytes of lines per output file"), ) .arg( Arg::new(OPT_LINES) .short('l') .long(OPT_LINES) + .allow_hyphen_values(true) .value_name("NUMBER") .default_value("1000") .help("put NUMBER lines/records per output file"), @@ -96,6 +267,7 @@ pub fn uu_app() -> Command { Arg::new(OPT_NUMBER) .short('n') .long(OPT_NUMBER) + .allow_hyphen_values(true) .value_name("CHUNKS") .help("generate CHUNKS output files; see explanation below"), ) @@ -103,6 +275,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_ADDITIONAL_SUFFIX) .long(OPT_ADDITIONAL_SUFFIX) + .allow_hyphen_values(true) .value_name("SUFFIX") .default_value("") .help("additional SUFFIX to append to output file names"), @@ -110,8 +283,9 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_FILTER) .long(OPT_FILTER) + .allow_hyphen_values(true) .value_name("COMMAND") - .value_hint(clap::ValueHint::CommandName) + .value_hint(ValueHint::CommandName) .help( "write to shell COMMAND; file name is $FILE (Currently not implemented for Windows)", ), @@ -124,28 +298,67 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_NUMERIC_SUFFIXES) + Arg::new(OPT_NUMERIC_SUFFIXES_SHORT) .short('d') + .action(ArgAction::SetTrue) + .overrides_with_all([ + OPT_NUMERIC_SUFFIXES, + OPT_NUMERIC_SUFFIXES_SHORT, + OPT_HEX_SUFFIXES, + OPT_HEX_SUFFIXES_SHORT + ]) + .help("use numeric suffixes starting at 0, not alphabetic"), + ) + .arg( + Arg::new(OPT_NUMERIC_SUFFIXES) .long(OPT_NUMERIC_SUFFIXES) - .default_missing_value("0") + .alias("numeric") + .require_equals(true) .num_args(0..=1) - .help("use numeric suffixes instead of alphabetic"), + .overrides_with_all([ + OPT_NUMERIC_SUFFIXES, + OPT_NUMERIC_SUFFIXES_SHORT, + OPT_HEX_SUFFIXES, + OPT_HEX_SUFFIXES_SHORT + ]) + .value_name("FROM") + .help("same as -d, but allow setting the start value"), + ) + .arg( + Arg::new(OPT_HEX_SUFFIXES_SHORT) + .short('x') + .action(ArgAction::SetTrue) + .overrides_with_all([ + OPT_NUMERIC_SUFFIXES, + OPT_NUMERIC_SUFFIXES_SHORT, + OPT_HEX_SUFFIXES, + OPT_HEX_SUFFIXES_SHORT + ]) + .help("use hex suffixes starting at 0, not alphabetic"), + ) + .arg( + Arg::new(OPT_HEX_SUFFIXES) + .long(OPT_HEX_SUFFIXES) + .alias("hex") + .require_equals(true) + .num_args(0..=1) + .overrides_with_all([ + OPT_NUMERIC_SUFFIXES, + OPT_NUMERIC_SUFFIXES_SHORT, + OPT_HEX_SUFFIXES, + OPT_HEX_SUFFIXES_SHORT + ]) + .value_name("FROM") + .help("same as -x, but allow setting the start value"), ) .arg( Arg::new(OPT_SUFFIX_LENGTH) .short('a') .long(OPT_SUFFIX_LENGTH) + .allow_hyphen_values(true) .value_name("N") .default_value(OPT_DEFAULT_SUFFIX_LENGTH) - .help("use suffixes of fixed length N. 0 implies dynamic length."), - ) - .arg( - Arg::new(OPT_HEX_SUFFIXES) - .short('x') - .long(OPT_HEX_SUFFIXES) - .default_missing_value("0") - .num_args(0..=1) - .help("use hex suffixes instead of alphabetic"), + .help("generate suffixes of length N (default 2)"), ) .arg( Arg::new(OPT_VERBOSE) @@ -153,6 +366,15 @@ pub fn uu_app() -> Command { .help("print a diagnostic just before each output file is opened") .action(ArgAction::SetTrue), ) + .arg( + Arg::new(OPT_SEPARATOR) + .short('t') + .long(OPT_SEPARATOR) + .allow_hyphen_values(true) + .value_name("SEP") + .action(ArgAction::Append) + .help("use SEP instead of newline as the record separator; '\\0' (zero) specifies the NUL character"), + ) .arg( Arg::new(OPT_IO) .long("io") @@ -168,7 +390,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(ARG_INPUT) .default_value("-") - .value_hint(clap::ValueHint::FilePath), + .value_hint(ValueHint::FilePath), ) .arg( Arg::new(ARG_PREFIX) @@ -182,6 +404,10 @@ enum NumberType { /// Split into a specific number of chunks by byte. Bytes(u64), + /// Split into a specific number of chunks by byte + /// but output only the *k*th chunk. + KthBytes(u64, u64), + /// Split into a specific number of chunks by line (approximately). Lines(u64), @@ -202,6 +428,7 @@ impl NumberType { fn num_chunks(&self) -> u64 { match self { Self::Bytes(n) => *n, + Self::KthBytes(_, n) => *n, Self::Lines(n) => *n, Self::KthLines(_, n) => *n, Self::RoundRobin(n) => *n, @@ -220,6 +447,7 @@ enum NumberTypeError { /// /// ```ignore /// -n N + /// -n K/N /// -n l/N /// -n l/K/N /// -n r/N @@ -230,9 +458,12 @@ enum NumberTypeError { /// The chunk number was invalid. /// /// This can happen if the value of `K` in any of the following - /// command-line options is not a positive integer: + /// command-line options is not a positive integer + /// or if `K` is 0 + /// or if `K` is greater than `N`: /// /// ```ignore + /// -n K/N /// -n l/K/N /// -n r/K/N /// ``` @@ -246,6 +477,7 @@ impl NumberType { /// /// ```ignore /// "N" + /// "K/N" /// "l/N" /// "l/K/N" /// "r/N" @@ -257,15 +489,20 @@ impl NumberType { /// /// # Errors /// - /// If the string is not one of the valid number types, if `K` is - /// not a nonnegative integer, or if `N` is not a positive - /// integer, then this function returns [`NumberTypeError`]. + /// If the string is not one of the valid number types, + /// if `K` is not a nonnegative integer, + /// or if `K` is 0, + /// or if `N` is not a positive integer, + /// or if `K` is greater than `N` + /// then this function returns [`NumberTypeError`]. fn from(s: &str) -> Result { + fn is_invalid_chunk(chunk_number: u64, num_chunks: u64) -> bool { + chunk_number > num_chunks || chunk_number == 0 + } let parts: Vec<&str> = s.split('/').collect(); match &parts[..] { [n_str] => { - let num_chunks = n_str - .parse() + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; if num_chunks > 0 { Ok(Self::Bytes(num_chunks)) @@ -273,34 +510,44 @@ impl NumberType { Err(NumberTypeError::NumberOfChunks(s.to_string())) } } + [k_str, n_str] if !k_str.starts_with('l') && !k_str.starts_with('r') => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + let chunk_number = parse_size_u64(k_str) + .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if is_invalid_chunk(chunk_number, num_chunks) { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } + Ok(Self::KthBytes(chunk_number, num_chunks)) + } ["l", n_str] => { - let num_chunks = n_str - .parse() + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; Ok(Self::Lines(num_chunks)) } ["l", k_str, n_str] => { - let num_chunks = n_str - .parse() + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = k_str - .parse() + let chunk_number = parse_size_u64(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if is_invalid_chunk(chunk_number, num_chunks) { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } Ok(Self::KthLines(chunk_number, num_chunks)) } ["r", n_str] => { - let num_chunks = n_str - .parse() + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; Ok(Self::RoundRobin(num_chunks)) } ["r", k_str, n_str] => { - let num_chunks = n_str - .parse() + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = k_str - .parse() + let chunk_number = parse_size_u64(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if is_invalid_chunk(chunk_number, num_chunks) { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } Ok(Self::KthRoundRobin(chunk_number, num_chunks)) } _ => Err(NumberTypeError::NumberOfChunks(s.to_string())), @@ -360,7 +607,7 @@ impl fmt::Display for StrategyError { impl Strategy { /// Parse a strategy from the command-line arguments. - fn from(matches: &ArgMatches) -> Result { + fn from(matches: &ArgMatches, obs_lines: &Option) -> Result { fn get_and_parse( matches: &ArgMatches, option: &str, @@ -368,7 +615,7 @@ impl Strategy { error: fn(ParseSizeError) -> StrategyError, ) -> Result { let s = matches.get_one::(option).unwrap(); - let n = parse_size(s).map_err(error)?; + let n = parse_size_u64_max(s).map_err(error)?; if n > 0 { Ok(strategy(n)) } else { @@ -378,28 +625,40 @@ impl Strategy { // 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". + // overrides_with_all() due to obsolete lines value option match ( + obs_lines, matches.value_source(OPT_LINES) == Some(ValueSource::CommandLine), matches.value_source(OPT_BYTES) == Some(ValueSource::CommandLine), matches.value_source(OPT_LINE_BYTES) == Some(ValueSource::CommandLine), matches.value_source(OPT_NUMBER) == Some(ValueSource::CommandLine), ) { - (false, false, false, false) => Ok(Self::Lines(1000)), - (true, false, false, false) => { + (Some(v), false, false, false, false) => { + let v = parse_size_u64_max(v).map_err(|_| { + StrategyError::Lines(ParseSizeError::ParseFailure(v.to_string())) + })?; + if v > 0 { + Ok(Self::Lines(v)) + } else { + Err(StrategyError::Lines(ParseSizeError::ParseFailure( + v.to_string(), + ))) + } + } + (None, false, false, false, false) => Ok(Self::Lines(1000)), + (None, true, false, false, false) => { get_and_parse(matches, OPT_LINES, Self::Lines, StrategyError::Lines) } - (false, true, false, false) => { + (None, false, true, false, false) => { get_and_parse(matches, OPT_BYTES, Self::Bytes, StrategyError::Bytes) } - (false, false, true, false) => get_and_parse( + (None, false, false, true, false) => get_and_parse( matches, OPT_LINE_BYTES, Self::LineBytes, StrategyError::Bytes, ), - (false, false, false, true) => { + (None, false, false, false, true) => { let s = matches.get_one::(OPT_NUMBER).unwrap(); let number_type = NumberType::from(s).map_err(StrategyError::NumberType)?; Ok(Self::Number(number_type)) @@ -409,25 +668,99 @@ impl Strategy { } } -/// Parse the suffix type from the command-line arguments. -fn suffix_type_from(matches: &ArgMatches) -> Result<(SuffixType, usize), SettingsError> { - if matches.value_source(OPT_NUMERIC_SUFFIXES) == Some(ValueSource::CommandLine) { - let suffix_start = matches.get_one::(OPT_NUMERIC_SUFFIXES); - let suffix_start = suffix_start.ok_or(SettingsError::SuffixNotParsable(String::new()))?; - let suffix_start = suffix_start - .parse() - .map_err(|_| SettingsError::SuffixNotParsable(suffix_start.to_string()))?; - Ok((SuffixType::Decimal, suffix_start)) - } else if matches.value_source(OPT_HEX_SUFFIXES) == Some(ValueSource::CommandLine) { - let suffix_start = matches.get_one::(OPT_HEX_SUFFIXES); - let suffix_start = suffix_start.ok_or(SettingsError::SuffixNotParsable(String::new()))?; - let suffix_start = usize::from_str_radix(suffix_start, 16) - .map_err(|_| SettingsError::SuffixNotParsable(suffix_start.to_string()))?; - Ok((SuffixType::Hexadecimal, suffix_start)) - } else { - // no numeric/hex suffix - Ok((SuffixType::Alphabetic, 0)) +/// Parse the suffix type, start and length from the command-line arguments. +/// Determine if the output file names suffix is allowed to auto-widen, +/// i.e. go beyond suffix_length, when more output files need to be written into. +/// Suffix auto-widening rules are: +/// - OFF when suffix length N is specified +/// `-a N` or `--suffix-length=N` +/// - OFF when suffix start number N is specified using long option with value +/// `--numeric-suffixes=N` or `--hex-suffixes=N` +/// - Exception to the above: ON with `-n`/`--number` option (N, K/N, l/N, l/K/N, r/N, r/K/N) +/// and suffix start < N number of files +/// - ON when suffix start number is NOT specified +fn suffix_from( + matches: &ArgMatches, + strategy: &Strategy, +) -> Result<(SuffixType, usize, bool, usize), SettingsError> { + let suffix_type: SuffixType; + + // Defaults + let mut suffix_start = 0; + let mut suffix_auto_widening = true; + + // Check if the user is specifying one or more than one suffix + // Any combination of suffixes is allowed + // Since all suffixes are setup with 'overrides_with_all()' against themselves and each other, + // last one wins, all others are ignored + match ( + matches.contains_id(OPT_NUMERIC_SUFFIXES), + matches.contains_id(OPT_HEX_SUFFIXES), + matches.get_flag(OPT_NUMERIC_SUFFIXES_SHORT), + matches.get_flag(OPT_HEX_SUFFIXES_SHORT), + ) { + (true, _, _, _) => { + suffix_type = SuffixType::Decimal; + let suffix_opt = matches.get_one::(OPT_NUMERIC_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value + if suffix_opt.is_some() { + (suffix_start, suffix_auto_widening) = + handle_long_suffix_opt(suffix_opt.unwrap(), strategy, false)?; + } + } + (_, true, _, _) => { + suffix_type = SuffixType::Hexadecimal; + let suffix_opt = matches.get_one::(OPT_HEX_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value + if suffix_opt.is_some() { + (suffix_start, suffix_auto_widening) = + handle_long_suffix_opt(suffix_opt.unwrap(), strategy, true)?; + } + } + (_, _, true, _) => suffix_type = SuffixType::Decimal, // short numeric suffix '-d' + (_, _, _, true) => suffix_type = SuffixType::Hexadecimal, // short hex suffix '-x' + _ => suffix_type = SuffixType::Alphabetic, // no numeric/hex suffix, using default alphabetic } + + let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap(); // safe to unwrap here as there is default value for this option + let suffix_length: usize = suffix_length_str + .parse() + .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?; + + // Override suffix_auto_widening if suffix length value came from command line + // and not from default value + if matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) { + suffix_auto_widening = false; + } + + Ok(( + suffix_type, + suffix_start, + suffix_auto_widening, + suffix_length, + )) +} + +/// Helper function to [`suffix_from`] function +fn handle_long_suffix_opt( + suffix_opt: &String, + strategy: &Strategy, + is_hex: bool, +) -> Result<(usize, bool), SettingsError> { + let suffix_start = if is_hex { + usize::from_str_radix(suffix_opt, 16) + .map_err(|_| SettingsError::SuffixNotParsable(suffix_opt.to_string()))? + } else { + suffix_opt + .parse::() + .map_err(|_| SettingsError::SuffixNotParsable(suffix_opt.to_string()))? + }; + + let suffix_auto_widening = if let Strategy::Number(ref number_type) = strategy { + let chunks = number_type.num_chunks(); + (suffix_start as u64) < chunks + } else { + false + }; + Ok((suffix_start, suffix_auto_widening)) } /// Parameters that control how a file gets split. @@ -439,12 +772,15 @@ struct Settings { suffix_type: SuffixType, suffix_length: usize, suffix_start: usize, + /// Whether or not suffix length should automatically widen + suffix_auto_widening: bool, additional_suffix: String, input: String, /// When supplied, a shell command to output to instead of xaa, xab … filter: Option, strategy: Strategy, verbose: bool, + separator: u8, /// Whether to *not* produce empty files when using `-n`. /// @@ -471,6 +807,18 @@ enum SettingsError { /// Suffix is not large enough to split into specified chunks SuffixTooSmall(usize), + /// Multi-character (Invalid) separator + MultiCharacterSeparator(String), + + /// Multiple different separator characters + MultipleSeparatorCharacters, + + /// Using `--filter` with `--number` option sub-strategies that print Kth chunk out of N chunks to stdout + /// K/N + /// l/K/N + /// r/K/N + FilterWithKthChunkNumber, + /// The `--filter` option is not supported on Windows. #[cfg(windows)] NotSupported, @@ -492,11 +840,20 @@ impl fmt::Display for SettingsError { Self::Strategy(e) => e.fmt(f), Self::SuffixNotParsable(s) => write!(f, "invalid suffix length: {}", s.quote()), Self::SuffixTooSmall(i) => write!(f, "the suffix length needs to be at least {i}"), + Self::MultiCharacterSeparator(s) => { + write!(f, "multi-character separator {}", s.quote()) + } + Self::MultipleSeparatorCharacters => { + write!(f, "multiple separator characters specified") + } Self::SuffixContainsSeparator(s) => write!( f, "invalid suffix {}, contains directory separator", s.quote() ), + Self::FilterWithKthChunkNumber => { + write!(f, "--filter does not process a chunk extracted to stdout") + } #[cfg(windows)] Self::NotSupported => write!( f, @@ -508,7 +865,7 @@ impl fmt::Display for SettingsError { impl Settings { /// Parse a strategy from the command-line arguments. - fn from(matches: &ArgMatches) -> Result { + fn from(matches: &ArgMatches, obs_lines: &Option) -> Result { let additional_suffix = matches .get_one::(OPT_ADDITIONAL_SUFFIX) .unwrap() @@ -516,15 +873,13 @@ impl Settings { if additional_suffix.contains('/') { return Err(SettingsError::SuffixContainsSeparator(additional_suffix)); } - let strategy = Strategy::from(matches).map_err(SettingsError::Strategy)?; - let (suffix_type, suffix_start) = suffix_type_from(matches)?; - let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap(); - let suffix_length: usize = suffix_length_str - .parse() - .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?; + let strategy = Strategy::from(matches, obs_lines).map_err(SettingsError::Strategy)?; + let (suffix_type, suffix_start, suffix_auto_widening, suffix_length) = + suffix_from(matches, &strategy)?; + if let Strategy::Number(ref number_type) = strategy { let chunks = number_type.num_chunks(); - if suffix_length != 0 { + if !suffix_auto_widening { let required_suffix_length = (chunks as f64).log(suffix_type.radix() as f64).ceil() as usize; if suffix_length < required_suffix_length { @@ -532,26 +887,60 @@ impl Settings { } } } + + // Make sure that separator is only one UTF8 character (if specified) + // defaults to '\n' - newline character + // If the same separator (the same value) was used multiple times - `split` should NOT fail + // If the separator was used multiple times but with different values (not all values are the same) - `split` should fail + let separator = match matches.get_many::(OPT_SEPARATOR) { + Some(mut sep_values) => { + let first = sep_values.next().unwrap(); // it is safe to just unwrap here since Clap should not return empty ValuesRef<'_,String> in the option from get_many() call + if !sep_values.all(|s| s == first) { + return Err(SettingsError::MultipleSeparatorCharacters); + } + match first.as_str() { + "\\0" => b'\0', + s if s.as_bytes().len() == 1 => s.as_bytes()[0], + s => return Err(SettingsError::MultiCharacterSeparator(s.to_owned())), + } + } + None => b'\n', + }; + let result = Self { - suffix_length: suffix_length_str - .parse() - .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?, + suffix_length, suffix_type, suffix_start, + suffix_auto_widening, additional_suffix, - verbose: matches.value_source("verbose") == Some(ValueSource::CommandLine), + verbose: matches.value_source(OPT_VERBOSE) == Some(ValueSource::CommandLine), + separator, strategy, input: matches.get_one::(ARG_INPUT).unwrap().to_owned(), prefix: matches.get_one::(ARG_PREFIX).unwrap().to_owned(), filter: matches.get_one::(OPT_FILTER).map(|s| s.to_owned()), elide_empty_files: matches.get_flag(OPT_ELIDE_EMPTY_FILES), }; + #[cfg(windows)] if result.filter.is_some() { // see https://github.com/rust-lang/rust/issues/29494 return Err(SettingsError::NotSupported); } + // Return an error if `--filter` option is used with any of the + // Kth chunk sub-strategies of `--number` option + // As those are writing to stdout of `split` and cannot write to filter command child process + let kth_chunk = matches!( + result.strategy, + Strategy::Number(NumberType::KthBytes(_, _)) + | Strategy::Number(NumberType::KthLines(_, _)) + | Strategy::Number(NumberType::KthRoundRobin(_, _)) + ); + if kth_chunk && result.filter.is_some() { + return Err(SettingsError::FilterWithKthChunkNumber); + } + Ok(result) } @@ -567,6 +956,47 @@ impl Settings { } } +/// When using `--filter` option, writing to child command process stdin +/// could fail with BrokenPipe error +/// It can be safely ignored +fn ignorable_io_error(error: &std::io::Error, settings: &Settings) -> bool { + error.kind() == ErrorKind::BrokenPipe && settings.filter.is_some() +} + +/// Custom wrapper for `write()` method +/// Follows similar approach to GNU implementation +/// If ignorable io error occurs, return number of bytes as if all bytes written +/// Should not be used for Kth chunk number sub-strategies +/// as those do not work with `--filter` option +fn custom_write( + bytes: &[u8], + writer: &mut T, + settings: &Settings, +) -> std::io::Result { + match writer.write(bytes) { + Ok(n) => Ok(n), + Err(e) if ignorable_io_error(&e, settings) => Ok(bytes.len()), + Err(e) => Err(e), + } +} + +/// Custom wrapper for `write_all()` method +/// Similar to [`custom_write`], but returns true or false +/// depending on if `--filter` stdin is still open (no BrokenPipe error) +/// Should not be used for Kth chunk number sub-strategies +/// as those do not work with `--filter` option +fn custom_write_all( + bytes: &[u8], + writer: &mut T, + settings: &Settings, +) -> std::io::Result { + match writer.write_all(bytes) { + Ok(()) => Ok(true), + Err(e) if ignorable_io_error(&e, settings) => Ok(false), + Err(e) => Err(e), + } +} + /// Write a certain number of bytes to one file, then move on to another one. /// /// This struct maintains an underlying writer representing the @@ -611,6 +1041,7 @@ impl<'a> ByteChunkWriter<'a> { settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, )?; let filename = filename_iterator .next() @@ -631,6 +1062,7 @@ impl<'a> ByteChunkWriter<'a> { } impl<'a> Write for ByteChunkWriter<'a> { + /// Implements `--bytes=SIZE` fn write(&mut self, mut buf: &[u8]) -> std::io::Result { // If the length of `buf` exceeds the number of bytes remaining // in the current chunk, we will need to write to multiple @@ -662,9 +1094,9 @@ impl<'a> Write for ByteChunkWriter<'a> { // bytes in `buf`, then write all the bytes in `buf`. Otherwise, // write enough bytes to fill the current chunk, then increment // the chunk number and repeat. - let n = buf.len(); - if (n as u64) < self.num_bytes_remaining_in_current_chunk { - let num_bytes_written = self.inner.write(buf)?; + let buf_len = buf.len(); + if (buf_len as u64) < self.num_bytes_remaining_in_current_chunk { + let num_bytes_written = custom_write(buf, &mut self.inner, self.settings)?; self.num_bytes_remaining_in_current_chunk -= num_bytes_written as u64; return Ok(carryover_bytes_written + num_bytes_written); } else { @@ -674,7 +1106,7 @@ impl<'a> Write for ByteChunkWriter<'a> { // self.num_bytes_remaining_in_current_chunk is lower than // n, which is already usize. let i = self.num_bytes_remaining_in_current_chunk as usize; - let num_bytes_written = self.inner.write(&buf[..i])?; + let num_bytes_written = custom_write(&buf[..i], &mut self.inner, self.settings)?; self.num_bytes_remaining_in_current_chunk -= num_bytes_written as u64; // It's possible that the underlying writer did not @@ -740,6 +1172,7 @@ impl<'a> LineChunkWriter<'a> { settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, )?; let filename = filename_iterator .next() @@ -760,6 +1193,7 @@ impl<'a> LineChunkWriter<'a> { } impl<'a> Write for LineChunkWriter<'a> { + /// Implements `--lines=NUMBER` fn write(&mut self, buf: &[u8]) -> std::io::Result { // If the number of lines in `buf` exceeds the number of lines // remaining in the current chunk, we will need to write to @@ -768,7 +1202,8 @@ impl<'a> Write for LineChunkWriter<'a> { // corresponds to the current chunk number. let mut prev = 0; let mut total_bytes_written = 0; - for i in memchr::memchr_iter(b'\n', buf) { + let sep = self.settings.separator; + for i in memchr::memchr_iter(sep, buf) { // If we have exceeded the number of lines to write in the // current chunk, then start a new chunk and its // corresponding writer. @@ -785,16 +1220,18 @@ impl<'a> Write for LineChunkWriter<'a> { } // Write the line, starting from *after* the previous - // newline character and ending *after* the current - // newline character. - let n = self.inner.write(&buf[prev..i + 1])?; - total_bytes_written += n; + // separator character and ending *after* the current + // separator character. + let num_bytes_written = + custom_write(&buf[prev..i + 1], &mut self.inner, self.settings)?; + total_bytes_written += num_bytes_written; prev = i + 1; self.num_lines_remaining_in_current_chunk -= 1; } - let n = self.inner.write(&buf[prev..buf.len()])?; - total_bytes_written += n; + let num_bytes_written = + custom_write(&buf[prev..buf.len()], &mut self.inner, self.settings)?; + total_bytes_written += num_bytes_written; Ok(total_bytes_written) } @@ -849,6 +1286,7 @@ impl<'a> LineBytesChunkWriter<'a> { settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, )?; let filename = filename_iterator .next() @@ -891,6 +1329,8 @@ impl<'a> Write for LineBytesChunkWriter<'a> { /// |------| |-------| |--------| |---| /// aaaaaaaa a\nbbbb\n cccc\ndd\n ee\n /// ``` + /// + /// Implements `--line-bytes=SIZE` fn write(&mut self, mut buf: &[u8]) -> std::io::Result { // The total number of bytes written during the loop below. // @@ -924,45 +1364,51 @@ impl<'a> Write for LineBytesChunkWriter<'a> { self.num_bytes_remaining_in_current_chunk = self.chunk_size.try_into().unwrap(); } - // Find the first newline character in the buffer. - match memchr::memchr(b'\n', buf) { - // If there is no newline character and the buffer is + // Find the first separator (default - newline character) in the buffer. + let sep = self.settings.separator; + match memchr::memchr(sep, buf) { + // If there is no separator character and the buffer is // not empty, then write as many bytes as we can and // then move on to the next chunk if necessary. None => { let end = self.num_bytes_remaining_in_current_chunk; // This is ugly but here to match GNU behavior. If the input - // doesn't end with a \n, pretend that it does for handling + // doesn't end with a separator, pretend that it does for handling // the second to last segment chunk. See `line-bytes.sh`. if end == buf.len() && self.num_bytes_remaining_in_current_chunk < self.chunk_size.try_into().unwrap() - && buf[buf.len() - 1] != b'\n' + && buf[buf.len() - 1] != sep { self.num_bytes_remaining_in_current_chunk = 0; } else { - let num_bytes_written = self.inner.write(&buf[..end.min(buf.len())])?; + let num_bytes_written = custom_write( + &buf[..end.min(buf.len())], + &mut self.inner, + self.settings, + )?; self.num_bytes_remaining_in_current_chunk -= num_bytes_written; total_bytes_written += num_bytes_written; buf = &buf[num_bytes_written..]; } } - // If there is a newline character and the line - // (including the newline character) will fit in the + // If there is a separator character and the line + // (including the separator character) will fit in the // current chunk, then write the entire line and // continue to the next iteration. (See chunk 1 in the // example comment above.) Some(i) if i < self.num_bytes_remaining_in_current_chunk => { - let num_bytes_written = self.inner.write(&buf[..i + 1])?; + let num_bytes_written = + custom_write(&buf[..i + 1], &mut self.inner, self.settings)?; self.num_bytes_remaining_in_current_chunk -= num_bytes_written; total_bytes_written += num_bytes_written; buf = &buf[num_bytes_written..]; } - // If there is a newline character, the line - // (including the newline character) will not fit in + // If there is a separator character, the line + // (including the separator character) will not fit in // the current chunk, *and* no other lines have been // written to the current chunk, then write as many // bytes as we can and continue to the next @@ -973,14 +1419,15 @@ impl<'a> Write for LineBytesChunkWriter<'a> { == self.chunk_size.try_into().unwrap() => { let end = self.num_bytes_remaining_in_current_chunk; - let num_bytes_written = self.inner.write(&buf[..end])?; + let num_bytes_written = + custom_write(&buf[..end], &mut self.inner, self.settings)?; self.num_bytes_remaining_in_current_chunk -= num_bytes_written; total_bytes_written += num_bytes_written; buf = &buf[num_bytes_written..]; } - // If there is a newline character, the line - // (including the newline character) will not fit in + // If there is a separator character, the line + // (including the separator character) will not fit in // the current chunk, and at least one other line has // been written to the current chunk, then signal to // the next iteration that a new chunk needs to be @@ -1009,6 +1456,10 @@ impl<'a> Write for LineBytesChunkWriter<'a> { /// /// This function returns an error if there is a problem reading from /// `reader` or writing to one of the output files. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * N fn split_into_n_chunks_by_byte( settings: &Settings, reader: &mut R, @@ -1044,7 +1495,7 @@ where // If we would have written zero chunks of output, then terminate // immediately. This happens on `split -e -n 3 /dev/null`, for // example. - if num_chunks == 0 { + if num_chunks == 0 || num_bytes == 0 { return Ok(()); } @@ -1059,6 +1510,7 @@ where settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, )?; // Create one writer for each chunk. This will create each @@ -1072,33 +1524,121 @@ where writers.push(writer); } - // Capture the result of the `std::io::copy()` calls to check for - // `BrokenPipe`. - let result: std::io::Result<()> = { - // Write `chunk_size` bytes from the reader into each writer - // except the last. - // - // The last writer gets all remaining bytes so that if the number - // of bytes in the input file was not evenly divisible by - // `num_chunks`, we don't leave any bytes behind. - for writer in writers.iter_mut().take(num_chunks - 1) { - io::copy(&mut reader.by_ref().take(chunk_size), writer)?; - } + // Write `chunk_size` bytes from the reader into each writer + // except the last. + // + // The last writer gets all remaining bytes so that if the number + // of bytes in the input file was not evenly divisible by + // `num_chunks`, we don't leave any bytes behind. + for writer in writers.iter_mut().take(num_chunks - 1) { + match io::copy(&mut reader.by_ref().take(chunk_size), writer) { + Ok(_) => continue, + Err(e) if ignorable_io_error(&e, settings) => continue, + Err(e) => return Err(uio_error!(e, "input/output error")), + }; + } - // Write all the remaining bytes to the last chunk. - let i = num_chunks - 1; - let last_chunk_size = num_bytes - (chunk_size * (num_chunks as u64 - 1)); - io::copy(&mut reader.by_ref().take(last_chunk_size), &mut writers[i])?; - - Ok(()) - }; - match result { + // Write all the remaining bytes to the last chunk. + let i = num_chunks - 1; + let last_chunk_size = num_bytes - (chunk_size * (num_chunks as u64 - 1)); + match io::copy(&mut reader.by_ref().take(last_chunk_size), &mut writers[i]) { Ok(_) => Ok(()), - Err(e) if e.kind() == ErrorKind::BrokenPipe => Ok(()), + Err(e) if ignorable_io_error(&e, settings) => Ok(()), Err(e) => Err(uio_error!(e, "input/output error")), } } +/// Print the k-th chunk of a file to stdout, splitting by byte. +/// +/// This function is like [`split_into_n_chunks_by_byte`], but instead +/// of writing each chunk to its own file, it only writes to stdout +/// the contents of the chunk identified by `chunk_number` +/// +/// # Errors +/// +/// This function returns an error if there is a problem reading from +/// `reader` or writing to stdout. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * K/N +fn kth_chunks_by_byte( + settings: &Settings, + reader: &mut R, + chunk_number: u64, + num_chunks: u64, +) -> UResult<()> +where + R: BufRead, +{ + // Get the size of the input file in bytes and compute the number + // of bytes per chunk. + // + // If the requested number of chunks exceeds the number of bytes + // in the file - just write empty byte string to stdout + // NOTE: the `elide_empty_files` parameter is ignored here + // as we do not generate any files + // and instead writing to stdout + let metadata = metadata(&settings.input).map_err(|_| { + USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) + })?; + + let num_bytes = metadata.len(); + // If input file is empty and we would have written zero chunks of output, + // then terminate immediately. + // This happens on `split -e -n 3 /dev/null`, for example. + if num_bytes == 0 { + return Ok(()); + } + + // Write to stdout instead of to a file. + let stdout = std::io::stdout(); + let mut writer = stdout.lock(); + + let chunk_size = (num_bytes / (num_chunks)).max(1); + let mut num_bytes: usize = num_bytes.try_into().unwrap(); + + let mut i = 1; + loop { + let buf: &mut Vec = &mut vec![]; + if num_bytes > 0 { + // Read `chunk_size` bytes from the reader into `buf` + // except the last. + // + // The last chunk gets all remaining bytes so that if the number + // of bytes in the input file was not evenly divisible by + // `num_chunks`, we don't leave any bytes behind. + let limit = { + if i == num_chunks { + num_bytes.try_into().unwrap() + } else { + chunk_size + } + }; + let n_bytes_read = reader.by_ref().take(limit).read_to_end(buf); + match n_bytes_read { + Ok(n_bytes) => { + num_bytes -= n_bytes; + } + Err(error) => { + return Err(USimpleError::new( + 1, + format!("{}: cannot read from input : {}", settings.input, error), + )); + } + } + if i == chunk_number { + writer.write_all(buf)?; + break; + } + i += 1; + } else { + break; + } + } + Ok(()) +} + /// Split a file into a specific number of chunks by line. /// /// This function always creates one output file for each chunk, even @@ -1115,6 +1655,10 @@ where /// /// * [`kth_chunk_by_line`], which splits its input in the same way, /// but writes only one specified chunk to stdout. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * l/N fn split_into_n_chunks_by_line( settings: &Settings, reader: &mut R, @@ -1125,7 +1669,9 @@ where { // Get the size of the input file in bytes and compute the number // of bytes per chunk. - let metadata = metadata(&settings.input).unwrap(); + let metadata = metadata(&settings.input).map_err(|_| { + USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) + })?; let num_bytes = metadata.len(); let chunk_size = (num_bytes / num_chunks) as usize; @@ -1136,6 +1682,7 @@ where settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, )?; // Create one writer for each chunk. This will create each @@ -1151,15 +1698,16 @@ where let mut num_bytes_remaining_in_current_chunk = chunk_size; let mut i = 0; - for line_result in reader.lines() { + let sep = settings.separator; + for line_result in reader.split(sep) { let line = line_result.unwrap(); let maybe_writer = writers.get_mut(i); let writer = maybe_writer.unwrap(); - let bytes = line.as_bytes(); - writer.write_all(bytes)?; - writer.write_all(b"\n")?; + let bytes = line.as_slice(); + custom_write_all(bytes, writer, settings)?; + custom_write_all(&[sep], writer, settings)?; - // Add one byte for the newline character. + // Add one byte for the separator character. let num_bytes = bytes.len() + 1; if num_bytes > num_bytes_remaining_in_current_chunk { num_bytes_remaining_in_current_chunk = chunk_size; @@ -1187,6 +1735,10 @@ where /// /// * [`split_into_n_chunks_by_line`], which splits its input in the /// same way, but writes each chunk to its own file. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * l/K/N fn kth_chunk_by_line( settings: &Settings, reader: &mut R, @@ -1198,7 +1750,9 @@ where { // Get the size of the input file in bytes and compute the number // of bytes per chunk. - let metadata = metadata(&settings.input).unwrap(); + let metadata = metadata(&settings.input).map_err(|_| { + USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) + })?; let num_bytes = metadata.len(); let chunk_size = (num_bytes / num_chunks) as usize; @@ -1207,16 +1761,17 @@ where let mut writer = stdout.lock(); let mut num_bytes_remaining_in_current_chunk = chunk_size; - let mut i = 0; - for line_result in reader.lines() { + let mut i = 1; + let sep = settings.separator; + for line_result in reader.split(sep) { let line = line_result?; - let bytes = line.as_bytes(); + let bytes = line.as_slice(); if i == chunk_number { writer.write_all(bytes)?; - writer.write_all(b"\n")?; + writer.write_all(&[sep])?; } - // Add one byte for the newline character. + // Add one byte for the separator character. let num_bytes = bytes.len() + 1; if num_bytes >= num_bytes_remaining_in_current_chunk { num_bytes_remaining_in_current_chunk = chunk_size; @@ -1233,6 +1788,27 @@ where Ok(()) } +/// Split a file into a specific number of chunks by line, but +/// assign lines via round-robin +/// +/// This function always creates one output file for each chunk, even +/// if there is an error reading or writing one of the chunks or if +/// the input file is truncated. However, if the `filter` option is +/// being used, then no files are created. +/// +/// # Errors +/// +/// This function returns an error if there is a problem reading from +/// `reader` or writing to one of the output files. +/// +/// # See also +/// +/// * [`split_into_n_chunks_by_line`], which splits its input in the same way, +/// but without round robin distribution. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * r/N fn split_into_n_chunks_by_line_round_robin( settings: &Settings, reader: &mut R, @@ -1248,7 +1824,9 @@ where settings.suffix_length, settings.suffix_type, settings.suffix_start, - )?; + settings.suffix_auto_widening, + ) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; // Create one writer for each chunk. This will create each // of the underlying files (if not in `--filter` mode). @@ -1256,24 +1834,87 @@ where for _ in 0..num_chunks { let filename = filename_iterator .next() - .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + .ok_or_else(|| io::Error::new(ErrorKind::Other, "output file suffixes exhausted"))?; let writer = settings.instantiate_current_writer(filename.as_str())?; writers.push(writer); } let num_chunks: usize = num_chunks.try_into().unwrap(); - for (i, line_result) in reader.lines().enumerate() { - let line = line_result.unwrap(); + let sep = settings.separator; + let mut closed_writers = 0; + for (i, line_result) in reader.split(sep).enumerate() { let maybe_writer = writers.get_mut(i % num_chunks); let writer = maybe_writer.unwrap(); - let bytes = line.as_bytes(); - writer.write_all(bytes)?; - writer.write_all(b"\n")?; + let mut line = line_result.unwrap(); + line.push(sep); + let bytes = line.as_slice(); + let writer_stdin_open = custom_write_all(bytes, writer, settings)?; + if !writer_stdin_open { + closed_writers += 1; + if closed_writers == num_chunks { + // all writers are closed - stop reading + break; + } + } } Ok(()) } +/// Print the k-th chunk of a file, splitting by line, but +/// assign lines via round-robin to the specified number of output +/// chunks, but output only the *k*th chunk. +/// +/// This function is like [`kth_chunk_by_line`], as it only writes to stdout and +/// prints out only *k*th chunk +/// It is also like [`split_into_n_chunks_by_line_round_robin`], as it is assigning chunks +/// using round robin distribution +/// +/// # Errors +/// +/// This function returns an error if there is a problem reading from +/// `reader` or writing to one of the output files. +/// +/// # See also +/// +/// * [`split_into_n_chunks_by_line_round_robin`], which splits its input in the +/// same way, but writes each chunk to its own file. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * r/K/N +fn kth_chunk_by_line_round_robin( + settings: &Settings, + reader: &mut R, + chunk_number: u64, + num_chunks: u64, +) -> UResult<()> +where + R: BufRead, +{ + // Write to stdout instead of to a file. + let stdout = std::io::stdout(); + let mut writer = stdout.lock(); + + let num_chunks: usize = num_chunks.try_into().unwrap(); + let chunk_number: usize = chunk_number.try_into().unwrap(); + let sep = settings.separator; + // The chunk number is given as a 1-indexed number, but it + // is a little easier to deal with a 0-indexed number + // since `.enumerate()` returns index `i` starting with 0 + let chunk_number = chunk_number - 1; + for (i, line_result) in reader.split(sep).enumerate() { + let line = line_result?; + let bytes = line.as_slice(); + if (i % num_chunks) == chunk_number { + writer.write_all(bytes)?; + writer.write_all(&[sep])?; + } + } + Ok(()) +} + +#[allow(clippy::cognitive_complexity)] fn split(settings: &Settings) -> UResult<()> { let mut reader = BufReader::new(if settings.input == "-" { Box::new(stdin()) as Box @@ -1291,19 +1932,21 @@ fn split(settings: &Settings) -> UResult<()> { Strategy::Number(NumberType::Bytes(num_chunks)) => { split_into_n_chunks_by_byte(settings, &mut reader, num_chunks) } + Strategy::Number(NumberType::KthBytes(chunk_number, num_chunks)) => { + kth_chunks_by_byte(settings, &mut reader, chunk_number, num_chunks) + } Strategy::Number(NumberType::Lines(num_chunks)) => { split_into_n_chunks_by_line(settings, &mut reader, num_chunks) } Strategy::Number(NumberType::KthLines(chunk_number, num_chunks)) => { - // The chunk number is given as a 1-indexed number, but it - // is a little easier to deal with a 0-indexed number. - let chunk_number = chunk_number - 1; kth_chunk_by_line(settings, &mut reader, chunk_number, num_chunks) } Strategy::Number(NumberType::RoundRobin(num_chunks)) => { split_into_n_chunks_by_line_round_robin(settings, &mut reader, num_chunks) } - Strategy::Number(_) => Err(USimpleError::new(1, "-n mode not yet fully implemented")), + Strategy::Number(NumberType::KthRoundRobin(chunk_number, num_chunks)) => { + kth_chunk_by_line_round_robin(settings, &mut reader, chunk_number, num_chunks) + } Strategy::Lines(chunk_size) => { let mut writer = LineChunkWriter::new(chunk_size, settings)?; match std::io::copy(&mut reader, &mut writer) { @@ -1318,7 +1961,6 @@ fn split(settings: &Settings) -> UResult<()> { // indicate that. A special error message needs to be // printed in that case. ErrorKind::Other => Err(USimpleError::new(1, format!("{e}"))), - ErrorKind::BrokenPipe => Ok(()), _ => Err(uio_error!(e, "input/output error")), }, } @@ -1337,7 +1979,6 @@ fn split(settings: &Settings) -> UResult<()> { // indicate that. A special error message needs to be // printed in that case. ErrorKind::Other => Err(USimpleError::new(1, format!("{e}"))), - ErrorKind::BrokenPipe => Ok(()), _ => Err(uio_error!(e, "input/output error")), }, } @@ -1356,7 +1997,6 @@ fn split(settings: &Settings) -> UResult<()> { // indicate that. A special error message needs to be // printed in that case. ErrorKind::Other => Err(USimpleError::new(1, format!("{e}"))), - ErrorKind::BrokenPipe => Ok(()), _ => Err(uio_error!(e, "input/output error")), }, } @@ -1389,6 +2029,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_number_type_from_error() { assert_eq!( NumberType::from("xyz").unwrap_err(), @@ -1406,6 +2047,18 @@ mod tests { NumberType::from("l/abc/456").unwrap_err(), NumberTypeError::ChunkNumber("abc".to_string()) ); + assert_eq!( + NumberType::from("l/456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); + assert_eq!( + NumberType::from("r/456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); + assert_eq!( + NumberType::from("456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); // In GNU split, the number of chunks get precedence: // // $ split -n l/abc/xyz @@ -1441,6 +2094,7 @@ mod tests { #[test] fn test_number_type_num_chunks() { assert_eq!(NumberType::from("123").unwrap().num_chunks(), 123); + assert_eq!(NumberType::from("123/456").unwrap().num_chunks(), 456); assert_eq!(NumberType::from("l/123").unwrap().num_chunks(), 123); assert_eq!(NumberType::from("l/123/456").unwrap().num_chunks(), 456); assert_eq!(NumberType::from("r/123").unwrap().num_chunks(), 123); diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index 91b479118..2974562f3 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stat" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "stat ~ (uutils) display FILE status" diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 69f3c2760..7d1fd574c 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -1,9 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. use clap::builder::ValueParser; use uucore::display::Quotable; @@ -406,6 +404,7 @@ fn print_unsigned_hex( } impl Stater { + #[allow(clippy::cognitive_complexity)] fn generate_tokens(format_str: &str, use_printf: bool) -> UResult> { let mut tokens = Vec::new(); let bound = format_str.len(); @@ -611,9 +610,14 @@ impl Stater { ret } + #[allow(clippy::cognitive_complexity)] fn do_stat(&self, file: &OsStr, stdin_is_fifo: bool) -> i32 { let display_name = file.to_string_lossy(); let file = if cfg!(unix) && display_name == "-" { + if self.show_fs { + show_error!("using '-' to denote standard input does not work in file system mode"); + return 1; + } if let Ok(p) = Path::new("/dev/stdin").canonicalize() { p.into_os_string() } else { @@ -622,7 +626,6 @@ impl Stater { } else { OsString::from(file) }; - if self.show_fs { #[cfg(unix)] let p = file.as_bytes(); @@ -632,7 +635,7 @@ impl Stater { Ok(meta) => { let tokens = &self.default_tokens; - for t in tokens.iter() { + for t in tokens { match *t { Token::Char(c) => print!("{c}"), Token::Directive { @@ -700,7 +703,7 @@ impl Stater { &self.default_dev_tokens }; - for t in tokens.iter() { + for t in tokens { match *t { Token::Char(c) => print!("{c}"), Token::Directive { @@ -944,6 +947,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_group_num() { assert_eq!("12,379,821,234", group_num("12379821234")); assert_eq!("21,234", group_num("21234")); diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 66b74f9c5..8168733af 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "stdbuf ~ (uutils) run COMMAND with modified standard stream buffering" @@ -20,7 +20,7 @@ tempfile = { workspace = true } uucore = { workspace = true } [build-dependencies] -libstdbuf = { version = "0.0.20", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } +libstdbuf = { version = "0.0.22", 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 a8472243a..7483aeacf 100644 --- a/src/uu/stdbuf/build.rs +++ b/src/uu/stdbuf/build.rs @@ -1,3 +1,7 @@ +// 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 (ToDO) dylib libstdbuf deps liblibstdbuf use std::env; diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index 25104db2b..5fdfa27e0 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.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "stdbuf/libstdbuf ~ (uutils); dynamic library required for stdbuf" diff --git a/src/uu/stdbuf/src/libstdbuf/build.rs b/src/uu/stdbuf/src/libstdbuf/build.rs index fc8ddeac8..6dcd6a869 100644 --- a/src/uu/stdbuf/src/libstdbuf/build.rs +++ b/src/uu/stdbuf/src/libstdbuf/build.rs @@ -1,3 +1,7 @@ +// 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 (ToDO) libstdbuf use cpp_build::Config; diff --git a/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs b/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs index ec99c3864..a29d01b78 100644 --- a/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs +++ b/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs @@ -1,3 +1,7 @@ +// 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 (ToDO) IOFBF IOLBF IONBF cstdio setvbuf use cpp::cpp; diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index e02dd28bc..857828275 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Dorota Kapturkiewicz -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) tempdir dyld dylib dragonflybsd optgrps libstdbuf @@ -16,7 +14,7 @@ use std::process; use tempfile::tempdir; use tempfile::TempDir; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::parse_size::parse_size; +use uucore::parse_size::parse_size_u64; use uucore::{crash, format_usage, help_about, help_section, help_usage}; const ABOUT: &str = help_about!("stdbuf.md"); @@ -103,7 +101,7 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result parse_size(x).map_or_else( + x => parse_size_u64(x).map_or_else( |e| crash!(125, "invalid mode {}", e), |m| { Ok(BufferType::Size(m.try_into().map_err(|_| { @@ -130,7 +128,7 @@ fn set_command_env(command: &mut process::Command, buffer_name: &str, buffer_typ } } -fn get_preload_env(tmp_dir: &mut TempDir) -> io::Result<(String, PathBuf)> { +fn get_preload_env(tmp_dir: &TempDir) -> io::Result<(String, PathBuf)> { let (preload, extension) = preload_strings(); let inject_path = tmp_dir.path().join("libstdbuf").with_extension(extension); @@ -152,8 +150,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut command = process::Command::new(command_values.next().unwrap()); let command_params: Vec<&str> = command_values.map(|s| s.as_ref()).collect(); - let mut tmp_dir = tempdir().unwrap(); - let (preload_env, libstdbuf) = get_preload_env(&mut tmp_dir).map_err_context(String::new)?; + let tmp_dir = tempdir().unwrap(); + let (preload_env, libstdbuf) = get_preload_env(&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); diff --git a/src/uu/stty/Cargo.toml b/src/uu/stty/Cargo.toml index aadae6c92..965de5cd8 100644 --- a/src/uu/stty/Cargo.toml +++ b/src/uu/stty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stty" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "stty ~ (uutils) print or change terminal characteristics" diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index 536c08d80..2c8e154e8 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 parenb parodd cmspar hupcl cstopb cread clocal crtscts CSIZE // spell-checker:ignore ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl ixoff ixon iuclc ixany imaxbel iutf diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index c933e48ae..669285750 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 clocal erange tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed cfsetospeed ushort vmin vtime @@ -14,10 +14,12 @@ use nix::sys::termios::{ OutputFlags, SpecialCharacterIndices, Termios, }; use nix::{ioctl_read_bad, ioctl_write_ptr_bad}; -use std::io::{self, stdout}; +use std::fs::File; +use std::io::{self, stdout, Stdout}; use std::ops::ControlFlow; +use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::fs::OpenOptionsExt; -use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, RawFd}; use uucore::error::{UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage}; @@ -91,10 +93,33 @@ mod options { struct Options<'a> { all: bool, save: bool, - file: RawFd, + file: Device, settings: Option>, } +enum Device { + File(File), + Stdout(Stdout), +} + +impl AsFd for Device { + fn as_fd(&self) -> BorrowedFd<'_> { + match self { + Self::File(f) => f.as_fd(), + Self::Stdout(stdout) => stdout.as_fd(), + } + } +} + +impl AsRawFd for Device { + fn as_raw_fd(&self) -> RawFd { + match self { + Self::File(f) => f.as_raw_fd(), + Self::Stdout(stdout) => stdout.as_raw_fd(), + } + } +} + impl<'a> Options<'a> { fn from(matches: &'a ArgMatches) -> io::Result { Ok(Self { @@ -110,12 +135,13 @@ impl<'a> Options<'a> { // will clean up the FD for us on exit, so it doesn't // matter. The alternative would be to have an enum of // BorrowedFd/OwnedFd to handle both cases. - Some(f) => std::fs::OpenOptions::new() - .read(true) - .custom_flags(O_NONBLOCK) - .open(f)? - .into_raw_fd(), - None => stdout().as_raw_fd(), + Some(f) => Device::File( + std::fs::OpenOptions::new() + .read(true) + .custom_flags(O_NONBLOCK) + .open(f)?, + ), + None => Device::Stdout(stdout()), }, settings: matches .get_many::(options::SETTINGS) @@ -175,7 +201,7 @@ fn stty(opts: &Options) -> UResult<()> { } // TODO: Figure out the right error message for when tcgetattr fails - let mut termios = tcgetattr(opts.file).expect("Could not get terminal attributes"); + let mut termios = tcgetattr(opts.file.as_fd()).expect("Could not get terminal attributes"); if let Some(settings) = &opts.settings { for setting in settings { @@ -187,8 +213,12 @@ fn stty(opts: &Options) -> UResult<()> { } } - tcsetattr(opts.file, nix::sys::termios::SetArg::TCSANOW, &termios) - .expect("Could not write terminal attributes"); + tcsetattr( + opts.file.as_fd(), + nix::sys::termios::SetArg::TCSANOW, + &termios, + ) + .expect("Could not write terminal attributes"); } else { print_settings(&termios, opts).expect("TODO: make proper error here from nix error"); } @@ -228,7 +258,7 @@ fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> { if opts.all { let mut size = TermSize::default(); - unsafe { tiocgwinsz(opts.file, &mut size as *mut _)? }; + unsafe { tiocgwinsz(opts.file.as_raw_fd(), &mut size as *mut _)? }; print!("rows {}; columns {}; ", size.rows, size.columns); } diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index 0879dd68f..58fa7a08d 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sum" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "sum ~ (uutils) display checksum and block counts for input" diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index e9b5a8e07..4616274d0 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) T. Jameson Little -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 (ToDO) sysv @@ -33,7 +31,7 @@ fn bsd_sum(mut reader: Box) -> (usize, u16) { match reader.read(&mut buf) { Ok(n) if n != 0 => { bytes_read += n; - for &byte in buf[..n].iter() { + for &byte in &buf[..n] { checksum = checksum.rotate_right(1); checksum = checksum.wrapping_add(u16::from(byte)); } @@ -56,7 +54,7 @@ fn sysv_sum(mut reader: Box) -> (usize, u16) { match reader.read(&mut buf) { Ok(n) if n != 0 => { bytes_read += n; - for &byte in buf[..n].iter() { + for &byte in &buf[..n] { ret = ret.wrapping_add(u32::from(byte)); } } diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index 0a0695ed5..2367561f8 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sync" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "sync ~ (uutils) synchronize cache writes to storage" diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 821ad639b..c0b8f3d00 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alexander Fomin -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. /* Last synced with: sync (GNU coreutils) 8.13 */ @@ -173,7 +171,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let path = Path::new(&f); if let Err(e) = open(path, OFlag::O_NONBLOCK, Mode::empty()) { if e != Errno::EACCES || (e == Errno::EACCES && path.is_dir()) { - return e.map_err_context(|| format!("error opening {}", f.quote()))?; + e.map_err_context(|| format!("error opening {}", f.quote()))?; } } } diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 0decf6194..1716a6d03 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uu_tac" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tac ~ (uutils) concatenate and display input lines in reverse order" diff --git a/src/uu/tac/src/error.rs b/src/uu/tac/src/error.rs index 43b03b970..7a737ad9b 100644 --- a/src/uu/tac/src/error.rs +++ b/src/uu/tac/src/error.rs @@ -1,7 +1,7 @@ -// * 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. +// 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; diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 96bb82f1e..b8cb61029 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) sbytes slen dlen memmem memmap Mmap mmap SIGBUS mod error; diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index a22b148fe..66775c8d9 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore (libs) kqueue fundu [package] name = "uu_tail" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tail ~ (uutils) display the last lines of input" @@ -22,7 +22,6 @@ memchr = { workspace = true } notify = { workspace = true } uucore = { workspace = true } same-file = { workspace = true } -is-terminal = { workspace = true } fundu = { workspace = true } [target.'cfg(windows)'.dependencies] diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 3b4984819..9b1729872 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (ToDO) kqueue Signum fundu @@ -10,12 +10,12 @@ use crate::{parse, platform, Quotable}; use clap::{crate_version, value_parser}; use clap::{Arg, ArgAction, ArgMatches, Command}; use fundu::{DurationParser, SaturatingInto}; -use is_terminal::IsTerminal; use same_file::Handle; use std::ffi::OsString; +use std::io::IsTerminal; use std::time::Duration; use uucore::error::{UResult, USimpleError, UUsageError}; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, ParseSizeError}; use uucore::{format_usage, help_about, help_usage, show_warning}; const ABOUT: &str = help_about!("tail.md"); @@ -414,7 +414,7 @@ fn parse_num(src: &str) -> Result { } } - match parse_size(size_string) { + match parse_size_u64(size_string) { Ok(n) => match (n, starting_with) { (0, true) => Ok(Signum::PlusZero), (0, false) => Ok(Signum::MinusZero), diff --git a/src/uu/tail/src/chunks.rs b/src/uu/tail/src/chunks.rs index 3aa380e20..6582bd251 100644 --- a/src/uu/tail/src/chunks.rs +++ b/src/uu/tail/src/chunks.rs @@ -1,7 +1,7 @@ -// * 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. +// 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. //! Iterating over a file by chunks, either starting at the end of the file with [`ReverseChunks`] //! or at the end of piped stdin with [`LinesChunk`] or [`BytesChunk`]. @@ -495,7 +495,7 @@ impl LinesChunk { fn calculate_bytes_offset_from(&self, offset: usize) -> usize { let mut lines_offset = offset; let mut bytes_offset = 0; - for byte in self.get_buffer().iter() { + for byte in self.get_buffer() { if lines_offset == 0 { break; } diff --git a/src/uu/tail/src/follow/files.rs b/src/uu/tail/src/follow/files.rs index 8686e73f4..e4f980267 100644 --- a/src/uu/tail/src/follow/files.rs +++ b/src/uu/tail/src/follow/files.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 tailable seekable stdlib (stdlib) diff --git a/src/uu/tail/src/follow/mod.rs b/src/uu/tail/src/follow/mod.rs index e31eb54d1..52eef318f 100644 --- a/src/uu/tail/src/follow/mod.rs +++ b/src/uu/tail/src/follow/mod.rs @@ -1,7 +1,7 @@ -// * 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. +// 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. mod files; mod watch; diff --git a/src/uu/tail/src/follow/watch.rs b/src/uu/tail/src/follow/watch.rs index 9569e6e21..fbda27aa0 100644 --- a/src/uu/tail/src/follow/watch.rs +++ b/src/uu/tail/src/follow/watch.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (ToDO) tailable untailable stdlib kqueue Uncategorized unwatch @@ -479,8 +479,7 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> { let mut process = platform::ProcessChecker::new(observer.pid); - let mut _event_counter = 0; - let mut _timeout_counter = 0; + let mut timeout_counter = 0; // main follow loop loop { @@ -529,8 +528,7 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> { .receiver .recv_timeout(settings.sleep_sec); if rx_result.is_ok() { - _event_counter += 1; - _timeout_counter = 0; + timeout_counter = 0; } let mut paths = vec![]; // Paths worth checking for new content to print @@ -569,7 +567,7 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> { } Ok(Err(e)) => return Err(USimpleError::new(1, format!("NotifyError: {e}"))), Err(mpsc::RecvTimeoutError::Timeout) => { - _timeout_counter += 1; + timeout_counter += 1; } Err(e) => return Err(USimpleError::new(1, format!("RecvTimeoutError: {e}"))), } @@ -586,7 +584,7 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> { _read_some = observer.files.tail_file(path, settings.verbose)?; } - if _timeout_counter == settings.max_unchanged_stats { + if timeout_counter == settings.max_unchanged_stats { /* TODO: [2021-10; jhscheer] implement timeout_counter for each file. ‘--max-unchanged-stats=n’ diff --git a/src/uu/tail/src/parse.rs b/src/uu/tail/src/parse.rs index 96cf1e918..3d6b2697e 100644 --- a/src/uu/tail/src/parse.rs +++ b/src/uu/tail/src/parse.rs @@ -1,7 +1,7 @@ -// * 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. +// 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; diff --git a/src/uu/tail/src/paths.rs b/src/uu/tail/src/paths.rs index 5ed654037..117cab8b0 100644 --- a/src/uu/tail/src/paths.rs +++ b/src/uu/tail/src/paths.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 tailable seekable stdlib (stdlib) diff --git a/src/uu/tail/src/platform/mod.rs b/src/uu/tail/src/platform/mod.rs index e5ae8b8d8..cd2953ffd 100644 --- a/src/uu/tail/src/platform/mod.rs +++ b/src/uu/tail/src/platform/mod.rs @@ -1,12 +1,7 @@ -/* - * This file is part of the uutils coreutils package. - * - * (c) Alexander Batischev - * (c) Thomas Queiroz - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ +// 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. #[cfg(unix)] pub use self::unix::{ diff --git a/src/uu/tail/src/platform/unix.rs b/src/uu/tail/src/platform/unix.rs index ed34b2cf9..a04582a2c 100644 --- a/src/uu/tail/src/platform/unix.rs +++ b/src/uu/tail/src/platform/unix.rs @@ -1,12 +1,7 @@ -/* - * This file is part of the uutils coreutils package. - * - * (c) Alexander Batischev - * (c) Thomas Queiroz - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ +// 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 (ToDO) stdlib, ISCHR, GETFD // spell-checker:ignore (options) EPERM, ENOSYS diff --git a/src/uu/tail/src/platform/windows.rs b/src/uu/tail/src/platform/windows.rs index 3e4cc7edc..592516162 100644 --- a/src/uu/tail/src/platform/windows.rs +++ b/src/uu/tail/src/platform/windows.rs @@ -1,11 +1,8 @@ -/* - * This file is part of the uutils coreutils package. - * - * (c) Alexander Batischev - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ +// 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 windows_sys::Win32::Foundation::{CloseHandle, BOOL, HANDLE, WAIT_FAILED, WAIT_OBJECT_0}; use windows_sys::Win32::System::Threading::{ OpenProcess, WaitForSingleObject, PROCESS_SYNCHRONIZE, diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index e07616c6f..0488e0808 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -1,11 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Morten Olsen Lysgaard -// * (c) Alexander Batischev -// * (c) Thomas Queiroz -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) seekable seek'd tail'ing ringbuffer ringbuf unwatch Uncategorized filehandle Signum // spell-checker:ignore (libs) kqueue diff --git a/src/uu/tail/src/text.rs b/src/uu/tail/src/text.rs index e7686d301..e7aa6c253 100644 --- a/src/uu/tail/src/text.rs +++ b/src/uu/tail/src/text.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (ToDO) kqueue diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index 10fa63768..787c0cb32 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tee" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tee ~ (uutils) display input and copy to FILE" diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index 5c388dd0e..ca6e8a7c6 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Aleksander Bielawski -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 clap::{builder::PossibleValue, crate_version, Arg, ArgAction, Command}; use std::fs::OpenOptions; diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index 4926bff32..696fa3662 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_test" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "test ~ (uutils) evaluate comparison and file type expressions" diff --git a/src/uu/test/src/error.rs b/src/uu/test/src/error.rs index ced4b216a..48238fa13 100644 --- a/src/uu/test/src/error.rs +++ b/src/uu/test/src/error.rs @@ -1,3 +1,7 @@ +// 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. /// Represents an error encountered while parsing a test expression #[derive(Debug)] pub enum ParseError { diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index db90098a1..2b847fa15 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Daniel Rocco -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index b0a8fc613..4f230a590 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -1,8 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) mahkoh (ju.orth [at] gmail [dot] com) -// (c) Daniel Rocco -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -221,7 +218,7 @@ fn files(a: &OsStr, b: &OsStr, op: &OsStr) -> ParseResult { #[cfg(not(unix))] Some("-ef") => unimplemented!(), Some("-nt") => f_a.modified().unwrap() > f_b.modified().unwrap(), - Some("-ot") => f_a.created().unwrap() > f_b.created().unwrap(), + Some("-ot") => f_a.modified().unwrap() < f_b.modified().unwrap(), _ => return Err(ParseError::UnknownOperator(op.quote().to_string())), }) } diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index 452a5b04a..b6cb700a4 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_timeout" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "timeout ~ (uutils) run COMMAND with a DURATION time limit" diff --git a/src/uu/timeout/src/status.rs b/src/uu/timeout/src/status.rs index 9a8558954..10103ab9b 100644 --- a/src/uu/timeout/src/status.rs +++ b/src/uu/timeout/src/status.rs @@ -1,7 +1,7 @@ -// * 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. +// 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. //! Exit status codes produced by `timeout`. use std::convert::From; use uucore::error::UError; diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 531f29e82..5e73fe2ab 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) tstr sigstr cmdname setpgid sigchld getpid mod status; diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index ce0752093..b44e8cf69 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_touch" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "touch ~ (uutils) change FILE timestamps" diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index baa28890c..d9399a051 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -1,15 +1,15 @@ // This file is part of the uutils coreutils package. // -// (c) Nick Platt -// (c) Jian Zeng -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. -// spell-checker:ignore (ToDO) filetime datetime lpszfilepath mktime DATETIME subsecond datelike timelike +// spell-checker:ignore (ToDO) filetime datetime lpszfilepath mktime DATETIME datelike timelike // spell-checker:ignore (FORMATS) MMDDhhmm YYYYMMDDHHMM YYMMDDHHMM YYYYMMDDHHMMS -use chrono::{DateTime, Datelike, Duration, Local, NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; +use chrono::{ + DateTime, Datelike, Duration, Local, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, + TimeZone, Timelike, +}; use clap::builder::ValueParser; use clap::{crate_version, Arg, ArgAction, ArgGroup, Command}; use filetime::{set_file_times, set_symlink_file_times, FileTime}; @@ -68,6 +68,10 @@ fn datetime_to_filetime(dt: &DateTime) -> FileTime { FileTime::from_unix_time(dt.timestamp(), dt.timestamp_subsec_nanos()) } +fn filetime_to_datetime(ft: &FileTime) -> Option> { + Some(DateTime::from_timestamp(ft.unix_seconds(), ft.nanoseconds())?.into()) +} + #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -88,35 +92,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ) { (Some(reference), Some(date)) => { let (atime, mtime) = stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?; - if let Ok(offset) = parse_datetime::from_str(date) { - let seconds = offset.num_seconds(); - let nanos = offset.num_nanoseconds().unwrap_or(0) % 1_000_000_000; - - let ref_atime_secs = atime.unix_seconds(); - let ref_atime_nanos = atime.nanoseconds(); - let atime = FileTime::from_unix_time( - ref_atime_secs + seconds, - ref_atime_nanos + nanos as u32, - ); - - let ref_mtime_secs = mtime.unix_seconds(); - let ref_mtime_nanos = mtime.nanoseconds(); - let mtime = FileTime::from_unix_time( - ref_mtime_secs + seconds, - ref_mtime_nanos + nanos as u32, - ); - - (atime, mtime) - } else { - let timestamp = parse_date(date)?; - (timestamp, timestamp) - } + let atime = filetime_to_datetime(&atime).ok_or_else(|| { + USimpleError::new(1, "Could not process the reference access time") + })?; + let mtime = filetime_to_datetime(&mtime).ok_or_else(|| { + USimpleError::new(1, "Could not process the reference modification time") + })?; + (parse_date(atime, date)?, parse_date(mtime, date)?) } (Some(reference), None) => { stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))? } (None, Some(date)) => { - let timestamp = parse_date(date)?; + let timestamp = parse_date(Local::now(), date)?; (timestamp, timestamp) } (None, None) => { @@ -336,7 +324,7 @@ fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> { )) } -fn parse_date(s: &str) -> UResult { +fn parse_date(ref_time: DateTime, s: &str) -> UResult { // This isn't actually compatible with GNU touch, but there doesn't seem to // be any simple specification for what format this parameter allows and I'm // not about to implement GNU parse_datetime. @@ -351,8 +339,8 @@ fn parse_date(s: &str) -> UResult { // Tue Dec 3 ... // ("%c", POSIX_LOCALE_FORMAT), // - if let Ok(parsed) = Local.datetime_from_str(s, format::POSIX_LOCALE) { - return Ok(datetime_to_filetime(&parsed)); + if let Ok(parsed) = NaiveDateTime::parse_from_str(s, format::POSIX_LOCALE) { + return Ok(datetime_to_filetime(&parsed.and_utc())); } // Also support other formats found in the GNU tests like @@ -364,8 +352,8 @@ fn parse_date(s: &str) -> UResult { format::YYYY_MM_DD_HH_MM, format::YYYYMMDDHHMM_OFFSET, ] { - if let Ok(parsed) = Utc.datetime_from_str(s, fmt) { - return Ok(datetime_to_filetime(&parsed)); + if let Ok(parsed) = NaiveDateTime::parse_from_str(s, fmt) { + return Ok(datetime_to_filetime(&parsed.and_utc())); } } @@ -385,8 +373,7 @@ fn parse_date(s: &str) -> UResult { } } - if let Ok(duration) = parse_datetime::from_str(s) { - let dt = Local::now() + duration; + if let Ok(dt) = parse_datetime::parse_datetime_at_date(ref_time, s) { return Ok(datetime_to_filetime(&dt)); } @@ -414,9 +401,17 @@ fn parse_timestamp(s: &str) -> UResult { } }; - let mut local = chrono::Local - .datetime_from_str(&ts, format) + let local = NaiveDateTime::parse_from_str(&ts, format) .map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", ts.quote())))?; + let mut local = match chrono::Local.from_local_datetime(&local) { + LocalResult::Single(dt) => dt, + _ => { + return Err(USimpleError::new( + 1, + format!("invalid date ts format {}", ts.quote()), + )) + } + }; // Chrono caps seconds at 59, but 60 is valid. It might be a leap second // or wrap to the next minute. But that doesn't really matter, because we @@ -494,7 +489,7 @@ fn pathbuf_from_stdout() -> UResult { format!("GetFinalPathNameByHandleW failed with code {ret}"), )) } - e if e == 0 => { + 0 => { return Err(USimpleError::new( 1, format!( diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index 7bc942485..c7e7bd606 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tr" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tr ~ (uutils) translate characters within input and display" diff --git a/src/uu/tr/src/convert.rs b/src/uu/tr/src/convert.rs index 00656ca49..28a80e9f5 100644 --- a/src/uu/tr/src/convert.rs +++ b/src/uu/tr/src/convert.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (strings) anychar combinator diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index f27ccc0b9..b7d74427a 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (strings) anychar combinator Alnum Punct Xdigit alnum punct xdigit cntrl diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 9abbca631..9c6e7a7da 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (ToDO) allocs bset dflag cflag sflag tflag diff --git a/src/uu/tr/src/unicode_table.rs b/src/uu/tr/src/unicode_table.rs index 43d9fd6f4..a00a30b8b 100644 --- a/src/uu/tr/src/unicode_table.rs +++ b/src/uu/tr/src/unicode_table.rs @@ -1,7 +1,7 @@ -// * 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. +// 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. pub static BEL: char = '\u{0007}'; pub static BS: char = '\u{0008}'; diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index d70cc0d7a..ec6c5d2f9 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_true" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "true ~ (uutils) do nothing and succeed" diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index 334652ce8..637758625 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Jordi Boggiano -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 clap::{Arg, ArgAction, Command}; use std::{ffi::OsString, io::Write}; use uucore::error::{set_exit_code, UResult}; diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index 4d756f46c..a9cc179a6 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_truncate" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "truncate ~ (uutils) truncate (or extend) FILE to SIZE" diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index f050b52b4..9368ce9b1 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alex Lyon -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) RFILE refsize rfilename fsize tsize use clap::{crate_version, Arg, ArgAction, Command}; @@ -14,7 +12,7 @@ use std::os::unix::fs::FileTypeExt; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, ParseSizeError}; use uucore::{format_usage, help_about, help_section, help_usage}; #[derive(Debug, Eq, PartialEq)] @@ -104,7 +102,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default(); if files.is_empty() { - return Err(UUsageError::new(1, "missing file operand")); + Err(UUsageError::new(1, "missing file operand")) } else { let io_blocks = matches.get_flag(options::IO_BLOCKS); let no_create = matches.get_flag(options::NO_CREATE); @@ -382,7 +380,7 @@ fn is_modifier(c: char) -> bool { /// Parse a size string with optional modifier symbol as its first character. /// -/// A size string is as described in [`parse_size`]. The first character +/// A size string is as described in [`parse_size_u64`]. The first character /// of `size_string` might be a modifier symbol, like `'+'` or /// `'<'`. The first element of the pair returned by this function /// indicates which modifier symbol was present, or @@ -408,7 +406,7 @@ fn parse_mode_and_size(size_string: &str) -> Result TruncateMode::Extend, '-' => TruncateMode::Reduce, '<' => TruncateMode::AtMost, diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index 1e2b72d2f..f72aeeda0 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tsort" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tsort ~ (uutils) topologically sort input (partially ordered) pairs" diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 68f51b213..e71710847 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Ben Eggers -// * (c) Akira Hayakawa -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 clap::{crate_version, Arg, Command}; use std::collections::{BTreeMap, BTreeSet}; use std::fs::File; @@ -157,6 +154,7 @@ impl Graph { self.result.push(n.clone()); let n_out_edges = self.out_edges.get_mut(&n).unwrap(); + #[allow(clippy::explicit_iter_loop)] for m in n_out_edges.iter() { let m_in_edges = self.in_edges.get_mut(m).unwrap(); m_in_edges.remove(&n); diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index f94026831..66d4b42c7 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tty" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tty ~ (uutils) display the name of the terminal connected to standard input" @@ -17,7 +17,6 @@ path = "src/tty.rs" [dependencies] clap = { workspace = true } nix = { workspace = true, features = ["term"] } -is-terminal = { workspace = true } uucore = { workspace = true, features = ["fs"] } [[bin]] diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index e2d9d1847..efda4a7be 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -1,17 +1,14 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Jordi Boggiano -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. // * // * Synced with http://lingrok.org/xref/coreutils/src/tty.c // spell-checker:ignore (ToDO) ttyname filedesc use clap::{crate_version, Arg, ArgAction, Command}; -use is_terminal::IsTerminal; -use std::io::Write; +use std::io::{IsTerminal, Write}; use std::os::unix::io::AsRawFd; use uucore::error::{set_exit_code, UResult}; use uucore::{format_usage, help_about, help_usage}; diff --git a/src/uu/uname/Cargo.toml b/src/uu/uname/Cargo.toml index 1c0421c65..10363b63c 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uname" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "uname ~ (uutils) display system information" diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 8b8679234..73ab07a63 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -1,8 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Joao Oliveira -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index 483881368..bab5b4e91 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unexpand" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "unexpand ~ (uutils) convert input spaces to tabs" diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index dd4471e2d..11ad43060 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -1,11 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Virgile Andreani -// * (c) kwantam -// * * 2015-04-28 ~ updated to work with both UTF-8 and non-UTF-8 encodings -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) nums aflag uflag scol prevtab amode ctype cwidth nbytes lastcol pctype Preprocess diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index b70cc993a..2fb0552ce 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uniq" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "uniq ~ (uutils) filter identical adjacent lines from input" diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 89141f35f..72338bf96 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -1,11 +1,9 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Chirag B Jadwani -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgMatches, Command}; +use clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgGroup, ArgMatches, Command}; use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::{self, stdin, stdout, BufRead, BufReader, BufWriter, Write}; @@ -25,6 +23,7 @@ pub mod options { pub static IGNORE_CASE: &str = "ignore-case"; pub static REPEATED: &str = "repeated"; pub static SKIP_FIELDS: &str = "skip-fields"; + pub static OBSOLETE_SKIP_FIELDS: &str = "obsolete_skip_field"; pub static SKIP_CHARS: &str = "skip-chars"; pub static UNIQUE: &str = "unique"; pub static ZERO_TERMINATED: &str = "zero-terminated"; @@ -55,6 +54,8 @@ struct Uniq { zero_terminated: bool, } +const OBSOLETE_SKIP_FIELDS_DIGITS: [&str; 10] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; + macro_rules! write_line_terminator { ($writer:expr, $line_terminator:expr) => { $writer @@ -238,6 +239,41 @@ fn opt_parsed(opt_name: &str, matches: &ArgMatches) -> UResult UResult> { + for opt_text in OBSOLETE_SKIP_FIELDS_DIGITS { + let argument = matches.get_one::(opt_text); + if matches.contains_id(opt_text) { + let mut full = opt_text.to_owned(); + if let Some(ar) = argument { + full.push_str(ar); + } + let value = full.parse::(); + + if let Ok(val) = value { + return Ok(Some(val)); + } else { + return Err(USimpleError { + code: 1, + message: format!("Invalid argument for skip-fields: {}", full), + } + .into()); + } + } + } + Ok(None) +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; @@ -249,6 +285,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|mut fi| (fi.next(), fi.next())) .unwrap_or_default(); + let skip_fields_modern: Option = opt_parsed(options::SKIP_FIELDS, &matches)?; + + let skip_fields_old: Option = obsolete_skip_field(&matches)?; + let uniq = Uniq { repeats_only: matches.get_flag(options::REPEATED) || matches.contains_id(options::ALL_REPEATED), @@ -257,7 +297,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { || matches.contains_id(options::GROUP), delimiters: get_delimiter(&matches), show_counts: matches.get_flag(options::COUNT), - skip_fields: opt_parsed(options::SKIP_FIELDS, &matches)?, + skip_fields: skip_fields_modern.or(skip_fields_old), slice_start: opt_parsed(options::SKIP_CHARS, &matches)?, slice_stop: opt_parsed(options::CHECK_CHARS, &matches)?, ignore_case: matches.get_flag(options::IGNORE_CASE), @@ -278,7 +318,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let mut cmd = Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) @@ -357,6 +397,7 @@ pub fn uu_app() -> Command { Arg::new(options::SKIP_FIELDS) .short('f') .long(options::SKIP_FIELDS) + .overrides_with_all(OBSOLETE_SKIP_FIELDS_DIGITS) .help("avoid comparing the first N fields") .value_name("N"), ) @@ -374,13 +415,42 @@ pub fn uu_app() -> Command { .help("end lines with 0 byte, not newline") .action(ArgAction::SetTrue), ) + .group( + // in GNU `uniq` every every digit of these arguments + // would be interpreted as a simple flag, + // these flags then are concatenated to get + // the number of fields to skip. + // in this way `uniq -1 -z -2` would be + // equal to `uniq -12 -q`, since this behavior + // is counterintuitive and it's hard to do in clap + // we handle it more like GNU `fold`: we have a flag + // for each possible initial digit, that takes the + // rest of the value as argument. + // we disallow explicitly multiple occurrences + // because then it would have a different behavior + // from GNU + ArgGroup::new(options::OBSOLETE_SKIP_FIELDS) + .multiple(false) + .args(OBSOLETE_SKIP_FIELDS_DIGITS) + ) .arg( Arg::new(ARG_FILES) .action(ArgAction::Append) .value_parser(ValueParser::os_string()) .num_args(0..=2) .value_hint(clap::ValueHint::FilePath), - ) + ); + + for i in OBSOLETE_SKIP_FIELDS_DIGITS { + cmd = cmd.arg( + Arg::new(i) + .short(i.chars().next().unwrap()) + .num_args(0..=1) + .hide(true), + ); + } + + cmd } fn get_delimiter(matches: &ArgMatches) -> Delimiters { diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index 401e0d591..aae56bf0b 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unlink" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "unlink ~ (uutils) remove a (file system) link to FILE" diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 5d1594a05..85e1ab4f5 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Colin Warren -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. /* last synced with: unlink (GNU coreutils) 8.21 */ diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index 1835340ad..f5fa083f9 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uptime" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "uptime ~ (uutils) display dynamic system information" diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 6f4e62084..778fbc920 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Jordi Boggiano -// * (c) Jian Zeng -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (ToDO) getloadavg upsecs updays nusers loadavg boottime uphours upmins diff --git a/src/uu/users/Cargo.toml b/src/uu/users/Cargo.toml index 40341cf77..d894ff4ba 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_users" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "users ~ (uutils) display names of currently logged-in users" diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 6a5e54f99..199882b7e 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -1,10 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) KokaKiwi -// * (c) Jian Zeng -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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 (paths) wtmp diff --git a/src/uu/vdir/Cargo.toml b/src/uu/vdir/Cargo.toml index 55624dad8..b63f8c9ea 100644 --- a/src/uu/vdir/Cargo.toml +++ b/src/uu/vdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_vdir" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -l -b" @@ -16,7 +16,7 @@ path = "src/vdir.rs" [dependencies] clap = { workspace = true, features = ["env"] } -uucore = { workspace = true, features = ["entries", "fs"] } +uucore = { workspace = true, features = ["entries", "fs", "quoting-style"] } uu_ls = { workspace = true } [[bin]] diff --git a/src/uu/vdir/src/vdir.rs b/src/uu/vdir/src/vdir.rs index c49a3f1b3..b9d80c401 100644 --- a/src/uu/vdir/src/vdir.rs +++ b/src/uu/vdir/src/vdir.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) gmnsii -// * -// * For the full copyright and license information, please view the LICENSE file -// * that was distributed with this source code. +// 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 clap::Command; use std::ffi::OsString; diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 396a0f8dc..6a233c1ad 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_wc" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "wc ~ (uutils) display newline, word, and byte counts for input" @@ -16,7 +16,7 @@ path = "src/wc.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["pipes"] } +uucore = { workspace = true, features = ["pipes", "quoting-style"] } bytecount = { workspace = true } thiserror = { workspace = true } unicode-width = { workspace = true } diff --git a/src/uu/wc/src/count_fast.rs b/src/uu/wc/src/count_fast.rs index d151c9c90..863625921 100644 --- a/src/uu/wc/src/count_fast.rs +++ b/src/uu/wc/src/count_fast.rs @@ -1,3 +1,7 @@ +// 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 crate::word_count::WordCount; use super::WordCountable; diff --git a/src/uu/wc/src/countable.rs b/src/uu/wc/src/countable.rs index b86b96fa2..643974464 100644 --- a/src/uu/wc/src/countable.rs +++ b/src/uu/wc/src/countable.rs @@ -1,3 +1,7 @@ +// 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. //! Traits and implementations for iterating over lines in a file-like object. //! //! This module provides a [`WordCountable`] trait and implementations diff --git a/src/uu/wc/src/utf8/mod.rs b/src/uu/wc/src/utf8/mod.rs index 31638e758..ea4f19392 100644 --- a/src/uu/wc/src/utf8/mod.rs +++ b/src/uu/wc/src/utf8/mod.rs @@ -1,3 +1,7 @@ +// 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 Sapin mod read; diff --git a/src/uu/wc/src/utf8/read.rs b/src/uu/wc/src/utf8/read.rs index 1b5bbbe7f..4a92d85e6 100644 --- a/src/uu/wc/src/utf8/read.rs +++ b/src/uu/wc/src/utf8/read.rs @@ -1,3 +1,7 @@ +// 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 bytestream use super::*; use std::error::Error; diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 9fb8ca7a6..663bbda15 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Boden Garman -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. // cSpell:ignore ilog wc wc's @@ -686,7 +684,7 @@ fn compute_number_width(inputs: &Inputs, settings: &Settings) -> usize { let mut minimum_width = 1; let mut total: u64 = 0; - for input in inputs.iter() { + for input in inputs { match input { Input::Stdin(_) => minimum_width = MINIMUM_WIDTH, Input::Path(path) => { @@ -704,7 +702,7 @@ fn compute_number_width(inputs: &Inputs, settings: &Settings) -> usize { if total == 0 { minimum_width } else { - let total_width = (1 + ilog10_u64(total)) + let total_width = (1 + total.ilog10()) .try_into() .expect("ilog of a u64 should fit into a usize"); max(total_width, minimum_width) @@ -859,29 +857,3 @@ fn print_stats( writeln!(stdout) } } - -// TODO: remove and just use usize::ilog10 once the MSRV is >= 1.67. -fn ilog10_u64(mut u: u64) -> u32 { - if u == 0 { - panic!("cannot compute log of 0") - } - let mut log = 0; - if u >= 10_000_000_000 { - log += 10; - u /= 10_000_000_000; - } - if u >= 100_000 { - log += 5; - u /= 100_000; - } - // Rust's standard library in versions >= 1.67 does something even more clever than this, but - // this should work just fine for the time being. - log + match u { - 1..=9 => 0, - 10..=99 => 1, - 100..=999 => 2, - 1000..=9999 => 3, - 10000..=99999 => 4, - _ => unreachable!(), - } -} diff --git a/src/uu/wc/src/word_count.rs b/src/uu/wc/src/word_count.rs index cf839175f..3c18d692b 100644 --- a/src/uu/wc/src/word_count.rs +++ b/src/uu/wc/src/word_count.rs @@ -1,3 +1,7 @@ +// 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::cmp::max; use std::ops::{Add, AddAssign}; diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index c727a3fc2..fdfc05897 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_who" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "who ~ (uutils) display information about currently logged-in users" diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index fbfff80d7..5d952efff 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -328,6 +326,7 @@ fn current_tty() -> String { } impl Who { + #[allow(clippy::cognitive_complexity)] fn exec(&mut self) -> UResult<()> { let run_level_chk = |_record: i16| { #[cfg(not(target_os = "linux"))] diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index 774831a66..59d011209 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_whoami" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "whoami ~ (uutils) display user name of current effective user ID" diff --git a/src/uu/whoami/src/platform/mod.rs b/src/uu/whoami/src/platform/mod.rs index b5064a8d2..bc6b2888c 100644 --- a/src/uu/whoami/src/platform/mod.rs +++ b/src/uu/whoami/src/platform/mod.rs @@ -1,11 +1,7 @@ -/* - * This file is part of the uutils coreutils package. - * - * (c) Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ +// 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 (ToDO) getusername diff --git a/src/uu/whoami/src/platform/unix.rs b/src/uu/whoami/src/platform/unix.rs index 1c0ea15d5..31ab16fba 100644 --- a/src/uu/whoami/src/platform/unix.rs +++ b/src/uu/whoami/src/platform/unix.rs @@ -1,12 +1,7 @@ -/* - * This file is part of the uutils coreutils package. - * - * (c) Jordi Boggiano - * (c) Jian Zeng - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ +// 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; use std::io; diff --git a/src/uu/whoami/src/platform/windows.rs b/src/uu/whoami/src/platform/windows.rs index 3bad1eb21..aad2eed3d 100644 --- a/src/uu/whoami/src/platform/windows.rs +++ b/src/uu/whoami/src/platform/windows.rs @@ -1,11 +1,7 @@ -/* - * This file is part of the uutils coreutils package. - * - * (c) Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ +// 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; use std::io; diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 04360fe7a..738f7509a 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -1,12 +1,12 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Jordi Boggiano -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. /* last synced with: whoami (GNU coreutils) 8.21 */ +use std::ffi::OsString; + use clap::{crate_version, Command}; use uucore::display::println_verbatim; @@ -21,11 +21,16 @@ const USAGE: &str = help_usage!("whoami.md"); #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { uu_app().try_get_matches_from(args)?; - let username = platform::get_username().map_err_context(|| "failed to get username".into())?; + let username = whoami()?; println_verbatim(username).map_err_context(|| "failed to print username".into())?; Ok(()) } +/// Get the current username +pub fn whoami() -> UResult { + platform::get_username().map_err_context(|| "failed to get username".into()) +} + pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index bd44c34f9..c36c2b189 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_yes" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "yes ~ (uutils) repeatedly display a line with STRING (or 'y')" diff --git a/src/uu/yes/src/splice.rs b/src/uu/yes/src/splice.rs index a0d41e06f..a95fc35af 100644 --- a/src/uu/yes/src/splice.rs +++ b/src/uu/yes/src/splice.rs @@ -1,3 +1,7 @@ +// 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. //! On Linux we can use vmsplice() to write data more efficiently. //! //! This does not always work. We're not allowed to splice to some targets, diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index 72c19b872..a58b73404 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -1,9 +1,7 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Jordi Boggiano -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// 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. /* last synced with: yes (GNU coreutils) 8.13 */ @@ -60,10 +58,7 @@ fn args_into_buffer<'a>( buf: &mut Vec, i: Option>, ) -> Result<(), Box> { - // TODO: this should be replaced with let/else once available in the MSRV. - let i = if let Some(i) = i { - i - } else { + let Some(i) = i else { buf.extend_from_slice(b"y\n"); return Ok(()); }; diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 1c1d4c754..460b47da7 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uucore" -version = "0.0.20" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "uutils ~ 'core' uutils code library (cross-platform)" @@ -20,9 +20,9 @@ path = "src/lib/lib.rs" [dependencies] clap = { workspace = true } uucore_procs = { workspace = true } -dns-lookup = { version = "2.0.2", optional = true } -dunce = "1.0.4" -wild = "2.1" +dns-lookup = { version = "2.0.4", optional = true } +dunce = { version = "1.0.4", optional = true } +wild = "2.2" glob = { workspace = true } # * optional itertools = { workspace = true, optional = true } @@ -40,16 +40,16 @@ libc = { workspace = true, optional = true } once_cell = { workspace = true } os_display = "0.1.3" -digest = { workspace = true } -hex = { workspace = true } -memchr = { workspace = true } -md-5 = { workspace = true } -sha1 = { workspace = true } -sha2 = { workspace = true } -sha3 = { workspace = true } -blake2b_simd = { workspace = true } -blake3 = { workspace = true } -sm3 = { workspace = true } +digest = { workspace = true, optional = true } +hex = { workspace = true, optional = true } +memchr = { workspace = true, optional = true } +md-5 = { workspace = true, optional = true } +sha1 = { workspace = true, optional = true } +sha2 = { workspace = true, optional = true } +sha3 = { workspace = true, optional = true } +blake2b_simd = { workspace = true, optional = true } +blake3 = { workspace = true, optional = true } +sm3 = { workspace = true, optional = true } [target.'cfg(unix)'.dependencies] walkdir = { workspace = true, optional = true } @@ -71,19 +71,35 @@ windows-sys = { workspace = true, optional = true, default-features = false, fea [features] default = [] # * non-default features +backup-control = [] encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"] entries = ["libc"] -fs = ["libc", "winapi-util", "windows-sys"] +fs = ["dunce", "libc", "winapi-util", "windows-sys"] fsext = ["libc", "time", "windows-sys"] lines = [] format = ["itertools"] mode = ["libc"] perms = ["libc", "walkdir"] +pipes = [] process = ["libc"] +quoting-style = [] +ranges = [] ringbuffer = [] signals = [] +sum = [ + "digest", + "hex", + "memchr", + "md-5", + "sha1", + "sha2", + "sha3", + "blake2b_simd", + "blake3", + "sm3", +] +update-control = [] utf8 = [] utmpx = ["time", "time/macros", "libc", "dns-lookup"] +version-cmp = [] wide = [] -pipes = [] -sum = [] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index fe4839987..133050954 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -1,5 +1,11 @@ +// 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. // features ~ feature-gated modules (core/bundler file) +#[cfg(feature = "backup-control")] +pub mod backup_control; #[cfg(feature = "encoding")] pub mod encoding; #[cfg(feature = "fs")] @@ -10,10 +16,22 @@ pub mod fsext; pub mod lines; #[cfg(feature = "format")] pub mod format; +#[cfg(feature = "memo")] +pub mod memo; +#[cfg(feature = "quoting-style")] +pub mod quoting_style; +#[cfg(feature = "ranges")] +pub mod ranges; #[cfg(feature = "ringbuffer")] pub mod ringbuffer; #[cfg(feature = "sum")] pub mod sum; +#[cfg(feature = "memo")] +mod tokenize; +#[cfg(feature = "update-control")] +pub mod update_control; +#[cfg(feature = "version-cmp")] +pub mod version_cmp; // * (platform-specific) feature-gated modules // ** non-windows (i.e. Unix + Fuchsia) diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/features/backup_control.rs similarity index 99% rename from src/uucore/src/lib/mods/backup_control.rs rename to src/uucore/src/lib/features/backup_control.rs index 9998c7560..86c7cd72b 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/features/backup_control.rs @@ -1,3 +1,7 @@ +// 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. //! Implement GNU-style backup functionality. //! //! This module implements the backup functionality as described in the [GNU diff --git a/src/uucore/src/lib/features/encoding.rs b/src/uucore/src/lib/features/encoding.rs index a42044eea..14fdbb38e 100644 --- a/src/uucore/src/lib/features/encoding.rs +++ b/src/uucore/src/lib/features/encoding.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index c0229aa3e..29c9b4372 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -220,7 +218,7 @@ impl Passwd { let mut ngroups: c_int = 8; let mut ngroups_old: c_int; let mut groups = vec![0; ngroups.try_into().unwrap()]; - let name = CString::new(self.name.clone()).unwrap(); + let name = CString::new(self.name.as_bytes()).unwrap(); loop { ngroups_old = ngroups; if unsafe { getgrouplist(name.as_ptr(), self.gid, groups.as_mut_ptr(), &mut ngroups) } diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index e92d0977f..84ed006d9 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -1,8 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Joseph Crail -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -31,6 +28,9 @@ use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR}; #[cfg(target_os = "windows")] use winapi_util::AsHandleRef; +/// Used to check if the `mode` has its `perm` bit set. +/// +/// This macro expands to `mode & perm != 0`. #[cfg(unix)] #[macro_export] macro_rules! has { @@ -114,6 +114,7 @@ impl FileInformation { not(target_vendor = "apple"), not(target_os = "android"), not(target_os = "freebsd"), + not(target_os = "netbsd"), not(target_arch = "aarch64"), not(target_arch = "riscv64"), target_pointer_width = "64" @@ -125,6 +126,7 @@ impl FileInformation { target_vendor = "apple", target_os = "android", target_os = "freebsd", + target_os = "netbsd", target_arch = "aarch64", target_arch = "riscv64", not(target_pointer_width = "64") @@ -137,9 +139,16 @@ impl FileInformation { #[cfg(unix)] pub fn inode(&self) -> u64 { - #[cfg(all(not(target_os = "freebsd"), target_pointer_width = "64"))] + #[cfg(all( + not(any(target_os = "freebsd", target_os = "netbsd")), + target_pointer_width = "64" + ))] return self.0.st_ino; - #[cfg(any(target_os = "freebsd", not(target_pointer_width = "64")))] + #[cfg(any( + target_os = "freebsd", + target_os = "netbsd", + not(target_pointer_width = "64") + ))] return self.0.st_ino.into(); } } @@ -426,6 +435,7 @@ pub fn canonicalize>( } #[cfg(not(unix))] +/// Display the permissions of a file pub fn display_permissions(metadata: &fs::Metadata, display_file_type: bool) -> String { let write = if metadata.permissions().readonly() { '-' @@ -450,12 +460,40 @@ pub fn display_permissions(metadata: &fs::Metadata, display_file_type: bool) -> #[cfg(unix)] /// Display the permissions of a file -/// On non unix like system, just show '----------' pub fn display_permissions(metadata: &fs::Metadata, display_file_type: bool) -> String { let mode: mode_t = metadata.mode() as mode_t; display_permissions_unix(mode, display_file_type) } +/// Returns a character representation of the file type based on its mode. +/// This function is specific to Unix-like systems. +/// +/// - `mode`: The mode of the file, typically obtained from file metadata. +/// +/// # Returns +/// - 'd' for directories +/// - 'c' for character devices +/// - 'b' for block devices +/// - '-' for regular files +/// - 'p' for FIFOs (named pipes) +/// - 'l' for symbolic links +/// - 's' for sockets +/// - '?' for any other unrecognized file types +#[cfg(unix)] +fn get_file_display(mode: mode_t) -> char { + match mode & S_IFMT { + S_IFDIR => 'd', + S_IFCHR => 'c', + S_IFBLK => 'b', + S_IFREG => '-', + S_IFIFO => 'p', + S_IFLNK => 'l', + S_IFSOCK => 's', + // TODO: Other file types + _ => '?', + } +} + // The logic below is more readable written this way. #[allow(clippy::if_not_else)] #[allow(clippy::cognitive_complexity)] @@ -465,17 +503,7 @@ pub fn display_permissions_unix(mode: mode_t, display_file_type: bool) -> String let mut result; if display_file_type { result = String::with_capacity(10); - result.push(match mode & S_IFMT { - S_IFDIR => 'd', - S_IFCHR => 'c', - S_IFBLK => 'b', - S_IFREG => '-', - S_IFIFO => 'p', - S_IFLNK => 'l', - S_IFSOCK => 's', - // TODO: Other file types - _ => '?', - }); + result.push(get_file_display(mode)); } else { result = String::with_capacity(9); } @@ -842,7 +870,7 @@ mod tests { let path1 = temp_file.path(); let path2 = temp_file.path(); - assert_eq!(are_hardlinks_to_same_file(&path1, &path2), true); + assert!(are_hardlinks_to_same_file(&path1, &path2)); } #[cfg(unix)] @@ -857,7 +885,7 @@ mod tests { let path1 = temp_file1.path(); let path2 = temp_file2.path(); - assert_eq!(are_hardlinks_to_same_file(&path1, &path2), false); + assert!(!are_hardlinks_to_same_file(&path1, &path2)); } #[cfg(unix)] @@ -870,6 +898,19 @@ mod tests { let path2 = temp_file.path().with_extension("hardlink"); fs::hard_link(&path1, &path2).unwrap(); - assert_eq!(are_hardlinks_to_same_file(&path1, &path2), true); + assert!(are_hardlinks_to_same_file(&path1, &path2)); + } + + #[cfg(unix)] + #[test] + fn test_get_file_display() { + assert_eq!(get_file_display(S_IFDIR | 0o755), 'd'); + assert_eq!(get_file_display(S_IFCHR | 0o644), 'c'); + assert_eq!(get_file_display(S_IFBLK | 0o600), 'b'); + assert_eq!(get_file_display(S_IFREG | 0o777), '-'); + assert_eq!(get_file_display(S_IFIFO | 0o666), 'p'); + assert_eq!(get_file_display(S_IFLNK | 0o777), 'l'); + assert_eq!(get_file_display(S_IFSOCK | 0o600), 's'); + assert_eq!(get_file_display(0o777), '?'); } } diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 6f831fb92..52c079e2e 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -1,11 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// (c) Fangxu Hu -// (c) Sylvestre Ledru -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. //! Set of functions to manage file systems diff --git a/src/uucore/src/lib/features/lines.rs b/src/uucore/src/lib/features/lines.rs index a7f4df76d..3e3c82b3a 100644 --- a/src/uucore/src/lib/features/lines.rs +++ b/src/uucore/src/lib/features/lines.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (vars) //! Iterate over lines, including the line ending character(s). //! diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index 9435e3201..147624891 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Alex Lyon -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -149,8 +147,7 @@ pub fn parse_mode(mode: &str) -> Result { #[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "android"))] let fperm = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { + let result = if mode.chars().any(|c| c.is_ascii_digit()) { parse_numeric(fperm, mode, true) } else { parse_symbolic(fperm, mode, get_umask(), true) diff --git a/src/uucore/src/lib/features/pipes.rs b/src/uucore/src/lib/features/pipes.rs index a76322de8..75749f721 100644 --- a/src/uucore/src/lib/features/pipes.rs +++ b/src/uucore/src/lib/features/pipes.rs @@ -1,3 +1,7 @@ +// 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. /// Thin pipe-related wrappers around functions from the `nix` crate. use std::fs::File; #[cfg(any(target_os = "linux", target_os = "android"))] diff --git a/src/uucore/src/lib/features/process.rs b/src/uucore/src/lib/features/process.rs index 4a52f0fc4..c7dff1f05 100644 --- a/src/uucore/src/lib/features/process.rs +++ b/src/uucore/src/lib/features/process.rs @@ -1,10 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Maciej Dziardziel -// (c) Jian Zeng -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. // spell-checker:ignore (vars) cvar exitstatus // spell-checker:ignore (sys/unix) WIFSIGNALED diff --git a/src/uucore/src/lib/mods/quoting_style.rs b/src/uucore/src/lib/features/quoting_style.rs similarity index 90% rename from src/uucore/src/lib/mods/quoting_style.rs rename to src/uucore/src/lib/features/quoting_style.rs index a6efb2898..b517dbd8d 100644 --- a/src/uucore/src/lib/mods/quoting_style.rs +++ b/src/uucore/src/lib/features/quoting_style.rs @@ -1,11 +1,17 @@ +// 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::char::from_digit; use std::ffi::OsStr; +use std::fmt; // These are characters with special meaning in the shell (e.g. bash). // The first const contains characters that only have a special meaning when they appear at the beginning of a name. const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#']; const SPECIAL_SHELL_CHARS: &str = "`$&*()|[]{};\\'\"<>?! "; +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum QuotingStyle { Shell { escape: bool, @@ -20,7 +26,7 @@ pub enum QuotingStyle { }, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Quotes { None, Single, @@ -312,6 +318,42 @@ pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { } } +impl fmt::Display for QuotingStyle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::Shell { + escape, + always_quote, + show_control, + } => { + let mut style = "shell".to_string(); + if escape { + style.push_str("-escape"); + } + if always_quote { + style.push_str("-always-quote"); + } + if show_control { + style.push_str("-show-control"); + } + f.write_str(&style) + } + Self::C { .. } => f.write_str("C"), + Self::Literal { .. } => f.write_str("literal"), + } + } +} + +impl fmt::Display for Quotes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::None => f.write_str("None"), + Self::Single => f.write_str("Single"), + Self::Double => f.write_str("Double"), + } + } +} + #[cfg(test)] mod tests { use crate::quoting_style::{escape_name, Quotes, QuotingStyle}; @@ -728,4 +770,45 @@ mod tests { ], ); } + + #[test] + fn test_quoting_style_display() { + let style = QuotingStyle::Shell { + escape: true, + always_quote: false, + show_control: false, + }; + assert_eq!(format!("{}", style), "shell-escape"); + + let style = QuotingStyle::Shell { + escape: false, + always_quote: true, + show_control: false, + }; + assert_eq!(format!("{}", style), "shell-always-quote"); + + let style = QuotingStyle::Shell { + escape: false, + always_quote: false, + show_control: true, + }; + assert_eq!(format!("{}", style), "shell-show-control"); + + let style = QuotingStyle::C { + quotes: Quotes::Double, + }; + assert_eq!(format!("{}", style), "C"); + + let style = QuotingStyle::Literal { + show_control: false, + }; + assert_eq!(format!("{}", style), "literal"); + } + + #[test] + fn test_quotes_display() { + assert_eq!(format!("{}", Quotes::None), "None"); + assert_eq!(format!("{}", Quotes::Single), "Single"); + assert_eq!(format!("{}", Quotes::Double), "Double"); + } } diff --git a/src/uucore/src/lib/mods/ranges.rs b/src/uucore/src/lib/features/ranges.rs similarity index 85% rename from src/uucore/src/lib/mods/ranges.rs rename to src/uucore/src/lib/features/ranges.rs index 76a61b9a6..19ba23fb2 100644 --- a/src/uucore/src/lib/mods/ranges.rs +++ b/src/uucore/src/lib/features/ranges.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Rolf Morel -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -40,6 +38,8 @@ impl FromStr for Range { fn parse(s: &str) -> Result { match s.parse::() { Ok(0) => Err("fields and positions are numbered from 1"), + // GNU fails when we are at the limit. Match their behavior + Ok(n) if n == usize::MAX => Err("byte/character offset is too large"), Ok(n) => Ok(n), Err(_) => Err("failed to parse range"), } @@ -161,6 +161,7 @@ pub fn contain(ranges: &[Range], n: usize) -> bool { #[cfg(test)] mod test { use super::{complement, Range}; + use std::str::FromStr; fn m(a: Vec, b: &[Range]) { assert_eq!(Range::merge(a), b); @@ -231,4 +232,33 @@ mod test { // With start and end assert_eq!(complement(&[r(1, 4), r(6, usize::MAX - 1)]), vec![r(5, 5)]); } + + #[test] + fn test_from_str() { + assert_eq!(Range::from_str("5"), Ok(Range { low: 5, high: 5 })); + assert_eq!(Range::from_str("3-5"), Ok(Range { low: 3, high: 5 })); + assert_eq!( + Range::from_str("5-3"), + Err("high end of range less than low end") + ); + assert_eq!(Range::from_str("-"), Err("invalid range with no endpoint")); + assert_eq!( + Range::from_str("3-"), + Ok(Range { + low: 3, + high: usize::MAX - 1 + }) + ); + assert_eq!(Range::from_str("-5"), Ok(Range { low: 1, high: 5 })); + assert_eq!( + Range::from_str("0"), + Err("fields and positions are numbered from 1") + ); + + let max_value = format!("{}", usize::MAX); + assert_eq!( + Range::from_str(&max_value), + Err("byte/character offset is too large") + ); + } } diff --git a/src/uucore/src/lib/features/ringbuffer.rs b/src/uucore/src/lib/features/ringbuffer.rs index 08073fae0..d58c8c498 100644 --- a/src/uucore/src/lib/features/ringbuffer.rs +++ b/src/uucore/src/lib/features/ringbuffer.rs @@ -1,3 +1,7 @@ +// 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. //! A fixed-size ring buffer. use std::collections::VecDeque; diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index c9f7295ee..61482024d 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -1,9 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Maciej Dziardziel -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. // spell-checker:ignore (vars/api) fcntl setrlimit setitimer // spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGPIPE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VTALRM XCPU XFSZ diff --git a/src/uucore/src/lib/features/sum.rs b/src/uucore/src/lib/features/sum.rs index c1cfaf5f8..e079d7a30 100644 --- a/src/uucore/src/lib/features/sum.rs +++ b/src/uucore/src/lib/features/sum.rs @@ -1,9 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Yuan YangHao -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. // spell-checker:ignore memmem algo @@ -178,7 +176,7 @@ impl Digest for CRC { } fn hash_update(&mut self, input: &[u8]) { - for &elt in input.iter() { + for &elt in input { self.update(elt); } self.size += input.len(); @@ -225,7 +223,7 @@ impl Digest for BSD { } fn hash_update(&mut self, input: &[u8]) { - for &byte in input.iter() { + for &byte in input { self.state = (self.state >> 1) + ((self.state & 1) << 15); self.state = self.state.wrapping_add(u16::from(byte)); } @@ -259,7 +257,7 @@ impl Digest for SYSV { } fn hash_update(&mut self, input: &[u8]) { - for &byte in input.iter() { + for &byte in input { self.state = self.state.wrapping_add(u32::from(byte)); } } diff --git a/src/uucore/src/lib/mods/update_control.rs b/src/uucore/src/lib/features/update_control.rs similarity index 98% rename from src/uucore/src/lib/mods/update_control.rs rename to src/uucore/src/lib/features/update_control.rs index e46afd185..bd4292514 100644 --- a/src/uucore/src/lib/mods/update_control.rs +++ b/src/uucore/src/lib/features/update_control.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) John Shin -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 35c5ac5b0..1b6ecbcf5 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // diff --git a/src/uucore/src/lib/mods/version_cmp.rs b/src/uucore/src/lib/features/version_cmp.rs similarity index 98% rename from src/uucore/src/lib/mods/version_cmp.rs rename to src/uucore/src/lib/features/version_cmp.rs index 828b7f2a6..d881a73a1 100644 --- a/src/uucore/src/lib/mods/version_cmp.rs +++ b/src/uucore/src/lib/features/version_cmp.rs @@ -1,3 +1,7 @@ +// 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::cmp::Ordering; /// Compares the non-digit parts of a version. diff --git a/src/uucore/src/lib/features/wide.rs b/src/uucore/src/lib/features/wide.rs index 6b9368f50..68341cd68 100644 --- a/src/uucore/src/lib/features/wide.rs +++ b/src/uucore/src/lib/features/wide.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Peter Atashian -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 7f5cc99db..0540275ee 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -1,8 +1,9 @@ +// 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. // library ~ (core/bundler file) -// Copyright (C) ~ Alex Lyon -// Copyright (C) ~ Roy Ivy III ; MIT license - // * feature-gated external crates (re-shared as public internal modules) #[cfg(feature = "libc")] pub extern crate libc; @@ -19,15 +20,11 @@ mod parser; // string parsing modules pub use uucore_procs::*; // * cross-platform modules -pub use crate::mods::backup_control; pub use crate::mods::display; pub use crate::mods::error; +pub use crate::mods::line_ending; pub use crate::mods::os; pub use crate::mods::panic; -pub use crate::mods::quoting_style; -pub use crate::mods::ranges; -pub use crate::mods::update_control; -pub use crate::mods::version_cmp; // * string parsing modules pub use crate::parser::parse_glob; @@ -36,6 +33,8 @@ pub use crate::parser::parse_time; pub use crate::parser::shortcut_value_parser; // * feature-gated modules +#[cfg(feature = "backup-control")] +pub use crate::features::backup_control; #[cfg(feature = "encoding")] pub use crate::features::encoding; #[cfg(feature = "fs")] @@ -46,10 +45,20 @@ pub use crate::features::fsext; pub use crate::features::lines; #[cfg(feature = "format")] pub use crate::features::format; +#[cfg(feature = "memo")] +pub use crate::features::memo; +#[cfg(feature = "quoting-style")] +pub use crate::features::quoting_style; +#[cfg(feature = "ranges")] +pub use crate::features::ranges; #[cfg(feature = "ringbuffer")] pub use crate::features::ringbuffer; #[cfg(feature = "sum")] pub use crate::features::sum; +#[cfg(feature = "update-control")] +pub use crate::features::update_control; +#[cfg(feature = "version-cmp")] +pub use crate::features::version_cmp; // * (platform-specific) feature-gated modules // ** non-windows (i.e. Unix + Fuchsia) @@ -86,6 +95,10 @@ use std::sync::atomic::Ordering; use once_cell::sync::Lazy; +/// Execute utility code for `util`. +/// +/// This macro expands to a main function that invokes the `uumain` function in `util` +/// Exits with code returned by `uumain`. #[macro_export] macro_rules! bin { ($util:ident) => { @@ -124,8 +137,8 @@ pub fn set_utility_is_second_arg() { static ARGV: Lazy> = Lazy::new(|| wild::args_os().collect()); static UTIL_NAME: Lazy = Lazy::new(|| { - let base_index = if get_utility_is_second_arg() { 1 } else { 0 }; - let is_man = if ARGV[base_index].eq("manpage") { 1 } else { 0 }; + let base_index = usize::from(get_utility_is_second_arg()); + let is_man = usize::from(ARGV[base_index].eq("manpage")); let argv_index = base_index + is_man; ARGV[argv_index].to_string_lossy().into_owned() diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index cc8bdafd8..ad86d5308 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -1,3 +1,8 @@ +// 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 fix broken links #![allow(rustdoc::broken_intra_doc_links)] //! Macros for the uucore utilities. @@ -36,8 +41,6 @@ use std::sync::atomic::AtomicBool; // This file is part of the uutils coreutils package. // -// (c) Alex Lyon -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index 71d288c69..986536d6d 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -1,12 +1,11 @@ +// 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. // mods ~ cross-platforms modules (core/bundler file) -pub mod backup_control; pub mod display; pub mod error; +pub mod line_ending; pub mod os; pub mod panic; -pub mod ranges; -pub mod update_control; -pub mod version_cmp; -// dir and vdir also need access to the quoting_style module -pub mod quoting_style; diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index 95288973a..db92c4887 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -1,3 +1,7 @@ +// 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. /// Utilities for printing paths, with special attention paid to special /// characters and invalid unicode. /// diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index f0f62569d..82644ae8a 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -1,3 +1,7 @@ +// 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 fix broken links #![allow(rustdoc::broken_intra_doc_links)] //! All utils return exit with an exit code. Usually, the following scheme is used: diff --git a/src/uucore/src/lib/mods/line_ending.rs b/src/uucore/src/lib/mods/line_ending.rs new file mode 100644 index 000000000..6fe608e7d --- /dev/null +++ b/src/uucore/src/lib/mods/line_ending.rs @@ -0,0 +1,53 @@ +// 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. +//! Provides consistent newline/zero terminator handling for `-z`/`--zero` flags. +//! +//! See the [`LineEnding`] struct for more information. +use std::fmt::Display; + +/// Line ending of either `\n` or `\0` +/// +/// Used by various utilities that have the option to separate lines by nul +/// characters instead of `\n`. Usually, this is specified with the `-z` or +/// `--zero` flag. +/// +/// The [`Display`] implementation writes the character corresponding to the +/// variant to the formatter. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub enum LineEnding { + #[default] + Newline = b'\n', + Nul = 0, +} + +impl Display for LineEnding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Newline => writeln!(f), + Self::Nul => write!(f, "\0"), + } + } +} + +impl From for u8 { + fn from(line_ending: LineEnding) -> Self { + line_ending as Self + } +} + +impl LineEnding { + /// Create a [`LineEnding`] from a `-z`/`--zero` flag + /// + /// If `is_zero_terminated` is true, [`LineEnding::Nul`] is returned, + /// otherwise [`LineEnding::Newline`]. + pub fn from_zero_flag(is_zero_terminated: bool) -> Self { + if is_zero_terminated { + Self::Nul + } else { + Self::Newline + } + } +} diff --git a/src/uucore/src/lib/mods/os.rs b/src/uucore/src/lib/mods/os.rs index dd06f4739..55bf844b5 100644 --- a/src/uucore/src/lib/mods/os.rs +++ b/src/uucore/src/lib/mods/os.rs @@ -1,3 +1,7 @@ +// 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. /// Test if the program is running under WSL // ref: @@ diff --git a/src/uucore/src/lib/mods/panic.rs b/src/uucore/src/lib/mods/panic.rs index 5a1e20d80..f42b29581 100644 --- a/src/uucore/src/lib/mods/panic.rs +++ b/src/uucore/src/lib/mods/panic.rs @@ -1,3 +1,7 @@ +// 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. //! Custom panic hooks that allow silencing certain types of errors. //! //! Use the [`mute_sigpipe_panic`] function to silence panics caused by diff --git a/src/uucore/src/lib/parser.rs b/src/uucore/src/lib/parser.rs index fc3e46b5c..a0de6c0d4 100644 --- a/src/uucore/src/lib/parser.rs +++ b/src/uucore/src/lib/parser.rs @@ -1,3 +1,7 @@ +// 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. pub mod parse_glob; pub mod parse_size; pub mod parse_time; diff --git a/src/uucore/src/lib/parser/parse_glob.rs b/src/uucore/src/lib/parser/parse_glob.rs index a321d470b..9215dd7bf 100644 --- a/src/uucore/src/lib/parser/parse_glob.rs +++ b/src/uucore/src/lib/parser/parse_glob.rs @@ -1,3 +1,7 @@ +// 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. //! Parsing a glob Pattern from a string. //! //! Use the [`from_str`] function to parse a [`Pattern`] from a string. diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 2ea84e389..0a46ce327 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -1,12 +1,13 @@ -// * 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. +// 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 (ToDO) hdsf ghead gtail ACDBK hexdigit use std::error::Error; use std::fmt; +use std::num::IntErrorKind; use crate::display::Quotable; @@ -50,7 +51,7 @@ impl<'parser> Parser<'parser> { /// Parse a size string into a number of bytes. /// /// A size string comprises an integer and an optional unit. The unit - /// may be K, M, G, T, P, E, Z or Y (powers of 1024), or KB, MB, + /// may be K, M, G, T, P, E, Z, Y, R or Q (powers of 1024), or KB, MB, /// etc. (powers of 1000), or b which is 512. /// Binary prefixes can be used, too: KiB=K, MiB=M, and so on. /// @@ -64,18 +65,23 @@ impl<'parser> Parser<'parser> { /// # Examples /// /// ```rust - /// use uucore::parse_size::parse_size; - /// assert_eq!(Ok(123), parse_size("123")); - /// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000 - /// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024 - /// assert_eq!(Ok(44251 * 1024), parse_size("0xACDBK")); + /// use uucore::parse_size::Parser; + /// let parser = Parser { + /// default_unit: Some("M"), + /// ..Default::default() + /// }; + /// assert_eq!(Ok(123 * 1024 * 1024), parser.parse("123M")); // M is 1024^2 + /// assert_eq!(Ok(123 * 1024 * 1024), parser.parse("123")); // default unit set to "M" on parser instance + /// assert_eq!(Ok(9 * 1000), parser.parse("9kB")); // kB is 1000 + /// assert_eq!(Ok(2 * 1024), parser.parse("2K")); // K is 1024 + /// assert_eq!(Ok(44251 * 1024), parser.parse("0xACDBK")); // 0xACDB is 44251 in decimal /// ``` - pub fn parse(&self, size: &str) -> Result { + pub fn parse(&self, size: &str) -> Result { if size.is_empty() { return Err(ParseSizeError::parse_failure(size)); } - let number_system: NumberSystem = self.determine_number_system(size); + let number_system = Self::determine_number_system(size); // Split the size argument into numeric and unit parts // For example, if the argument is "123K", the numeric part is "123", and @@ -134,6 +140,8 @@ impl<'parser> Parser<'parser> { "EiB" | "eiB" | "E" | "e" => (1024, 6), "ZiB" | "ziB" | "Z" | "z" => (1024, 7), "YiB" | "yiB" | "Y" | "y" => (1024, 8), + "RiB" | "riB" | "R" | "r" => (1024, 9), + "QiB" | "qiB" | "Q" | "q" => (1024, 10), "KB" | "kB" => (1000, 1), "MB" | "mB" => (1000, 2), "GB" | "gB" => (1000, 3), @@ -142,30 +150,29 @@ impl<'parser> Parser<'parser> { "EB" | "eB" => (1000, 6), "ZB" | "zB" => (1000, 7), "YB" | "yB" => (1000, 8), + "RB" | "rB" => (1000, 9), + "QB" | "qB" => (1000, 10), _ if numeric_string.is_empty() => return Err(ParseSizeError::parse_failure(size)), _ => return Err(ParseSizeError::invalid_suffix(size)), }; - let factor = match u64::try_from(base.pow(exponent)) { - Ok(n) => n, - Err(_) => return Err(ParseSizeError::size_too_big(size)), - }; + let factor = base.pow(exponent); - // parse string into u64 - let number: u64 = match number_system { + // parse string into u128 + let number: u128 = match number_system { NumberSystem::Decimal => { if numeric_string.is_empty() { 1 } else { - self.parse_number(&numeric_string, 10, size)? + Self::parse_number(&numeric_string, 10, size)? } } NumberSystem::Octal => { let trimmed_string = numeric_string.trim_start_matches('0'); - self.parse_number(trimmed_string, 8, size)? + Self::parse_number(trimmed_string, 8, size)? } NumberSystem::Hexadecimal => { let trimmed_string = numeric_string.trim_start_matches("0x"); - self.parse_number(trimmed_string, 16, size)? + Self::parse_number(trimmed_string, 16, size)? } }; @@ -174,7 +181,58 @@ impl<'parser> Parser<'parser> { .ok_or_else(|| ParseSizeError::size_too_big(size)) } - fn determine_number_system(&self, size: &str) -> NumberSystem { + /// Explicit u128 alias for `parse()` + pub fn parse_u128(&self, size: &str) -> Result { + self.parse(size) + } + + /// Same as `parse()` but tries to return u64 + pub fn parse_u64(&self, size: &str) -> Result { + match self.parse(size) { + Ok(num_u128) => { + let num_u64 = match u64::try_from(num_u128) { + Ok(n) => n, + Err(_) => return Err(ParseSizeError::size_too_big(size)), + }; + Ok(num_u64) + } + Err(e) => Err(e), + } + } + + /// Same as `parse_u64()`, except returns `u64::MAX` on overflow + /// GNU lib/coreutils include similar functionality + /// and GNU test suite checks this behavior for some utils (`split` for example) + pub fn parse_u64_max(&self, size: &str) -> Result { + let result = self.parse_u64(size); + match result { + Ok(_) => result, + Err(error) => { + if let ParseSizeError::SizeTooBig(_) = error { + Ok(u64::MAX) + } else { + Err(error) + } + } + } + } + + /// Same as `parse_u64_max()`, except for u128, i.e. returns `u128::MAX` on overflow + pub fn parse_u128_max(&self, size: &str) -> Result { + let result = self.parse_u128(size); + match result { + Ok(_) => result, + Err(error) => { + if let ParseSizeError::SizeTooBig(_) = error { + Ok(u128::MAX) + } else { + Err(error) + } + } + } + } + + fn determine_number_system(size: &str) -> NumberSystem { if size.len() <= 1 { return NumberSystem::Decimal; } @@ -197,42 +255,55 @@ impl<'parser> Parser<'parser> { } fn parse_number( - &self, numeric_string: &str, radix: u32, original_size: &str, - ) -> Result { - u64::from_str_radix(numeric_string, radix) - .map_err(|_| ParseSizeError::ParseFailure(original_size.to_string())) + ) -> Result { + u128::from_str_radix(numeric_string, radix).map_err(|e| match e.kind() { + IntErrorKind::PosOverflow => ParseSizeError::size_too_big(original_size), + _ => ParseSizeError::ParseFailure(original_size.to_string()), + }) } } -/// Parse a size string into a number of bytes. -/// -/// A size string comprises an integer and an optional unit. The unit -/// may be K, M, G, T, P, E, Z or Y (powers of 1024), or KB, MB, -/// etc. (powers of 1000), or b which is 512. -/// Binary prefixes can be used, too: KiB=K, MiB=M, and so on. -/// -/// # Errors -/// -/// Will return `ParseSizeError` if it's not possible to parse this -/// string into a number, e.g. if the string does not begin with a -/// numeral, or if the unit is not one of the supported units described -/// in the preceding section. +/// Parse a size string into a number of bytes +/// using Default Parser (no custom settings) /// /// # Examples /// /// ```rust -/// use uucore::parse_size::parse_size; -/// assert_eq!(Ok(123), parse_size("123")); -/// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000 -/// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024 +/// use uucore::parse_size::parse_size_u128; +/// assert_eq!(Ok(123), parse_size_u128("123")); +/// assert_eq!(Ok(9 * 1000), parse_size_u128("9kB")); // kB is 1000 +/// assert_eq!(Ok(2 * 1024), parse_size_u128("2K")); // K is 1024 +/// assert_eq!(Ok(44251 * 1024), parse_size_u128("0xACDBK")); /// ``` -pub fn parse_size(size: &str) -> Result { +pub fn parse_size_u128(size: &str) -> Result { Parser::default().parse(size) } +/// Same as `parse_size_u128()`, but for u64 +pub fn parse_size_u64(size: &str) -> Result { + Parser::default().parse_u64(size) +} + +#[deprecated = "Please use parse_size_u64(size: &str) -> Result OR parse_size_u128(size: &str) -> Result instead."] +pub fn parse_size(size: &str) -> Result { + parse_size_u64(size) +} + +/// Same as `parse_size_u64()`, except returns `u64::MAX` on overflow +/// GNU lib/coreutils include similar functionality +/// and GNU test suite checks this behavior for some utils +pub fn parse_size_u64_max(size: &str) -> Result { + Parser::default().parse_u64_max(size) +} + +/// Same as `parse_size_u128()`, except returns `u128::MAX` on overflow +pub fn parse_size_u128_max(size: &str) -> Result { + Parser::default().parse_u128_max(size) +} + #[derive(Debug, PartialEq, Eq)] pub enum ParseSizeError { InvalidSuffix(String), // Suffix @@ -336,7 +407,7 @@ mod tests { #[test] fn all_suffixes() { - // Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000). + // Units are K,M,G,T,P,E,Z,Y,R,Q (powers of 1024) or KB,MB,... (powers of 1000). // Binary prefixes can be used, too: KiB=K, MiB=M, and so on. let suffixes = [ ('K', 1u32), @@ -345,52 +416,90 @@ mod tests { ('T', 4u32), ('P', 5u32), ('E', 6u32), - // The following will always result ParseSizeError::SizeTooBig as they cannot fit in u64 - // ('Z', 7u32), - // ('Y', 8u32), + ('Z', 7u32), + ('Y', 8u32), + ('R', 9u32), + ('Q', 10u32), ]; for &(c, exp) in &suffixes { let s = format!("2{c}B"); // KB - assert_eq!(Ok((2 * (1000_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok((2 * (1000_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("2{c}"); // K - assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("2{c}iB"); // KiB - assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("2{}iB", c.to_lowercase()); // kiB - assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u128), parse_size_u128(&s)); // suffix only let s = format!("{c}B"); // KB - assert_eq!(Ok(((1000_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok(((1000_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("{c}"); // K - assert_eq!(Ok(((1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("{c}iB"); // KiB - assert_eq!(Ok(((1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("{}iB", c.to_lowercase()); // kiB - assert_eq!(Ok(((1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as u128), parse_size_u128(&s)); } } #[test] #[cfg(not(target_pointer_width = "128"))] fn overflow_x64() { - assert!(parse_size("10000000000000000000000").is_err()); - assert!(parse_size("1000000000T").is_err()); - assert!(parse_size("100000P").is_err()); - assert!(parse_size("100E").is_err()); - assert!(parse_size("1Z").is_err()); - assert!(parse_size("1Y").is_err()); + assert!(parse_size_u64("10000000000000000000000").is_err()); + assert!(parse_size_u64("1000000000T").is_err()); + assert!(parse_size_u64("100000P").is_err()); + assert!(parse_size_u64("100E").is_err()); + assert!(parse_size_u64("1Z").is_err()); + assert!(parse_size_u64("1Y").is_err()); + assert!(parse_size_u64("1R").is_err()); + assert!(parse_size_u64("1Q").is_err()); assert!(variant_eq( - &parse_size("1Z").unwrap_err(), + &parse_size_u64("1Z").unwrap_err(), &ParseSizeError::SizeTooBig(String::new()) )); assert_eq!( ParseSizeError::SizeTooBig("'1Y': Value too large for defined data type".to_string()), - parse_size("1Y").unwrap_err() + parse_size_u64("1Y").unwrap_err() ); + assert_eq!( + ParseSizeError::SizeTooBig("'1R': Value too large for defined data type".to_string()), + parse_size_u64("1R").unwrap_err() + ); + assert_eq!( + ParseSizeError::SizeTooBig("'1Q': Value too large for defined data type".to_string()), + parse_size_u64("1Q").unwrap_err() + ); + } + + #[test] + #[cfg(not(target_pointer_width = "128"))] + fn overflow_to_max_u64() { + assert_eq!(Ok(1_099_511_627_776), parse_size_u64_max("1T")); + assert_eq!(Ok(1_125_899_906_842_624), parse_size_u64_max("1P")); + assert_eq!(Ok(u64::MAX), parse_size_u64_max("18446744073709551616")); + assert_eq!(Ok(u64::MAX), parse_size_u64_max("10000000000000000000000")); + assert_eq!(Ok(u64::MAX), parse_size_u64_max("1Y")); + assert_eq!(Ok(u64::MAX), parse_size_u64_max("1R")); + assert_eq!(Ok(u64::MAX), parse_size_u64_max("1Q")); + } + + #[test] + #[cfg(not(target_pointer_width = "128"))] + fn overflow_to_max_u128() { + assert_eq!( + Ok(12_379_400_392_853_802_748_991_242_240), + parse_size_u128_max("10R") + ); + assert_eq!( + Ok(12_676_506_002_282_294_014_967_032_053_760), + parse_size_u128_max("10Q") + ); + assert_eq!(Ok(u128::MAX), parse_size_u128_max("1000000000000R")); + assert_eq!(Ok(u128::MAX), parse_size_u128_max("1000000000Q")); } #[test] @@ -398,7 +507,7 @@ mod tests { let test_strings = ["5mib", "1eb", "1H"]; for &test_string in &test_strings { assert_eq!( - parse_size(test_string).unwrap_err(), + parse_size_u64(test_string).unwrap_err(), ParseSizeError::InvalidSuffix(format!("{}", test_string.quote())) ); } @@ -409,7 +518,7 @@ mod tests { let test_strings = ["biB", "-", "+", "", "-1", "∞"]; for &test_string in &test_strings { assert_eq!( - parse_size(test_string).unwrap_err(), + parse_size_u64(test_string).unwrap_err(), ParseSizeError::ParseFailure(format!("{}", test_string.quote())) ); } @@ -417,57 +526,83 @@ mod tests { #[test] fn b_suffix() { - assert_eq!(Ok(3 * 512), parse_size("3b")); // b is 512 + assert_eq!(Ok(3 * 512), parse_size_u64("3b")); // b is 512 } #[test] fn no_suffix() { - assert_eq!(Ok(1234), parse_size("1234")); - assert_eq!(Ok(0), parse_size("0")); - assert_eq!(Ok(5), parse_size("5")); - assert_eq!(Ok(999), parse_size("999")); + assert_eq!(Ok(1234), parse_size_u64("1234")); + assert_eq!(Ok(0), parse_size_u64("0")); + assert_eq!(Ok(5), parse_size_u64("5")); + assert_eq!(Ok(999), parse_size_u64("999")); } #[test] fn kilobytes_suffix() { - assert_eq!(Ok(123 * 1000), parse_size("123KB")); // KB is 1000 - assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000 - assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024 - assert_eq!(Ok(0), parse_size("0K")); - assert_eq!(Ok(0), parse_size("0KB")); - assert_eq!(Ok(1000), parse_size("KB")); - assert_eq!(Ok(1024), parse_size("K")); - assert_eq!(Ok(2000), parse_size("2kB")); - assert_eq!(Ok(4000), parse_size("4KB")); + assert_eq!(Ok(123 * 1000), parse_size_u64("123KB")); // KB is 1000 + assert_eq!(Ok(9 * 1000), parse_size_u64("9kB")); // kB is 1000 + assert_eq!(Ok(2 * 1024), parse_size_u64("2K")); // K is 1024 + assert_eq!(Ok(0), parse_size_u64("0K")); + assert_eq!(Ok(0), parse_size_u64("0KB")); + assert_eq!(Ok(1000), parse_size_u64("KB")); + assert_eq!(Ok(1024), parse_size_u64("K")); + assert_eq!(Ok(2000), parse_size_u64("2kB")); + assert_eq!(Ok(4000), parse_size_u64("4KB")); } #[test] fn megabytes_suffix() { - assert_eq!(Ok(123 * 1024 * 1024), parse_size("123M")); - assert_eq!(Ok(123 * 1000 * 1000), parse_size("123MB")); - assert_eq!(Ok(1024 * 1024), parse_size("M")); - assert_eq!(Ok(1000 * 1000), parse_size("MB")); - assert_eq!(Ok(2 * 1_048_576), parse_size("2m")); - assert_eq!(Ok(4 * 1_048_576), parse_size("4M")); - assert_eq!(Ok(2_000_000), parse_size("2mB")); - assert_eq!(Ok(4_000_000), parse_size("4MB")); + assert_eq!(Ok(123 * 1024 * 1024), parse_size_u64("123M")); + assert_eq!(Ok(123 * 1000 * 1000), parse_size_u64("123MB")); + assert_eq!(Ok(1024 * 1024), parse_size_u64("M")); + assert_eq!(Ok(1000 * 1000), parse_size_u64("MB")); + assert_eq!(Ok(2 * 1_048_576), parse_size_u64("2m")); + assert_eq!(Ok(4 * 1_048_576), parse_size_u64("4M")); + assert_eq!(Ok(2_000_000), parse_size_u64("2mB")); + assert_eq!(Ok(4_000_000), parse_size_u64("4MB")); } #[test] fn gigabytes_suffix() { - assert_eq!(Ok(1_073_741_824), parse_size("1G")); - assert_eq!(Ok(2_000_000_000), parse_size("2GB")); + assert_eq!(Ok(1_073_741_824), parse_size_u64("1G")); + assert_eq!(Ok(2_000_000_000), parse_size_u64("2GB")); } #[test] #[cfg(target_pointer_width = "64")] fn x64() { - assert_eq!(Ok(1_099_511_627_776), parse_size("1T")); - assert_eq!(Ok(1_125_899_906_842_624), parse_size("1P")); - assert_eq!(Ok(1_152_921_504_606_846_976), parse_size("1E")); - assert_eq!(Ok(2_000_000_000_000), parse_size("2TB")); - assert_eq!(Ok(2_000_000_000_000_000), parse_size("2PB")); - assert_eq!(Ok(2_000_000_000_000_000_000), parse_size("2EB")); + assert_eq!(Ok(1_099_511_627_776), parse_size_u64("1T")); + assert_eq!(Ok(1_125_899_906_842_624), parse_size_u64("1P")); + assert_eq!(Ok(1_152_921_504_606_846_976), parse_size_u64("1E")); + + assert_eq!(Ok(1_180_591_620_717_411_303_424), parse_size_u128("1Z")); + assert_eq!(Ok(1_208_925_819_614_629_174_706_176), parse_size_u128("1Y")); + assert_eq!( + Ok(1_237_940_039_285_380_274_899_124_224), + parse_size_u128("1R") + ); + assert_eq!( + Ok(1_267_650_600_228_229_401_496_703_205_376), + parse_size_u128("1Q") + ); + + assert_eq!(Ok(2_000_000_000_000), parse_size_u64("2TB")); + assert_eq!(Ok(2_000_000_000_000_000), parse_size_u64("2PB")); + assert_eq!(Ok(2_000_000_000_000_000_000), parse_size_u64("2EB")); + + assert_eq!(Ok(2_000_000_000_000_000_000_000), parse_size_u128("2ZB")); + assert_eq!( + Ok(2_000_000_000_000_000_000_000_000), + parse_size_u128("2YB") + ); + assert_eq!( + Ok(2_000_000_000_000_000_000_000_000_000), + parse_size_u128("2RB") + ); + assert_eq!( + Ok(2_000_000_000_000_000_000_000_000_000_000), + parse_size_u128("2QB") + ); } #[test] @@ -490,7 +625,7 @@ mod tests { parser .with_allow_list(&[ - "b", "k", "K", "m", "M", "MB", "g", "G", "t", "T", "P", "E", "Z", "Y", + "b", "k", "K", "m", "M", "MB", "g", "G", "t", "T", "P", "E", "Z", "Y", "R", "Q", ]) .with_default_unit("K") .with_b_byte_count(true); @@ -500,6 +635,14 @@ mod tests { assert_eq!(Ok(1000 * 1000), parser.parse("1MB")); assert_eq!(Ok(1024 * 1024), parser.parse("1M")); assert_eq!(Ok(1024 * 1024 * 1024), parser.parse("1G")); + assert_eq!( + Ok(1_237_940_039_285_380_274_899_124_224), + parser.parse_u128("1R") + ); + assert_eq!( + Ok(1_267_650_600_228_229_401_496_703_205_376), + parser.parse_u128("1Q") + ); assert_eq!(Ok(1), parser.parse("1b")); assert_eq!(Ok(1024), parser.parse("1024b")); @@ -512,15 +655,15 @@ mod tests { #[test] fn parse_octal_size() { - assert_eq!(Ok(63), parse_size("077")); - assert_eq!(Ok(528), parse_size("01020")); - assert_eq!(Ok(668 * 1024), parse_size("01234K")); + assert_eq!(Ok(63), parse_size_u64("077")); + assert_eq!(Ok(528), parse_size_u64("01020")); + assert_eq!(Ok(668 * 1024), parse_size_u128("01234K")); } #[test] fn parse_hex_size() { - assert_eq!(Ok(10), parse_size("0xA")); - assert_eq!(Ok(94722), parse_size("0x17202")); - assert_eq!(Ok(44251 * 1024), parse_size("0xACDBK")); + assert_eq!(Ok(10), parse_size_u64("0xA")); + assert_eq!(Ok(94722), parse_size_u64("0x17202")); + assert_eq!(Ok(44251 * 1024), parse_size_u128("0xACDBK")); } } diff --git a/src/uucore/src/lib/parser/parse_time.rs b/src/uucore/src/lib/parser/parse_time.rs index 1a7b66e90..727ee28b1 100644 --- a/src/uucore/src/lib/parser/parse_time.rs +++ b/src/uucore/src/lib/parser/parse_time.rs @@ -1,7 +1,5 @@ // This file is part of the uutils coreutils package. // -// (c) Alex Lyon -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/src/uucore/src/lib/parser/shortcut_value_parser.rs b/src/uucore/src/lib/parser/shortcut_value_parser.rs index 0b0716158..49bb2b62b 100644 --- a/src/uucore/src/lib/parser/shortcut_value_parser.rs +++ b/src/uucore/src/lib/parser/shortcut_value_parser.rs @@ -1,3 +1,8 @@ +// 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 abcdefgh use clap::{ builder::{PossibleValue, TypedValueParser}, error::{ContextKind, ContextValue, ErrorKind}, @@ -15,6 +20,34 @@ impl ShortcutValueParser { pub fn new(values: impl Into) -> Self { values.into() } + + fn generate_clap_error( + &self, + cmd: &clap::Command, + arg: Option<&clap::Arg>, + value: &str, + ) -> clap::Error { + let mut err = clap::Error::new(ErrorKind::InvalidValue).with_cmd(cmd); + + if let Some(arg) = arg { + err.insert( + ContextKind::InvalidArg, + ContextValue::String(arg.to_string()), + ); + } + + err.insert( + ContextKind::InvalidValue, + ContextValue::String(value.to_string()), + ); + + err.insert( + ContextKind::ValidValue, + ContextValue::Strings(self.0.iter().map(|x| x.get_name().to_string()).collect()), + ); + + err + } } impl TypedValueParser for ShortcutValueParser { @@ -36,29 +69,16 @@ impl TypedValueParser for ShortcutValueParser { .filter(|x| x.get_name().starts_with(value)) .collect(); - if matched_values.len() == 1 { - Ok(matched_values[0].get_name().to_string()) - } else { - let mut err = clap::Error::new(ErrorKind::InvalidValue).with_cmd(cmd); - - if let Some(arg) = arg { - err.insert( - ContextKind::InvalidArg, - ContextValue::String(arg.to_string()), - ); + match matched_values.len() { + 0 => Err(self.generate_clap_error(cmd, arg, value)), + 1 => Ok(matched_values[0].get_name().to_string()), + _ => { + if let Some(direct_match) = matched_values.iter().find(|x| x.get_name() == value) { + Ok(direct_match.get_name().to_string()) + } else { + Err(self.generate_clap_error(cmd, arg, value)) + } } - - err.insert( - ContextKind::InvalidValue, - ContextValue::String(value.to_string()), - ); - - err.insert( - ContextKind::ValidValue, - ContextValue::Strings(self.0.iter().map(|x| x.get_name().to_string()).collect()), - ); - - Err(err) } } @@ -127,6 +147,14 @@ mod tests { assert_eq!("abef", result.unwrap()); } + #[test] + fn test_parse_ref_with_ambiguous_value_that_is_a_possible_value() { + let cmd = Command::new("cmd"); + let parser = ShortcutValueParser::new(["abcd", "abcdefgh"]); + let result = parser.parse_ref(&cmd, None, OsStr::new("abcd")); + assert_eq!("abcd", result.unwrap()); + } + #[test] #[cfg(unix)] fn test_parse_ref_with_invalid_utf8() { diff --git a/src/uucore_procs/Cargo.toml b/src/uucore_procs/Cargo.toml index 976ea014d..55d880961 100644 --- a/src/uucore_procs/Cargo.toml +++ b/src/uucore_procs/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore uuhelp [package] name = "uucore_procs" -version = "0.0.20" +version = "0.0.22" authors = ["Roy Ivy III "] license = "MIT" description = "uutils ~ 'uucore' proc-macros" @@ -19,4 +19,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.20" } +uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.22" } diff --git a/src/uucore_procs/src/lib.rs b/src/uucore_procs/src/lib.rs index b78da7822..cbe915936 100644 --- a/src/uucore_procs/src/lib.rs +++ b/src/uucore_procs/src/lib.rs @@ -1,4 +1,8 @@ -// Copyright (C) ~ Roy Ivy III ; MIT license +// 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 backticks uuhelp use std::{fs::File, io::Read, path::PathBuf}; diff --git a/src/uuhelp_parser/Cargo.toml b/src/uuhelp_parser/Cargo.toml index 6a7aecd80..007f1ebb2 100644 --- a/src/uuhelp_parser/Cargo.toml +++ b/src/uuhelp_parser/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore uuhelp [package] name = "uuhelp_parser" -version = "0.0.20" +version = "0.0.22" edition = "2021" license = "MIT" description = "A collection of functions to parse the markdown code of help files" diff --git a/tests/benches/factor/benches/gcd.rs b/tests/benches/factor/benches/gcd.rs index a9d2a8c55..61545145f 100644 --- a/tests/benches/factor/benches/gcd.rs +++ b/tests/benches/factor/benches/gcd.rs @@ -1,3 +1,7 @@ +// 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 criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use uu_factor::numeric; diff --git a/tests/benches/factor/benches/table.rs b/tests/benches/factor/benches/table.rs index 59e8db1f3..f666d72d5 100644 --- a/tests/benches/factor/benches/table.rs +++ b/tests/benches/factor/benches/table.rs @@ -1,3 +1,7 @@ +// 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 array_init::array_init; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use uu_factor::{table::*, Factors}; diff --git a/tests/by-util/test_arch.rs b/tests/by-util/test_arch.rs index 603e1bc49..daf4e32f5 100644 --- a/tests/by-util/test_arch.rs +++ b/tests/by-util/test_arch.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index 395c96c2c..8bb5bda54 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -1,9 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. // use crate::common::util::TestScenario; diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index 7cd44eefd..b46507fae 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 88b0c71c8..73b44ff75 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -1,3 +1,7 @@ +// 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 (words) reallylongexecutable use crate::common::util::TestScenario; diff --git a/tests/by-util/test_basenc.rs b/tests/by-util/test_basenc.rs index 401c23d45..6c71b63f7 100644 --- a/tests/by-util/test_basenc.rs +++ b/tests/by-util/test_basenc.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index c32828a83..27f40356d 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -1,3 +1,7 @@ +// 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 NOFILE #[cfg(not(windows))] diff --git a/tests/by-util/test_chcon.rs b/tests/by-util/test_chcon.rs index bea5462b4..71405e451 100644 --- a/tests/by-util/test_chcon.rs +++ b/tests/by-util/test_chcon.rs @@ -1,3 +1,7 @@ +// 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 (jargon) xattributes #![cfg(feature = "feat_selinux")] diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index cf6c62ff7..07966b67b 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -1,3 +1,7 @@ +// 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 (words) nosuchgroup groupname use crate::common::util::TestScenario; diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 9e3c7d2da..be730a8c0 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::{AtPath, TestScenario, UCommand}; use once_cell::sync::Lazy; use std::fs::{metadata, set_permissions, OpenOptions, Permissions}; diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 7a1a4a6bd..0a2e23c6d 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -1,3 +1,7 @@ +// 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 (words) agroupthatdoesntexist auserthatdoesntexist cuuser groupname notexisting passgrp use crate::common::util::{is_ci, run_ucmd_as_root, CmdResult, TestScenario}; @@ -744,7 +748,7 @@ fn test_chown_file_notexisting() { } #[test] -fn test_chown_no_change_to_user_from_user() { +fn test_chown_no_change_to_user() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -755,99 +759,22 @@ fn test_chown_no_change_to_user_from_user() { let user_name = String::from(result.stdout_str().trim()); assert!(!user_name.is_empty()); - let file = "f"; - at.touch(file); - scene - .ucmd() - .arg("-v") - .arg("--from=42") - .arg("43") - .arg(file) - .succeeds() - .stdout_only(format!("ownership of '{file}' retained as {user_name}\n")); + for (i, from) in ["42", ":42", "42:42"].iter().enumerate() { + let file = i.to_string(); + at.touch(&file); + scene + .ucmd() + .arg("-v") + .arg(format!("--from={from}")) + .arg("43") + .arg(&file) + .succeeds() + .stdout_only(format!("ownership of '{file}' retained as {user_name}\n")); + } } #[test] -fn test_chown_no_change_to_user_from_group() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - let result = scene.cmd("whoami").run(); - if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { - return; - } - let user_name = String::from(result.stdout_str().trim()); - assert!(!user_name.is_empty()); - - let file = "f"; - at.touch(file); - scene - .ucmd() - .arg("-v") - .arg("--from=:42") - .arg("43") - .arg(file) - .succeeds() - .stdout_only(format!("ownership of '{file}' retained as {user_name}\n")); -} - -#[test] -fn test_chown_no_change_to_user_from_user_group() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - let result = scene.cmd("whoami").run(); - if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { - return; - } - let user_name = String::from(result.stdout_str().trim()); - assert!(!user_name.is_empty()); - - let file = "f"; - at.touch(file); - scene - .ucmd() - .arg("-v") - .arg("--from=42:42") - .arg("43") - .arg(file) - .succeeds() - .stdout_only(format!("ownership of '{file}' retained as {user_name}\n")); -} - -#[test] -fn test_chown_no_change_to_group_from_user() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - let result = scene.cmd("whoami").run(); - if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { - return; - } - let user_name = String::from(result.stdout_str().trim()); - assert!(!user_name.is_empty()); - - let result = scene.cmd("id").arg("-ng").run(); - if skipping_test_is_okay(&result, "id: cannot find name for group ID") { - return; - } - let group_name = String::from(result.stdout_str().trim()); - assert!(!group_name.is_empty()); - - let file = "f"; - at.touch(file); - scene - .ucmd() - .arg("-v") - .arg("--from=42") - .arg(":43") - .arg(file) - .succeeds() - .stdout_only(format!("ownership of '{file}' retained as {group_name}\n")); -} - -#[test] -fn test_chown_no_change_to_group_from_group() { +fn test_chown_no_change_to_group() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -864,20 +791,22 @@ fn test_chown_no_change_to_group_from_group() { let group_name = String::from(result.stdout_str().trim()); assert!(!group_name.is_empty()); - let file = "f"; - at.touch(file); - scene - .ucmd() - .arg("-v") - .arg("--from=:42") - .arg(":43") - .arg(file) - .succeeds() - .stdout_only(format!("ownership of '{file}' retained as {group_name}\n")); + for (i, from) in ["42", ":42", "42:42"].iter().enumerate() { + let file = i.to_string(); + at.touch(&file); + scene + .ucmd() + .arg("-v") + .arg(format!("--from={from}")) + .arg(":43") + .arg(&file) + .succeeds() + .stdout_only(format!("ownership of '{file}' retained as {group_name}\n")); + } } #[test] -fn test_chown_no_change_to_group_from_user_group() { +fn test_chown_no_change_to_user_group() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -894,111 +823,18 @@ fn test_chown_no_change_to_group_from_user_group() { let group_name = String::from(result.stdout_str().trim()); assert!(!group_name.is_empty()); - let file = "f"; - at.touch(file); - scene - .ucmd() - .arg("-v") - .arg("--from=42:42") - .arg(":43") - .arg(file) - .succeeds() - .stdout_only(format!("ownership of '{file}' retained as {group_name}\n")); -} - -#[test] -fn test_chown_no_change_to_user_group_from_user() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - let result = scene.cmd("whoami").run(); - if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { - return; - } - let user_name = String::from(result.stdout_str().trim()); - assert!(!user_name.is_empty()); - - let result = scene.cmd("id").arg("-ng").run(); - if skipping_test_is_okay(&result, "id: cannot find name for group ID") { - return; - } - let group_name = String::from(result.stdout_str().trim()); - assert!(!group_name.is_empty()); - - let file = "f"; - at.touch(file); - scene - .ucmd() - .arg("-v") - .arg("--from=42") - .arg("43:43") - .arg(file) - .succeeds() - .stdout_only(format!( - "ownership of '{file}' retained as {user_name}:{group_name}\n" - )); -} - -#[test] -fn test_chown_no_change_to_user_group_from_group() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - let result = scene.cmd("whoami").run(); - if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { - return; - } - let user_name = String::from(result.stdout_str().trim()); - assert!(!user_name.is_empty()); - let result = scene.cmd("id").arg("-ng").run(); - if skipping_test_is_okay(&result, "id: cannot find name for group ID") { - return; - } - let group_name = String::from(result.stdout_str().trim()); - assert!(!group_name.is_empty()); - - let file = "f"; - at.touch(file); - scene - .ucmd() - .arg("-v") - .arg("--from=:42") - .arg("43:43") - .arg(file) - .succeeds() - .stdout_only(format!( - "ownership of '{file}' retained as {user_name}:{group_name}\n" - )); -} - -#[test] -fn test_chown_no_change_to_user_group_from_user_group() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - let result = scene.cmd("whoami").run(); - if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { - return; - } - let user_name = String::from(result.stdout_str().trim()); - assert!(!user_name.is_empty()); - let result = scene.cmd("id").arg("-ng").run(); - if skipping_test_is_okay(&result, "id: cannot find name for group ID") { - return; - } - let group_name = String::from(result.stdout_str().trim()); - assert!(!group_name.is_empty()); - - let file = "f"; - at.touch(file); - scene - .ucmd() - .arg("-v") - .arg("--from=42:42") - .arg("43:43") - .arg(file) - .succeeds() - .stdout_only(format!( - "ownership of '{file}' retained as {user_name}:{group_name}\n" - )); + for (i, from) in ["42", ":42", "42:42"].iter().enumerate() { + let file = i.to_string(); + at.touch(&file); + scene + .ucmd() + .arg("-v") + .arg(format!("--from={from}")) + .arg("43:43") + .arg(&file) + .succeeds() + .stdout_only(format!( + "ownership of '{file}' retained as {user_name}:{group_name}\n" + )); + } } diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 697be8775..1fc2231d5 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -1,3 +1,7 @@ +// 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 (words) araba newroot userspec chdir pwd's isroot #[cfg(not(target_os = "android"))] diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 41ddc2ee0..a9d9b272b 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -1,3 +1,7 @@ +// 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 (words) asdf algo algos use crate::common::util::TestScenario; diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index 42c8358bb..e2bcc1c44 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -1,3 +1,7 @@ +// 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 (words) defaultcheck nocheck use crate::common::util::TestScenario; diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 18f3829a2..c79367afb 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1,4 +1,8 @@ -// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs ROOTDIR USERDIR procfs outfile +// 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 (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs ROOTDIR USERDIR procfs outfile uufs use crate::common::util::TestScenario; #[cfg(not(windows))] @@ -7,7 +11,7 @@ use std::fs::set_permissions; #[cfg(not(windows))] use std::os::unix::fs; -#[cfg(all(unix, not(target_os = "freebsd")))] +#[cfg(unix)] use std::os::unix::fs::MetadataExt; #[cfg(all(unix, not(target_os = "freebsd")))] use std::os::unix::fs::PermissionsExt; @@ -479,7 +483,8 @@ fn test_cp_arg_interactive_verbose() { ucmd.args(&["-vi", "a", "b"]) .pipe_in("N\n") .fails() - .stdout_is("skipped 'b'\n"); + .stderr_is("cp: overwrite 'b'? ") + .no_stdout(); } #[test] @@ -490,7 +495,8 @@ fn test_cp_arg_interactive_verbose_clobber() { at.touch("b"); ucmd.args(&["-vin", "a", "b"]) .fails() - .stdout_is("skipped 'b'\n"); + .stderr_is("cp: not replacing 'b'\n") + .no_stdout(); } #[test] @@ -1349,6 +1355,228 @@ fn test_cp_preserve_xattr_fails_on_android() { .fails(); } +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_1() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("a"); + at.hard_link("a", "b"); + at.mkdir("c"); + + ucmd.arg("-d").arg("a").arg("b").arg("c").succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_2() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("a"); + at.symlink_file("a", "b"); + at.mkdir("c"); + + ucmd.arg("--preserve=links") + .arg("-R") + .arg("-H") + .arg("a") + .arg("b") + .arg("c") + .succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_3() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("d"); + at.touch("d/a"); + at.symlink_file("d/a", "d/b"); + + ucmd.arg("--preserve=links") + .arg("-R") + .arg("-L") + .arg("d") + .arg("c") + .succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_4() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("d"); + at.touch("d/a"); + at.hard_link("d/a", "d/b"); + + ucmd.arg("--preserve=links") + .arg("-R") + .arg("-L") + .arg("d") + .arg("c") + .succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_5() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("d"); + at.touch("d/a"); + at.hard_link("d/a", "d/b"); + + ucmd.arg("-dR") + .arg("--no-preserve=links") + .arg("d") + .arg("c") + .succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_ne!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_6() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("a"); + at.hard_link("a", "b"); + at.mkdir("c"); + + ucmd.arg("-d").arg("a").arg("b").arg("c").succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_7() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("src"); + at.touch("src/f"); + at.hard_link("src/f", "src/g"); + + at.mkdir("dest"); + at.touch("dest/g"); + + ucmd.arg("-n") + .arg("--preserve=links") + .arg("src/f") + .arg("src/g") + .arg("dest") + .fails() + .stderr_contains("not replacing"); + + assert!(at.dir_exists("dest")); + assert!(at.plus("dest").join("f").exists()); + assert!(at.plus("dest").join("g").exists()); +} + +#[test] +#[cfg(unix)] +fn test_cp_no_preserve_mode() { + use libc::umask; + use uucore::fs as uufs; + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("a"); + at.set_mode("a", 0o731); + unsafe { umask(0o077) }; + + ucmd.arg("-a") + .arg("--no-preserve=mode") + .arg("a") + .arg("b") + .succeeds(); + + assert!(at.file_exists("b")); + + let metadata_b = std::fs::metadata(at.subdir.join("b")).unwrap(); + let permission_b = uufs::display_permissions(&metadata_b, false); + assert_eq!(permission_b, "rw-------".to_string()); + + unsafe { umask(0o022) }; +} + #[test] // For now, disable the test on Windows. Symlinks aren't well support on Windows. // It works on Unix for now and it works locally when run from a powershell @@ -2022,12 +2250,12 @@ fn test_cp_reflink_always_override() { const USERDIR: &str = "dir/"; const MOUNTPOINT: &str = "mountpoint/"; - let src1_path: &str = &vec![MOUNTPOINT, USERDIR, "src1"].concat(); - let src2_path: &str = &vec![MOUNTPOINT, USERDIR, "src2"].concat(); - let dst_path: &str = &vec![MOUNTPOINT, USERDIR, "dst"].concat(); + let src1_path: &str = &[MOUNTPOINT, USERDIR, "src1"].concat(); + let src2_path: &str = &[MOUNTPOINT, USERDIR, "src2"].concat(); + let dst_path: &str = &[MOUNTPOINT, USERDIR, "dst"].concat(); scene.fixtures.mkdir(ROOTDIR); - scene.fixtures.mkdir(vec![ROOTDIR, USERDIR].concat()); + scene.fixtures.mkdir([ROOTDIR, USERDIR].concat()); // Setup: // Because neither `mkfs.btrfs` not btrfs `mount` options allow us to have a mountpoint owned @@ -2625,6 +2853,22 @@ fn test_cp_mode_hardlink_no_dereference() { assert_eq!(at.read_symlink("z"), "slink"); } +#[test] +fn test_remove_destination_with_destination_being_a_symlink_to_source() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "file"; + let symlink = "symlink"; + + at.touch(file); + at.symlink_file(file, symlink); + + ucmd.args(&["--remove-destination", file, symlink]) + .succeeds(); + assert!(!at.symlink_exists(symlink)); + assert!(at.file_exists(file)); + assert!(at.file_exists(symlink)); +} + #[test] fn test_remove_destination_symbolic_link_loop() { let (at, mut ucmd) = at_and_ucmd!(); @@ -3227,3 +3471,61 @@ fn test_cp_debug_sparse_always_reflink_auto() { panic!("Failure: stdout was \n{stdout_str}"); } } + +#[test] +fn test_cp_only_source_no_target() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let result = ts.ucmd().arg("a").fails(); + let stderr_str = result.stderr_str(); + if !stderr_str.contains("missing destination file operand after \"a\"") { + panic!("Failure: stderr was \n{stderr_str}"); + } +} + +#[test] +fn test_cp_dest_no_permissions() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.touch("valid.txt"); + at.touch("invalid_perms.txt"); + at.set_readonly("invalid_perms.txt"); + + ts.ucmd() + .args(&["valid.txt", "invalid_perms.txt"]) + .fails() + .stderr_contains("invalid_perms.txt") + .stderr_contains("denied"); +} + +#[test] +#[cfg(all(unix, not(target_os = "freebsd")))] +fn test_cp_attributes_only() { + let (at, mut ucmd) = at_and_ucmd!(); + let a = "file_a"; + let b = "file_b"; + let mode_a = 0o0500; + let mode_b = 0o0777; + + at.write(a, "a"); + at.write(b, "b"); + at.set_mode(a, mode_a); + at.set_mode(b, mode_b); + + let mode_a = at.metadata(a).mode(); + let mode_b = at.metadata(b).mode(); + + // --attributes-only doesn't do anything without other attribute preservation flags + ucmd.arg("--attributes-only") + .arg(a) + .arg(b) + .succeeds() + .no_output(); + + assert_eq!("a", at.read(a)); + assert_eq!("b", at.read(b)); + assert_eq!(mode_a, at.metadata(a).mode()); + assert_eq!(mode_b, at.metadata(b).mode()); +} diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index eecaccfaf..b83d5e0ee 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use glob::glob; diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index a82950beb..184e413a8 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; static INPUT: &str = "lists.txt"; @@ -113,6 +117,14 @@ fn test_whitespace_with_char() { .code_is(1); } +#[test] +fn test_too_large() { + new_ucmd!() + .args(&["-b1-18446744073709551615", "/dev/null"]) + .fails() + .code_is(1); +} + #[test] fn test_specify_delimiter() { for param in ["-d", "--delimiter", "--del"] { diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 669f02e33..a65f02fa4 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use regex::Regex; #[cfg(all(unix, not(target_os = "macos")))] @@ -395,3 +399,12 @@ fn test_invalid_date_string() { .no_stdout() .stderr_contains("invalid date"); } + +#[test] +fn test_date_overflow() { + new_ucmd!() + .arg("-d68888888888888sms") + .fails() + .no_stdout() + .stderr_contains("invalid date"); +} diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index b43f32c24..f560e3526 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1,5 +1,11 @@ +// 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 fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg +#[cfg(unix)] +use crate::common::util::run_ucmd_as_root_with_stdin_stdout; use crate::common::util::TestScenario; #[cfg(all(not(windows), feature = "printf"))] use crate::common::util::{UCommand, TESTS_BINARY}; @@ -9,8 +15,6 @@ use regex::Regex; use std::fs::{File, OpenOptions}; use std::io::{BufReader, Read, Write}; use std::path::PathBuf; -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] -use std::process::{Command, Stdio}; #[cfg(not(windows))] use std::thread::sleep; #[cfg(not(windows))] @@ -1453,74 +1457,48 @@ fn test_sparse() { assert_eq!(at.metadata("infile").len(), at.metadata("outfile").len()); } -// TODO These FIFO tests should work on macos, but some issue is -// causing our implementation of dd to wait indefinitely when it -// shouldn't. - /// Test that a seek on an output FIFO results in a read. #[test] -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] +#[cfg(unix)] fn test_seek_output_fifo() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; at.mkfifo("fifo"); - // TODO When `dd` is a bit more advanced, we could use the uutils - // version of dd here as well. - let child = Command::new("dd") - .current_dir(&at.subdir) - .args([ - "count=1", - "if=/dev/zero", - &format!("of={}", at.plus_as_string("fifo")), - "status=noxfer", - ]) - .stderr(Stdio::piped()) - .spawn() - .expect("failed to execute child process"); - - ts.ucmd() + let mut ucmd = ts.ucmd(); + let child = ucmd .args(&["count=0", "seek=1", "of=fifo", "status=noxfer"]) - .succeeds() - .stderr_only("0+0 records in\n0+0 records out\n"); + .run_no_wait(); - let output = child.wait_with_output().unwrap(); - assert!(output.status.success()); - assert!(output.stdout.is_empty()); - assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n"); + std::fs::write(at.plus("fifo"), &vec![0; 512]).unwrap(); + + child + .wait() + .unwrap() + .success() + .stderr_only("0+0 records in\n0+0 records out\n"); } /// Test that a skip on an input FIFO results in a read. #[test] -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] +#[cfg(unix)] fn test_skip_input_fifo() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; at.mkfifo("fifo"); - // TODO When `dd` is a bit more advanced, we could use the uutils - // version of dd here as well. - let child = Command::new("dd") - .current_dir(&at.subdir) - .args([ - "count=1", - "if=/dev/zero", - &format!("of={}", at.plus_as_string("fifo")), - "status=noxfer", - ]) - .stderr(Stdio::piped()) - .spawn() - .expect("failed to execute child process"); - - ts.ucmd() + let mut ucmd = ts.ucmd(); + let child = ucmd .args(&["count=0", "skip=1", "if=fifo", "status=noxfer"]) - .succeeds() - .stderr_only("0+0 records in\n0+0 records out\n"); + .run_no_wait(); - let output = child.wait_with_output().unwrap(); - assert!(output.status.success()); - assert!(output.stdout.is_empty()); - assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n"); + std::fs::write(at.plus("fifo"), &vec![0; 512]).unwrap(); + + child + .wait() + .unwrap() + .success() + .stderr_only("0+0 records in\n0+0 records out\n"); } /// Test for reading part of stdin from each of two child processes. @@ -1562,3 +1540,45 @@ fn test_nocache_file() { .succeeds() .stderr_only("2048+0 records in\n2048+0 records out\n"); } + +#[test] +#[cfg(unix)] +fn test_skip_past_dev() { + // NOTE: This test intends to trigger code which can only be reached with root permissions. + let ts = TestScenario::new(util_name!()); + + if let Ok(result) = run_ucmd_as_root_with_stdin_stdout( + &ts, + &["bs=1", "skip=10000000000000000", "count=0", "status=noxfer"], + Some("/dev/sda1"), + None, + ) { + result.stderr_contains("dd: 'standard input': cannot skip: Invalid argument"); + result.stderr_contains("0+0 records in"); + result.stderr_contains("0+0 records out"); + result.code_is(1); + } else { + print!("TEST SKIPPED"); + } +} + +#[test] +#[cfg(unix)] +fn test_seek_past_dev() { + // NOTE: This test intends to trigger code which can only be reached with root permissions. + let ts = TestScenario::new(util_name!()); + + if let Ok(result) = run_ucmd_as_root_with_stdin_stdout( + &ts, + &["bs=1", "seek=10000000000000000", "count=0", "status=noxfer"], + None, + Some("/dev/sda1"), + ) { + result.stderr_contains("dd: 'standard output': cannot seek: Invalid argument"); + result.stderr_contains("0+0 records in"); + result.stderr_contains("0+0 records out"); + result.code_is(1); + } else { + print!("TEST SKIPPED"); + } +} diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index 926d1be5d..227121ef4 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -1,3 +1,7 @@ +// 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 udev pcent iuse itotal iused ipcent use std::collections::HashSet; diff --git a/tests/by-util/test_dir.rs b/tests/by-util/test_dir.rs index fd94f3a8f..3d16f8a67 100644 --- a/tests/by-util/test_dir.rs +++ b/tests/by-util/test_dir.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use regex::Regex; diff --git a/tests/by-util/test_dircolors.rs b/tests/by-util/test_dircolors.rs index 2d83c76b5..d4fa0a3b0 100644 --- a/tests/by-util/test_dircolors.rs +++ b/tests/by-util/test_dircolors.rs @@ -1,3 +1,7 @@ +// 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 overridable use crate::common::util::TestScenario; diff --git a/tests/by-util/test_dirname.rs b/tests/by-util/test_dirname.rs index ae689041b..8471b48c4 100644 --- a/tests/by-util/test_dirname.rs +++ b/tests/by-util/test_dirname.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index d365bd87e..9a508da25 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (paths) sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink #[cfg(not(windows))] diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 1c07a6737..875ff66cb 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -1,3 +1,7 @@ +// 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 (words) araba merci use crate::common::util::TestScenario; @@ -118,7 +122,7 @@ fn test_escape_no_further_output() { new_ucmd!() .args(&["-e", "a\\cb", "c"]) .succeeds() - .stdout_only("a\n"); + .stdout_only("a"); } #[test] @@ -232,3 +236,47 @@ fn test_hyphen_values_between() { .success() .stdout_is("dumdum dum dum dum -e dum\n"); } + +#[test] +fn wrapping_octal() { + // Some odd behavior of GNU. Values of \0400 and greater do not fit in the + // u8 that we write to stdout. So we test that it wraps: + // + // We give it this input: + // \o501 = 1_0100_0001 (yes, **9** bits) + // This should be wrapped into: + // \o101 = 'A' = 0100_0001, + // because we only write a single character + new_ucmd!() + .arg("-e") + .arg("\\0501") + .succeeds() + .stdout_is("A\n"); +} + +#[test] +fn old_octal_syntax() { + new_ucmd!() + .arg("-e") + .arg("\\1foo") + .succeeds() + .stdout_is("\x01foo\n"); + + new_ucmd!() + .arg("-e") + .arg("\\43foo") + .succeeds() + .stdout_is("#foo\n"); + + new_ucmd!() + .arg("-e") + .arg("\\101 foo") + .succeeds() + .stdout_is("A foo\n"); + + new_ucmd!() + .arg("-e") + .arg("\\1011") + .succeeds() + .stdout_is("A1\n"); +} diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index bd206c8e5..8ce55a1d3 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -1,3 +1,7 @@ +// 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 (words) bamf chdir rlimit prlimit COMSPEC use crate::common::util::TestScenario; diff --git a/tests/by-util/test_expand.rs b/tests/by-util/test_expand.rs index 88c1b1670..f6802358c 100644 --- a/tests/by-util/test_expand.rs +++ b/tests/by-util/test_expand.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use uucore::display::Quotable; // spell-checker:ignore (ToDO) taaaa tbbbb tcccc diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 235a91120..28cfcf0ec 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -1,3 +1,7 @@ +// 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 αbcdef ; (people) kkos use crate::common::util::TestScenario; @@ -109,6 +113,44 @@ fn test_or() { .args(&["foo", "|", "bar"]) .succeeds() .stdout_only("foo\n"); + + new_ucmd!() + .args(&["14", "|", "1"]) + .succeeds() + .stdout_only("14\n"); + + new_ucmd!() + .args(&["-14", "|", "1"]) + .succeeds() + .stdout_only("-14\n"); + + new_ucmd!() + .args(&["1", "|", "a", "/", "5"]) + .succeeds() + .stdout_only("1\n"); + + new_ucmd!() + .args(&["foo", "|", "a", "/", "5"]) + .succeeds() + .stdout_only("foo\n"); + + new_ucmd!() + .args(&["0", "|", "10", "/", "5"]) + .succeeds() + .stdout_only("2\n"); + + new_ucmd!() + .args(&["12", "|", "9a", "+", "1"]) + .succeeds() + .stdout_only("12\n"); + + new_ucmd!().args(&["", "|", ""]).run().stdout_only("0\n"); + + new_ucmd!().args(&["", "|", "0"]).run().stdout_only("0\n"); + + new_ucmd!().args(&["", "|", "00"]).run().stdout_only("0\n"); + + new_ucmd!().args(&["", "|", "-0"]).run().stdout_only("0\n"); } #[test] @@ -118,7 +160,34 @@ fn test_and() { .succeeds() .stdout_only("foo\n"); - new_ucmd!().args(&["", "&", "1"]).run().stdout_is("0\n"); + new_ucmd!() + .args(&["14", "&", "1"]) + .succeeds() + .stdout_only("14\n"); + + new_ucmd!() + .args(&["-14", "&", "1"]) + .succeeds() + .stdout_only("-14\n"); + + new_ucmd!() + .args(&["-1", "&", "10", "/", "5"]) + .succeeds() + .stdout_only("-1\n"); + + new_ucmd!() + .args(&["0", "&", "a", "/", "5"]) + .run() + .stdout_only("0\n"); + + new_ucmd!() + .args(&["", "&", "a", "/", "5"]) + .run() + .stdout_only("0\n"); + + new_ucmd!().args(&["", "&", "1"]).run().stdout_only("0\n"); + + new_ucmd!().args(&["", "&", ""]).run().stdout_only("0\n"); } #[test] @@ -224,3 +293,36 @@ fn test_invalid_substr() { .code_is(1) .stdout_only("\n"); } + +#[test] +fn test_escape() { + new_ucmd!().args(&["+", "1"]).succeeds().stdout_only("1\n"); + + new_ucmd!() + .args(&["1", "+", "+", "1"]) + .succeeds() + .stdout_only("2\n"); + + new_ucmd!() + .args(&["2", "*", "+", "3"]) + .succeeds() + .stdout_only("6\n"); + + new_ucmd!() + .args(&["(", "1", ")", "+", "1"]) + .succeeds() + .stdout_only("2\n"); +} + +#[test] +fn test_invalid_syntax() { + let invalid_syntaxes = [["12", "12"], ["12", "|"], ["|", "12"]]; + + for invalid_syntax in invalid_syntaxes { + new_ucmd!() + .args(&invalid_syntax) + .fails() + .code_is(2) + .stderr_contains("syntax error"); + } +} diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index cf3744964..3326a1ace 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -1,9 +1,7 @@ // This file is part of the uutils coreutils package. // -// (c) kwantam -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. #![allow(clippy::unreadable_literal)] // spell-checker:ignore (methods) hexdigest @@ -165,15 +163,12 @@ fn test_random() { let mut factors = Vec::new(); while product < min { // log distribution---higher probability for lower numbers - let factor; - loop { + let factor = loop { let next = rng.gen_range(0_f64..log_num_primes).exp2().floor() as usize; if next < NUM_PRIMES { - factor = primes[next]; - break; + break primes[next]; } - } - let factor = factor; + }; match product.checked_mul(factor) { Some(p) => { @@ -317,7 +312,7 @@ fn run(input_string: &[u8], output_string: &[u8]) { fn test_primes_with_exponents() { let mut input_string = String::new(); let mut output_string = String::new(); - for primes in PRIMES_BY_BITS.iter() { + for primes in PRIMES_BY_BITS { for &prime in *primes { input_string.push_str(&(format!("{prime} "))[..]); output_string.push_str(&(format!("{prime}: {prime}\n"))[..]); @@ -342,6 +337,37 @@ fn test_primes_with_exponents() { .stdout_is(String::from_utf8(output_string.as_bytes().to_owned()).unwrap()); } +#[test] +fn fails_on_invalid_number() { + new_ucmd!().arg("not-a-valid-number").fails(); + new_ucmd!() + .arg("not-a-valid-number") + .arg("12") + .fails() + .stdout_contains("12: 2 2 3"); +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +fn short_circuit_write_error() { + use std::fs::OpenOptions; + + // Check that the error is printed exactly once and factor does not move on + // to the next number when a write error happens. + // + // Note: Technically, GNU prints the error twice, not because it does not + // short circuit the error, but because it always prints the error twice, + // for any number of inputs. That's silly behavior and printing once is + // clearly better. + let f = OpenOptions::new().write(true).open("/dev/full").unwrap(); + new_ucmd!() + .arg("12") + .arg("10") + .set_stdout(f) + .fails() + .stderr_is("factor: write error: No space left on device\n"); +} + const PRIMES_BY_BITS: &[&[u64]] = &[ PRIMES14, PRIMES15, PRIMES16, PRIMES17, PRIMES18, PRIMES19, PRIMES20, PRIMES21, PRIMES22, PRIMES23, PRIMES24, PRIMES25, PRIMES26, PRIMES27, PRIMES28, PRIMES29, PRIMES30, PRIMES31, diff --git a/tests/by-util/test_false.rs b/tests/by-util/test_false.rs index c6c663404..01916ec62 100644 --- a/tests/by-util/test_false.rs +++ b/tests/by-util/test_false.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] use std::fs::OpenOptions; diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index afce9acd8..4fd059080 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] @@ -43,3 +47,57 @@ fn test_fmt_width_too_big() { .stderr_is("fmt: invalid width: '2501': Numerical result out of range\n"); } } + +#[test] +fn test_fmt_invalid_width() { + for param in ["-w", "--width"] { + new_ucmd!() + .args(&["one-word-per-line.txt", param, "invalid"]) + .fails() + .code_is(1) + .stderr_contains("invalid value 'invalid'"); + } +} + +#[ignore] +#[test] +fn test_fmt_goal() { + for param in ["-g", "--goal"] { + new_ucmd!() + .args(&["one-word-per-line.txt", param, "7"]) + .succeeds() + .stdout_is("this is a\nfile with one\nword per line\n"); + } +} + +#[test] +fn test_fmt_goal_too_big() { + for param in ["-g", "--goal"] { + new_ucmd!() + .args(&["one-word-per-line.txt", "--width=75", param, "76"]) + .fails() + .code_is(1) + .stderr_is("fmt: GOAL cannot be greater than WIDTH.\n"); + } +} + +#[test] +fn test_fmt_invalid_goal() { + for param in ["-g", "--goal"] { + new_ucmd!() + .args(&["one-word-per-line.txt", param, "invalid"]) + .fails() + .code_is(1) + .stderr_contains("invalid value 'invalid'"); + } +} + +#[test] +fn test_fmt_set_goal_not_contain_width() { + for param in ["-g", "--goal"] { + new_ucmd!() + .args(&["one-word-per-line.txt", param, "74"]) + .succeeds() + .stdout_is("this is a file with one word per line\n"); + } +} diff --git a/tests/by-util/test_fold.rs b/tests/by-util/test_fold.rs index b61d9f5ed..6895f51b6 100644 --- a/tests/by-util/test_fold.rs +++ b/tests/by-util/test_fold.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index 4f6bb7f1b..47cb89249 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 coreutil diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 3650047b2..31471495b 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; // spell-checker:ignore checkfile, nonames, testf, ntestf macro_rules! get_hash( @@ -351,3 +355,19 @@ fn test_check_md5sum_mixed_format() { fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } + +#[test] +fn test_tag() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("foobar", "foo bar\n"); + scene + .ccmd("sha256sum") + .arg("--tag") + .arg("foobar") + .succeeds() + .stdout_is( + "SHA256 (foobar) = 1f2ec52b774368781bed1d1fb140a92e0eb6348090619c9291f9a5a3c8e8d151\n", + ); +} diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 0e1eafc86..f536b26ae 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (words) bogusfile emptyfile abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstu @@ -297,11 +297,15 @@ fn test_head_invalid_num() { new_ucmd!() .args(&["-c", "1024R", "emptyfile.txt"]) .fails() - .stderr_is("head: invalid number of bytes: '1024R'\n"); + .stderr_is( + "head: invalid number of bytes: '1024R': Value too large for defined data type\n", + ); new_ucmd!() .args(&["-n", "1024R", "emptyfile.txt"]) .fails() - .stderr_is("head: invalid number of lines: '1024R'\n"); + .stderr_is( + "head: invalid number of lines: '1024R': Value too large for defined data type\n", + ); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["-c", "1Y", "emptyfile.txt"]) diff --git a/tests/by-util/test_hostid.rs b/tests/by-util/test_hostid.rs index b42ec211d..e9336116b 100644 --- a/tests/by-util/test_hostid.rs +++ b/tests/by-util/test_hostid.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use regex::Regex; diff --git a/tests/by-util/test_hostname.rs b/tests/by-util/test_hostname.rs index 3c01a1197..84cb73e2e 100644 --- a/tests/by-util/test_hostname.rs +++ b/tests/by-util/test_hostname.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 4174bdde6..5c2a67199 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (ToDO) coreutil diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index d76ce1e01..7387748c6 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -1,3 +1,7 @@ +// 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 (words) helloworld nodir objdump n'source use crate::common::util::{is_ci, TestScenario}; diff --git a/tests/by-util/test_join.rs b/tests/by-util/test_join.rs index f6fbe14c3..e2e5dc868 100644 --- a/tests/by-util/test_join.rs +++ b/tests/by-util/test_join.rs @@ -1,3 +1,7 @@ +// 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 (words) autoformat nocheck use crate::common::util::TestScenario; diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index d36023298..a9094ecf6 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use regex::Regex; use std::os::unix::process::ExitStatusExt; diff --git a/tests/by-util/test_link.rs b/tests/by-util/test_link.rs index 3c068af93..8d48931c4 100644 --- a/tests/by-util/test_link.rs +++ b/tests/by-util/test_link.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] @@ -58,7 +62,7 @@ fn test_link_one_argument() { #[test] fn test_link_three_arguments() { let (_, mut ucmd) = at_and_ucmd!(); - let arguments = vec![ + let arguments = [ "test_link_argument1", "test_link_argument2", "test_link_argument3", diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index 3be07c4d7..dc31f7261 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use std::path::PathBuf; diff --git a/tests/by-util/test_logname.rs b/tests/by-util/test_logname.rs index b0cda1a9d..883397555 100644 --- a/tests/by-util/test_logname.rs +++ b/tests/by-util/test_logname.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::{is_ci, TestScenario}; use std::env; diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 15ded8e6b..cdd0292e1 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1,4 +1,8 @@ -// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff +// 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 (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs #[cfg(any(unix, feature = "feat_selinux"))] use crate::common::util::expected_result; @@ -1733,7 +1737,12 @@ fn test_ls_styles() { .stdout_matches(&re_custom_format); // Also fails due to not having full clap support for time_styles - scene.ucmd().arg("-l").arg("-time-style=invalid").fails(); + scene + .ucmd() + .arg("-l") + .arg("--time-style=invalid") + .fails() + .code_is(2); //Overwrite options tests scene @@ -1924,6 +1933,35 @@ fn test_ls_recursive() { result.stdout_contains("a\\b:\nb"); } +#[test] +fn test_ls_recursive_1() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("x"); + at.mkdir("y"); + at.mkdir("a"); + at.mkdir("b"); + at.mkdir("c"); + at.mkdir("a/1"); + at.mkdir("a/2"); + at.mkdir("a/3"); + at.touch("f"); + at.touch("a/1/I"); + at.touch("a/1/II"); + #[cfg(unix)] + let out = "a:\n1\n2\n3\n\na/1:\nI\nII\n\na/2:\n\na/3:\n\nb:\n\nc:\n"; + #[cfg(windows)] + let out = "a:\n1\n2\n3\n\na\\1:\nI\nII\n\na\\2:\n\na\\3:\n\nb:\n\nc:\n"; + scene + .ucmd() + .arg("-R1") + .arg("a") + .arg("b") + .arg("c") + .succeeds() + .stdout_is(out); +} + #[test] fn test_ls_color() { let scene = TestScenario::new(util_name!()); @@ -3499,3 +3537,279 @@ fn test_invalid_utf8() { at.touch(filename); ucmd.succeeds(); } + +#[cfg(all(unix, feature = "chmod"))] +#[test] +fn test_ls_perm_io_errors() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("d"); + at.symlink_file("/", "d/s"); + + scene.ccmd("chmod").arg("600").arg("d").succeeds(); + + scene + .ucmd() + .arg("-l") + .arg("d") + .fails() + .code_is(1) + .stderr_contains("Permission denied"); +} + +#[test] +fn test_ls_dired_incompatible() { + let scene = TestScenario::new(util_name!()); + + scene + .ucmd() + .arg("--dired") + .fails() + .code_is(1) + .stderr_contains("--dired requires --format=long"); +} + +#[test] +fn test_ls_dired_and_zero_are_incompatible() { + let scene = TestScenario::new(util_name!()); + + scene + .ucmd() + .arg("--dired") + .arg("-l") + .arg("--zero") + .fails() + .code_is(2) + .stderr_contains("--dired and --zero are incompatible"); +} + +#[test] +fn test_ls_dired_recursive() { + let scene = TestScenario::new(util_name!()); + + scene + .ucmd() + .arg("--dired") + .arg("-l") + .arg("-R") + .succeeds() + .stdout_does_not_contain("//DIRED//") + .stdout_contains(" total 0") + .stdout_contains("//SUBDIRED// 2 3") + .stdout_contains("//DIRED-OPTIONS// --quoting-style"); +} + +#[test] +fn test_ls_dired_recursive_multiple() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("d"); + at.mkdir("d/d1"); + at.mkdir("d/d2"); + at.touch("d/d2/a"); + at.touch("d/d2/c2"); + at.touch("d/d1/f1"); + at.touch("d/d1/file-long"); + + let mut cmd = scene.ucmd(); + cmd.arg("--dired").arg("-l").arg("-R").arg("d"); + + let result = cmd.succeeds(); + + let output = result.stdout_str().to_string(); + println!("Output:\n{}", output); + + let dired_line = output + .lines() + .find(|&line| line.starts_with("//DIRED//")) + .unwrap(); + let positions: Vec = dired_line + .split_whitespace() + .skip(1) + .map(|s| s.parse().unwrap()) + .collect(); + println!("Parsed byte positions: {:?}", positions); + assert_eq!(positions.len() % 2, 0); // Ensure there's an even number of positions + + let filenames: Vec = positions + .chunks(2) + .map(|chunk| { + let start_pos = chunk[0]; + let end_pos = chunk[1]; + let filename = String::from_utf8(output.as_bytes()[start_pos..=end_pos].to_vec()) + .unwrap() + .trim() + .to_string(); + println!("Extracted filename: {}", filename); + filename + }) + .collect(); + + println!("Extracted filenames: {:?}", filenames); + assert_eq!(filenames, vec!["d1", "d2", "f1", "file-long", "a", "c2"]); +} + +#[test] +fn test_ls_dired_simple() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + scene + .ucmd() + .arg("--dired") + .arg("-l") + .succeeds() + .stdout_contains(" total 0"); + + at.mkdir("d"); + at.touch("d/a1"); + let mut cmd = scene.ucmd(); + cmd.arg("--dired").arg("-l").arg("d"); + let result = cmd.succeeds(); + result.stdout_contains(" total 0"); + println!(" result.stdout = {:#?}", result.stdout_str()); + + let dired_line = result + .stdout_str() + .lines() + .find(|&line| line.starts_with("//DIRED//")) + .unwrap(); + let positions: Vec = dired_line + .split_whitespace() + .skip(1) + .map(|s| s.parse().unwrap()) + .collect(); + + assert_eq!(positions.len(), 2); + + let start_pos = positions[0]; + let end_pos = positions[1]; + + // Extract the filename using the positions + let filename = + String::from_utf8(result.stdout_str().as_bytes()[start_pos..end_pos].to_vec()).unwrap(); + + assert_eq!(filename, "a1"); +} + +#[test] +fn test_ls_dired_complex() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("d"); + at.mkdir("d/d"); + at.touch("d/a1"); + at.touch("d/a22"); + at.touch("d/a333"); + at.touch("d/a4444"); + + let mut cmd = scene.ucmd(); + cmd.arg("--dired").arg("-l").arg("d"); + let result = cmd.succeeds(); + + // Number of blocks. We run this test only if the default size of a newly created directory is + // 4096 bytes to prevent it from failing where this is not the case (e.g. using tmpfs for /tmp). + #[cfg(target_os = "linux")] + if at.metadata("d/d").len() == 4096 { + result.stdout_contains(" total 4"); + } + + let output = result.stdout_str().to_string(); + println!("Output:\n{}", output); + + let dired_line = output + .lines() + .find(|&line| line.starts_with("//DIRED//")) + .unwrap(); + let positions: Vec = dired_line + .split_whitespace() + .skip(1) + .map(|s| s.parse().unwrap()) + .collect(); + println!("{:?}", positions); + println!("Parsed byte positions: {:?}", positions); + assert_eq!(positions.len() % 2, 0); // Ensure there's an even number of positions + + let filenames: Vec = positions + .chunks(2) + .map(|chunk| { + let start_pos = chunk[0]; + let end_pos = chunk[1]; + let filename = String::from_utf8(output.as_bytes()[start_pos..=end_pos].to_vec()) + .unwrap() + .trim() + .to_string(); + println!("Extracted filename: {}", filename); + filename + }) + .collect(); + + println!("Extracted filenames: {:?}", filenames); + assert_eq!(filenames, vec!["a1", "a22", "a333", "a4444", "d"]); +} + +#[test] +fn test_ls_subdired_complex() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("dir1"); + at.mkdir("dir1/d"); + at.mkdir("dir1/c2"); + at.touch("dir1/a1"); + at.touch("dir1/a22"); + at.touch("dir1/a333"); + at.touch("dir1/c2/a4444"); + + let mut cmd = scene.ucmd(); + cmd.arg("--dired").arg("-l").arg("-R").arg("dir1"); + let result = cmd.succeeds(); + + let output = result.stdout_str().to_string(); + println!("Output:\n{}", output); + + let dired_line = output + .lines() + .find(|&line| line.starts_with("//SUBDIRED//")) + .unwrap(); + let positions: Vec = dired_line + .split_whitespace() + .skip(1) + .map(|s| s.parse().unwrap()) + .collect(); + println!("Parsed byte positions: {:?}", positions); + assert_eq!(positions.len() % 2, 0); // Ensure there's an even number of positions + + let dirnames: Vec = positions + .chunks(2) + .map(|chunk| { + let start_pos = chunk[0]; + let end_pos = chunk[1]; + let dirname = + String::from_utf8(output.as_bytes()[start_pos..end_pos].to_vec()).unwrap(); + println!("Extracted dirname: {}", dirname); + dirname + }) + .collect(); + + println!("Extracted dirnames: {:?}", dirnames); + #[cfg(unix)] + assert_eq!(dirnames, vec!["dir1", "dir1/c2", "dir1/d"]); + #[cfg(windows)] + assert_eq!(dirnames, vec!["dir1", "dir1\\c2", "dir1\\d"]); +} + +#[ignore = "issue #5396"] +#[test] +fn test_ls_cf_output_should_be_delimited_by_tab() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("e"); + at.mkdir("e/a2345"); + at.mkdir("e/b"); + + ucmd.args(&["-CF", "e"]) + .succeeds() + .stdout_is("a2345/\tb/\n"); +} diff --git a/tests/by-util/test_mkdir.rs b/tests/by-util/test_mkdir.rs index 11a860d5a..8a6eae27b 100644 --- a/tests/by-util/test_mkdir.rs +++ b/tests/by-util/test_mkdir.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[cfg(not(windows))] use libc::{mode_t, umask}; diff --git a/tests/by-util/test_mkfifo.rs b/tests/by-util/test_mkfifo.rs index d4ebab640..731b6c1d5 100644 --- a/tests/by-util/test_mkfifo.rs +++ b/tests/by-util/test_mkfifo.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_mknod.rs b/tests/by-util/test_mknod.rs index deea8bb4e..2d83d250d 100644 --- a/tests/by-util/test_mknod.rs +++ b/tests/by-util/test_mknod.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 4544b6b30..611a42e43 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -1,3 +1,7 @@ +// 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 (words) gpghome use crate::common::util::TestScenario; diff --git a/tests/by-util/test_more.rs b/tests/by-util/test_more.rs index d94a92185..e80020d39 100644 --- a/tests/by-util/test_more.rs +++ b/tests/by-util/test_more.rs @@ -1,5 +1,9 @@ +// 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 crate::common::util::TestScenario; -use is_terminal::IsTerminal; +use std::io::IsTerminal; #[test] fn test_more_no_arg() { diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index ceaa4ba22..c54d24ea9 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1,3 +1,9 @@ +// 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 mydir use crate::common::util::TestScenario; use filetime::FileTime; use std::thread::sleep; @@ -1278,7 +1284,7 @@ fn test_mv_verbose() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] // mkdir does not support -m on windows. Freebsd doesn't return a permission error either. -#[cfg(features = "mkdir")] +#[cfg(feature = "mkdir")] fn test_mv_permission_error() { let scene = TestScenario::new("mkdir"); let folder1 = "bar"; @@ -1319,7 +1325,7 @@ fn test_mv_interactive_error() { } #[test] -fn test_mv_info_self() { +fn test_mv_into_self() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let dir1 = "dir1"; @@ -1346,7 +1352,7 @@ fn test_mv_arg_interactive_skipped() { .ignore_stdin_write_error() .fails() .stderr_is("mv: overwrite 'b'? ") - .stdout_is("skipped 'b'\n"); + .no_stdout(); } #[test] @@ -1356,7 +1362,8 @@ fn test_mv_arg_interactive_skipped_vin() { at.touch("b"); ucmd.args(&["-vin", "a", "b"]) .fails() - .stdout_is("skipped 'b'\n"); + .stderr_is("mv: not replacing 'b'\n") + .no_stdout(); } #[test] @@ -1384,6 +1391,58 @@ fn test_mv_into_self_data() { assert!(at.file_exists(file2)); assert!(!at.file_exists(file1)); } + +#[test] +fn test_mv_directory_into_subdirectory_of_itself_fails() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dir1 = "mydir"; + let dir2 = "mydir/mydir_2"; + at.mkdir(dir1); + at.mkdir(dir2); + scene.ucmd().arg(dir1).arg(dir2).fails().stderr_contains( + "mv: cannot move 'mydir' to a subdirectory of itself, 'mydir/mydir_2/mydir'", + ); + + // check that it also errors out with / + scene + .ucmd() + .arg(format!("{}/", dir1)) + .arg(dir2) + .fails() + .stderr_contains( + "mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/mydir_2/mydir/'", + ); +} + +#[test] +fn test_mv_file_into_dir_where_both_are_files() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("a"); + at.touch("b"); + scene + .ucmd() + .arg("a") + .arg("b/") + .fails() + .stderr_contains("mv: failed to access 'b/': Not a directory"); +} + +#[test] +fn test_mv_dir_into_file_where_both_are_files() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("a"); + at.touch("b"); + scene + .ucmd() + .arg("a/") + .arg("b") + .fails() + .stderr_contains("mv: cannot stat 'a/': Not a directory"); +} + // Todo: // $ at.touch a b diff --git a/tests/by-util/test_nice.rs b/tests/by-util/test_nice.rs index 4e8d5a2ee..994b0e856 100644 --- a/tests/by-util/test_nice.rs +++ b/tests/by-util/test_nice.rs @@ -1,3 +1,7 @@ +// 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 libc's use crate::common::util::TestScenario; diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index 08f009765..78c8975a8 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -1,4 +1,9 @@ -// spell-checker:ignore iinvalid linvalid ninvalid vinvalid winvalid +// 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 binvalid finvalid hinvalid iinvalid linvalid nabcabc nabcabcabc ninvalid vinvalid winvalid use crate::common::util::TestScenario; #[test] @@ -52,15 +57,15 @@ fn test_sections_and_styles() { for (fixture, output) in [ ( "section.txt", - "\nHEADER1\nHEADER2\n\n1 |BODY1\n2 \ - |BODY2\n\nFOOTER1\nFOOTER2\n\nNEXTHEADER1\nNEXTHEADER2\n\n1 \ - |NEXTBODY1\n2 |NEXTBODY2\n\nNEXTFOOTER1\nNEXTFOOTER2\n", + "\n HEADER1\n HEADER2\n\n1 |BODY1\n2 \ + |BODY2\n\n FOOTER1\n FOOTER2\n\n NEXTHEADER1\n NEXTHEADER2\n\n1 \ + |NEXTBODY1\n2 |NEXTBODY2\n\n NEXTFOOTER1\n NEXTFOOTER2\n", ), ( "joinblanklines.txt", - "1 |Nonempty\n2 |Nonempty\n3 |Followed by 10x empty\n\n\n\n\n4 \ - |\n\n\n\n\n5 |\n6 |Followed by 5x empty\n\n\n\n\n7 |\n8 \ - |Followed by 4x empty\n\n\n\n\n9 |Nonempty\n10 |Nonempty\n11 \ + "1 |Nonempty\n2 |Nonempty\n3 |Followed by 10x empty\n \n \n \n \n4 \ + |\n \n \n \n \n5 |\n6 |Followed by 5x empty\n \n \n \n \n7 |\n8 \ + |Followed by 4x empty\n \n \n \n \n9 |Nonempty\n10 |Nonempty\n11 \ |Nonempty.\n", ), ] { @@ -77,7 +82,11 @@ fn test_sections_and_styles() { #[test] fn test_no_renumber() { for arg in ["-p", "--no-renumber"] { - new_ucmd!().arg(arg).succeeds(); + new_ucmd!() + .arg(arg) + .pipe_in("a\n\\:\\:\nb") + .succeeds() + .stdout_is(" 1\ta\n\n 2\tb\n"); } } @@ -257,6 +266,50 @@ fn test_invalid_line_increment() { } } +#[test] +fn test_join_blank_lines() { + for arg in ["-l3", "--join-blank-lines=3"] { + new_ucmd!() + .arg(arg) + .arg("--body-numbering=a") + .pipe_in("\n\n\n\n\n\n") + .succeeds() + .stdout_is(concat!( + " \n", + " \n", + " 1\t\n", + " \n", + " \n", + " 2\t\n", + )); + } +} + +#[test] +fn test_join_blank_lines_multiple_files() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("a.txt", "\n\n"); + at.write("b.txt", "\n\n"); + at.write("c.txt", "\n\n"); + + for arg in ["-l3", "--join-blank-lines=3"] { + scene + .ucmd() + .args(&[arg, "--body-numbering=a", "a.txt", "b.txt", "c.txt"]) + .succeeds() + .stdout_is(concat!( + " \n", + " \n", + " 1\t\n", + " \n", + " \n", + " 2\t\n", + )); + } +} + #[test] fn test_join_blank_lines_zero() { for arg in ["-l0", "--join-blank-lines=0"] { @@ -275,3 +328,309 @@ fn test_invalid_join_blank_lines() { .stderr_contains("invalid value 'invalid'"); } } + +#[test] +fn test_default_body_numbering() { + new_ucmd!() + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" 1\ta\n \n 2\tb\n"); +} + +#[test] +fn test_default_body_numbering_multiple_files() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.write("a.txt", "a"); + at.write("b.txt", "b"); + at.write("c.txt", "c"); + + ucmd.args(&["a.txt", "b.txt", "c.txt"]) + .succeeds() + .stdout_is(" 1\ta\n 2\tb\n 3\tc\n"); +} + +#[test] +fn test_default_body_numbering_multiple_files_and_stdin() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.write("a.txt", "a"); + at.write("c.txt", "c"); + + ucmd.args(&["a.txt", "-", "c.txt"]) + .pipe_in("b") + .succeeds() + .stdout_is(" 1\ta\n 2\tb\n 3\tc\n"); +} + +#[test] +fn test_body_numbering_all_lines_without_delimiter() { + for arg in ["-ba", "--body-numbering=a"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" 1\ta\n 2\t\n 3\tb\n"); + } +} + +#[test] +fn test_body_numbering_no_lines_without_delimiter() { + for arg in ["-bn", "--body-numbering=n"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" a\n \n b\n"); + } +} + +#[test] +fn test_body_numbering_non_empty_lines_without_delimiter() { + for arg in ["-bt", "--body-numbering=t"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" 1\ta\n \n 2\tb\n"); + } +} + +#[test] +fn test_body_numbering_matched_lines_without_delimiter() { + for arg in ["-bp^[ac]", "--body-numbering=p^[ac]"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\nb\nc") + .succeeds() + .stdout_is(" 1\ta\n b\n 2\tc\n"); + } +} + +#[test] +fn test_numbering_all_lines() { + let delimiters_and_args = [ + ("\\:\\:\\:\n", ["-ha", "--header-numbering=a"]), + ("\\:\\:\n", ["-ba", "--body-numbering=a"]), + ("\\:\n", ["-fa", "--footer-numbering=a"]), + ]; + + for (delimiter, args) in delimiters_and_args { + for arg in args { + new_ucmd!() + .arg(arg) + .pipe_in(format!("{delimiter}a\n\nb")) + .succeeds() + .stdout_is("\n 1\ta\n 2\t\n 3\tb\n"); + } + } +} + +#[test] +fn test_numbering_no_lines() { + let delimiters_and_args = [ + ("\\:\\:\\:\n", ["-hn", "--header-numbering=n"]), + ("\\:\\:\n", ["-bn", "--body-numbering=n"]), + ("\\:\n", ["-fn", "--footer-numbering=n"]), + ]; + + for (delimiter, args) in delimiters_and_args { + for arg in args { + new_ucmd!() + .arg(arg) + .pipe_in(format!("{delimiter}a\n\nb")) + .succeeds() + .stdout_is("\n a\n \n b\n"); + } + } +} + +#[test] +fn test_numbering_non_empty_lines() { + let delimiters_and_args = [ + ("\\:\\:\\:\n", ["-ht", "--header-numbering=t"]), + ("\\:\\:\n", ["-bt", "--body-numbering=t"]), + ("\\:\n", ["-ft", "--footer-numbering=t"]), + ]; + + for (delimiter, args) in delimiters_and_args { + for arg in args { + new_ucmd!() + .arg(arg) + .pipe_in(format!("{delimiter}a\n\nb")) + .succeeds() + .stdout_is("\n 1\ta\n \n 2\tb\n"); + } + } +} + +#[test] +fn test_numbering_matched_lines() { + let delimiters_and_args = [ + ("\\:\\:\\:\n", ["-hp^[ac]", "--header-numbering=p^[ac]"]), + ("\\:\\:\n", ["-bp^[ac]", "--body-numbering=p^[ac]"]), + ("\\:\n", ["-fp^[ac]", "--footer-numbering=p^[ac]"]), + ]; + + for (delimiter, args) in delimiters_and_args { + for arg in args { + new_ucmd!() + .arg(arg) + .pipe_in(format!("{delimiter}a\nb\nc")) + .succeeds() + .stdout_is("\n 1\ta\n b\n 2\tc\n"); + } + } +} + +#[test] +fn test_invalid_numbering() { + let invalid_args = [ + "-hinvalid", + "--header-numbering=invalid", + "-binvalid", + "--body-numbering=invalid", + "-finvalid", + "--footer-numbering=invalid", + ]; + + for invalid_arg in invalid_args { + new_ucmd!() + .arg(invalid_arg) + .fails() + .stderr_contains("invalid numbering style: 'invalid'"); + } +} + +#[test] +fn test_invalid_regex_numbering() { + let invalid_args = [ + "-hp[", + "--header-numbering=p[", + "-bp[", + "--body-numbering=p[", + "-fp[", + "--footer-numbering=p[", + ]; + + for invalid_arg in invalid_args { + new_ucmd!() + .arg(invalid_arg) + .fails() + .stderr_contains("invalid regular expression"); + } +} + +#[test] +fn test_line_number_overflow() { + new_ucmd!() + .arg(format!("--starting-line-number={}", i64::MAX)) + .pipe_in("a\nb") + .fails() + .stdout_is(format!("{}\ta\n", i64::MAX)) + .stderr_is("nl: line number overflow\n"); + + new_ucmd!() + .arg(format!("--starting-line-number={}", i64::MIN)) + .arg("--line-increment=-1") + .pipe_in("a\nb") + .fails() + .stdout_is(format!("{}\ta\n", i64::MIN)) + .stderr_is("nl: line number overflow\n"); +} + +#[test] +fn test_line_number_no_overflow() { + new_ucmd!() + .arg(format!("--starting-line-number={}", i64::MAX)) + .pipe_in("a\n\\:\\:\nb") + .succeeds() + .stdout_is(format!("{0}\ta\n\n{0}\tb\n", i64::MAX)); + + new_ucmd!() + .arg(format!("--starting-line-number={}", i64::MIN)) + .arg("--line-increment=-1") + .pipe_in("a\n\\:\\:\nb") + .succeeds() + .stdout_is(format!("{0}\ta\n\n{0}\tb\n", i64::MIN)); +} + +#[test] +fn test_section_delimiter() { + for arg in ["-dabc", "--section-delimiter=abc"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\nabcabcabc\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nabcabc\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nabc\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_one_char_section_delimiter_expansion() { + for arg in ["-da", "--section-delimiter=a"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\na:a:a:\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\na:a:\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\na:\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_non_ascii_one_char_section_delimiter() { + for arg in ["-dä", "--section-delimiter=ä"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\näää\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nää\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nä\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_empty_section_delimiter() { + for arg in ["-d ''", "--section-delimiter=''"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" 1\ta\n \n 2\tb\n"); + } +} diff --git a/tests/by-util/test_nohup.rs b/tests/by-util/test_nohup.rs index c14a2e494..b014c31aa 100644 --- a/tests/by-util/test_nohup.rs +++ b/tests/by-util/test_nohup.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use std::thread::sleep; diff --git a/tests/by-util/test_nproc.rs b/tests/by-util/test_nproc.rs index 2e3c5c603..22523352b 100644 --- a/tests/by-util/test_nproc.rs +++ b/tests/by-util/test_nproc.rs @@ -1,3 +1,7 @@ +// 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 incorrectnumber use crate::common::util::TestScenario; diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index 561752db3..2c2e95d0b 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -1,3 +1,7 @@ +// 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 (paths) gnutest use crate::common::util::TestScenario; diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index 54ac06384..78c4e1b04 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -1,8 +1,9 @@ +// 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 abcdefghijklmnopqrstuvwxyz -// * 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 crate::common::util::TestScenario; use std::env; diff --git a/tests/by-util/test_paste.rs b/tests/by-util/test_paste.rs index da92daa32..0fbdb75d2 100644 --- a/tests/by-util/test_paste.rs +++ b/tests/by-util/test_paste.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; struct TestData<'b> { diff --git a/tests/by-util/test_pathchk.rs b/tests/by-util/test_pathchk.rs index 771fbd0a9..d66ecb9ef 100644 --- a/tests/by-util/test_pathchk.rs +++ b/tests/by-util/test_pathchk.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] @@ -15,8 +19,16 @@ fn test_default_mode() { // accept non-portable chars new_ucmd!().args(&["dir#/$file"]).succeeds().no_stdout(); - // accept empty path - new_ucmd!().args(&[""]).succeeds().no_stdout(); + // fail on empty path + new_ucmd!() + .args(&[""]) + .fails() + .stderr_only("pathchk: '': No such file or directory\n"); + + new_ucmd!().args(&["", ""]).fails().stderr_only( + "pathchk: '': No such file or directory\n\ + pathchk: '': No such file or directory\n", + ); // fail on long path new_ucmd!() diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index f266175f5..57413c4c9 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 crate::common::util::{expected_result, TestScenario}; use pinky::Capitalize; diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index b62fa4a96..195d40567 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -1,3 +1,7 @@ +// 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 (ToDO) Sdivide use crate::common::util::{TestScenario, UCommand}; diff --git a/tests/by-util/test_printenv.rs b/tests/by-util/test_printenv.rs index fa7d420e1..c9eb3c60e 100644 --- a/tests/by-util/test_printenv.rs +++ b/tests/by-util/test_printenv.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index e9b25954f..d7ba5679e 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_ptx.rs b/tests/by-util/test_ptx.rs index db4a4f3cc..a106972ac 100644 --- a/tests/by-util/test_ptx.rs +++ b/tests/by-util/test_ptx.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_pwd.rs b/tests/by-util/test_pwd.rs index 1e43f5be6..89341c0c3 100644 --- a/tests/by-util/test_pwd.rs +++ b/tests/by-util/test_pwd.rs @@ -1,3 +1,7 @@ +// 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 (words) symdir somefakedir use std::path::PathBuf; diff --git a/tests/by-util/test_readlink.rs b/tests/by-util/test_readlink.rs index f08671583..973e30a35 100644 --- a/tests/by-util/test_readlink.rs +++ b/tests/by-util/test_readlink.rs @@ -1,3 +1,7 @@ +// 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 regfile use crate::common::util::{get_root_path, TestScenario}; diff --git a/tests/by-util/test_realpath.rs b/tests/by-util/test_realpath.rs index 707378c97..c3a15fba4 100644 --- a/tests/by-util/test_realpath.rs +++ b/tests/by-util/test_realpath.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::{get_root_path, TestScenario}; #[cfg(windows)] diff --git a/tests/by-util/test_relpath.rs b/tests/by-util/test_relpath.rs deleted file mode 100644 index 65cbd391e..000000000 --- a/tests/by-util/test_relpath.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::common::util::TestScenario; -use std::borrow::Cow; -use std::path::Path; - -struct TestCase<'a> { - from: &'a str, - to: &'a str, - expected: &'a str, -} - -const TESTS: [TestCase; 10] = [ - TestCase { - from: "A/B/C", - to: "A", - expected: "../..", - }, - TestCase { - from: "A/B/C", - to: "A/B", - expected: "..", - }, - TestCase { - from: "A/B/C", - to: "A/B/C", - expected: "", - }, - TestCase { - from: "A/B/C", - to: "A/B/C/D", - expected: "D", - }, - TestCase { - from: "A/B/C", - to: "A/B/C/D/E", - expected: "D/E", - }, - TestCase { - from: "A/B/C", - to: "A/B/D", - expected: "../D", - }, - TestCase { - from: "A/B/C", - to: "A/B/D/E", - expected: "../D/E", - }, - TestCase { - from: "A/B/C", - to: "A/D", - expected: "../../D", - }, - TestCase { - from: "A/B/C", - to: "D/E/F", - expected: "../../../D/E/F", - }, - TestCase { - from: "A/B/C", - to: "A/D/E", - expected: "../../D/E", - }, -]; - -#[allow(clippy::needless_lifetimes)] -fn convert_path<'a>(path: &'a str) -> Cow<'a, str> { - #[cfg(windows)] - return path.replace('/', "\\").into(); - #[cfg(not(windows))] - return path.into(); -} - -#[test] -fn test_relpath_with_from_no_d() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - for test in &TESTS { - let from: &str = &convert_path(test.from); - let to: &str = &convert_path(test.to); - let expected: &str = &convert_path(test.expected); - - at.mkdir_all(to); - at.mkdir_all(from); - - scene - .ucmd() - .arg(to) - .arg(from) - .succeeds() - .stdout_only(&format!("{expected}\n")); - } -} - -#[test] -fn test_relpath_with_from_with_d() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - for test in &TESTS { - let from: &str = &convert_path(test.from); - let to: &str = &convert_path(test.to); - let pwd = at.as_string(); - at.mkdir_all(to); - at.mkdir_all(from); - - // d is part of subpath -> expect relative path - let mut _result_stdout = scene - .ucmd() - .arg(to) - .arg(from) - .arg(&format!("-d{pwd}")) - .succeeds() - .stdout_move_str(); - // relax rules for windows test environment - #[cfg(not(windows))] - assert!(Path::new(&_result_stdout).is_relative()); - - // d is not part of subpath -> expect absolute path - _result_stdout = scene - .ucmd() - .arg(to) - .arg(from) - .arg("-dnon_existing") // spell-checker:disable-line - .succeeds() - .stdout_move_str(); - assert!(Path::new(&_result_stdout).is_absolute()); - } -} - -#[test] -fn test_relpath_no_from_no_d() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - for test in &TESTS { - let to: &str = &convert_path(test.to); - at.mkdir_all(to); - - let _result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str(); - #[cfg(not(windows))] - assert_eq!(_result_stdout, format!("{to}\n")); - // relax rules for windows test environment - #[cfg(windows)] - assert!(_result_stdout.ends_with(&format!("{to}\n"))); - } -} - -#[test] -fn test_relpath_no_from_with_d() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - for test in &TESTS { - let to: &str = &convert_path(test.to); - let pwd = at.as_string(); - at.mkdir_all(to); - - // d is part of subpath -> expect relative path - let _result_stdout = scene - .ucmd() - .arg(to) - .arg(&format!("-d{pwd}")) - .succeeds() - .stdout_move_str(); - // relax rules for windows test environment - #[cfg(not(windows))] - assert!(Path::new(&_result_stdout).is_relative()); - - // d is not part of subpath -> expect absolute path - let result_stdout = scene - .ucmd() - .arg(to) - .arg("-dnon_existing") // spell-checker:disable-line - .succeeds() - .stdout_move_str(); - assert!(Path::new(&result_stdout).is_absolute()); - } -} diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 737c4fa79..5125c746d 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -1,3 +1,7 @@ +// 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::process::Stdio; use crate::common::util::TestScenario; @@ -478,7 +482,7 @@ fn test_rm_prompts() { // Needed for talking with stdin on platforms where CRLF or LF matters const END_OF_LINE: &str = if cfg!(windows) { "\r\n" } else { "\n" }; - let mut answers = vec![ + let mut answers = [ "rm: descend into directory 'a'?", "rm: remove write-protected regular empty file 'a/empty-no-write'?", "rm: remove symbolic link 'a/slink'?", @@ -643,6 +647,21 @@ fn test_prompt_write_protected_no() { assert!(at.file_exists(file_2)); } +#[cfg(feature = "chmod")] +#[test] +fn test_remove_inaccessible_dir() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dir_1 = "test_rm_protected"; + + at.mkdir(dir_1); + + scene.ccmd("chmod").arg("0").arg(dir_1).succeeds(); + + scene.ucmd().arg("-rf").arg(dir_1).succeeds(); + assert!(!at.dir_exists(dir_1)); +} + #[test] #[cfg(not(windows))] fn test_fifo_removal() { diff --git a/tests/by-util/test_rmdir.rs b/tests/by-util/test_rmdir.rs index 56e801d7e..086d43748 100644 --- a/tests/by-util/test_rmdir.rs +++ b/tests/by-util/test_rmdir.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; const DIR: &str = "dir"; diff --git a/tests/by-util/test_runcon.rs b/tests/by-util/test_runcon.rs index dd4445625..8e8b9b6b5 100644 --- a/tests/by-util/test_runcon.rs +++ b/tests/by-util/test_runcon.rs @@ -1,3 +1,7 @@ +// 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 (jargon) xattributes #![cfg(feature = "feat_selinux")] @@ -133,7 +137,7 @@ fn custom_context() { } fn get_sestatus_context(output: &[u8]) -> &str { - let re = regex::bytes::Regex::new(r#"Current context:\s*(\S+)\s*"#) + let re = regex::bytes::Regex::new(r"Current context:\s*(\S+)\s*") .expect("Invalid regular expression"); output diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index de0781912..da28181eb 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -1,3 +1,7 @@ +// 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 lmnop xlmnop use crate::common::util::TestScenario; use std::process::Stdio; @@ -619,11 +623,21 @@ fn test_neg_inf() { run(&["--", "-inf", "0"], b"-inf\n-inf\n-inf\n"); } +#[test] +fn test_neg_infinity() { + run(&["--", "-infinity", "0"], b"-inf\n-inf\n-inf\n"); +} + #[test] fn test_inf() { run(&["inf"], b"1\n2\n3\n"); } +#[test] +fn test_infinity() { + run(&["infinity"], b"1\n2\n3\n"); +} + #[test] fn test_inf_width() { run( diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index d98b840c4..83d2890ed 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_shuf.rs b/tests/by-util/test_shuf.rs index 44282b8a3..13df0fa48 100644 --- a/tests/by-util/test_shuf.rs +++ b/tests/by-util/test_shuf.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] @@ -71,7 +75,7 @@ fn test_echo() { #[test] fn test_head_count() { let repeat_limit = 5; - let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let input_seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let input = input_seq .iter() .map(ToString::to_string) @@ -102,7 +106,7 @@ fn test_head_count() { #[test] fn test_repeat() { let repeat_limit = 15000; - let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let input_seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let input = input_seq .iter() .map(ToString::to_string) diff --git a/tests/by-util/test_sleep.rs b/tests/by-util/test_sleep.rs index 13aa882b9..dd99c7406 100644 --- a/tests/by-util/test_sleep.rs +++ b/tests/by-util/test_sleep.rs @@ -1,3 +1,7 @@ +// 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 rstest::rstest; // spell-checker:ignore dont @@ -10,7 +14,7 @@ fn test_invalid_time_interval() { new_ucmd!() .arg("xyz") .fails() - .usage_error("invalid time interval 'xyz': Invalid input: 'xyz' at position 1"); + .usage_error("invalid time interval 'xyz': Invalid input: xyz"); new_ucmd!() .args(&["--", "-1"]) .fails() @@ -211,7 +215,7 @@ fn test_sleep_when_input_has_only_whitespace_then_error(#[case] input: &str) { #[test] fn test_sleep_when_multiple_input_some_with_error_then_shows_all_errors() { - let expected = "invalid time interval 'abc': Invalid input: 'abc' at position 1\n\ + let expected = "invalid time interval 'abc': Invalid input: abc\n\ sleep: invalid time interval '1years': Invalid time unit: 'years' at position 2\n\ sleep: invalid time interval ' ': Found only whitespace in input"; diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 0c8af8969..690623c1c 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (words) ints diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 35e5ebb05..5760be560 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -1,8 +1,8 @@ -// * 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 xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines nbbbb +// 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 xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines nbbbb dxen ncccc use crate::common::util::{AtPath, TestScenario}; use rand::{thread_rng, Rng, SeedableRng}; @@ -170,6 +170,22 @@ fn test_split_str_prefixed_chunks_by_bytes() { assert_eq!(glob.collate(), at.read_bytes(name)); } +/// Test short bytes option concatenated with value +#[test] +fn test_split_by_bytes_short_concatenated_with_value() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_by_bytes_short_concatenated_with_value"; + RandomFile::new(&at, name).add_bytes(10000); + ucmd.args(&["-b1000", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 10); + for filename in glob.collect() { + assert_eq!(glob.directory.metadata(&filename).len(), 1000); + } + assert_eq!(glob.collate(), at.read_bytes(name)); +} + // This is designed to test what happens when the desired part size is not a // multiple of the buffer size and we hopefully don't overshoot the desired part // size. @@ -238,6 +254,18 @@ fn test_additional_suffix_no_slash() { .usage_error("invalid suffix 'a/b', contains directory separator"); } +#[test] +fn test_split_additional_suffix_hyphen_value() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_additional_suffix"; + RandomFile::new(&at, name).add_lines(2000); + ucmd.args(&["--additional-suffix", "-300", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]-300$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + // note: the test_filter* tests below are unix-only // windows support has been waived for now because of the difficulty of getting // the `cmd` call right @@ -299,6 +327,47 @@ fn test_filter_command_fails() { .fails(); } +#[test] +#[cfg(unix)] +fn test_filter_broken_pipe() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "filter-big-input"; + + RandomFile::new(&at, name).add_lines(1024 * 10); + ucmd.args(&["--filter=head -c1 > /dev/null", "-n", "r/1", name]) + .succeeds(); +} + +#[test] +#[cfg(unix)] +fn test_filter_with_kth_chunk() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .args(&["--filter='some'", "--number=1/2"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("--filter does not process a chunk extracted to stdout"); + scene + .ucmd() + .args(&["--filter='some'", "--number=l/1/2"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("--filter does not process a chunk extracted to stdout"); + scene + .ucmd() + .args(&["--filter='some'", "--number=r/1/2"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("--filter does not process a chunk extracted to stdout"); +} + #[test] fn test_split_lines_number() { // Test if stdout/stderr for '--lines' option is correct @@ -312,29 +381,301 @@ fn test_split_lines_number() { .succeeds() .no_stderr() .no_stdout(); + scene + .ucmd() + .args(&["--lines", "0", "file"]) + .fails() + .code_is(1) + .stderr_only("split: invalid number of lines: 0\n"); + scene + .ucmd() + .args(&["-0", "file"]) + .fails() + .code_is(1) + .stderr_only("split: invalid number of lines: 0\n"); scene .ucmd() .args(&["--lines", "2fb", "file"]) .fails() .code_is(1) .stderr_only("split: invalid number of lines: '2fb'\n"); + scene + .ucmd() + .args(&["--lines", "file"]) + .fails() + .code_is(1) + .stderr_only("split: invalid number of lines: 'file'\n"); +} + +/// Test short lines option with value concatenated +#[test] +fn test_split_lines_short_concatenated_with_value() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_num_prefixed_chunks_by_lines"; + RandomFile::new(&at, name).add_lines(10000); + ucmd.args(&["-l1000", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 10); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +/// Test for obsolete lines option standalone +#[test] +fn test_split_obs_lines_standalone() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "obs-lines-standalone"; + RandomFile::new(&at, name).add_lines(4); + ucmd.args(&["-2", name]).succeeds().no_stderr().no_stdout(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +/// Test for obsolete lines option standalone overflow +#[test] +fn test_split_obs_lines_standalone_overflow() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "obs-lines-standalone"; + RandomFile::new(&at, name).add_lines(4); + ucmd.args(&["-99999999999999999991", name]) + .succeeds() + .no_stderr() + .no_stdout(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 1); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +/// Test for obsolete lines option as part of invalid combined short options +#[test] +fn test_split_obs_lines_within_invalid_combined_shorts() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + + scene + .ucmd() + .args(&["-2fb", "file"]) + .fails() + .code_is(1) + .stderr_contains("error: unexpected argument '-f' found\n"); +} + +/// Test for obsolete lines option as part of combined short options +#[test] +fn test_split_obs_lines_within_combined_shorts() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let name = "obs-lines-within-shorts"; + RandomFile::new(at, name).add_lines(400); + + scene + .ucmd() + .args(&["-x200de", name]) + .succeeds() + .no_stderr() + .no_stdout(); + let glob = Glob::new(at, ".", r"x\d\d$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +/// Test for obsolete lines option as part of combined short options with tailing suffix length with value +#[test] +fn test_split_obs_lines_within_combined_shorts_tailing_suffix_length() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "obs-lines-combined-shorts-tailing-suffix-length"; + RandomFile::new(&at, name).add_lines(1000); + ucmd.args(&["-d200a4", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x\d\d\d\d$"); + assert_eq!(glob.count(), 5); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +/// Test for obsolete lines option starts as part of combined short options +#[test] +fn test_split_obs_lines_starts_combined_shorts() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let name = "obs-lines-starts-shorts"; + RandomFile::new(at, name).add_lines(400); + + scene + .ucmd() + .args(&["-200xd", name]) + .succeeds() + .no_stderr() + .no_stdout(); + let glob = Glob::new(at, ".", r"x\d\d$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +/// Test for using both obsolete lines (standalone) option and short/long lines option simultaneously +#[test] +fn test_split_both_lines_and_obs_lines_standalone() { + // This test will ensure that: + // if both lines option '-l' or '--lines' (with value) and obsolete lines option '-100' are used - it fails + // if standalone lines option is used incorrectly and treated as a hyphen prefixed value of other option - it fails + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + + scene + .ucmd() + .args(&["-l", "2", "-2", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: cannot split in more than one way\n"); + scene + .ucmd() + .args(&["--lines", "2", "-2", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: cannot split in more than one way\n"); +} + +/// Test for using obsolete lines option incorrectly, so it is treated as a hyphen prefixed value of other option +#[test] +fn test_split_obs_lines_as_other_option_value() { + // This test will ensure that: + // if obsolete lines option is used incorrectly and treated as a hyphen prefixed value of other option - it fails + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + + scene + .ucmd() + .args(&["--lines", "-200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of lines: '-200'\n"); + scene + .ucmd() + .args(&["-l", "-200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of lines: '-200'\n"); + scene + .ucmd() + .args(&["-a", "-200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid suffix length: '-200'\n"); + scene + .ucmd() + .args(&["--suffix-length", "-d200e", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid suffix length: '-d200e'\n"); + scene + .ucmd() + .args(&["-C", "-200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of bytes: '-200'\n"); + scene + .ucmd() + .args(&["--line-bytes", "-x200a4", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of bytes: '-x200a4'\n"); + scene + .ucmd() + .args(&["-b", "-200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of bytes: '-200'\n"); + scene + .ucmd() + .args(&["--bytes", "-200xd", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of bytes: '-200xd'\n"); + scene + .ucmd() + .args(&["-n", "-200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of chunks: -200\n"); + scene + .ucmd() + .args(&["--number", "-e200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of chunks: -e200\n"); +} + +/// Test for using more than one obsolete lines option (standalone) +/// last one wins +#[test] +fn test_split_multiple_obs_lines_standalone() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let name = "multiple-obs-lines"; + RandomFile::new(at, name).add_lines(400); + + scene + .ucmd() + .args(&["-3000", "-200", name]) + .succeeds() + .no_stderr() + .no_stdout(); + let glob = Glob::new(at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +/// Test for using more than one obsolete lines option within combined shorts +/// last one wins +#[test] +fn test_split_multiple_obs_lines_within_combined() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let name = "multiple-obs-lines"; + RandomFile::new(at, name).add_lines(400); + + scene + .ucmd() + .args(&["-d5000x", "-e200d", name]) + .succeeds() + .no_stderr() + .no_stdout(); + let glob = Glob::new(at, ".", r"x\d\d$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +/// Test for using both obsolete lines option within combined shorts with conflicting -n option simultaneously +#[test] +fn test_split_obs_lines_within_combined_with_number() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + + scene + .ucmd() + .args(&["-3dxen", "4", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: cannot split in more than one way\n"); + scene + .ucmd() + .args(&["-dxe30n", "4", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: cannot split in more than one way\n"); } #[test] fn test_split_invalid_bytes_size() { new_ucmd!() - .args(&["-b", "1024R"]) + .args(&["-b", "1024W"]) .fails() .code_is(1) - .stderr_only("split: invalid number of bytes: '1024R'\n"); - #[cfg(not(target_pointer_width = "128"))] - new_ucmd!() - .args(&["-b", "1Y"]) - .fails() - .code_is(1) - .stderr_only( - "split: invalid number of bytes: '1Y': Value too large for defined data type\n", - ); + .stderr_only("split: invalid number of bytes: '1024W'\n"); #[cfg(target_pointer_width = "32")] { let sizes = ["1000G", "10T"]; @@ -345,19 +686,29 @@ fn test_split_invalid_bytes_size() { } #[test] +fn test_split_overflow_bytes_size() { + #[cfg(not(target_pointer_width = "128"))] + let (at, mut ucmd) = at_and_ucmd!(); + let name = "test_split_overflow_bytes_size"; + RandomFile::new(&at, name).add_bytes(1000); + ucmd.args(&["-b", "1Y", name]).succeeds(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 1); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +#[test] +#[cfg(target_pointer_width = "32")] fn test_split_chunks_num_chunks_oversized_32() { - #[cfg(target_pointer_width = "32")] - { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - at.touch("file"); - scene - .ucmd() - .args(&["--number", "5000000000", "file"]) - .fails() - .code_is(1) - .stderr_only("split: Number of chunks too big\n"); - } + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + scene + .ucmd() + .args(&["--number", "5000000000", "sixhundredfiftyonebytes.txt"]) + .fails() + .code_is(1) + .stderr_only("split: Number of chunks too big\n"); } #[test] @@ -369,17 +720,39 @@ fn test_split_stdin_num_chunks() { .stderr_only("split: -: cannot determine file size\n"); } +#[test] +fn test_split_stdin_num_kth_chunk() { + new_ucmd!() + .args(&["--number=1/2"]) + .fails() + .code_is(1) + .stderr_only("split: -: cannot determine file size\n"); +} + +#[test] +fn test_split_stdin_num_line_chunks() { + new_ucmd!() + .args(&["--number=l/2"]) + .fails() + .code_is(1) + .stderr_only("split: -: cannot determine file size\n"); +} + +#[test] +fn test_split_stdin_num_kth_line_chunk() { + new_ucmd!() + .args(&["--number=l/2/5"]) + .fails() + .code_is(1) + .stderr_only("split: -: cannot determine file size\n"); +} + 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() { @@ -396,9 +769,9 @@ fn test_alphabetic_dynamic_suffix_length() { // 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),); + for i in b'a'..=b'y' { + for j in b'a'..=b'z' { + let filename = format!("x{}{}", i as char, j as char); let contents = file_read(&at, &filename); assert_eq!(contents, "a"); } @@ -476,7 +849,7 @@ creating file 'xaf' } #[test] -fn test_number() { +fn test_number_n() { let (at, mut ucmd) = at_and_ucmd!(); let file_read = |f| { let mut s = String::new(); @@ -489,6 +862,98 @@ fn test_number() { assert_eq!(file_read("xac"), "klmno"); assert_eq!(file_read("xad"), "pqrst"); assert_eq!(file_read("xae"), "uvwxyz\n"); + #[cfg(unix)] + new_ucmd!() + .args(&["--number=100", "/dev/null"]) + .succeeds() + .stdout_only(""); +} + +#[test] +fn test_number_kth_of_n() { + new_ucmd!() + .args(&["--number=3/5", "asciilowercase.txt"]) + .succeeds() + .stdout_only("klmno"); + new_ucmd!() + .args(&["--number=5/5", "asciilowercase.txt"]) + .succeeds() + .stdout_only("uvwxyz\n"); + new_ucmd!() + .args(&["-e", "--number=99/100", "asciilowercase.txt"]) + .succeeds() + .stdout_only(""); + #[cfg(unix)] + new_ucmd!() + .args(&["--number=3/10", "/dev/null"]) + .succeeds() + .stdout_only(""); + #[cfg(target_pointer_width = "64")] + new_ucmd!() + .args(&[ + "--number=r/9223372036854775807/18446744073709551615", + "asciilowercase.txt", + ]) + .succeeds() + .stdout_only(""); + new_ucmd!() + .args(&["--number=0/5", "asciilowercase.txt"]) + .fails() + .stderr_contains("split: invalid chunk number: 0"); + new_ucmd!() + .args(&["--number=10/5", "asciilowercase.txt"]) + .fails() + .stderr_contains("split: invalid chunk number: 10"); + #[cfg(target_pointer_width = "64")] + new_ucmd!() + .args(&[ + "--number=9223372036854775807/18446744073709551616", + "asciilowercase.txt", + ]) + .fails() + .stderr_contains("split: invalid number of chunks: 18446744073709551616"); +} + +#[test] +fn test_number_kth_of_n_round_robin() { + new_ucmd!() + .args(&["--number", "r/2/3", "fivelines.txt"]) + .succeeds() + .stdout_only("2\n5\n"); + new_ucmd!() + .args(&["--number", "r/1/4", "fivelines.txt"]) + .succeeds() + .stdout_only("1\n5\n"); + new_ucmd!() + .args(&["-e", "--number", "r/7/7", "fivelines.txt"]) + .succeeds() + .stdout_only(""); + #[cfg(target_pointer_width = "64")] + new_ucmd!() + .args(&[ + "--number", + "r/9223372036854775807/18446744073709551615", + "fivelines.txt", + ]) + .succeeds() + .stdout_only(""); + #[cfg(target_pointer_width = "64")] + new_ucmd!() + .args(&[ + "--number", + "r/9223372036854775807/18446744073709551616", + "fivelines.txt", + ]) + .fails() + .stderr_contains("split: invalid number of chunks: 18446744073709551616"); + new_ucmd!() + .args(&["--number", "r/0/3", "fivelines.txt"]) + .fails() + .stderr_contains("split: invalid chunk number: 0"); + new_ucmd!() + .args(&["--number", "r/10/3", "fivelines.txt"]) + .fails() + .stderr_contains("split: invalid chunk number: 10"); } #[test] @@ -529,6 +994,19 @@ fn test_invalid_suffix_length() { .stderr_contains("invalid suffix length: 'xyz'"); } +/// Test short suffix length option with value concatenated +#[test] +fn test_split_suffix_length_short_concatenated_with_value() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_num_prefixed_chunks_by_lines"; + RandomFile::new(&at, name).add_lines(10000); + ucmd.args(&["-a4", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]][[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 10); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + #[test] fn test_include_newlines() { let (at, mut ucmd) = at_and_ucmd!(); @@ -547,6 +1025,19 @@ fn test_include_newlines() { assert_eq!(s, "5\n"); } +/// Test short number of chunks option concatenated with value +#[test] +fn test_split_number_chunks_short_concatenated_with_value() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-n3", "threebytes.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("xaa"), "a"); + assert_eq!(at.read("xab"), "b"); + assert_eq!(at.read("xac"), "c"); +} + #[test] fn test_allow_empty_files() { let (at, mut ucmd) = at_and_ucmd!(); @@ -621,6 +1112,25 @@ fn test_line_bytes() { assert_eq!(at.read("xad"), "ee\n"); } +#[test] +#[cfg(target_pointer_width = "64")] +fn test_line_bytes_overflow() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-C", "18446744073709551616", "letters.txt"]) + .succeeds(); + assert_eq!(at.read("xaa"), "aaaaaaaaa\nbbbb\ncccc\ndd\nee\n"); +} + +#[test] +fn test_line_bytes_concatenated_with_value() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-C8", "letters.txt"]).succeeds(); + assert_eq!(at.read("xaa"), "aaaaaaaa"); + assert_eq!(at.read("xab"), "a\nbbbb\n"); + assert_eq!(at.read("xac"), "cccc\ndd\n"); + assert_eq!(at.read("xad"), "ee\n"); +} + #[test] fn test_line_bytes_no_final_newline() { let (at, mut ucmd) = at_and_ucmd!(); @@ -720,7 +1230,20 @@ fn test_multiple_of_input_chunk() { #[test] fn test_numeric_suffix() { let (at, mut ucmd) = at_and_ucmd!(); - ucmd.args(&["-n", "4", "--numeric-suffixes", "9", "threebytes.txt"]) + ucmd.args(&["-n", "4", "--numeric-suffixes=9", "threebytes.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x09"), "a"); + assert_eq!(at.read("x10"), "b"); + assert_eq!(at.read("x11"), "c"); + assert_eq!(at.read("x12"), ""); +} + +#[test] +fn test_numeric_suffix_alias() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-n", "4", "--numeric=9", "threebytes.txt"]) .succeeds() .no_stdout() .no_stderr(); @@ -733,7 +1256,7 @@ fn test_numeric_suffix() { #[test] fn test_hex_suffix() { let (at, mut ucmd) = at_and_ucmd!(); - ucmd.args(&["-n", "4", "--hex-suffixes", "9", "threebytes.txt"]) + ucmd.args(&["-n", "4", "--hex-suffixes=9", "threebytes.txt"]) .succeeds() .no_stdout() .no_stderr(); @@ -743,6 +1266,203 @@ fn test_hex_suffix() { assert_eq!(at.read("x0c"), ""); } +#[test] +fn test_hex_suffix_alias() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-n", "4", "--hex=9", "threebytes.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x09"), "a"); + assert_eq!(at.read("x0a"), "b"); + assert_eq!(at.read("x0b"), "c"); + assert_eq!(at.read("x0c"), ""); +} + +#[test] +fn test_numeric_suffix_no_equal() { + new_ucmd!() + .args(&["-n", "4", "--numeric-suffixes", "9", "threebytes.txt"]) + .fails() + .stderr_contains("split: cannot open '9' for reading: No such file or directory"); +} + +#[test] +fn test_hex_suffix_no_equal() { + new_ucmd!() + .args(&["-n", "4", "--hex-suffixes", "9", "threebytes.txt"]) + .fails() + .stderr_contains("split: cannot open '9' for reading: No such file or directory"); +} + +/// Test for short numeric suffix not having any value +#[test] +fn test_short_numeric_suffix_no_value() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-l", "9", "-d", "onehundredlines.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x00"), "00\n01\n02\n03\n04\n05\n06\n07\n08\n"); + assert_eq!(at.read("x01"), "09\n10\n11\n12\n13\n14\n15\n16\n17\n"); + assert_eq!(at.read("x02"), "18\n19\n20\n21\n22\n23\n24\n25\n26\n"); + assert_eq!(at.read("x03"), "27\n28\n29\n30\n31\n32\n33\n34\n35\n"); + assert_eq!(at.read("x04"), "36\n37\n38\n39\n40\n41\n42\n43\n44\n"); + assert_eq!(at.read("x05"), "45\n46\n47\n48\n49\n50\n51\n52\n53\n"); + assert_eq!(at.read("x06"), "54\n55\n56\n57\n58\n59\n60\n61\n62\n"); + assert_eq!(at.read("x07"), "63\n64\n65\n66\n67\n68\n69\n70\n71\n"); + assert_eq!(at.read("x08"), "72\n73\n74\n75\n76\n77\n78\n79\n80\n"); + assert_eq!(at.read("x09"), "81\n82\n83\n84\n85\n86\n87\n88\n89\n"); + assert_eq!(at.read("x10"), "90\n91\n92\n93\n94\n95\n96\n97\n98\n"); + assert_eq!(at.read("x11"), "99\n"); +} + +/// Test for long numeric suffix not having any value +#[test] +fn test_numeric_suffix_no_value() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-l", "9", "--numeric-suffixes", "onehundredlines.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x00"), "00\n01\n02\n03\n04\n05\n06\n07\n08\n"); + assert_eq!(at.read("x01"), "09\n10\n11\n12\n13\n14\n15\n16\n17\n"); + assert_eq!(at.read("x02"), "18\n19\n20\n21\n22\n23\n24\n25\n26\n"); + assert_eq!(at.read("x03"), "27\n28\n29\n30\n31\n32\n33\n34\n35\n"); + assert_eq!(at.read("x04"), "36\n37\n38\n39\n40\n41\n42\n43\n44\n"); + assert_eq!(at.read("x05"), "45\n46\n47\n48\n49\n50\n51\n52\n53\n"); + assert_eq!(at.read("x06"), "54\n55\n56\n57\n58\n59\n60\n61\n62\n"); + assert_eq!(at.read("x07"), "63\n64\n65\n66\n67\n68\n69\n70\n71\n"); + assert_eq!(at.read("x08"), "72\n73\n74\n75\n76\n77\n78\n79\n80\n"); + assert_eq!(at.read("x09"), "81\n82\n83\n84\n85\n86\n87\n88\n89\n"); + assert_eq!(at.read("x10"), "90\n91\n92\n93\n94\n95\n96\n97\n98\n"); + assert_eq!(at.read("x11"), "99\n"); +} + +/// Test for short hex suffix not having any value +#[test] +fn test_short_hex_suffix_no_value() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-l", "9", "-x", "onehundredlines.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x00"), "00\n01\n02\n03\n04\n05\n06\n07\n08\n"); + assert_eq!(at.read("x01"), "09\n10\n11\n12\n13\n14\n15\n16\n17\n"); + assert_eq!(at.read("x02"), "18\n19\n20\n21\n22\n23\n24\n25\n26\n"); + assert_eq!(at.read("x03"), "27\n28\n29\n30\n31\n32\n33\n34\n35\n"); + assert_eq!(at.read("x04"), "36\n37\n38\n39\n40\n41\n42\n43\n44\n"); + assert_eq!(at.read("x05"), "45\n46\n47\n48\n49\n50\n51\n52\n53\n"); + assert_eq!(at.read("x06"), "54\n55\n56\n57\n58\n59\n60\n61\n62\n"); + assert_eq!(at.read("x07"), "63\n64\n65\n66\n67\n68\n69\n70\n71\n"); + assert_eq!(at.read("x08"), "72\n73\n74\n75\n76\n77\n78\n79\n80\n"); + assert_eq!(at.read("x09"), "81\n82\n83\n84\n85\n86\n87\n88\n89\n"); + assert_eq!(at.read("x0a"), "90\n91\n92\n93\n94\n95\n96\n97\n98\n"); + assert_eq!(at.read("x0b"), "99\n"); +} + +/// Test for long hex suffix not having any value +#[test] +fn test_hex_suffix_no_value() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-l", "9", "--hex-suffixes", "onehundredlines.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x00"), "00\n01\n02\n03\n04\n05\n06\n07\n08\n"); + assert_eq!(at.read("x01"), "09\n10\n11\n12\n13\n14\n15\n16\n17\n"); + assert_eq!(at.read("x02"), "18\n19\n20\n21\n22\n23\n24\n25\n26\n"); + assert_eq!(at.read("x03"), "27\n28\n29\n30\n31\n32\n33\n34\n35\n"); + assert_eq!(at.read("x04"), "36\n37\n38\n39\n40\n41\n42\n43\n44\n"); + assert_eq!(at.read("x05"), "45\n46\n47\n48\n49\n50\n51\n52\n53\n"); + assert_eq!(at.read("x06"), "54\n55\n56\n57\n58\n59\n60\n61\n62\n"); + assert_eq!(at.read("x07"), "63\n64\n65\n66\n67\n68\n69\n70\n71\n"); + assert_eq!(at.read("x08"), "72\n73\n74\n75\n76\n77\n78\n79\n80\n"); + assert_eq!(at.read("x09"), "81\n82\n83\n84\n85\n86\n87\n88\n89\n"); + assert_eq!(at.read("x0a"), "90\n91\n92\n93\n94\n95\n96\n97\n98\n"); + assert_eq!(at.read("x0b"), "99\n"); +} + +/// Test for short numeric suffix having value provided after space - should fail +#[test] +fn test_short_numeric_suffix_with_value_spaced() { + new_ucmd!() + .args(&["-n", "4", "-d", "9", "threebytes.txt"]) + .fails() + .stderr_contains("split: cannot open '9' for reading: No such file or directory"); +} + +/// Test for short numeric suffix having value provided after space - should fail +#[test] +fn test_short_hex_suffix_with_value_spaced() { + new_ucmd!() + .args(&["-n", "4", "-x", "9", "threebytes.txt"]) + .fails() + .stderr_contains("split: cannot open '9' for reading: No such file or directory"); +} + +/// Test for some combined short options +#[test] +fn test_short_combination() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-dxen", "4", "threebytes.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x00"), "a"); + assert_eq!(at.read("x01"), "b"); + assert_eq!(at.read("x02"), "c"); + assert!(!at.file_exists("x03")); +} + +/// Test for the last effective suffix, ignoring all others - numeric long last +/// Any combination of short and long (as well as duplicates) should be allowed +#[test] +fn test_effective_suffix_numeric_last() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&[ + "-n", + "4", + "--numeric-suffixes=7", + "--hex-suffixes=4", + "-d", + "-x", + "--numeric-suffixes=9", + "threebytes.txt", + ]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x09"), "a"); + assert_eq!(at.read("x10"), "b"); + assert_eq!(at.read("x11"), "c"); + assert_eq!(at.read("x12"), ""); +} + +/// Test for the last effective suffix, ignoring all others - hex long last +/// Any combination of short and long (as well as duplicates) should be allowed +#[test] +fn test_effective_suffix_hex_last() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&[ + "-n", + "4", + "--hex-suffixes=7", + "--numeric-suffixes=4", + "-x", + "-d", + "--hex-suffixes=9", + "threebytes.txt", + ]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x09"), "a"); + assert_eq!(at.read("x0a"), "b"); + assert_eq!(at.read("x0b"), "c"); + assert_eq!(at.read("x0c"), ""); +} + #[test] fn test_round_robin() { let (at, mut ucmd) = at_and_ucmd!(); @@ -791,3 +1511,288 @@ fn test_split_invalid_input() { .no_stdout() .stderr_contains("split: invalid number of chunks: 0"); } + +/// Test if there are invalid (non UTF-8) in the arguments - unix +/// clap is expected to fail/panic +#[test] +#[cfg(unix)] +fn test_split_non_utf8_argument_unix() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + let (at, mut ucmd) = at_and_ucmd!(); + let name = "test_split_non_utf8_argument"; + let opt = OsStr::from_bytes("--additional-suffix".as_bytes()); + RandomFile::new(&at, name).add_lines(2000); + // Here, the values 0x66 and 0x6f correspond to 'f' and 'o' + // respectively. The value 0x80 is a lone continuation byte, invalid + // in a UTF-8 sequence. + let opt_value = [0x66, 0x6f, 0x80, 0x6f]; + let opt_value = OsStr::from_bytes(&opt_value[..]); + let name = OsStr::from_bytes(name.as_bytes()); + ucmd.args(&[opt, opt_value, name]) + .fails() + .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); +} + +/// Test if there are invalid (non UTF-8) in the arguments - windows +/// clap is expected to fail/panic +#[test] +#[cfg(windows)] +fn test_split_non_utf8_argument_windows() { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + + let (at, mut ucmd) = at_and_ucmd!(); + let name = "test_split_non_utf8_argument"; + let opt = OsString::from("--additional-suffix"); + RandomFile::new(&at, name).add_lines(2000); + // Here the values 0x0066 and 0x006f correspond to 'f' and 'o' + // respectively. The value 0xD800 is a lone surrogate half, invalid + // in a UTF-16 sequence. + let opt_value = [0x0066, 0x006f, 0xD800, 0x006f]; + let opt_value = OsString::from_wide(&opt_value[..]); + let name = OsString::from(name); + ucmd.args(&[opt, opt_value, name]) + .fails() + .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); +} + +// Test '--separator' / '-t' option following GNU tests example +// test separators: '\n' , '\0' , ';' +// test with '--lines=2' , '--line-bytes=4' , '--number=l/3' , '--number=r/3' , '--number=l/1/3' , '--number=r/1/3' +#[test] +fn test_split_separator_nl_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--lines=2", "-t", "\n"]) + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n2\n"); + assert_eq!(file_read(&at, "xab"), "3\n4\n"); + assert_eq!(file_read(&at, "xac"), "5\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nl_line_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--line-bytes=4", "-t", "\n"]) + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n2\n"); + assert_eq!(file_read(&at, "xab"), "3\n4\n"); + assert_eq!(file_read(&at, "xac"), "5\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nl_number_l() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/3", "--separator=\n", "fivelines.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n2\n"); + assert_eq!(file_read(&at, "xab"), "3\n4\n"); + assert_eq!(file_read(&at, "xac"), "5\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nl_number_r() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=r/3", "--separator", "\n", "fivelines.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n4\n"); + assert_eq!(file_read(&at, "xab"), "2\n5\n"); + assert_eq!(file_read(&at, "xac"), "3\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--lines=2", "-t", "\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\x002\0"); + assert_eq!(file_read(&at, "xab"), "3\x004\0"); + assert_eq!(file_read(&at, "xac"), "5\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_line_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--line-bytes=4", "-t", "\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\x002\0"); + assert_eq!(file_read(&at, "xab"), "3\x004\0"); + assert_eq!(file_read(&at, "xac"), "5\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_number_l() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/3", "--separator=\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\x002\0"); + assert_eq!(file_read(&at, "xab"), "3\x004\0"); + assert_eq!(file_read(&at, "xac"), "5\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_number_r() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=r/3", "--separator=\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\x004\0"); + assert_eq!(file_read(&at, "xab"), "2\x005\0"); + assert_eq!(file_read(&at, "xac"), "3\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--lines=2", "-t", ";", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;2;"); + assert_eq!(file_read(&at, "xab"), "3;4;"); + assert_eq!(file_read(&at, "xac"), "5;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_line_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--line-bytes=4", "-t", ";", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;2;"); + assert_eq!(file_read(&at, "xab"), "3;4;"); + assert_eq!(file_read(&at, "xac"), "5;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_number_l() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/3", "--separator=;", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;2;"); + assert_eq!(file_read(&at, "xab"), "3;4;"); + assert_eq!(file_read(&at, "xac"), "5;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_number_r() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=r/3", "--separator=;", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;4;"); + assert_eq!(file_read(&at, "xab"), "2;5;"); + assert_eq!(file_read(&at, "xac"), "3;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_number_kth_l() { + new_ucmd!() + .args(&[ + "--number=l/1/3", + "--separator", + ";", + "separator_semicolon.txt", + ]) + .succeeds() + .stdout_only("1;2;"); +} + +#[test] +fn test_split_separator_semicolon_number_kth_r() { + new_ucmd!() + .args(&[ + "--number=r/1/3", + "--separator", + ";", + "separator_semicolon.txt", + ]) + .succeeds() + .stdout_only("1;4;"); +} + +// Test error edge cases for separator option +#[test] +fn test_split_separator_no_value() { + new_ucmd!() + .args(&["-t"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .stderr_contains( + "error: a value is required for '--separator ' but none was supplied", + ); +} + +#[test] +fn test_split_separator_invalid_usage() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .args(&["--separator=xx"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("split: multi-character separator 'xx'"); + scene + .ucmd() + .args(&["-ta", "-tb"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("split: multiple separator characters specified"); + scene + .ucmd() + .args(&["-t'\n'", "-tb"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("split: multiple separator characters specified"); +} + +// Test using same separator multiple times +#[test] +fn test_split_separator_same_multiple() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .args(&["--separator=:", "--separator=:", "fivelines.txt"]) + .succeeds(); + scene + .ucmd() + .args(&["-t:", "--separator=:", "fivelines.txt"]) + .succeeds(); + scene + .ucmd() + .args(&["-t", ":", "-t", ":", "fivelines.txt"]) + .succeeds(); + scene + .ucmd() + .args(&["-t:", "-t:", "-t,", "fivelines.txt"]) + .fails(); +} diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 92a8bcd98..e918d54e9 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 crate::common::util::{expected_result, TestScenario}; @@ -304,6 +304,19 @@ fn test_stdin_pipe_fifo2() { .succeeded(); } +#[test] +#[cfg(all(unix, not(target_os = "android")))] +fn test_stdin_with_fs_option() { + // $ stat -f - + new_ucmd!() + .arg("-f") + .arg("-") + .set_stdin(std::process::Stdio::null()) + .fails() + .code_is(1) + .stderr_contains("using '-' to denote standard input does not work in file system mode"); +} + #[test] #[cfg(all( unix, diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 334715706..9a67dad9e 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -1,3 +1,7 @@ +// 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. #[cfg(not(target_os = "windows"))] use crate::common::util::TestScenario; @@ -61,7 +65,7 @@ fn test_stdbuf_invalid_mode_fails() { .args(&[*option, "1024R", "head"]) .fails() .code_is(125) - .stderr_only("stdbuf: invalid mode '1024R'\n"); + .stderr_only("stdbuf: invalid mode '1024R': Value too large for defined data type\n"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&[*option, "1Y", "head"]) diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index e85c11082..a9a9209b0 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -1,3 +1,7 @@ +// 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 parenb parmrk ixany iuclc onlcr ofdel icanon noflsh use crate::common::util::TestScenario; diff --git a/tests/by-util/test_sum.rs b/tests/by-util/test_sum.rs index 0a43a3043..13f453ba1 100644 --- a/tests/by-util/test_sum.rs +++ b/tests/by-util/test_sum.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_sync.rs b/tests/by-util/test_sync.rs index 9cae3b8a0..9a824cd48 100644 --- a/tests/by-util/test_sync.rs +++ b/tests/by-util/test_sync.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use std::fs; use tempfile::tempdir; diff --git a/tests/by-util/test_tac.rs b/tests/by-util/test_tac.rs index cc98ec616..fa5f67f13 100644 --- a/tests/by-util/test_tac.rs +++ b/tests/by-util/test_tac.rs @@ -1,3 +1,7 @@ +// 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 axxbxx bxxaxx axxx axxxx xxaxx xxax xxxxa axyz zyax zyxa use crate::common::util::TestScenario; diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 75abb8eb6..bc89f56a0 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile file siette ocho nueve diez MULT // spell-checker:ignore (libs) kqueue diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index 51d552d67..2b3fd2670 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -1,4 +1,9 @@ +// 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 crate::common::util::TestScenario; +use std::fmt::Write; // tests for basic tee functionality. // inspired by: @@ -70,7 +75,10 @@ fn test_tee_append() { fn test_tee_no_more_writeable_1() { // equals to 'tee /dev/full out2 (); + let content = (1..=10).fold(String::new(), |mut output, x| { + let _ = writeln!(output, "{x}"); + output + }); let file_out = "tee_file_out"; ucmd.arg("/dev/full") @@ -90,7 +98,10 @@ 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!("{x}\n")).collect::(); + let _content = (1..=10).fold(String::new(), |mut output, x| { + let _ = writeln!(output, "{x}"); + output + }); let file_out_a = "tee_file_out_a"; let file_out_b = "tee_file_out_b"; @@ -110,6 +121,7 @@ fn test_tee_no_more_writeable_2() { mod linux_only { use crate::common::util::{AtPath, TestScenario, UCommand}; + use std::fmt::Write; use std::fs::File; use std::process::{Output, Stdio}; @@ -131,7 +143,10 @@ mod linux_only { } fn run_tee(proc: &mut UCommand) -> (String, Output) { - let content = (1..=100_000).map(|x| format!("{x}\n")).collect::(); + let content = (1..=100_000).fold(String::new(), |mut output, x| { + let _ = writeln!(output, "{x}"); + output + }); #[allow(deprecated)] let output = proc diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index bf5c50b7f..922d854c6 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -1,12 +1,7 @@ -// // This file is part of the uutils coreutils package. // -// (c) mahkoh (ju.orth [at] gmail [dot] com) -// (c) Daniel Rocco -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// // spell-checker:ignore (words) egid euid pseudofloat @@ -322,7 +317,7 @@ fn test_file_is_itself() { } #[test] -#[cfg(not(any(target_env = "musl", target_os = "android")))] +#[cfg(not(target_os = "android"))] fn test_file_is_newer_than_and_older_than_itself() { // odd but matches GNU new_ucmd!() @@ -369,8 +364,7 @@ fn test_same_device_inode() { } #[test] -#[cfg(not(any(target_env = "musl", target_os = "android")))] -// musl: creation time is not available on this platform currently +#[cfg(not(target_os = "android"))] fn test_newer_file() { let scenario = TestScenario::new(util_name!()); @@ -382,10 +376,21 @@ fn test_newer_file() { .ucmd() .args(&["newer_file", "-nt", "regular_file"]) .succeeds(); + + scenario + .ucmd() + .args(&["regular_file", "-nt", "newer_file"]) + .fails(); + + scenario + .ucmd() + .args(&["regular_file", "-ot", "newer_file"]) + .succeeds(); + scenario .ucmd() .args(&["newer_file", "-ot", "regular_file"]) - .succeeds(); + .fails(); } #[test] diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 8e6e5db55..6c4a00eb5 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -1,3 +1,7 @@ +// 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 dont use crate::common::util::TestScenario; diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 0ebbee1d7..7b659fc51 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -1,7 +1,10 @@ +// 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 (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms datetime mktime use crate::common::util::{AtPath, TestScenario}; -use chrono::TimeZone; use filetime::{self, FileTime}; use std::fs::remove_file; use std::path::PathBuf; @@ -28,7 +31,7 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) { } fn str_to_filetime(format: &str, s: &str) -> FileTime { - let tm = chrono::Utc.datetime_from_str(s, format).unwrap(); + let tm = chrono::NaiveDateTime::parse_from_str(s, format).unwrap(); FileTime::from_unix_time(tm.timestamp(), tm.timestamp_subsec_nanos()) } @@ -841,3 +844,15 @@ fn test_touch_dash() { ucmd.args(&["-h", "-"]).succeeds().no_stderr().no_stdout(); } + +#[test] +// Chrono panics for now +#[ignore] +fn test_touch_invalid_date_format() { + let (_at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_invalid_date_format"; + + ucmd.args(&["-m", "-t", "+1000000000000 years", file]) + .fails() + .stderr_contains("touch: invalid date format ‘+1000000000000 years’"); +} diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 4e437609e..7c475a492 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1,3 +1,7 @@ +// 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 aabbaa aabbcc aabc abbb abcc abcdefabcdef abcdefghijk abcdefghijklmn abcdefghijklmnop ABCDEFGHIJKLMNOPQRS abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFZZ abcxyz ABCXYZ abcxyzabcxyz ABCXYZABCXYZ acbdef alnum amzamz AMZXAMZ bbbd cclass cefgm cntrl compl dabcdef dncase Gzabcdefg PQRST upcase wxyzz xdigit xycde xyyye xyyz xyzzzzxyzzzz ZABCDEF Zamz Cdefghijkl Cdefghijklmn use crate::common::util::TestScenario; diff --git a/tests/by-util/test_true.rs b/tests/by-util/test_true.rs index 1675b3903..750c60132 100644 --- a/tests/by-util/test_true.rs +++ b/tests/by-util/test_true.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] use std::fs::OpenOptions; diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 7a0bac6e9..81b87ed2e 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (words) RFILE @@ -248,7 +248,7 @@ fn test_truncate_bytes_size() { .args(&["--size", "1024R", "file"]) .fails() .code_is(1) - .stderr_only("truncate: Invalid number: '1024R'\n"); + .stderr_only("truncate: Invalid number: '1024R': Value too large for defined data type\n"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["--size", "1Y", "file"]) diff --git a/tests/by-util/test_tsort.rs b/tests/by-util/test_tsort.rs index 62a74c31d..188894516 100644 --- a/tests/by-util/test_tsort.rs +++ b/tests/by-util/test_tsort.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_tty.rs b/tests/by-util/test_tty.rs index 87dcbac85..0f2c58806 100644 --- a/tests/by-util/test_tty.rs +++ b/tests/by-util/test_tty.rs @@ -1,3 +1,7 @@ +// 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::fs::File; use crate::common::util::TestScenario; diff --git a/tests/by-util/test_uname.rs b/tests/by-util/test_uname.rs index 5076334e5..3676eefba 100644 --- a/tests/by-util/test_uname.rs +++ b/tests/by-util/test_uname.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_unexpand.rs b/tests/by-util/test_unexpand.rs index c833c93e4..ddbe3343e 100644 --- a/tests/by-util/test_unexpand.rs +++ b/tests/by-util/test_unexpand.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_uniq.rs b/tests/by-util/test_uniq.rs index 57f0ec111..aa41de827 100644 --- a/tests/by-util/test_uniq.rs +++ b/tests/by-util/test_uniq.rs @@ -1,3 +1,7 @@ +// 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::io::Write; // spell-checker:ignore nabcd @@ -76,7 +80,7 @@ fn test_stdin_skip_and_check_2_chars() { } #[test] -fn test_stdin_skip_1_field() { +fn test_stdin_skip_2_fields() { new_ucmd!() .args(&["-f2"]) .pipe_in_fixture(SKIP_FIELDS) @@ -84,6 +88,42 @@ fn test_stdin_skip_1_field() { .stdout_is_fixture("skip-2-fields.expected"); } +#[test] +fn test_stdin_skip_2_fields_obsolete() { + new_ucmd!() + .args(&["-2"]) + .pipe_in_fixture(SKIP_FIELDS) + .run() + .stdout_is_fixture("skip-2-fields.expected"); +} + +#[test] +fn test_stdin_skip_21_fields() { + new_ucmd!() + .args(&["-f21"]) + .pipe_in_fixture(SKIP_FIELDS) + .run() + .stdout_is_fixture("skip-21-fields.expected"); +} + +#[test] +fn test_stdin_skip_21_fields_obsolete() { + new_ucmd!() + .args(&["-21"]) + .pipe_in_fixture(SKIP_FIELDS) + .run() + .stdout_is_fixture("skip-21-fields.expected"); +} + +#[test] +fn test_stdin_skip_invalid_fields_obsolete() { + new_ucmd!() + .args(&["-5deadbeef"]) + .run() + .failure() + .stderr_only("uniq: Invalid argument for skip-fields: 5deadbeef\n"); +} + #[test] fn test_stdin_all_repeated() { new_ucmd!() @@ -432,15 +472,15 @@ fn gnu_tests() { stderr: None, exit: None, }, - // // Obsolete syntax for "-f 1" - // TestCase { - // name: "obs30", - // args: &["-1"], - // input: "a a\nb a\n", - // stdout: Some("a a\n"), - // stderr: None, - // exit: None, - // }, + // Obsolete syntax for "-f 1" + TestCase { + name: "obs30", + args: &["-1"], + input: "a a\nb a\n", + stdout: Some("a a\n"), + stderr: None, + exit: None, + }, TestCase { name: "31", args: &["-f", "1"], @@ -514,23 +554,25 @@ fn gnu_tests() { stderr: None, exit: None, }, - // // Obsolete syntax for "-s 1" - // TestCase { - // name: "obs-plus44", - // args: &["+1", "--"], - // input: "aaa\naaa\n", - // stdout: Some("aaa\n"), - // stderr: None, - // exit: None, - // }, - // TestCase { - // name: "obs-plus45", - // args: &["+1", "--"], - // input: "baa\naaa\n", - // stdout: Some("baa\n"), - // stderr: None, - // exit: None, - // }, + /* + // Obsolete syntax for "-s 1" + TestCase { + name: "obs-plus44", + args: &["+1", "--"], + input: "aaa\naaa\n", + stdout: Some("aaa\n"), + stderr: None, + exit: None, + }, + TestCase { + name: "obs-plus45", + args: &["+1", "--"], + input: "baa\naaa\n", + stdout: Some("baa\n"), + stderr: None, + exit: None, + }, + */ TestCase { name: "50", args: &["-f", "1", "-s", "1"], diff --git a/tests/by-util/test_unlink.rs b/tests/by-util/test_unlink.rs index 5313c9eab..055f47f10 100644 --- a/tests/by-util/test_unlink.rs +++ b/tests/by-util/test_unlink.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_uptime.rs b/tests/by-util/test_uptime.rs index 628f4cead..3967d0252 100644 --- a/tests/by-util/test_uptime.rs +++ b/tests/by-util/test_uptime.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use regex::Regex; diff --git a/tests/by-util/test_users.rs b/tests/by-util/test_users.rs index 6a54aa8d2..766378a9d 100644 --- a/tests/by-util/test_users.rs +++ b/tests/by-util/test_users.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; #[test] diff --git a/tests/by-util/test_vdir.rs b/tests/by-util/test_vdir.rs index f6498567f..97d5b847f 100644 --- a/tests/by-util/test_vdir.rs +++ b/tests/by-util/test_vdir.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::TestScenario; use regex::Regex; diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index aba5ed350..6417470c5 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -1,3 +1,7 @@ +// 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 crate::common::util::{vec_of_size, TestScenario}; // spell-checker:ignore (flags) lwmcL clmwL ; (path) bogusfile emptyfile manyemptylines moby notrailingnewline onelongemptyline onelongword weirdchars diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 31b46c3bf..3bacc38c1 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (flags) runlevel mesg diff --git a/tests/by-util/test_whoami.rs b/tests/by-util/test_whoami.rs index 9e6c35be6..d32c4ec24 100644 --- a/tests/by-util/test_whoami.rs +++ b/tests/by-util/test_whoami.rs @@ -1,7 +1,7 @@ -// * 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. +// 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. #[cfg(unix)] use crate::common::util::expected_result; diff --git a/tests/by-util/test_yes.rs b/tests/by-util/test_yes.rs index a674f8245..f40d6d5b8 100644 --- a/tests/by-util/test_yes.rs +++ b/tests/by-util/test_yes.rs @@ -1,3 +1,7 @@ +// 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::OsStr; use std::process::{ExitStatus, Stdio}; diff --git a/tests/common/macros.rs b/tests/common/macros.rs index 4f5965d5a..4902ca49b 100644 --- a/tests/common/macros.rs +++ b/tests/common/macros.rs @@ -1,7 +1,7 @@ -// * 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. +// 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. /// Platform-independent helper for constructing a `PathBuf` from individual elements #[macro_export] diff --git a/tests/common/mod.rs b/tests/common/mod.rs index f73cd42af..05e2b1382 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,3 +1,7 @@ +// 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. #[macro_use] pub mod macros; pub mod random; diff --git a/tests/common/random.rs b/tests/common/random.rs index bf1e6a907..42b6eaa77 100644 --- a/tests/common/random.rs +++ b/tests/common/random.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 rand::distributions::{Distribution, Uniform}; use rand::{thread_rng, Rng}; diff --git a/tests/common/util.rs b/tests/common/util.rs index 995312f08..6f4e76d42 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -1,7 +1,7 @@ -// * 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. +// 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 (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL canonicalized @@ -541,7 +541,7 @@ impl CmdResult { let contents = String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap(); let possible_values = template_vars.iter().map(|vars| { let mut contents = contents.clone(); - for kv in vars.iter() { + for kv in vars { contents = contents.replace(&kv.0, &kv.1); } contents @@ -2211,12 +2211,12 @@ impl UChild { let join_handle = thread::spawn(move || { let mut writer = BufWriter::new(stdin); - match writer.write_all(&content).and_then(|_| writer.flush()) { + match writer.write_all(&content).and_then(|()| writer.flush()) { Err(error) if !ignore_stdin_write_error => Err(io::Error::new( io::ErrorKind::Other, format!("failed to write to stdin of child: {error}"), )), - Ok(_) | Err(_) => Ok(()), + Ok(()) | Err(_) => Ok(()), } }); @@ -2263,12 +2263,12 @@ impl UChild { pub fn try_write_in>>(&mut self, data: T) -> io::Result<()> { let stdin = self.raw.stdin.as_mut().unwrap(); - match stdin.write_all(&data.into()).and_then(|_| stdin.flush()) { + match stdin.write_all(&data.into()).and_then(|()| stdin.flush()) { Err(error) if !self.ignore_stdin_write_error => Err(io::Error::new( io::ErrorKind::Other, format!("failed to write to stdin of child: {error}"), )), - Ok(_) | Err(_) => Ok(()), + Ok(()) | Err(_) => Ok(()), } } @@ -2505,11 +2505,11 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result< /// This is a convenience wrapper to run a ucmd with root permissions. /// It can be used to test programs when being root is needed -/// This runs 'sudo -E --non-interactive target/debug/coreutils util_name args` +/// This runs `sudo -E --non-interactive target/debug/coreutils util_name args` /// This is primarily designed to run in an environment where whoami is in $path /// and where non-interactive sudo is possible. /// To check if i) non-interactive sudo is possible and ii) if sudo works, this runs: -/// 'sudo -E --non-interactive whoami' first. +/// `sudo -E --non-interactive whoami` first. /// /// This return an `Err()` if run inside CICD because there's no 'sudo'. /// @@ -2532,6 +2532,16 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result< pub fn run_ucmd_as_root( ts: &TestScenario, args: &[&str], +) -> std::result::Result { + run_ucmd_as_root_with_stdin_stdout(ts, args, None, None) +} + +#[cfg(unix)] +pub fn run_ucmd_as_root_with_stdin_stdout( + ts: &TestScenario, + args: &[&str], + stdin: Option<&str>, + stdout: Option<&str>, ) -> std::result::Result { if is_ci() { Err(format!("{UUTILS_INFO}: {}", "cannot run inside CI")) @@ -2546,16 +2556,21 @@ pub fn run_ucmd_as_root( Ok(output) if String::from_utf8_lossy(&output.stdout).eq("root\n") => { // we can run sudo and we're root // run ucmd as root: - Ok(ts - .cmd("sudo") - .env("PATH", PATH) + let mut cmd = ts.cmd("sudo"); + cmd.env("PATH", PATH) .envs(DEFAULT_ENV) .arg("-E") .arg("--non-interactive") .arg(&ts.bin_path) .arg(&ts.util_name) - .args(args) - .run()) + .args(args); + if let Some(stdin) = stdin { + cmd.set_stdin(File::open(stdin).unwrap()); + } + if let Some(stdout) = stdout { + cmd.set_stdout(File::open(stdout).unwrap()); + } + Ok(cmd.run()) } Ok(output) if String::from_utf8_lossy(&output.stderr).eq("sudo: a password is required\n") => @@ -3279,7 +3294,7 @@ mod tests { std::assert_eq!(error.to_string(), "kill: Timeout of '0s' reached"); } Err(error) => panic!("Assertion failed: Expected error with timeout but was: {error}"), - Ok(_) => panic!("Assertion failed: Expected timeout of `try_kill`."), + Ok(()) => panic!("Assertion failed: Expected timeout of `try_kill`."), } } diff --git a/tests/fixtures/install/helloworld.rs b/tests/fixtures/install/helloworld.rs index 47ad8c634..bb2a816fc 100644 --- a/tests/fixtures/install/helloworld.rs +++ b/tests/fixtures/install/helloworld.rs @@ -1,3 +1,7 @@ +// 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. fn main() { println!("Hello World!"); } diff --git a/tests/fixtures/split/separator_nul.txt b/tests/fixtures/split/separator_nul.txt new file mode 100644 index 000000000..c4c49609d Binary files /dev/null and b/tests/fixtures/split/separator_nul.txt differ diff --git a/tests/fixtures/split/separator_semicolon.txt b/tests/fixtures/split/separator_semicolon.txt new file mode 100644 index 000000000..a8396d8ee --- /dev/null +++ b/tests/fixtures/split/separator_semicolon.txt @@ -0,0 +1 @@ +1;2;3;4;5; \ No newline at end of file diff --git a/tests/fixtures/uniq/skip-2-fields.expected b/tests/fixtures/uniq/skip-2-fields.expected index b971c2b2b..c7f9bde9d 100644 --- a/tests/fixtures/uniq/skip-2-fields.expected +++ b/tests/fixtures/uniq/skip-2-fields.expected @@ -1,2 +1,5 @@ aaa ⟪⟫ a aa a +a a a a a a a a a a a a a a a a a a a b a a a +a a a a a a a a a a a a a a a a a a a a b a a +a a a a a a a a a a a a a a a a a a a a a b a diff --git a/tests/fixtures/uniq/skip-21-fields.expected b/tests/fixtures/uniq/skip-21-fields.expected new file mode 100644 index 000000000..1f5295afa --- /dev/null +++ b/tests/fixtures/uniq/skip-21-fields.expected @@ -0,0 +1,3 @@ + aaa ⟪⟫ a +a a a a a a a a a a a a a a a a a a a b a a a +a a a a a a a a a a a a a a a a a a a a a b a diff --git a/tests/fixtures/uniq/skip-fields.txt b/tests/fixtures/uniq/skip-fields.txt index 4ec2744c6..0ca708a74 100644 --- a/tests/fixtures/uniq/skip-fields.txt +++ b/tests/fixtures/uniq/skip-fields.txt @@ -6,3 +6,6 @@ ZZZ aa a aa a a +a a a a a a a a a a a a a a a a a a a b a a a +a a a a a a a a a a a a a a a a a a a a b a a +a a a a a a a a a a a a a a a a a a a a a b a diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index bf0ea2fa3..45edc7dc7 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -1,3 +1,7 @@ +// 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. #![allow(unused_imports)] mod common; diff --git a/tests/tests.rs b/tests/tests.rs index 02c3bfdab..1fb5735eb 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,3 +1,7 @@ +// 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. #[macro_use] mod common; @@ -265,10 +269,6 @@ mod test_readlink; #[path = "by-util/test_realpath.rs"] mod test_realpath; -#[cfg(feature = "relpath")] -#[path = "by-util/test_relpath.rs"] -mod test_relpath; - #[cfg(feature = "rm")] #[path = "by-util/test_rm.rs"] mod test_rm; diff --git a/util/build-gnu.sh b/util/build-gnu.sh index d852ed66f..7c0691c06 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -3,7 +3,7 @@ # # UU_MAKE_PROFILE == 'debug' | 'release' ## build profile for *uutils* build; may be supplied by caller, defaults to 'release' -# spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW baddecode submodules ; (vars/env) SRCDIR vdir rcexp xpart +# spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW baddecode submodules ; (vars/env) SRCDIR vdir rcexp xpart dired set -e @@ -18,10 +18,30 @@ path_GNU="$(readlink -fm -- "${path_GNU:-${path_UUTILS}/../gnu}")" ### +# On MacOS there is no system /usr/bin/timeout +# and trying to add it to /usr/bin (with symlink of copy binary) will fail unless system integrity protection is disabled (not ideal) +# ref: https://support.apple.com/en-us/102149 +# On MacOS the Homebrew coreutils could be installed and then "sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout" +# Set to /usr/local/bin/timeout instead if /usr/bin/timeout is not found +SYSTEM_TIMEOUT="timeout" +if [ -x /usr/bin/timeout ]; then + SYSTEM_TIMEOUT="/usr/bin/timeout" +elif [ -x /usr/local/bin/timeout ]; then + SYSTEM_TIMEOUT="/usr/local/bin/timeout" +fi + +### + +release_tag_GNU="v9.4" + if test ! -d "${path_GNU}"; then echo "Could not find GNU coreutils (expected at '${path_GNU}')" echo "Run the following to download into the expected path:" echo "git clone --recurse-submodules https://github.com/coreutils/coreutils.git \"${path_GNU}\"" + echo "After downloading GNU coreutils to \"${path_GNU}\" run the following commands to checkout latest release tag" + echo "cd \"${path_GNU}\"" + echo "git fetch --all --tags" + echo "git checkout tags/${release_tag_GNU}" exit 1 fi @@ -82,7 +102,7 @@ else ./bootstrap --skip-po ./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|^"\$@|'"${SYSTEM_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='${UU_BUILD_DIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh @@ -130,36 +150,37 @@ grep -rl 'path_prepend_' tests/* | xargs sed -i 's| path_prepend_ ./src||' # Remove tests checking for --version & --help # Not really interesting for us and logs are too big sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \ - -e '/tests\/misc\/help-version.sh/ D' \ - -e '/tests\/misc\/help-version-getopt.sh/ D' \ + -e '/tests\/help\/help-version.sh/ D' \ + -e '/tests\/help\/help-version-getopt.sh/ D' \ Makefile # logs are clotted because of this test -sed -i -e '/tests\/misc\/seq-precision.sh/ D' \ +sed -i -e '/tests\/seq\/seq-precision.sh/ D' \ Makefile # printf doesn't limit the values used in its arg, so this produced ~2GB of output -sed -i '/INT_OFLOW/ D' tests/misc/printf.sh +sed -i '/INT_OFLOW/ D' tests/printf/printf.sh # Use the system coreutils where the test fails due to error in a util that is not the one being tested -sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh +sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh tests/sort/sort-compress-proc.sh sed -i 's|ls -|/usr/bin/ls -|' tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh -sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/mv/i-2.sh tests/misc/shuf.sh -sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh -sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh -sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh +sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail/tail-n0f.sh tests/cp/fail-perm.sh tests/mv/i-2.sh tests/shuf/shuf.sh +sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/test/test-N.sh +sed -i 's|split |/usr/bin/split |' tests/factor/factor-parallel.sh +sed -i 's|id -|/usr/bin/id -|' tests/runcon/runcon-no-reorder.sh # tests/ls/abmon-align.sh - https://github.com/uutils/coreutils/issues/3505 -sed -i 's|touch |/usr/bin/touch |' tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh tests/ls/abmon-align.sh +sed -i 's|touch |/usr/bin/touch |' tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/ls/ls-time.sh tests/stat/stat-nanoseconds.sh tests/misc/time-style.sh tests/test/test-N.sh tests/ls/abmon-align.sh sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh -sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh -sed -i 's|timeout |/usr/bin/timeout |' tests/tail-2/follow-stdin.sh +sed -i 's|paste |/usr/bin/paste |' tests/od/od-endian.sh +sed -i 's|timeout |'"${SYSTEM_TIMEOUT}"' |' tests/tail/follow-stdin.sh # Add specific timeout to tests that currently hang to limit time spent waiting -sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh +sed -i 's|\(^\s*\)seq \$|\1'"${SYSTEM_TIMEOUT}"' 0.1 seq \$|' tests/seq/seq-precision.sh tests/seq/seq-long-double.sh -# Remove dup of /usr/bin/ when executed several times +# Remove dup of /usr/bin/ and /usr/local/bin/ when executed several times 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' +grep -rlE '/usr/local/bin/\s?/usr/local/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/local/bin/\s?/usr/local/bin/|/usr/local/bin/|g' #### Adjust tests to make them work with Rust/coreutils # in some cases, what we are doing in rust/coreutils is good (or better) @@ -168,6 +189,8 @@ grep -rlE '/usr/bin/\s?/usr/bin' init.cfg tests/* | xargs --no-run-if-empty sed sed -i -e "s|rm: cannot remove 'e/slink'|rm: cannot remove 'e'|g" tests/rm/fail-eacces.sh +sed -i -e "s|rm: cannot remove 'a/b'|rm: cannot remove 'a'|g" tests/rm/fail-2eperm.sh + sed -i -e "s|rm: cannot remove 'a/b/file'|rm: cannot remove 'a'|g" tests/rm/cycle.sh sed -i -e "s|rm: cannot remove directory 'b/a/p'|rm: cannot remove 'b'|g" tests/rm/rm1.sh @@ -181,10 +204,14 @@ sed -i -e "s|rm: cannot remove 'rel': Permission denied|rm: cannot remove 'rel': # overlay-headers.sh test intends to check for inotify events, # however there's a bug because `---dis` is an alias for: `---disable-inotify` -sed -i -e "s|---dis ||g" tests/tail-2/overlay-headers.sh +sed -i -e "s|---dis ||g" tests/tail/overlay-headers.sh test -f "${UU_BUILD_DIR}/getlimits" || cp src/getlimits "${UU_BUILD_DIR}" +# pr produces very long log and this command isn't super interesting +# SKIP for now +sed -i -e "s|my \$prog = 'pr';$|my \$prog = 'pr';CuSkip::skip \"\$prog: SKIP for producing too long logs\";|" tests/pr/pr-tests.pl + # When decoding an invalid base32/64 string, gnu writes everything it was able to decode until # it hit the decode error, while we don't write anything if the input is invalid. sed -i "s/\(baddecode.*OUT=>\"\).*\"/\1\"/g" tests/misc/base64.pl @@ -238,11 +265,20 @@ sed -i -e "s/Try 'mv --help' for more information/For more information, try '--h # GNU doesn't support width > INT_MAX # disable these test cases -sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/misc/printf-cov.pl +sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/printf/printf-cov.pl sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold ' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh +awk 'BEGIN {count=0} /compare exp out2/ && count < 6 {sub(/compare exp out2/, "grep -q \"cannot be used with\" out2"); count++} 1' tests/df/df-output.sh > tests/df/df-output.sh.tmp && mv tests/df/df-output.sh.tmp tests/df/df-output.sh + +# with ls --dired, in case of error, we have a slightly different error position +sed -i -e "s|44 45|48 49|" tests/ls/stat-failed.sh + +# small difference in the error message +sed -i -e "/ls: invalid argument 'XX' for 'time style'/,/Try 'ls --help' for more information\./c\ +ls: invalid --time-style argument 'XX'\nPossible values are: [\"full-iso\", \"long-iso\", \"iso\", \"locale\", \"+FORMAT (e.g., +%H:%M) for a 'date'-style format\"]\n\nFor more information try --help" tests/ls/time-style-diag.sh + # disable two kind of tests: # "hostid BEFORE --help" doesn't fail for GNU. we fail. we are probably doing better # "hostid BEFORE --help AFTER " same for this -sed -i -e "s/env \$prog \$BEFORE \$opt > out2/env \$prog \$BEFORE \$opt > out2 #/" -e "s/env \$prog \$BEFORE \$opt AFTER > out3/env \$prog \$BEFORE \$opt AFTER > out3 #/" -e "s/compare exp out2/compare exp out2 #/" -e "s/compare exp out3/compare exp out3 #/" tests/misc/help-version-getopt.sh +sed -i -e "s/env \$prog \$BEFORE \$opt > out2/env \$prog \$BEFORE \$opt > out2 #/" -e "s/env \$prog \$BEFORE \$opt AFTER > out3/env \$prog \$BEFORE \$opt AFTER > out3 #/" -e "s/compare exp out2/compare exp out2 #/" -e "s/compare exp out3/compare exp out3 #/" tests/help/help-version-getopt.sh diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh index 55ba8cefc..1abb476b7 100755 --- a/util/run-gnu-test.sh +++ b/util/run-gnu-test.sh @@ -29,15 +29,27 @@ cd "${path_GNU}" && echo "[ pwd:'${PWD}' ]" export RUST_BACKTRACE=1 -if test $# -ge 1; then - # if set, run only the tests passed - SPECIFIC_TESTS="" - for t in "$@"; do - SPECIFIC_TESTS="$SPECIFIC_TESTS $t" - done - # trim it - SPECIFIC_TESTS=$(echo $SPECIFIC_TESTS | xargs) - echo "Running specific tests: $SPECIFIC_TESTS" +if test "$1" != "run-root"; then + if test $# -ge 1; then + # if set, run only the tests passed + SPECIFIC_TESTS="" + for t in "$@"; do + + # Construct the full path + full_path="$path_GNU/$t" + + # Check if the file exists with .sh, .pl extension or without any extension in the $path_GNU directory + if [ -f "$full_path" ] || [ -f "$full_path.sh" ] || [ -f "$full_path.pl" ]; then + SPECIFIC_TESTS="$SPECIFIC_TESTS $t" + else + echo "Error: Test file $full_path, $full_path.sh, or $full_path.pl does not exist!" + exit 1 + fi + done + # trim it + SPECIFIC_TESTS=$(echo $SPECIFIC_TESTS | xargs) + echo "Running specific tests: $SPECIFIC_TESTS" + fi fi # * timeout used to kill occasionally errant/"stuck" processes (note: 'release' testing takes ~1 hour; 'debug' testing takes ~2.5 hours) diff --git a/util/show-utils.BAT b/util/show-utils.BAT index 9fa5e2ffa..f6d900734 100644 --- a/util/show-utils.BAT +++ b/util/show-utils.BAT @@ -2,7 +2,7 @@ @echo off @rem ::# spell-checker:ignore (CMD) ERRORLEVEL -@rem ::# spell-checker:ignore (utils) cksum coreutils dircolors hashsum mkdir mktemp printenv printf readlink realpath relpath rmdir shuf tsort unexpand +@rem ::# spell-checker:ignore (utils) cksum coreutils dircolors hashsum mkdir mktemp printenv printf readlink realpath rmdir shuf tsort unexpand @rem ::# spell-checker:ignore (jq) deps startswith set "ME=%~0" @@ -12,7 +12,7 @@ set "ME_parent_dir=%~dp0.\.." @rem refs: , @rem :: default ("Tier 1" cross-platform) utility list -set "default_utils=base32 base64 basename cat cksum comm cp cut date dircolors dirname echo env expand expr factor false fmt fold hashsum head join link ln ls mkdir mktemp more mv nl od paste printenv printf ptx pwd readlink realpath relpath rm rmdir seq shred shuf sleep sort split sum tac tail tee test tr true truncate tsort unexpand uniq wc yes" +set "default_utils=base32 base64 basename cat cksum comm cp cut date dircolors dirname echo env expand expr factor false fmt fold hashsum head join link ln ls mkdir mktemp more mv nl od paste printenv printf ptx pwd readlink realpath rm rmdir seq shred shuf sleep sort split sum tac tail tee test tr true truncate tsort unexpand uniq wc yes" set "project_dir=%ME_parent_dir%" cd "%project_dir%" diff --git a/util/show-utils.sh b/util/show-utils.sh index b6a0f9856..dda01abe2 100755 --- a/util/show-utils.sh +++ b/util/show-utils.sh @@ -1,6 +1,6 @@ #!/bin/sh -# spell-checker:ignore (utils) cksum coreutils dircolors hashsum mkdir mktemp printenv printf readlink realpath relpath rmdir shuf tsort unexpand +# spell-checker:ignore (utils) cksum coreutils dircolors hashsum mkdir mktemp printenv printf readlink realpath rmdir shuf tsort unexpand # spell-checker:ignore (jq) deps startswith ME="${0}" @@ -12,7 +12,7 @@ ME_parent_dir_abs="$(realpath -mP -- "${ME_parent_dir}" || realpath -- "${ME_par # refs: , # default ("Tier 1" cross-platform) utility list -default_utils="base32 base64 basename cat cksum comm cp cut date dircolors dirname echo env expand expr factor false fmt fold hashsum head join link ln ls mkdir mktemp more mv nl od paste printenv printf ptx pwd readlink realpath relpath rm rmdir seq shred shuf sleep sort split sum tac tail tee test tr true truncate tsort unexpand uniq wc yes" +default_utils="base32 base64 basename cat cksum comm cp cut date dircolors dirname echo env expand expr factor false fmt fold hashsum head join link ln ls mkdir mktemp more mv nl od paste printenv printf ptx pwd readlink realpath rm rmdir seq shred shuf sleep sort split sum tac tail tee test tr true truncate tsort unexpand uniq wc yes" project_main_dir="${ME_parent_dir_abs}" # printf 'project_main_dir="%s"\n' "${project_main_dir}" diff --git a/util/update-version.sh b/util/update-version.sh index 8b6168782..bab1c4e0a 100755 --- a/util/update-version.sh +++ b/util/update-version.sh @@ -9,13 +9,16 @@ # 2) run it: sh util/update-version.sh # 3) Do a spot check with "git diff" # 4) cargo test --release --features unix -# 5) git commit -m "New release" +# 5) git commit -m "New release" (make sure it includes Cargo.lock) # 6) Run util/publish.sh in dry mode (it will fail as packages needs more recent version of uucore) # 7) Run util/publish.sh --do-it # 8) In some cases, you might have to fix dependencies and run import +# 9) Tag the release - "git tag 0.0.X && git push --tags" +# 10) Create the release on github https://github.com/uutils/coreutils/releases/new +# 11) Make sure we have good release notes -FROM="0.0.19" -TO="0.0.20" +FROM="0.0.21" +TO="0.0.22" PROGS=$(ls -1d src/uu/*/Cargo.toml src/uu/stdbuf/src/libstdbuf/Cargo.toml src/uucore/Cargo.toml Cargo.toml)