diff --git a/.cargo/config b/.cargo/config index 58e1381b1..0a8fd3d00 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,11 @@ [target.x86_64-unknown-redox] linker = "x86_64-unknown-redox-gcc" + +[target.'cfg(feature = "cargo-clippy")'] +rustflags = [ + "-Wclippy::use_self", + "-Wclippy::needless_pass_by_value", + "-Wclippy::semicolon_if_nothing_returned", + "-Wclippy::single_char_pattern", + "-Wclippy::explicit_iter_loop", +] diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 000000000..0d369b50f --- /dev/null +++ b/.clippy.toml @@ -0,0 +1 @@ +msrv = "1.56.0" diff --git a/.editorconfig b/.editorconfig index d93fa7c0e..53ccc4f9a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# EditorConfig (is awesome): http://EditorConfig.org +# EditorConfig (is awesome!; ref: http://EditorConfig.org; v2022.02.11 [rivy]) # * top-most EditorConfig file root = true @@ -13,27 +13,49 @@ insert_final_newline = true max_line_length = 100 trim_trailing_whitespace = true -[[Mm]akefile{,.*}, *.{mk,[Mm][Kk]}] +[{[Mm]akefile{,.*},*.{mak,mk,[Mm][Aa][Kk],[Mm][Kk]},[Gg][Nn][Uu]makefile}] # makefiles ~ TAB-style indentation indent_style = tab +[*.bash] +# `bash` shell scripts +indent_size = 4 +indent_style = space +# * ref: +# shell_variant = bash ## allow `shellcheck` to decide via script hash-bang/sha-bang line +switch_case_indent = true + [*.{bat,cmd,[Bb][Aa][Tt],[Cc][Mm][Dd]}] # BAT/CMD ~ DOS/Win requires BAT/CMD files to have CRLF EOLNs end_of_line = crlf +[*.{cjs,cjx,cts,ctx,js,jsx,mjs,mts,mtx,ts,tsx,json,jsonc}] +# js/ts/json ~ Prettier/XO-style == TAB indention + SPACE alignment +indent_size = 2 +indent_style = tab + [*.go] # go ~ TAB-style indentation (SPACE-style alignment); ref: @@ indent_style = tab -[*.{cjs,js,json,mjs,ts}] -# js/ts -indent_size = 2 - [*.{markdown,md,mkd,[Mm][Dd],[Mm][Kk][Dd],[Mm][Dd][Oo][Ww][Nn],[Mm][Kk][Dd][Oo][Ww][Nn],[Mm][Aa][Rr][Kk][Dd][Oo][Ww][Nn]}] # markdown indent_size = 2 indent_style = space +[*.sh] +# POSIX shell scripts +indent_size = 4 +indent_style = space +# * ref: +# shell_variant = posix ## allow `shellcheck` to decide via script hash-bang/sha-bang line +switch_case_indent = true + +[*.{sln,vc{,x}proj{,.*},[Ss][Ln][Nn],[Vv][Cc]{,[Xx]}[Pp][Rr][Oo][Jj]{,.*}}] +# MSVC sln/vcproj/vcxproj files, when used, will persistantly revert to CRLF EOLNs and eat final EOLs +end_of_line = crlf +insert_final_newline = false + [*.{yaml,yml,[Yy][Mm][Ll],[Yy][Aa][Mm][Ll]}] # YAML indent_size = 2 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..ef7519b88 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 5 diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index d096ff43c..08803afb1 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1,11 +1,11 @@ name: CICD # spell-checker:ignore (acronyms) CICD MSVC musl -# spell-checker:ignore (env/flags) Awarnings Ccodegen Coverflow Cpanic RUSTDOCFLAGS RUSTFLAGS Zpanic -# spell-checker:ignore (jargon) SHAs deps softprops toolchain +# spell-checker:ignore (env/flags) Awarnings Ccodegen Coverflow Cpanic Dwarnings RUSTDOCFLAGS RUSTFLAGS Zpanic +# spell-checker:ignore (jargon) SHAs deps dequote softprops subshell toolchain # spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy -# spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs -# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend runtest tempfile testsuite uutils +# spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rsync rustc rustfmt rustup shopt xargs +# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend pell runtest tempfile testsuite uutils DESTDIR multisize Swatinem # ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45 @@ -13,48 +13,88 @@ env: PROJECT_NAME: coreutils PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_AUTH: "uutils" - RUST_MIN_SRV: "1.47.0" ## MSRV v1.47.0 + RUST_MIN_SRV: "1.56.0" ## MSRV v1.56.0 + # * style job configuration + STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis on: [push, pull_request] jobs: - code_deps: - name: Style/dependencies + cargo-deny: + name: Style/cargo-deny + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: EmbarkStudios/cargo-deny-action@v1 + + style_deps: + ## ToDO: [2021-11-10; rivy] 'Style/deps' needs more informative output and better integration of results into the GHA dashboard + name: Style/deps runs-on: ${{ matrix.job.os }} + # env: + # STYLE_FAIL_ON_FAULT: false # overrides workflow default strategy: fail-fast: false matrix: job: - - { os: ubuntu-latest , features: feat_os_unix } + # note: `cargo-udeps` panics when processing stdbuf/libstdbuf ("uu_stdbuf_libstdbuf"); either b/c of the 'cpp' crate or 'libstdbuf' itself + # ... b/c of the panic, a more limited feature set is tested (though only excluding `stdbuf`) + - { os: ubuntu-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" } + - { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" } + - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash run: | ## VARs setup outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='' ; if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi outputs CARGO_FEATURES_OPTION + ## note: requires 'nightly' toolchain b/c `cargo-udeps` uses the `rustc` '-Z save-analysis' option + ## * ... ref: - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly-2022-03-21 default: true - profile: minimal # minimal component installation (ie, no documentation) - - name: "`cargo update` testing" + profile: minimal + - name: Install `cargo-udeps` + uses: actions-rs/install@v0.1 + with: + crate: cargo-udeps + version: latest + use-tool-cache: false + env: + RUSTUP_TOOLCHAIN: stable + - name: Detect unused dependencies shell: bash run: | - ## `cargo update` testing - # * convert any warnings to GHA UI annotations; ref: - cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::'Cargo.lock' file requires update (use \`cargo +${{ env.RUST_MIN_SRV }} update\`)" ; exit 1 ; } + ## Detect unused dependencies + unset fault + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # + cargo +nightly-2022-03-21 udeps ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all-targets &> udeps.log || cat udeps.log + grep --ignore-case "all deps seem to have been used" udeps.log || { printf "%s\n" "::${fault_type} ::${fault_prefix}: \`cargo udeps\`: style violation (unused dependency found)" ; fault=true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi - code_format: + style_format: name: Style/format runs-on: ${{ matrix.job.os }} + # env: + # STYLE_FAIL_ON_FAULT: false # overrides workflow default strategy: fail-fast: false matrix: @@ -62,12 +102,19 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash run: | ## VARs setup outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='' ; @@ -80,48 +127,172 @@ jobs: default: true profile: minimal # minimal component installation (ie, no documentation) components: rustfmt - - name: "`fmt` testing" + - name: "`cargo fmt` testing" shell: bash run: | - ## `fmt` testing - # * convert any warnings to GHA UI annotations; ref: - S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::error file=\1,line=\2::ERROR: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; exit 1 ; } - - name: "`fmt` testing of tests" + ## `cargo fmt` testing + unset fault + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # * convert any errors/warnings to GHA UI annotations; ref: + S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt -- \"\1\"\`)/p" ; fault=true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi + - name: "`cargo fmt` testing of integration tests" if: success() || failure() # run regardless of prior step success/failure shell: bash run: | - ## `fmt` testing of tests - # * convert any warnings to GHA UI annotations; ref: - S=$(find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::error file=\1,line=\2::ERROR: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; exit 1 ; } + ## `cargo fmt` testing of integration tests + unset fault + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # 'tests' is the standard/usual integration test directory + if [ -d tests ]; then + # * convert any errors/warnings to GHA UI annotations; ref: + S=$(find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; fault=true ; } + fi + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi - code_lint: + style_lint: name: Style/lint runs-on: ${{ matrix.job.os }} + # env: + # STYLE_FAIL_ON_FAULT: false # overrides workflow default strategy: fail-fast: false matrix: job: - - { os: ubuntu-latest } + - { os: ubuntu-latest , features: feat_os_unix } - { os: macos-latest , features: feat_os_macos } - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v2 - - name: Install/setup prerequisites - shell: bash - run: | - case '${{ matrix.job.os }}' in - macos-latest) brew install coreutils ;; # needed for show-utils.sh - esac + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash run: | ## VARs setup outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='--all-features' ; - if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features ${{ matrix.job.features }}' ; fi + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi + outputs CARGO_FEATURES_OPTION + # * determine sub-crate utility list + UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" + echo UTILITY_LIST=${UTILITY_LIST} + CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" + outputs CARGO_UTILITY_LIST_OPTIONS + - name: Install/setup prerequisites + shell: bash + run: | + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for show-utils.sh + esac + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + profile: minimal # minimal component installation (ie, no documentation) + components: clippy + - 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_FEATURES_OPTION }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi + + style_spellcheck: + name: Style/spelling + runs-on: ${{ matrix.job.os }} + # env: + # STYLE_FAIL_ON_FAULT: false # overrides workflow default + strategy: + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + # * 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 }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } +# for now, don't build it on mac & windows because the doc is only published from linux +# + it needs a bunch of duplication for build +# and I don't want to add a doc step in the regular build to avoid long builds +# - { os: macos-latest , features: feat_os_macos } +# - { os: windows-latest , features: feat_os_windows } + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE + # target-specific options + # * CARGO_FEATURES_OPTION + CARGO_FEATURES_OPTION='--all-features' ; + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi outputs CARGO_FEATURES_OPTION # * determine sub-crate utility list UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" @@ -131,39 +302,18 @@ jobs: - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable default: true profile: minimal # minimal component installation (ie, no documentation) components: clippy - - name: "`clippy` lint testing" + - name: "`cargo doc` with warnings" shell: bash run: | - ## `clippy` lint testing - # * convert any warnings to GHA UI annotations; ref: - S=$(cargo +nightly clippy --all-targets ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+${PWD//\//\\/}\/(.*):([0-9]+):([0-9]+).*$/::error file=\2,line=\3,col=\4::ERROR: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; exit 1 ; } + RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items - code_spellcheck: - name: Style/spelling - runs-on: ${{ matrix.job.os }} - strategy: - matrix: - job: - - { os: ubuntu-latest } - steps: - - uses: actions/checkout@v2 - - name: Install/setup prerequisites - shell: bash - run: | - ## Install/setup prerequisites - sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell -g ; - - name: Run `cspell` - shell: bash - run: | - ## Run `cspell` - cspell --config .vscode/cSpell.json --no-summary --no-progress "**/*" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::error file=\1,line=\2,col=\3::ERROR: \4 (file:'\1', line:\2)/p" min_version: - name: MinRustV # Minimum supported rust version + name: MinRustV # Minimum supported rust version (aka, MinSRV or MSRV) runs-on: ${{ matrix.job.os }} strategy: matrix: @@ -171,6 +321,18 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # target-specific options + # * CARGO_FEATURES_OPTION + unset CARGO_FEATURES_OPTION + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi + outputs CARGO_FEATURES_OPTION - name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }}) uses: actions-rs/toolchain@v1 with: @@ -191,6 +353,13 @@ jobs: ## Confirm MinSRV compatible 'Cargo.lock' # * 'Cargo.lock' is required to be in a format that `cargo` of MinSRV can interpret (eg, v1-format for MinSRV < v1.38) cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::Incompatible (or out-of-date) 'Cargo.lock' file; update using \`cargo +${{ env.RUST_MIN_SRV }} update\`" ; exit 1 ; } + - name: Confirm MinSRV equivalence for '.clippy.toml' + shell: bash + run: | + ## Confirm MinSRV equivalence for '.clippy.toml' + # * ensure '.clippy.toml' MSRV configuration setting is equal to ${{ env.RUST_MIN_SRV }} + CLIPPY_MSRV=$(grep -P "(?i)^\s*msrv\s*=\s*" .clippy.toml | grep -oP "\d+([.]\d+)+") + if [ "${CLIPPY_MSRV}" != "${{ env.RUST_MIN_SRV }}" ]; then { echo "::error file=.clippy.toml::Incorrect MSRV configuration for clippy (found '${CLIPPY_MSRV}'; should be '${{ env.RUST_MIN_SRV }}'); update '.clippy.toml' with 'msrv = \"${{ env.RUST_MIN_SRV }}\"'" ; exit 1 ; } ; fi - name: Info shell: bash run: | @@ -208,38 +377,59 @@ jobs: cargo-tree tree -V # dependencies echo "## dependency list" - cargo fetch --locked --quiet ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors - RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique + RUSTUP_TOOLCHAIN=stable cargo fetch --locked --quiet + RUSTUP_TOOLCHAIN=stable cargo-tree tree --all --locked --no-dev-dependencies --no-indent ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} | grep -vE "$PWD" | sort --unique - name: Test uses: actions-rs/cargo@v1 with: command: test - args: --features "feat_os_unix" -p uucore -p coreutils + args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils env: - RUSTFLAGS: '-Awarnings' + RUSTFLAGS: "-Awarnings" - build_makefile: - name: Build/Makefile + deps: + name: Dependencies runs-on: ${{ matrix.job.os }} strategy: fail-fast: false matrix: job: - - { os: ubuntu-latest } + - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable default: true profile: minimal # minimal component installation (ie, no documentation) - - name: Install/setup prerequisites + - name: "`cargo update` testing" shell: bash run: | - ## Install/setup prerequisites - sudo apt-get -y update ; sudo apt-get -y install python3-sphinx ; + ## `cargo update` testing + # * convert any errors/warnings to GHA UI annotations; ref: + cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::'Cargo.lock' file requires update (use \`cargo +${{ env.RUST_MIN_SRV }} update\`)" ; exit 1 ; } + + build_makefile: + name: Build/Makefile + needs: [ min_version, deps ] + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + profile: minimal # minimal component installation (ie, no documentation) - name: "`make build`" shell: bash run: | @@ -249,42 +439,137 @@ jobs: run: | make test - build: - name: Build + + build_rust_stable: + name: Build/stable + needs: [ min_version, deps ] runs-on: ${{ matrix.job.os }} strategy: fail-fast: false matrix: job: - # { os, target, cargo-options, features, use-cross, toolchain } - - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , features: feat_os_unix_gnueabihf , use-cross: use-cross } + - { os: ubuntu-latest , features: feat_os_unix } + - { os: macos-latest , features: feat_os_macos } + - { os: windows-latest , features: feat_os_windows } + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + profile: minimal # minimal component installation (ie, no documentation) + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + + build_rust_nightly: + name: Build/nightly + needs: [ min_version, deps ] + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + - { os: macos-latest , features: feat_os_macos } + - { os: windows-latest , features: feat_os_windows } + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2022-03-21 + default: true + profile: minimal # minimal component installation (ie, no documentation) + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + + compute_size: + name: Binary sizes + needs: [ min_version, deps ] + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Install dependencies + shell: bash + run: | + ## Install dependencies + sudo apt-get update + sudo apt-get install jq + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + profile: minimal # minimal component installation (ie, no documentation) + - name: "`make install`" + shell: bash + run: | + make install DESTDIR=target/size-release/ + make install MULTICALL=y DESTDIR=target/size-multi-release/ + # strip the results + strip target/size*/usr/local/bin/* + - name: "Compute sizes" + shell: bash + run: | + SIZE=$(du -s target/size-release/usr/local/bin/|awk '{print $1}') + SIZEMULTI=$(du -s target/size-multi-release/usr/local/bin/|awk '{print $1}') + jq -n \ + --arg date "$(date --rfc-email)" \ + --arg sha "$GITHUB_SHA" \ + --arg size "$SIZE" \ + --arg multisize "$SIZEMULTI" \ + '{($date): { sha: $sha, size: $size, multisize: $multisize, }}' > size-result.json + - uses: actions/upload-artifact@v2 + with: + name: size-result + path: size-result.json + + build: + name: Build + needs: [ min_version, deps ] + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + # { os , target , cargo-options , features , use-cross , toolchain } + - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf, features: feat_os_unix_gnueabihf, use-cross: use-cross, } - { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross } - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } # - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_selinux , use-cross: use-cross } # - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only # - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only - - { os: ubuntu-18.04 , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } - - { os: ubuntu-18.04 , target: i686-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } - - { os: ubuntu-18.04 , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } - - { os: ubuntu-18.04 , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + - { os: ubuntu-latest , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + - { os: ubuntu-latest , target: i686-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + - { os: ubuntu-latest , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + # Commented until https://github.com/uutils/coreutils/issues/3210 is fixed + #- { os: ubuntu-18.04 , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + #- { os: ubuntu-18.04 , target: i686-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + #- { os: ubuntu-18.04 , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + #- { os: ubuntu-18.04 , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } - { os: macos-latest , target: x86_64-apple-darwin , features: feat_os_macos } - - { os: windows-latest , target: i686-pc-windows-gnu , features: feat_os_windows } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } - - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly + - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } steps: - uses: actions/checkout@v2 - - name: Install/setup prerequisites - shell: bash - run: | - ## Install/setup prerequisites - case '${{ matrix.job.target }}' in - arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; - aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; - esac - case '${{ matrix.job.os }}' in - macos-latest) brew install coreutils ;; # needed for testing - esac + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash @@ -353,7 +638,7 @@ jobs: # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='' ; - if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features=${{ matrix.job.features }}' ; fi outputs CARGO_FEATURES_OPTION # * CARGO_USE_CROSS (truthy) CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac; @@ -380,14 +665,36 @@ jobs: mkdir -p '${{ steps.vars.outputs.STAGING }}' mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}' mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg' + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + case '${{ matrix.job.target }}' in + arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; + aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; + esac + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for testing + esac + case '${{ matrix.job.os }}' in + ubuntu-*) + # pinky is a tool to show logged-in users from utmp, and gecos fields from /etc/passwd. + # In GitHub Action *nix VMs, no accounts log in, even the "runner" account that runs the commands. The account also has empty gecos fields. + # To work around this for pinky tests, we create a fake login entry for the GH runner account... + FAKE_UTMP='[7] [999999] [tty2] [runner] [tty2] [] [0.0.0.0] [2022-02-22T22:22:22,222222+00:00]' + # ... by dumping the login records, adding our fake line, then reverse dumping ... + (utmpdump /var/run/utmp ; echo $FAKE_UTMP) | sudo utmpdump -r -o /var/run/utmp + # ... and add a full name to each account with a gecos field but no full name. + sudo sed -i 's/:,/:runner name,/' /etc/passwd + # We also create a couple optional files pinky looks for + touch /home/runner/.project + echo "foo" > /home/runner/.plan + ;; + esac - name: rust toolchain ~ install uses: actions-rs/toolchain@v1 - env: - # Override auto-detection of RAM for Rustc install. - # https://github.com/rust-lang/rustup/issues/2229#issuecomment-585855925 - RUSTUP_UNPACK_RAM: "21474836480" with: - toolchain: ${{ steps.vars.outputs.TOOLCHAIN }} + toolchain: ${{ env.RUST_MIN_SRV }} target: ${{ matrix.job.target }} default: true profile: minimal # minimal component installation (ie, no documentation) @@ -398,7 +705,7 @@ jobs: ## Dependent VARs setup outputs() { step_id="dep_vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # * determine sub-crate utility list - UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" + UTILITY_LIST="$(./util/show-utils.sh ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }})" echo UTILITY_LIST=${UTILITY_LIST} CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" outputs CARGO_UTILITY_LIST_OPTIONS @@ -439,18 +746,21 @@ jobs: use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }} command: build args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + toolchain: ${{ env.RUST_MIN_SRV }} - name: Test uses: actions-rs/cargo@v1 with: use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }} command: test args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + toolchain: ${{ env.RUST_MIN_SRV }} - name: Test individual utilities uses: actions-rs/cargo@v1 with: use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }} command: test args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} + toolchain: ${{ env.RUST_MIN_SRV }} - name: Archive executable artifacts uses: actions/upload-artifact@v2 with: @@ -502,6 +812,7 @@ jobs: test_busybox: name: Tests/BusyBox test suite + needs: [ min_version, deps ] runs-on: ${{ matrix.job.os }} strategy: fail-fast: false @@ -510,16 +821,18 @@ jobs: - { os: ubuntu-latest } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + make prepare-busytest - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable default: true profile: minimal # minimal component installation (ie, no documentation) - - name: Install/setup prerequisites - shell: bash - run: | - make prepare-busytest - name: "Run BusyBox test suite" shell: bash run: | @@ -532,71 +845,87 @@ jobs: if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi test_freebsd: - runs-on: macos-latest name: Tests/FreeBSD test suite + needs: [ min_version, deps ] + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: macos-10.15 , features: unix } ## GHA MacOS-11.0 VM won't have VirtualBox; refs: , env: mem: 2048 steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Prepare, build and test - id: test + ## spell-checker:ignore (ToDO) sshfs usesh vmactions uses: vmactions/freebsd-vm@v0.1.5 with: usesh: true + # sync: sshfs prepare: pkg install -y curl gmake sudo run: | - # Need to be run in the same block. Otherwise, we are back on the mac host. + ## Prepare, build, and test + # implementation modelled after ref: + # * NOTE: All steps need to be run in this block, otherwise, we are operating back on the mac host set -e - pw adduser -n cuuser -d /root/ -g wheel -c "Coreutils user to build" -w random - chown -R cuuser:wheel /root/ /Users/runner/work/coreutils/ + # + TEST_USER=tester + REPO_NAME=${GITHUB_WORKSPACE##*/} + WORKSPACE_PARENT="/Users/runner/work/${REPO_NAME}" + WORKSPACE="${WORKSPACE_PARENT}/${REPO_NAME}" + # + pw adduser -n ${TEST_USER} -d /root/ -g wheel -c "Coreutils user to build" -w random + # chown -R ${TEST_USER}:wheel /root/ "${WORKSPACE_PARENT}"/ + chown -R ${TEST_USER}:wheel /root/ "/Users/runner/work/${REPO_NAME}"/ whoami - - # Needs to be done in a sudo as we are changing users - sudo -i -u cuuser sh << EOF + # + # Further work needs to be done in a sudo as we are changing users + sudo -i -u ${TEST_USER} sh << EOF set -e whoami curl https://sh.rustup.rs -sSf --output rustup.sh sh rustup.sh -y --profile=minimal + . $HOME/.cargo/env ## Info # environment echo "## environment" echo "CI='${CI}'" - # tooling info display - echo "## tooling" - . $HOME/.cargo/env + echo "REPO_NAME='${REPO_NAME}'" + echo "TEST_USER='${TEST_USER}'" + echo "WORKSPACE_PARENT='${WORKSPACE_PARENT}'" + echo "WORKSPACE='${WORKSPACE}'" + env | sort + # tooling info + echo "## tooling info" cargo -V rustc -V - env - - # where the files are resynced - cd /Users/runner/work/coreutils/coreutils/ - cargo build - cargo test --features feat_os_unix -p uucore -p coreutils + # + cd "${WORKSPACE}" + unset FAULT + cargo build || FAULT=1 + cargo test --features "${{ matrix.job.features }}" || FAULT=1 + cargo test --features "${{ matrix.job.features }}" -p uucore || FAULT=1 # Clean to avoid to rsync back the files cargo clean + if (test -n "$FAULT"); then exit 1 ; fi EOF - coverage: name: Code Coverage + needs: build runs-on: ${{ matrix.job.os }} strategy: fail-fast: true matrix: - # job: [ { os: ubuntu-latest }, { os: macos-latest }, { os: windows-latest } ] job: - { os: ubuntu-latest , features: unix } - { os: macos-latest , features: macos } - { os: windows-latest , features: windows } steps: - uses: actions/checkout@v2 - - name: Install/setup prerequisites - shell: bash - run: | - ## Install/setup prerequisites - case '${{ matrix.job.os }}' in - macos-latest) brew install coreutils ;; # needed for testing - esac + - uses: Swatinem/rust-cache@v1 # - name: Reattach HEAD ## may be needed for accurate code coverage info # run: git checkout ${{ github.head_ref }} - name: Initialize workflow variables @@ -606,7 +935,7 @@ jobs: ## VARs setup outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # toolchain - TOOLCHAIN="nightly" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support + TOOLCHAIN="nightly-2022-03-21" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support # * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac; # * use requested TOOLCHAIN if specified @@ -615,19 +944,36 @@ jobs: # staging directory STAGING='_staging' outputs STAGING - ## # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see ) - ## # note: CODECOV_TOKEN / HAS_CODECOV_TOKEN is not needed for public repositories when using AppVeyor, Azure Pipelines, CircleCI, GitHub Actions, Travis (see ) - ## unset HAS_CODECOV_TOKEN - ## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi - ## outputs HAS_CODECOV_TOKEN # target-specific options # * CARGO_FEATURES_OPTION CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage - if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features=${{ matrix.job.features }}' ; fi outputs CARGO_FEATURES_OPTION # * CODECOV_FLAGS CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' ) outputs CODECOV_FLAGS + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for testing + esac + case '${{ matrix.job.os }}' in + ubuntu-latest) + # pinky is a tool to show logged-in users from utmp, and gecos fields from /etc/passwd. + # In GitHub Action *nix VMs, no accounts log in, even the "runner" account that runs the commands. The account also has empty gecos fields. + # To work around this for pinky tests, we create a fake login entry for the GH runner account... + FAKE_UTMP='[7] [999999] [tty2] [runner] [tty2] [] [0.0.0.0] [2022-02-22T22:22:22,222222+00:00]' + # ... by dumping the login records, adding our fake line, then reverse dumping ... + (utmpdump /var/run/utmp ; echo $FAKE_UTMP) | sudo utmpdump -r -o /var/run/utmp + # ... and add a full name to each account with a gecos field but no full name. + sudo sed -i 's/:,/:runner name,/' /etc/passwd + # We also create a couple optional files pinky looks for + touch /home/runner/.project + echo "foo" > /home/runner/.plan + ;; + esac - name: rust toolchain ~ install uses: actions-rs/toolchain@v1 with: @@ -641,7 +987,7 @@ jobs: ## Dependent VARs setup outputs() { step_id="dep_vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # * determine sub-crate utility list - UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" + UTILITY_LIST="$(./util/show-utils.sh ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }})" CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" outputs CARGO_UTILITY_LIST_OPTIONS - name: Test uucore @@ -650,10 +996,10 @@ jobs: command: test args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast -p uucore env: - CARGO_INCREMENTAL: '0' - RUSTC_WRAPPER: '' - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort' - RUSTDOCFLAGS: '-Cpanic=abort' + CARGO_INCREMENTAL: "0" + RUSTC_WRAPPER: "" + RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTDOCFLAGS: "-Cpanic=abort" # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }} - name: Test uses: actions-rs/cargo@v1 @@ -661,10 +1007,10 @@ jobs: command: test args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast env: - CARGO_INCREMENTAL: '0' - RUSTC_WRAPPER: '' - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort' - RUSTDOCFLAGS: '-Cpanic=abort' + CARGO_INCREMENTAL: "0" + RUSTC_WRAPPER: "" + RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTDOCFLAGS: "-Cpanic=abort" # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }} - name: Test individual utilities uses: actions-rs/cargo@v1 @@ -672,10 +1018,10 @@ jobs: command: test args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} env: - CARGO_INCREMENTAL: '0' - RUSTC_WRAPPER: '' - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort' - RUSTDOCFLAGS: '-Cpanic=abort' + CARGO_INCREMENTAL: "0" + RUSTC_WRAPPER: "" + RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTDOCFLAGS: "-Cpanic=abort" # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }} - name: "`grcov` ~ install" uses: actions-rs/install@v0.1 @@ -690,13 +1036,13 @@ jobs: ## Generate coverage data COVERAGE_REPORT_DIR="target/debug" COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info" - # GRCOV_IGNORE_OPTION='--ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?) + # GRCOV_IGNORE_OPTION='--ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?) # GRCOV_EXCLUDE_OPTION='--excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"' ## `grcov` ignores these params when passed as an environment variable (why?) mkdir -p "${COVERAGE_REPORT_DIR}" # display coverage files - grcov . --output-type files --ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique + grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique # generate coverage report - grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" + grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" echo ::set-output name=report::${COVERAGE_REPORT_FILE} - name: Upload coverage results (to Codecov.io) uses: codecov/codecov-action@v1 @@ -708,35 +1054,3 @@ jobs: flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} name: codecov-umbrella fail_ci_if_error: false - - unused_deps: - name: Unused deps - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - - { os: ubuntu-latest , features: feat_os_unix } - - { os: macos-latest , features: feat_os_macos } - - { os: windows-latest , features: feat_os_windows } - steps: - - uses: actions/checkout@v2 - - name: Install `rust` toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - default: true - profile: minimal - - name: Install `cargo-udeps` - uses: actions-rs/install@v0.1 - with: - crate: cargo-udeps - version: latest - use-tool-cache: true - env: - RUSTUP_TOOLCHAIN: stable - - name: Confirms there isn't any unused deps - shell: bash - run: | - cargo +nightly udeps --all-targets &> udeps.log || cat udeps.log - grep "seem to have been used" udeps.log diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml index d3f8a86b8..2a5382e27 100644 --- a/.github/workflows/FixPR.yml +++ b/.github/workflows/FixPR.yml @@ -1,18 +1,20 @@ name: FixPR +# spell-checker:ignore Swatinem + # Trigger automated fixes for PRs being merged (with associated commits) # ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45 env: - BRANCH_TARGET: master + BRANCH_TARGET: main on: # * only trigger on pull request closed to specific branches # ref: https://github.community/t/trigger-workflow-only-on-pull-request-merge/17359/9 pull_request: branches: - - master # == env.BRANCH_TARGET ## unfortunately, env context variables are only available in jobs/steps (see ) + - main # == env.BRANCH_TARGET ## unfortunately, env context variables are only available in jobs/steps (see ) types: [ closed ] jobs: @@ -27,6 +29,7 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize job variables id: vars shell: bash @@ -98,6 +101,7 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize job variables id: vars shell: bash diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index dad53f20c..579bea4c0 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -1,6 +1,8 @@ name: GnuTests -# spell-checker:ignore (names) gnulib ; (utils) autopoint gperf pyinotify texinfo ; (vars) XPASS +# spell-checker:ignore (names) gnulib ; (jargon) submodules ; (people) Dawid Dziurla * dawidd ; (utils) autopoint chksum gperf pyinotify shopt texinfo ; (vars) FILESET SUBDIRS XPASS + +# * note: to run a single test => `REPO/util/run-gnu-test.sh PATH/TO/TEST/SCRIPT` on: [push, pull_request] @@ -9,23 +11,52 @@ jobs: name: Run GNU tests runs-on: ubuntu-latest steps: - - name: Checkout code uutil + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # * config + path_GNU="gnu" + path_GNU_tests="${path_GNU}/tests" + path_UUTILS="uutils" + path_reference="reference" + outputs path_GNU path_GNU_tests path_reference path_UUTILS + # + repo_default_branch="${{ github.event.repository.default_branch }}" + repo_GNU_ref="v9.0" + repo_reference_branch="${{ github.event.repository.default_branch }}" + outputs repo_default_branch repo_GNU_ref repo_reference_branch + # + SUITE_LOG_FILE="${path_GNU_tests}/test-suite.log" + TEST_LOGS_GLOB="${path_GNU_tests}/**/*.log" ## note: not usable at bash CLI; [why] double globstar not enabled by default b/c MacOS includes only bash v3 which doesn't have double globstar support + TEST_FILESET_PREFIX='test-fileset-IDs.sha1#' + TEST_FILESET_SUFFIX='.txt' + TEST_SUMMARY_FILE='gnu-result.json' + TEST_FULL_SUMMARY_FILE='gnu-full-result.json' + outputs 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@v2 with: - path: 'uutils' - - name: Checkout GNU coreutils + path: '${{ steps.vars.outputs.path_UUTILS }}' + - name: Checkout code (GNU coreutils) uses: actions/checkout@v2 with: repository: 'coreutils/coreutils' - path: 'gnu' - ref: v8.32 - - name: Checkout GNU coreutils library (gnulib) - uses: actions/checkout@v2 + path: '${{ steps.vars.outputs.path_GNU }}' + ref: ${{ steps.vars.outputs.repo_GNU_ref }} + submodules: recursive + - name: Retrieve reference artifacts + uses: dawidd6/action-download-artifact@v2 + # ref: + continue-on-error: true ## don't break the build for missing reference artifacts (may be expired or just not generated yet) with: - repository: 'coreutils/gnulib' - path: 'gnulib' - ref: 8e99f24c0931a38880c6ee9b8287c7da80b0036b - fetch-depth: 0 # gnu gets upset if gnulib is a shallow checkout + workflow: GnuTests.yml + branch: "${{ steps.vars.outputs.repo_reference_branch }}" + # workflow_conclusion: success ## (default); * but, if commit with failed GnuTests is merged into the default branch, future commits will all show regression errors in GnuTests CI until o/w fixed + workflow_conclusion: completed ## continually recalibrates to last commit of default branch with a successful GnuTests (ie, "self-heals" from GnuTest regressions, but needs more supervision for/of regressions) + path: "${{ steps.vars.outputs.path_reference }}" - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: @@ -38,35 +69,58 @@ jobs: run: | ## Install dependencies sudo apt-get update - sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx jq + sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify jq valgrind libexpect-perl + - name: Add various locales + shell: bash + run: | + echo "Before:" + locale -a + ## Some tests fail with 'cannot change locale (en_US.ISO-8859-1): No such file or directory' + ## Some others need a French locale + sudo locale-gen + sudo locale-gen fr_FR + sudo locale-gen fr_FR.UTF-8 + sudo update-locale + echo "After:" + locale -a - name: Build binaries shell: bash run: | ## Build binaries - cd uutils + cd '${{ steps.vars.outputs.path_UUTILS }}' bash util/build-gnu.sh - name: Run GNU tests shell: bash run: | - bash uutils/util/run-gnu-test.sh - - name: Extract testing info + path_GNU='${{ steps.vars.outputs.path_GNU }}' + path_UUTILS='${{ steps.vars.outputs.path_UUTILS }}' + bash "${path_UUTILS}/util/run-gnu-test.sh" + - name: Extract testing info into JSON + shell: bash + run : | + path_UUTILS='${{ steps.vars.outputs.path_UUTILS }}' + python ${path_UUTILS}/util/gnu-json-result.py ${{ steps.vars.outputs.path_GNU_tests }} > ${{ steps.vars.outputs.TEST_FULL_SUMMARY_FILE }} + - name: Extract/summarize testing info + id: summary shell: bash run: | - ## Extract testing info - LOG_FILE=gnu/tests/test-suite.log - if test -f "$LOG_FILE" + ## Extract/summarize testing info + outputs() { step_id="summary"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # + SUITE_LOG_FILE='${{ steps.vars.outputs.SUITE_LOG_FILE }}' + if test -f "${SUITE_LOG_FILE}" then - TOTAL=$(sed -n "s/.*# TOTAL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - PASS=$(sed -n "s/.*# PASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - SKIP=$(sed -n "s/.*# SKIP: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) + TOTAL=$(sed -n "s/.*# TOTAL: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) + PASS=$(sed -n "s/.*# PASS: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) + SKIP=$(sed -n "s/.*# SKIP: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) + FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) + XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) + ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) if [[ "$TOTAL" -eq 0 || "$TOTAL" -eq 1 ]]; then - echo "Error in the execution, failing early" - exit 1 + echo "::error ::Failed to parse test results from '${SUITE_LOG_FILE}'; failing early" + exit 1 fi - output="GNU tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR" + output="GNU tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR / SKIP: $SKIP" echo "${output}" if [[ "$FAIL" -gt 0 || "$ERROR" -gt 0 ]]; then echo "::warning ::${output}" ; fi jq -n \ @@ -78,54 +132,149 @@ jobs: --arg fail "$FAIL" \ --arg xpass "$XPASS" \ --arg error "$ERROR" \ - '{($date): { sha: $sha, total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }}' > gnu-result.json + '{($date): { sha: $sha, total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }}' > '${{ steps.vars.outputs.TEST_SUMMARY_FILE }}' + HASH=$(sha1sum '${{ steps.vars.outputs.TEST_SUMMARY_FILE }}' | cut --delim=" " -f 1) + outputs HASH else - echo "::error ::Failed to get summary of test results" + echo "::error ::Failed to find summary of test results (missing '${SUITE_LOG_FILE}'); failing early" + exit 1 fi - - uses: actions/upload-artifact@v2 + # Compress logs before upload (fails otherwise) + gzip ${{ steps.vars.outputs.TEST_LOGS_GLOB }} + - name: Reserve SHA1/ID of 'test-summary' + uses: actions/upload-artifact@v2 with: - name: test-report - path: gnu/tests/**/*.log - - uses: actions/upload-artifact@v2 + name: "${{ steps.summary.outputs.HASH }}" + path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" + - name: Reserve test results summary + uses: actions/upload-artifact@v2 with: - name: gnu-result - path: gnu-result.json - - name: Download the result - uses: dawidd6/action-download-artifact@v2 + name: test-summary + path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" + - name: Reserve test logs + uses: actions/upload-artifact@v2 with: - workflow: GnuTests.yml - name: gnu-result - repo: uutils/coreutils - branch: master - path: dl - - name: Download the log - uses: dawidd6/action-download-artifact@v2 + name: test-logs + path: "${{ steps.vars.outputs.TEST_LOGS_GLOB }}" + - name: Upload full json results + uses: actions/upload-artifact@v2 with: - workflow: GnuTests.yml - name: test-report - repo: uutils/coreutils - branch: master - path: dl - - name: Compare failing tests against master + name: gnu-full-result.json + path: ${{ steps.vars.outputs.TEST_FULL_SUMMARY_FILE }} + - name: Compare test failures VS reference shell: bash run: | - OLD_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" dl/test-suite.log | sort) - NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" gnu/tests/test-suite.log | sort) - for LINE in $OLD_FAILING - do - if ! grep -Fxq $LINE<<<"$NEW_FAILING"; then - echo "::warning ::Congrats! The gnu test $LINE is now passing!" - fi - done - for LINE in $NEW_FAILING - do - if ! grep -Fxq $LINE<<<"$OLD_FAILING" - then - echo "::error ::GNU test failed: $LINE. $LINE is passing on 'master'. Maybe you have to rebase?" - fi - done - - name: Compare against master results + have_new_failures="" + REF_LOG_FILE='${{ steps.vars.outputs.path_reference }}/test-logs/test-suite.log' + REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json' + REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}' + if test -f "${REF_LOG_FILE}"; then + echo "Reference SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")" + REF_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort) + NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | sort) + for LINE in ${REF_FAILING} + do + if ! grep -Fxq ${LINE}<<<"${NEW_FAILING}"; then + echo "::warning ::Congrats! The gnu test ${LINE} is no longer failing!" + fi + done + for LINE in ${NEW_FAILING} + do + if ! grep -Fxq ${LINE}<<<"${REF_FAILING}" + then + echo "::error ::GNU test failed: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?" + have_new_failures="true" + fi + done + else + echo "::warning ::Skipping test failure comparison; no prior reference test logs are available." + fi + if test -n "${have_new_failures}" ; then exit -1 ; fi + - name: Compare test summary VS reference + if: success() || failure() # run regardless of prior step success/failure shell: bash run: | - mv dl/gnu-result.json master-gnu-result.json - python uutils/util/compare_gnu_result.py + REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json' + if test -f "${REF_SUMMARY_FILE}"; then + echo "Reference SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")" + mv "${REF_SUMMARY_FILE}" main-gnu-result.json + python uutils/util/compare_gnu_result.py + else + echo "::warning ::Skipping test summary comparison; no prior reference summary is available." + fi + + gnu_coverage: + name: Run GNU tests with coverage + runs-on: ubuntu-latest + steps: + - name: Checkout code uutil + uses: actions/checkout@v2 + with: + path: 'uutils' + - name: Checkout GNU coreutils + uses: actions/checkout@v2 + with: + repository: 'coreutils/coreutils' + path: 'gnu' + ref: 'v9.0' + submodules: recursive + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2022-03-21 + default: true + profile: minimal # minimal component installation (ie, no documentation) + components: rustfmt + - name: Install dependencies + run: | + sudo apt update + sudo apt install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify jq valgrind libexpect-perl -y + - name: Add various locales + run: | + echo "Before:" + locale -a + ## Some tests fail with 'cannot change locale (en_US.ISO-8859-1): No such file or directory' + ## Some others need a French locale + sudo locale-gen + sudo locale-gen fr_FR + sudo locale-gen fr_FR.UTF-8 + sudo update-locale + echo "After:" + locale -a + - name: Build binaries + env: + CARGO_INCREMENTAL: "0" + RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTDOCFLAGS: "-Cpanic=abort" + run: | + cd uutils + UU_MAKE_PROFILE=debug bash util/build-gnu.sh + - name: Run GNU tests + run: bash uutils/util/run-gnu-test.sh + - name: "`grcov` ~ install" + uses: actions-rs/install@v0.1 + with: + crate: grcov + version: latest + use-tool-cache: false + - name: Generate coverage data (via `grcov`) + id: coverage + run: | + ## Generate coverage data + cd uutils + COVERAGE_REPORT_DIR="target/debug" + COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info" + mkdir -p "${COVERAGE_REPORT_DIR}" + sudo chown -R "$(whoami)" "${COVERAGE_REPORT_DIR}" + # display coverage files + grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique + # generate coverage report + grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" + echo ::set-output name=report::${COVERAGE_REPORT_FILE} + - name: Upload coverage results (to Codecov.io) + uses: codecov/codecov-action@v2 + with: + file: ${{ steps.coverage.outputs.report }} + flags: gnutests + name: gnutests + working-directory: uutils diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 000000000..2a0c75141 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +# * using all default `cargo fmt`/`rustfmt` options diff --git a/.travis/redox-toolchain.sh b/.travis/redox-toolchain.sh index 83bc8fc45..d8b43b489 100755 --- a/.travis/redox-toolchain.sh +++ b/.travis/redox-toolchain.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh rustup target add x86_64-unknown-redox sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys AA12E97F0881517F diff --git a/.vscode/.gitattributes b/.vscode/.gitattributes new file mode 100644 index 000000000..c050fbbf3 --- /dev/null +++ b/.vscode/.gitattributes @@ -0,0 +1,2 @@ +# Configure GitHub to not mark comments in configuration files as errors +*.json linguist-language=jsonc diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json index 498360139..6dfb3b666 100644 --- a/.vscode/cSpell.json +++ b/.vscode/cSpell.json @@ -1,7 +1,12 @@ // `cspell` settings { - "version": "0.1", // Version of the setting file. Always 0.1 - "language": "en", // language - current active spelling language + // version of the setting file + "version": "0.2", + + // spelling language + "language": "en", + + // custom dictionaries "dictionaries": ["acronyms+names", "jargon", "people", "shell", "workspace"], "dictionaryDefinitions": [ { "name": "acronyms+names", "path": "./cspell.dictionaries/acronyms+names.wordlist.txt" }, @@ -10,10 +15,19 @@ { "name": "shell", "path": "./cspell.dictionaries/shell.wordlist.txt" }, { "name": "workspace", "path": "./cspell.dictionaries/workspace.wordlist.txt" } ], - // ignorePaths - a list of globs to specify which files are to be ignored - "ignorePaths": ["Cargo.lock", "target/**", "tests/**/fixtures/**", "src/uu/dd/test-resources/**"], - // ignoreWords - a list of words to be ignored (even if they are in the flagWords) + + // files to ignore (globs supported) + "ignorePaths": [ + "Cargo.lock", + "target/**", + "tests/**/fixtures/**", + "src/uu/dd/test-resources/**", + "vendor/**" + ], + + // words to ignore (even if they are in the flagWords) "ignoreWords": [], - // words - list of words to be always considered correct + + // words to always consider correct "words": [] } diff --git a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt index a46448a32..81bc3bc5f 100644 --- a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt +++ b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt @@ -35,6 +35,7 @@ WASM XFS aarch flac +impls lzma # * names @@ -47,6 +48,7 @@ EditorConfig FreeBSD Gmail GNU +Illumos Irix MS-DOS MSDOS diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 9b1d0a8da..13f340f6a 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -11,6 +11,7 @@ canonicalize canonicalizing codepoint codepoints +codegen colorizable colorize coprime @@ -28,6 +29,7 @@ devs discoverability duplicative dsync +endianness enqueue errored executable @@ -36,6 +38,8 @@ exponentiate eval falsey fileio +filesystem +filesystems flamegraph fullblock getfacl @@ -59,6 +63,7 @@ kibibytes libacl lcase lossily +lstat mebi mebibytes mergeable diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index d37a59465..99ac20ea2 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -25,6 +25,7 @@ getrandom globset itertools lscolors +mdbook memchr multifilereader onig @@ -43,6 +44,7 @@ termsize termwidth textwrap thiserror +ureq walkdir winapi xattr @@ -182,6 +184,7 @@ getgrgid getgrnam getgrouplist getgroups +getpwent getpwnam getpwuid getuid @@ -321,6 +324,7 @@ ucommand utmpx uucore uucore_procs +uudoc uumain uutil uutils diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 46b105d37..bd9dcf485 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,12 +1,13 @@ +// spell-checker:ignore (misc) matklad +// see for the documentation about the extensions.json format +// * +// "foxundermoon.shell-format" ~ shell script formatting ; note: ENABLE "Use EditorConfig" +// "matklad.rust-analyzer" ~ `rust` language support +// "streetsidesoftware.code-spell-checker" ~ `cspell` spell-checker support { - // spell-checker:ignore (misc) matklad - // see for the documentation about the extensions.json format - "recommendations": [ - // Rust language support. - "rust-lang.rust", - // Provides support for rust-analyzer: novel LSP server for the Rust programming language. - "matklad.rust-analyzer", - // `cspell` spell-checker support - "streetsidesoftware.code-spell-checker" - ] + "recommendations": [ + "matklad.rust-analyzer", + "streetsidesoftware.code-spell-checker", + "foxundermoon.shell-format" + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..54df63a5b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{ "cSpell.import": [".vscode/cspell.json"] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a8a80315..15adfe488 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to coreutils -Contributions are very welcome, and should target Rust's master branch until the +Contributions are very welcome, and should target Rust's main branch until the standard libraries are stabilized. You may *claim* an item on the to-do list by following these steps: @@ -94,6 +94,16 @@ uutils: add new utility gitignore: add temporary files ``` +## 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 +``` + ## Licensing uutils is distributed under the terms of the MIT License; see the `LICENSE` file diff --git a/Cargo.lock b/Cargo.lock index 2d6a40df9..02ab122b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.4.7" @@ -20,7 +26,7 @@ version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ - "memchr 2.4.0", + "memchr 2.4.1", ] [[package]] @@ -29,15 +35,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -55,9 +52,9 @@ checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" -version = "0.5.2" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "atty" @@ -76,6 +73,23 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bigdecimal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "binary-heap-plus" version = "0.4.1" @@ -87,65 +101,38 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.59.1" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" +checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" dependencies = [ "bitflags", "cexpr", "clang-sys", - "clap", - "env_logger 0.8.4", + "clap 2.34.0", + "env_logger 0.9.0", "lazy_static", "lazycell", "log", "peeking_take_while", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "regex", "rustc-hash", "shlex", "which", ] -[[package]] -name = "bit-set" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitvec" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "blake2b_simd" -version = "0.5.11" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" dependencies = [ "arrayref", "arrayvec", @@ -153,37 +140,50 @@ dependencies = [ ] [[package]] -name = "block-buffer" -version = "0.2.0" +name = "blake3" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 1.0.0", + "constant_time_eq", + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" dependencies = [ - "byte-tools", "generic-array", ] [[package]] name = "bstr" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", - "memchr 2.4.0", + "memchr 2.4.1", "regex-automata", ] [[package]] -name = "byte-tools" -version = "0.2.0" +name = "bumpalo" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "byte-unit" -version = "4.0.12" +version = "4.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063197e6eb4b775b64160dedde7a0986bb2836cce140e9492e9e96f28e18bcd8" +checksum = "956ffc5b0ec7d7a6949e3f21fd63ba5af4cffdc2ba1e0b7bf62b481458c4ae7f" dependencies = [ "utf8-width", ] @@ -202,15 +202,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.69" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" [[package]] name = "cexpr" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] @@ -241,10 +241,16 @@ dependencies = [ ] [[package]] -name = "clang-sys" -version = "1.2.0" +name = "chunked_transfer" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "clang-sys" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" dependencies = [ "glob", "libc", @@ -253,27 +259,43 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.3" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "ansi_term 0.11.0", + "ansi_term", "atty", "bitflags", - "strsim", - "term_size", + "strsim 0.8.0", "textwrap 0.11.0", "unicode-width", "vec_map", ] [[package]] -name = "cloudabi" -version = "0.0.3" +name = "clap" +version = "3.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" dependencies = [ + "atty", "bitflags", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim 0.10.0", + "termcolor", + "terminal_size", + "textwrap 0.15.0", +] + +[[package]] +name = "clap_complete" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6f3613c0a3cddfd78b41b10203eb322cb29b600cbdf808a7d3db95691b8e25" +dependencies = [ + "clap 3.1.6", ] [[package]] @@ -299,28 +321,33 @@ dependencies = [ [[package]] name = "coreutils" -version = "0.0.8" +version = "0.0.13" dependencies = [ "atty", "chrono", - "clap", + "clap 3.1.6", + "clap_complete", "conv", "filetime", "glob", + "hex-literal", "lazy_static", "libc", - "nix 0.20.0", + "nix", + "phf", + "phf_codegen", "pretty_assertions", - "rand 0.7.3", + "rand", "regex", "rlimit", "selinux", "sha1", "tempfile", - "textwrap 0.14.2", + "textwrap 0.15.0", "time", "unindent", "unix_socket", + "ureq", "users", "uu_arch", "uu_base32", @@ -424,6 +451,7 @@ dependencies = [ "uu_yes", "uucore", "walkdir", + "zip", ] [[package]] @@ -494,7 +522,7 @@ dependencies = [ "if_rust_version", "lazy_static", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", ] @@ -530,10 +558,28 @@ dependencies = [ ] [[package]] -name = "crossbeam-channel" -version = "0.5.1" +name = "cpufeatures" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -552,9 +598,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -565,9 +611,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" dependencies = [ "cfg-if 1.0.0", "lazy_static", @@ -575,14 +621,14 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.20.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" +checksum = "f1fd7173631a4e9e2ca8b32ae2fad58aab9843ea5aaf56642661937d87e28a3e" dependencies = [ "bitflags", "crossterm_winapi", "libc", - "mio 0.7.7", + "mio 0.7.14", "parking_lot", "signal-hook", "signal-hook-mio", @@ -591,30 +637,40 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" dependencies = [ "winapi 0.3.9", ] [[package]] -name = "ctor" -version = "0.1.20" +name = "crypto-common" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ - "quote 1.0.9", + "generic-array", + "typenum", +] + +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote 1.0.14", "syn", ] [[package]] name = "ctrlc" -version = "3.1.9" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a" +checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" dependencies = [ - "nix 0.20.0", + "nix", "winapi 0.3.9", ] @@ -650,17 +706,6 @@ dependencies = [ "syn", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote 1.0.9", - "syn", -] - [[package]] name = "diff" version = "0.1.12" @@ -669,11 +714,13 @@ checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" [[package]] name = "digest" -version = "0.6.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecae1c064e29fcabb6c2e9939e53dc7da72ed90234ae36ebfe03a478742efbd1" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "generic-array", + "block-buffer", + "crypto-common", + "subtle", ] [[package]] @@ -682,14 +729,14 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68df3f2b690c1b86e65ef7830956aededf3cb0a16f898f79b9a6f421a7b6211b" dependencies = [ - "rand 0.8.4", + "rand", ] [[package]] name = "dns-lookup" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093d88961fd18c4ecacb8c80cd0b356463ba941ba11e0e01f9cf5271380b79dc" +checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872" dependencies = [ "cfg-if 1.0.0", "libc", @@ -711,9 +758,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "env_logger" -version = "0.7.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "log", "regex", @@ -721,9 +768,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.8.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", "humantime", @@ -734,24 +781,24 @@ dependencies = [ [[package]] name = "exacl" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "769bbd173781e84865b957cf83449f0d2869f4c9d2f191cbbffffb3d9751ba2b" +checksum = "9d5d9a2fa7d72579802c22bb97c37953cf1007f21f7ac2247d150c4c2d40c2ab" dependencies = [ "bitflags", "log", - "nix 0.21.0", - "num_enum", "scopeguard", - "serde", "uuid", ] [[package]] -name = "fake-simd" -version = "0.1.2" +name = "fastrand" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" +dependencies = [ + "instant", +] [[package]] name = "file_diff" @@ -771,12 +818,34 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "fs_extra" version = "1.2.0" @@ -802,59 +871,30 @@ dependencies = [ "libc", ] -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - [[package]] name = "gcd" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7cd301bf2ab11ae4e5bdfd79c221d97a25e46c089144a62ee9d09cb32d2b92" +checksum = "f37978dab2ca789938a83b2f8bc1ef32db6633af9051a6cd409eff72cbaaa79a" +dependencies = [ + "paste", +] [[package]] name = "generic-array" -version = "0.8.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2297fb0e3ea512e380da24b52dca3924028f59df5e3a17a18f81d8349ca7ebe" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ - "nodrop", "typenum", -] - -[[package]] -name = "getopts" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -dependencies = [ - "unicode-width", + "version_check", ] [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if 1.0.0", "libc", @@ -867,24 +907,11 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "globset" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - [[package]] name = "half" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" @@ -895,6 +922,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + [[package]] name = "heck" version = "0.3.3" @@ -915,9 +948,15 @@ dependencies = [ [[package]] name = "hex" -version = "0.2.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" [[package]] name = "hostname" @@ -936,12 +975,33 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "if_rust_version" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec" +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown 0.11.2", +] + [[package]] name = "inotify" version = "0.9.4" @@ -964,37 +1024,43 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "ioctl-sys" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c429fffa658f288669529fc26565f728489a2e39bc7b24a428aaaf51355182e" +checksum = "8bd11f3a29434026f5ff98c730b668ba74b1033637b8817940b54d040696133c" [[package]] name = "itertools" -version = "0.8.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] [[package]] -name = "itertools" -version = "0.10.1" +name = "js-sys" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ - "either", + "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1039,15 +1105,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.101" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" [[package]] name = "libloading" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi 0.3.9", @@ -1055,9 +1121,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -1077,7 +1143,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", ] [[package]] @@ -1087,16 +1153,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] -name = "maybe-uninit" -version = "2.0.0" +name = "matches" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] -name = "md5" -version = "0.3.8" +name = "md-5" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" +checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +dependencies = [ + "digest", +] [[package]] name = "memchr" @@ -1109,33 +1178,49 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap2" -version = "0.5.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] [[package]] -name = "mio" -version = "0.7.7" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", @@ -1169,33 +1254,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.19.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", -] - -[[package]] -name = "nix" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", -] - -[[package]] -name = "nix" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ "bitflags", "cc", @@ -1204,21 +1265,14 @@ dependencies = [ "memoffset", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nom" -version = "6.1.2" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ - "bitvec", - "funty", - "memchr 2.4.0", + "memchr 2.4.1", + "minimal-lexical", "version_check", ] @@ -1251,9 +1305,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg", "num-integer", @@ -1281,36 +1335,14 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", ] -[[package]] -name = "num_enum" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" -dependencies = [ - "derivative", - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote 1.0.9", - "syn", -] - [[package]] name = "number_prefix" version = "0.4.0" @@ -1325,15 +1357,15 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "once_cell" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "onig" -version = "4.3.3" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8518fcb2b1b8c2f45f0ad499df4fda6087fc3475ca69a185c173b8315d2fb383" +checksum = "67ddfe2c93bb389eea6e6d713306880c7f6dcc99a75b659ce145d962c861b225" dependencies = [ "bitflags", "lazy_static", @@ -1343,9 +1375,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.1.0" +version = "69.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0" +checksum = "5dd3eee045c84695b53b20255bb7317063df090b68e18bfac0abb6c39cf7f33e" dependencies = [ "cc", "pkg-config", @@ -1358,14 +1390,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485" dependencies = [ "dlv-list", - "hashbrown", + "hashbrown 0.9.1", +] + +[[package]] +name = "os_display" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "748cc1d0dc55247316a5bedd8dc8c5478c8a0c2e2001176b38ce7c0ed732c7a5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr 2.4.1", ] [[package]] name = "ouroboros" -version = "0.10.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84236d64f1718c387232287cf036eb6632a5ecff226f4ff9dccb8c2b79ba0bde" +checksum = "9f31a3b678685b150cba82b702dcdc5e155893f63610cf388d30cd988d4ca2bf" dependencies = [ "aliasable", "ouroboros_macro", @@ -1374,14 +1424,14 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.10.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f463857a6eb96c0136b1d56e56c718350cef30412ec065b48294799a088bca68" +checksum = "084fd65d5dd8b3772edccb5ffd1e4b7eba43897ecd0f9401e330e8c542959408" dependencies = [ "Inflector", "proc-macro-error", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", ] @@ -1396,47 +1446,32 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" dependencies = [ "cfg-if 1.0.0", - "instant", "libc", "redox_syscall", - "smallvec 1.6.1", - "winapi 0.3.9", + "smallvec", + "windows-sys", ] [[package]] name = "paste" -version = "0.1.18" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -dependencies = [ - "paste-impl", - "proc-macro-hack", -] - -[[package]] -name = "paste-impl" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -dependencies = [ - "proc-macro-hack", -] +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" [[package]] name = "peeking_take_while" @@ -1445,16 +1480,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] -name = "pkg-config" -version = "0.3.19" +name = "percent-encoding" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" [[package]] name = "platform-info" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ea9cd21d89bffb387b6c7363d23bead0807be9de676c671b474dd29e7436d3" +checksum = "84332c4de03d567e6f5ea143e35e63ceed534a34f768218aabf57879d7edf2a0" dependencies = [ "libc", "winapi 0.3.9", @@ -1462,32 +1541,22 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pretty_assertions" -version = "0.7.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" +checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", "ctor", "diff", "output_vt100", ] -[[package]] -name = "proc-macro-crate" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" -dependencies = [ - "thiserror", - "toml", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1496,7 +1565,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", "version_check", ] @@ -1508,31 +1577,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid 0.2.2", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.1" @@ -1541,14 +1598,13 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quickcheck" -version = "0.9.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "env_logger 0.7.1", + "env_logger 0.8.4", "log", - "rand 0.7.3", - "rand_core 0.5.1", + "rand", ] [[package]] @@ -1559,46 +1615,13 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" [[package]] name = "quote" -version = "1.0.9" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" - -[[package]] -name = "rand" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "winapi 0.3.9", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", - "rand_pcg", -] - [[package]] name = "rand" version = "0.8.4" @@ -1606,19 +1629,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", - "rand_hc 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", + "rand_hc", ] [[package]] @@ -1628,31 +1641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -1661,16 +1650,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", + "getrandom", ] [[package]] @@ -1679,16 +1659,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ - "rand_core 0.6.3", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", + "rand_core", ] [[package]] @@ -1718,9 +1689,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0" dependencies = [ "bitflags", ] @@ -1747,7 +1718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", - "memchr 2.4.0", + "memchr 2.4.1", "regex-syntax", ] @@ -1774,9 +1745,24 @@ dependencies = [ [[package]] name = "retain_mut" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b" +checksum = "53552c6c49e1e13f1a203ef0080ab3bbef0beb570a528993e83df057a9d9bba1" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] [[package]] name = "rlimit" @@ -1804,6 +1790,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustls" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b323592e3164322f5b193dc4302e4e36cd8d37158a712d664efae1a5c2791700" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + [[package]] name = "same-file" version = "1.0.6" @@ -1820,10 +1824,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "selinux" -version = "0.2.3" +name = "sct" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cf704a543fe60d898f3253f1cc37655d0f0e9cdb68ef6230557e0e031b80608" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "selinux" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09715d6b4356e916047e61e4dce40a67ac93036851957b91713d3d9c282d1548" dependencies = [ "bitflags", "libc", @@ -1845,68 +1859,49 @@ dependencies = [ "walkdir", ] -[[package]] -name = "serde" -version = "1.0.130" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.130" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" -dependencies = [ - "proc-macro2", - "quote 1.0.9", - "syn", -] - [[package]] name = "sha1" -version = "0.6.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] [[package]] name = "sha2" -version = "0.6.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d963c78ce367df26d7ea8b8cc655c651b42e8a1e584e869c1e17dae3ccb116a" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ - "block-buffer", - "byte-tools", + "cfg-if 1.0.0", + "cpufeatures", "digest", - "fake-simd", - "generic-array", ] [[package]] name = "sha3" -version = "0.6.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26405905b6a56a94c60109cfda62610507ac14a65be531f5767dec5c5a8dd6a0" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" dependencies = [ - "block-buffer", - "byte-tools", "digest", - "generic-array", + "keccak", ] [[package]] name = "shlex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.9" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" +checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" dependencies = [ "libc", "signal-hook-registry", @@ -1919,7 +1914,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" dependencies = [ "libc", - "mio 0.7.7", + "mio 0.7.14", "signal-hook", ] @@ -1933,19 +1928,16 @@ dependencies = [ ] [[package]] -name = "smallvec" -version = "0.6.14" +name = "siphasher" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] +checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" [[package]] name = "smallvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smawk" @@ -1955,15 +1947,20 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" [[package]] name = "socket2" -version = "0.3.19" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +checksum = "0f82496b90c36d70af5fcd482edaa2e0bd16fade569de1330405fecbbdac736b" dependencies = [ - "cfg-if 1.0.0", "libc", "winapi 0.3.9", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1977,49 +1974,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] -name = "strum" -version = "0.21.0" +name = "strsim" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" [[package]] name = "strum_macros" -version = "0.21.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" dependencies = [ "heck", "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", + "rustversion", "syn", ] [[package]] -name = "syn" -version = "1.0.74" +name = "subtle" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "unicode-xid 0.2.2", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", + "fastrand", "libc", - "rand 0.8.4", "redox_syscall", "remove_dir_all", "winapi 0.3.9", @@ -2034,16 +2038,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -dependencies = [ - "libc", - "winapi 0.3.9", -] - [[package]] name = "termcolor" version = "1.1.2" @@ -2094,15 +2088,14 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "term_size", "unicode-width", ] [[package]] name = "textwrap" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" dependencies = [ "smawk", "terminal_size", @@ -2112,21 +2105,21 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.14", "syn", ] @@ -2141,19 +2134,31 @@ dependencies = [ ] [[package]] -name = "toml" -version = "0.5.8" +name = "tinyvec" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" dependencies = [ - "serde", + "tinyvec_macros", ] [[package]] -name = "typenum" -version = "1.13.0" +name = "tinyvec_macros" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" [[package]] name = "unicode-linebreak" @@ -2165,16 +2170,25 @@ dependencies = [ ] [[package]] -name = "unicode-segmentation" -version = "1.8.0" +name = "unicode-normalization" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" @@ -2190,9 +2204,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "unindent" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" +checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8" [[package]] name = "unix_socket" @@ -2204,6 +2218,41 @@ dependencies = [ "libc", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9399fa2f927a3d327187cbd201480cee55bee6ac5d3c77dd27f0c6814cff16d5" +dependencies = [ + "base64", + "chunked_transfer", + "flate2", + "log", + "once_cell", + "rustls", + "url", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + [[package]] name = "users" version = "0.10.0" @@ -2228,150 +2277,131 @@ checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" [[package]] name = "uu_arch" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "platform-info", "uucore", - "uucore_procs", ] [[package]] name = "uu_base32" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_base64" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", "uu_base32", "uucore", - "uucore_procs", ] [[package]] name = "uu_basename" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_basenc" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uu_base32", "uucore", - "uucore_procs", ] [[package]] name = "uu_cat" -version = "0.0.8" +version = "0.0.13" dependencies = [ "atty", - "clap", - "nix 0.20.0", + "clap 3.1.6", + "nix", "thiserror", "unix_socket", "uucore", - "uucore_procs", - "winapi-util", ] [[package]] name = "uu_chcon" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "fts-sys", "libc", "selinux", "thiserror", "uucore", - "uucore_procs", ] [[package]] name = "uu_chgrp" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_chmod" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", - "walkdir", ] [[package]] name = "uu_chown" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_chroot" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_cksum" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_comm" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_cp" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "exacl", "filetime", "ioctl-sys", "libc", - "quick-error 1.2.3", + "quick-error", "selinux", "uucore", - "uucore_procs", "walkdir", "winapi 0.3.9", "xattr", @@ -2379,322 +2409,285 @@ dependencies = [ [[package]] name = "uu_csplit" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "regex", "thiserror", "uucore", - "uucore_procs", ] [[package]] name = "uu_cut" -version = "0.0.8" +version = "0.0.13" dependencies = [ "atty", "bstr", - "clap", - "memchr 2.4.0", + "clap 3.1.6", + "memchr 2.4.1", "uucore", - "uucore_procs", ] [[package]] name = "uu_date" -version = "0.0.8" +version = "0.0.13" dependencies = [ "chrono", - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", "winapi 0.3.9", ] [[package]] name = "uu_dd" -version = "0.0.8" +version = "0.0.13" dependencies = [ "byte-unit", - "clap", + "clap 3.1.6", "gcd", "libc", "signal-hook", - "tempfile", "uucore", - "uucore_procs", ] [[package]] name = "uu_df" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "number_prefix", "uucore", - "uucore_procs", ] [[package]] name = "uu_dircolors" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "glob", "uucore", - "uucore_procs", ] [[package]] name = "uu_dirname" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_du" -version = "0.0.8" +version = "0.0.13" dependencies = [ "chrono", - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", "winapi 0.3.9", ] [[package]] name = "uu_echo" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_env" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", + "clap 3.1.6", "rust-ini", "uucore", - "uucore_procs", ] [[package]] name = "uu_expand" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "unicode-width", "uucore", - "uucore_procs", ] [[package]] name = "uu_expr" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", + "clap 3.1.6", "num-bigint", "num-traits", "onig", "uucore", - "uucore_procs", ] [[package]] name = "uu_factor" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "coz", "num-traits", "paste", "quickcheck", - "rand 0.7.3", - "smallvec 0.6.14", + "rand", + "smallvec", "uucore", - "uucore_procs", ] [[package]] name = "uu_false" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_fmt" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", + "clap 3.1.6", "unicode-width", "uucore", - "uucore_procs", ] [[package]] name = "uu_fold" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_groups" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_hashsum" -version = "0.0.8" +version = "0.0.13" dependencies = [ "blake2b_simd", - "clap", + "blake3", + "clap 3.1.6", "digest", "hex", - "libc", - "md5", - "memchr 2.4.0", + "md-5", + "memchr 2.4.1", "regex", - "regex-syntax", "sha1", "sha2", "sha3", "uucore", - "uucore_procs", ] [[package]] name = "uu_head" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "memchr 2.4.0", + "clap 3.1.6", + "memchr 2.4.1", "uucore", - "uucore_procs", ] [[package]] name = "uu_hostid" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_hostname" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "hostname", - "libc", "uucore", - "uucore_procs", "winapi 0.3.9", ] [[package]] name = "uu_id" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "selinux", "uucore", - "uucore_procs", ] [[package]] name = "uu_install" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "file_diff", "filetime", "libc", - "time", "uucore", - "uucore_procs", ] [[package]] name = "uu_join" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", + "memchr 2.4.1", "uucore", - "uucore_procs", ] [[package]] name = "uu_kill" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_link" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_ln" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_logname" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_ls" -version = "0.0.8" +version = "0.0.13" dependencies = [ "atty", "chrono", - "clap", - "globset", + "clap 3.1.6", + "glob", "lazy_static", "lscolors", "number_prefix", @@ -2704,664 +2697,591 @@ dependencies = [ "termsize", "unicode-width", "uucore", - "uucore_procs", ] [[package]] name = "uu_mkdir" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_mkfifo" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_mknod" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_mktemp" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "rand 0.5.6", + "clap 3.1.6", + "rand", "tempfile", "uucore", - "uucore_procs", ] [[package]] name = "uu_more" -version = "0.0.8" +version = "0.0.13" dependencies = [ "atty", - "clap", + "clap 3.1.6", "crossterm", - "nix 0.19.1", - "redox_syscall", - "redox_termios", + "nix", "unicode-segmentation", "unicode-width", "uucore", - "uucore_procs", ] [[package]] name = "uu_mv" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "fs_extra", "uucore", - "uucore_procs", ] [[package]] name = "uu_nice" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", - "nix 0.20.0", + "nix", "uucore", - "uucore_procs", ] [[package]] name = "uu_nl" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "aho-corasick", - "clap", - "libc", - "memchr 2.4.0", + "clap 3.1.6", "regex", - "regex-syntax", "uucore", - "uucore_procs", ] [[package]] name = "uu_nohup" -version = "0.0.8" +version = "0.0.13" dependencies = [ "atty", - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_nproc" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "num_cpus", "uucore", - "uucore_procs", ] [[package]] name = "uu_numfmt" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_od" -version = "0.0.8" +version = "0.0.13" dependencies = [ "byteorder", - "clap", + "clap 3.1.6", "half", - "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_paste" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_pathchk" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_pinky" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_pr" -version = "0.0.8" +version = "0.0.13" dependencies = [ "chrono", - "clap", - "getopts", - "itertools 0.10.1", - "quick-error 2.0.1", + "clap 3.1.6", + "itertools", + "quick-error", "regex", "uucore", - "uucore_procs", ] [[package]] name = "uu_printenv" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_printf" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "itertools 0.8.2", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_ptx" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "aho-corasick", - "clap", - "libc", - "memchr 2.4.0", + "clap 3.1.6", "regex", - "regex-syntax", "uucore", - "uucore_procs", ] [[package]] name = "uu_pwd" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_readlink" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_realpath" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_relpath" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_rm" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "remove_dir_all", "uucore", - "uucore_procs", "walkdir", "winapi 0.3.9", ] [[package]] name = "uu_rmdir" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_runcon" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "fts-sys", + "clap 3.1.6", "libc", "selinux", "thiserror", "uucore", - "uucore_procs", ] [[package]] name = "uu_seq" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "bigdecimal", + "clap 3.1.6", "num-bigint", "num-traits", "uucore", - "uucore_procs", ] [[package]] name = "uu_shred" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "libc", - "rand 0.7.3", + "clap 3.1.6", + "rand", "uucore", - "uucore_procs", ] [[package]] name = "uu_shuf" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "rand 0.5.6", + "clap 3.1.6", + "rand", + "rand_core", "uucore", - "uucore_procs", ] [[package]] name = "uu_sleep" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_sort" -version = "0.0.8" +version = "0.0.13" dependencies = [ "binary-heap-plus", - "clap", + "clap 3.1.6", "compare", "ctrlc", "fnv", - "itertools 0.10.1", - "memchr 2.4.0", + "itertools", + "memchr 2.4.1", "ouroboros", - "rand 0.7.3", + "rand", "rayon", "tempfile", "unicode-width", "uucore", - "uucore_procs", ] [[package]] name = "uu_split" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", + "memchr 2.4.1", "uucore", - "uucore_procs", ] [[package]] name = "uu_stat" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_stdbuf" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "tempfile", "uu_stdbuf_libstdbuf", "uucore", - "uucore_procs", ] [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.8" +version = "0.0.13" dependencies = [ "cpp", "cpp_build", "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_sum" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_sync" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", "winapi 0.3.9", ] [[package]] name = "uu_tac" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "memchr 2.4.0", + "clap 3.1.6", + "memchr 2.4.1", "memmap2", "regex", "uucore", - "uucore_procs", ] [[package]] name = "uu_tail" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", - "nix 0.20.0", + "nix", "notify", - "redox_syscall", "uucore", - "uucore_procs", "winapi 0.3.9", ] [[package]] name = "uu_tee" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "retain_mut", "uucore", - "uucore_procs", ] [[package]] name = "uu_test" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "redox_syscall", "uucore", - "uucore_procs", ] [[package]] name = "uu_timeout" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", - "nix 0.20.0", + "nix", "uucore", - "uucore_procs", ] [[package]] name = "uu_touch" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "filetime", "time", "uucore", - "uucore_procs", + "winapi 0.3.9", ] [[package]] name = "uu_tr" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "bit-set", - "clap", - "fnv", + "clap 3.1.6", + "nom", "uucore", - "uucore_procs", ] [[package]] name = "uu_true" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_truncate" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_tsort" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_tty" -version = "0.0.8" +version = "0.0.13" dependencies = [ "atty", - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", ] [[package]] name = "uu_uname" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "platform-info", "uucore", - "uucore_procs", ] [[package]] name = "uu_unexpand" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "unicode-width", "uucore", - "uucore_procs", ] [[package]] name = "uu_uniq" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "strum", "strum_macros", "uucore", - "uucore_procs", ] [[package]] name = "uu_unlink" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_uptime" -version = "0.0.8" +version = "0.0.13" dependencies = [ "chrono", - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_users" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_wc" -version = "0.0.8" +version = "0.0.13" dependencies = [ "bytecount", - "clap", + "clap 3.1.6", "libc", - "nix 0.20.0", + "nix", "unicode-width", "utf-8", "uucore", - "uucore_procs", ] [[package]] name = "uu_who" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "uucore", - "uucore_procs", ] [[package]] name = "uu_whoami" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "libc", "uucore", - "uucore_procs", "winapi 0.3.9", ] [[package]] name = "uu_yes" -version = "0.0.8" +version = "0.0.13" dependencies = [ - "clap", - "nix 0.20.0", + "clap 3.1.6", + "nix", "uucore", - "uucore_procs", ] [[package]] name = "uucore" -version = "0.0.10" +version = "0.0.13" dependencies = [ - "clap", + "clap 3.1.6", "data-encoding", "data-encoding-macro", "dns-lookup", "dunce", - "getopts", + "itertools", "lazy_static", "libc", - "nix 0.20.0", + "nix", "once_cell", - "termion", + "os_display", "thiserror", "time", + "uucore_procs", "walkdir", "wild", "winapi 0.3.9", + "winapi-util", "z85", ] [[package]] name = "uucore_procs" -version = "0.0.7" +version = "0.0.13" dependencies = [ "proc-macro2", - "quote 1.0.9", - "syn", + "quote 1.0.14", ] [[package]] @@ -3378,9 +3298,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" @@ -3393,12 +3313,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" @@ -3412,11 +3326,96 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "which" -version = "3.1.1" +name = "wasm-bindgen" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote 1.0.14", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +dependencies = [ + "quote 1.0.14", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +dependencies = [ + "proc-macro2", + "quote 1.0.14", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" +dependencies = [ + "webpki", +] + +[[package]] +name = "which" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +dependencies = [ + "either", + "lazy_static", "libc", ] @@ -3473,10 +3472,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "wyz" -version = "0.2.0" +name = "windows-sys" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" [[package]] name = "xattr" @@ -3489,6 +3525,18 @@ dependencies = [ [[package]] name = "z85" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b56e4f9906a4ef5412875e9ce448364023335cec645fd457ecf51d4f2781" +checksum = "af896e93db81340b74b65f74276a99b210c086f3d34ed0abf433182a462af856" + +[[package]] +name = "zip" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +dependencies = [ + "byteorder", + "crc32fast", + "flate2", + "thiserror", +] diff --git a/Cargo.toml b/Cargo.toml index 9ecea79c2..377f466eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,11 @@ [package] name = "coreutils" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" +default-run = "coreutils" homepage = "https://github.com/uutils/coreutils" repository = "https://github.com/uutils/coreutils" @@ -244,113 +245,117 @@ test = [ "uu_test" ] [workspace] [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +clap_complete = "3.1" +phf = "0.10.1" lazy_static = { version="1.3" } -textwrap = { version="0.14", features=["terminal_size"] } -uucore = { version=">=0.0.10", package="uucore", path="src/uucore" } -selinux = { version="0.2.3", optional = true } +textwrap = { version="0.15", features=["terminal_size"] } +uucore = { version=">=0.0.11", package="uucore", path="src/uucore" } +selinux = { version="0.2", optional = true } +ureq = "2.4.0" +zip = { version = "0.5.13", default_features=false, features=["deflate"] } # * uutils -uu_test = { optional=true, version="0.0.8", package="uu_test", path="src/uu/test" } +uu_test = { optional=true, version="0.0.13", package="uu_test", path="src/uu/test" } # -arch = { optional=true, version="0.0.8", package="uu_arch", path="src/uu/arch" } -base32 = { optional=true, version="0.0.8", package="uu_base32", path="src/uu/base32" } -base64 = { optional=true, version="0.0.8", package="uu_base64", path="src/uu/base64" } -basename = { optional=true, version="0.0.8", package="uu_basename", path="src/uu/basename" } -basenc = { optional=true, version="0.0.8", package="uu_basenc", path="src/uu/basenc" } -cat = { optional=true, version="0.0.8", package="uu_cat", path="src/uu/cat" } -chcon = { optional=true, version="0.0.8", package="uu_chcon", path="src/uu/chcon" } -chgrp = { optional=true, version="0.0.8", package="uu_chgrp", path="src/uu/chgrp" } -chmod = { optional=true, version="0.0.8", package="uu_chmod", path="src/uu/chmod" } -chown = { optional=true, version="0.0.8", package="uu_chown", path="src/uu/chown" } -chroot = { optional=true, version="0.0.8", package="uu_chroot", path="src/uu/chroot" } -cksum = { optional=true, version="0.0.8", package="uu_cksum", path="src/uu/cksum" } -comm = { optional=true, version="0.0.8", package="uu_comm", path="src/uu/comm" } -cp = { optional=true, version="0.0.8", package="uu_cp", path="src/uu/cp" } -csplit = { optional=true, version="0.0.8", package="uu_csplit", path="src/uu/csplit" } -cut = { optional=true, version="0.0.8", package="uu_cut", path="src/uu/cut" } -date = { optional=true, version="0.0.8", package="uu_date", path="src/uu/date" } -dd = { optional=true, version="0.0.8", package="uu_dd", path="src/uu/dd" } -df = { optional=true, version="0.0.8", package="uu_df", path="src/uu/df" } -dircolors= { optional=true, version="0.0.8", package="uu_dircolors", path="src/uu/dircolors" } -dirname = { optional=true, version="0.0.8", package="uu_dirname", path="src/uu/dirname" } -du = { optional=true, version="0.0.8", package="uu_du", path="src/uu/du" } -echo = { optional=true, version="0.0.8", package="uu_echo", path="src/uu/echo" } -env = { optional=true, version="0.0.8", package="uu_env", path="src/uu/env" } -expand = { optional=true, version="0.0.8", package="uu_expand", path="src/uu/expand" } -expr = { optional=true, version="0.0.8", package="uu_expr", path="src/uu/expr" } -factor = { optional=true, version="0.0.8", package="uu_factor", path="src/uu/factor" } -false = { optional=true, version="0.0.8", package="uu_false", path="src/uu/false" } -fmt = { optional=true, version="0.0.8", package="uu_fmt", path="src/uu/fmt" } -fold = { optional=true, version="0.0.8", package="uu_fold", path="src/uu/fold" } -groups = { optional=true, version="0.0.8", package="uu_groups", path="src/uu/groups" } -hashsum = { optional=true, version="0.0.8", package="uu_hashsum", path="src/uu/hashsum" } -head = { optional=true, version="0.0.8", package="uu_head", path="src/uu/head" } -hostid = { optional=true, version="0.0.8", package="uu_hostid", path="src/uu/hostid" } -hostname = { optional=true, version="0.0.8", package="uu_hostname", path="src/uu/hostname" } -id = { optional=true, version="0.0.8", package="uu_id", path="src/uu/id" } -install = { optional=true, version="0.0.8", package="uu_install", path="src/uu/install" } -join = { optional=true, version="0.0.8", package="uu_join", path="src/uu/join" } -kill = { optional=true, version="0.0.8", package="uu_kill", path="src/uu/kill" } -link = { optional=true, version="0.0.8", package="uu_link", path="src/uu/link" } -ln = { optional=true, version="0.0.8", package="uu_ln", path="src/uu/ln" } -ls = { optional=true, version="0.0.8", package="uu_ls", path="src/uu/ls" } -logname = { optional=true, version="0.0.8", package="uu_logname", path="src/uu/logname" } -mkdir = { optional=true, version="0.0.8", package="uu_mkdir", path="src/uu/mkdir" } -mkfifo = { optional=true, version="0.0.8", package="uu_mkfifo", path="src/uu/mkfifo" } -mknod = { optional=true, version="0.0.8", package="uu_mknod", path="src/uu/mknod" } -mktemp = { optional=true, version="0.0.8", package="uu_mktemp", path="src/uu/mktemp" } -more = { optional=true, version="0.0.8", package="uu_more", path="src/uu/more" } -mv = { optional=true, version="0.0.8", package="uu_mv", path="src/uu/mv" } -nice = { optional=true, version="0.0.8", package="uu_nice", path="src/uu/nice" } -nl = { optional=true, version="0.0.8", package="uu_nl", path="src/uu/nl" } -nohup = { optional=true, version="0.0.8", package="uu_nohup", path="src/uu/nohup" } -nproc = { optional=true, version="0.0.8", package="uu_nproc", path="src/uu/nproc" } -numfmt = { optional=true, version="0.0.8", package="uu_numfmt", path="src/uu/numfmt" } -od = { optional=true, version="0.0.8", package="uu_od", path="src/uu/od" } -paste = { optional=true, version="0.0.8", package="uu_paste", path="src/uu/paste" } -pathchk = { optional=true, version="0.0.8", package="uu_pathchk", path="src/uu/pathchk" } -pinky = { optional=true, version="0.0.8", package="uu_pinky", path="src/uu/pinky" } -pr = { optional=true, version="0.0.8", package="uu_pr", path="src/uu/pr" } -printenv = { optional=true, version="0.0.8", package="uu_printenv", path="src/uu/printenv" } -printf = { optional=true, version="0.0.8", package="uu_printf", path="src/uu/printf" } -ptx = { optional=true, version="0.0.8", package="uu_ptx", path="src/uu/ptx" } -pwd = { optional=true, version="0.0.8", package="uu_pwd", path="src/uu/pwd" } -readlink = { optional=true, version="0.0.8", package="uu_readlink", path="src/uu/readlink" } -realpath = { optional=true, version="0.0.8", package="uu_realpath", path="src/uu/realpath" } -relpath = { optional=true, version="0.0.8", package="uu_relpath", path="src/uu/relpath" } -rm = { optional=true, version="0.0.8", package="uu_rm", path="src/uu/rm" } -rmdir = { optional=true, version="0.0.8", package="uu_rmdir", path="src/uu/rmdir" } -runcon = { optional=true, version="0.0.8", package="uu_runcon", path="src/uu/runcon" } -seq = { optional=true, version="0.0.8", package="uu_seq", path="src/uu/seq" } -shred = { optional=true, version="0.0.8", package="uu_shred", path="src/uu/shred" } -shuf = { optional=true, version="0.0.8", package="uu_shuf", path="src/uu/shuf" } -sleep = { optional=true, version="0.0.8", package="uu_sleep", path="src/uu/sleep" } -sort = { optional=true, version="0.0.8", package="uu_sort", path="src/uu/sort" } -split = { optional=true, version="0.0.8", package="uu_split", path="src/uu/split" } -stat = { optional=true, version="0.0.8", package="uu_stat", path="src/uu/stat" } -stdbuf = { optional=true, version="0.0.8", package="uu_stdbuf", path="src/uu/stdbuf" } -sum = { optional=true, version="0.0.8", package="uu_sum", path="src/uu/sum" } -sync = { optional=true, version="0.0.8", package="uu_sync", path="src/uu/sync" } -tac = { optional=true, version="0.0.8", package="uu_tac", path="src/uu/tac" } -tail = { optional=true, version="0.0.8", package="uu_tail", path="src/uu/tail" } -tee = { optional=true, version="0.0.8", package="uu_tee", path="src/uu/tee" } -timeout = { optional=true, version="0.0.8", package="uu_timeout", path="src/uu/timeout" } -touch = { optional=true, version="0.0.8", package="uu_touch", path="src/uu/touch" } -tr = { optional=true, version="0.0.8", package="uu_tr", path="src/uu/tr" } -true = { optional=true, version="0.0.8", package="uu_true", path="src/uu/true" } -truncate = { optional=true, version="0.0.8", package="uu_truncate", path="src/uu/truncate" } -tsort = { optional=true, version="0.0.8", package="uu_tsort", path="src/uu/tsort" } -tty = { optional=true, version="0.0.8", package="uu_tty", path="src/uu/tty" } -uname = { optional=true, version="0.0.8", package="uu_uname", path="src/uu/uname" } -unexpand = { optional=true, version="0.0.8", package="uu_unexpand", path="src/uu/unexpand" } -uniq = { optional=true, version="0.0.8", package="uu_uniq", path="src/uu/uniq" } -unlink = { optional=true, version="0.0.8", package="uu_unlink", path="src/uu/unlink" } -uptime = { optional=true, version="0.0.8", package="uu_uptime", path="src/uu/uptime" } -users = { optional=true, version="0.0.8", package="uu_users", path="src/uu/users" } -wc = { optional=true, version="0.0.8", package="uu_wc", path="src/uu/wc" } -who = { optional=true, version="0.0.8", package="uu_who", path="src/uu/who" } -whoami = { optional=true, version="0.0.8", package="uu_whoami", path="src/uu/whoami" } -yes = { optional=true, version="0.0.8", package="uu_yes", path="src/uu/yes" } +arch = { optional=true, version="0.0.13", package="uu_arch", path="src/uu/arch" } +base32 = { optional=true, version="0.0.13", package="uu_base32", path="src/uu/base32" } +base64 = { optional=true, version="0.0.13", package="uu_base64", path="src/uu/base64" } +basename = { optional=true, version="0.0.13", package="uu_basename", path="src/uu/basename" } +basenc = { optional=true, version="0.0.13", package="uu_basenc", path="src/uu/basenc" } +cat = { optional=true, version="0.0.13", package="uu_cat", path="src/uu/cat" } +chcon = { optional=true, version="0.0.13", package="uu_chcon", path="src/uu/chcon" } +chgrp = { optional=true, version="0.0.13", package="uu_chgrp", path="src/uu/chgrp" } +chmod = { optional=true, version="0.0.13", package="uu_chmod", path="src/uu/chmod" } +chown = { optional=true, version="0.0.13", package="uu_chown", path="src/uu/chown" } +chroot = { optional=true, version="0.0.13", package="uu_chroot", path="src/uu/chroot" } +cksum = { optional=true, version="0.0.13", package="uu_cksum", path="src/uu/cksum" } +comm = { optional=true, version="0.0.13", package="uu_comm", path="src/uu/comm" } +cp = { optional=true, version="0.0.13", package="uu_cp", path="src/uu/cp" } +csplit = { optional=true, version="0.0.13", package="uu_csplit", path="src/uu/csplit" } +cut = { optional=true, version="0.0.13", package="uu_cut", path="src/uu/cut" } +date = { optional=true, version="0.0.13", package="uu_date", path="src/uu/date" } +dd = { optional=true, version="0.0.13", package="uu_dd", path="src/uu/dd" } +df = { optional=true, version="0.0.13", package="uu_df", path="src/uu/df" } +dircolors= { optional=true, version="0.0.13", package="uu_dircolors", path="src/uu/dircolors" } +dirname = { optional=true, version="0.0.13", package="uu_dirname", path="src/uu/dirname" } +du = { optional=true, version="0.0.13", package="uu_du", path="src/uu/du" } +echo = { optional=true, version="0.0.13", package="uu_echo", path="src/uu/echo" } +env = { optional=true, version="0.0.13", package="uu_env", path="src/uu/env" } +expand = { optional=true, version="0.0.13", package="uu_expand", path="src/uu/expand" } +expr = { optional=true, version="0.0.13", package="uu_expr", path="src/uu/expr" } +factor = { optional=true, version="0.0.13", package="uu_factor", path="src/uu/factor" } +false = { optional=true, version="0.0.13", package="uu_false", path="src/uu/false" } +fmt = { optional=true, version="0.0.13", package="uu_fmt", path="src/uu/fmt" } +fold = { optional=true, version="0.0.13", package="uu_fold", path="src/uu/fold" } +groups = { optional=true, version="0.0.13", package="uu_groups", path="src/uu/groups" } +hashsum = { optional=true, version="0.0.13", package="uu_hashsum", path="src/uu/hashsum" } +head = { optional=true, version="0.0.13", package="uu_head", path="src/uu/head" } +hostid = { optional=true, version="0.0.13", package="uu_hostid", path="src/uu/hostid" } +hostname = { optional=true, version="0.0.13", package="uu_hostname", path="src/uu/hostname" } +id = { optional=true, version="0.0.13", package="uu_id", path="src/uu/id" } +install = { optional=true, version="0.0.13", package="uu_install", path="src/uu/install" } +join = { optional=true, version="0.0.13", package="uu_join", path="src/uu/join" } +kill = { optional=true, version="0.0.13", package="uu_kill", path="src/uu/kill" } +link = { optional=true, version="0.0.13", package="uu_link", path="src/uu/link" } +ln = { optional=true, version="0.0.13", package="uu_ln", path="src/uu/ln" } +ls = { optional=true, version="0.0.13", package="uu_ls", path="src/uu/ls" } +logname = { optional=true, version="0.0.13", package="uu_logname", path="src/uu/logname" } +mkdir = { optional=true, version="0.0.13", package="uu_mkdir", path="src/uu/mkdir" } +mkfifo = { optional=true, version="0.0.13", package="uu_mkfifo", path="src/uu/mkfifo" } +mknod = { optional=true, version="0.0.13", package="uu_mknod", path="src/uu/mknod" } +mktemp = { optional=true, version="0.0.13", package="uu_mktemp", path="src/uu/mktemp" } +more = { optional=true, version="0.0.13", package="uu_more", path="src/uu/more" } +mv = { optional=true, version="0.0.13", package="uu_mv", path="src/uu/mv" } +nice = { optional=true, version="0.0.13", package="uu_nice", path="src/uu/nice" } +nl = { optional=true, version="0.0.13", package="uu_nl", path="src/uu/nl" } +nohup = { optional=true, version="0.0.13", package="uu_nohup", path="src/uu/nohup" } +nproc = { optional=true, version="0.0.13", package="uu_nproc", path="src/uu/nproc" } +numfmt = { optional=true, version="0.0.13", package="uu_numfmt", path="src/uu/numfmt" } +od = { optional=true, version="0.0.13", package="uu_od", path="src/uu/od" } +paste = { optional=true, version="0.0.13", package="uu_paste", path="src/uu/paste" } +pathchk = { optional=true, version="0.0.13", package="uu_pathchk", path="src/uu/pathchk" } +pinky = { optional=true, version="0.0.13", package="uu_pinky", path="src/uu/pinky" } +pr = { optional=true, version="0.0.13", package="uu_pr", path="src/uu/pr" } +printenv = { optional=true, version="0.0.13", package="uu_printenv", path="src/uu/printenv" } +printf = { optional=true, version="0.0.13", package="uu_printf", path="src/uu/printf" } +ptx = { optional=true, version="0.0.13", package="uu_ptx", path="src/uu/ptx" } +pwd = { optional=true, version="0.0.13", package="uu_pwd", path="src/uu/pwd" } +readlink = { optional=true, version="0.0.13", package="uu_readlink", path="src/uu/readlink" } +realpath = { optional=true, version="0.0.13", package="uu_realpath", path="src/uu/realpath" } +relpath = { optional=true, version="0.0.13", package="uu_relpath", path="src/uu/relpath" } +rm = { optional=true, version="0.0.13", package="uu_rm", path="src/uu/rm" } +rmdir = { optional=true, version="0.0.13", package="uu_rmdir", path="src/uu/rmdir" } +runcon = { optional=true, version="0.0.13", package="uu_runcon", path="src/uu/runcon" } +seq = { optional=true, version="0.0.13", package="uu_seq", path="src/uu/seq" } +shred = { optional=true, version="0.0.13", package="uu_shred", path="src/uu/shred" } +shuf = { optional=true, version="0.0.13", package="uu_shuf", path="src/uu/shuf" } +sleep = { optional=true, version="0.0.13", package="uu_sleep", path="src/uu/sleep" } +sort = { optional=true, version="0.0.13", package="uu_sort", path="src/uu/sort" } +split = { optional=true, version="0.0.13", package="uu_split", path="src/uu/split" } +stat = { optional=true, version="0.0.13", package="uu_stat", path="src/uu/stat" } +stdbuf = { optional=true, version="0.0.13", package="uu_stdbuf", path="src/uu/stdbuf" } +sum = { optional=true, version="0.0.13", package="uu_sum", path="src/uu/sum" } +sync = { optional=true, version="0.0.13", package="uu_sync", path="src/uu/sync" } +tac = { optional=true, version="0.0.13", package="uu_tac", path="src/uu/tac" } +tail = { optional=true, version="0.0.13", package="uu_tail", path="src/uu/tail" } +tee = { optional=true, version="0.0.13", package="uu_tee", path="src/uu/tee" } +timeout = { optional=true, version="0.0.13", package="uu_timeout", path="src/uu/timeout" } +touch = { optional=true, version="0.0.13", package="uu_touch", path="src/uu/touch" } +tr = { optional=true, version="0.0.13", package="uu_tr", path="src/uu/tr" } +true = { optional=true, version="0.0.13", package="uu_true", path="src/uu/true" } +truncate = { optional=true, version="0.0.13", package="uu_truncate", path="src/uu/truncate" } +tsort = { optional=true, version="0.0.13", package="uu_tsort", path="src/uu/tsort" } +tty = { optional=true, version="0.0.13", package="uu_tty", path="src/uu/tty" } +uname = { optional=true, version="0.0.13", package="uu_uname", path="src/uu/uname" } +unexpand = { optional=true, version="0.0.13", package="uu_unexpand", path="src/uu/unexpand" } +uniq = { optional=true, version="0.0.13", package="uu_uniq", path="src/uu/uniq" } +unlink = { optional=true, version="0.0.13", package="uu_unlink", path="src/uu/unlink" } +uptime = { optional=true, version="0.0.13", package="uu_uptime", path="src/uu/uptime" } +users = { optional=true, version="0.0.13", package="uu_users", path="src/uu/users" } +wc = { optional=true, version="0.0.13", package="uu_wc", path="src/uu/wc" } +who = { optional=true, version="0.0.13", package="uu_who", path="src/uu/who" } +whoami = { optional=true, version="0.0.13", package="uu_whoami", path="src/uu/whoami" } +yes = { optional=true, version="0.0.13", package="uu_yes", path="src/uu/yes" } # this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)" # factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" } @@ -361,32 +366,38 @@ yes = { optional=true, version="0.0.8", package="uu_yes", path="src/uu/yes" #pin_cc = { version="1.0.61, < 1.0.62", package="cc" } ## cc v1.0.62 has compiler errors for MinRustV v1.32.0, requires 1.34 (for `std::str::split_ascii_whitespace()`) [dev-dependencies] -chrono = "0.4.11" +chrono = "^0.4.11" conv = "0.3" filetime = "0.2" glob = "0.3.0" libc = "0.2" -pretty_assertions = "0.7.2" -rand = "0.7" +pretty_assertions = "1" +rand = "0.8" regex = "1.0" -sha1 = { version="0.6", features=["std"] } -tempfile = "3.2.0" +sha1 = { version="0.10", features=["std"] } +tempfile = "3" time = "0.1" unindent = "0.1" -uucore = { version=">=0.0.10", package="uucore", path="src/uucore", features=["entries", "process"] } +uucore = { version=">=0.0.11", package="uucore", path="src/uucore", features=["entries", "process"] } walkdir = "2.2" atty = "0.2" +hex-literal = "0.3.1" [target.'cfg(target_os = "linux")'.dev-dependencies] rlimit = "0.4.0" [target.'cfg(unix)'.dev-dependencies] -nix = "0.20.0" - +nix = "0.23.1" rust-users = { version="0.10", package="users" } unix_socket = "0.5.0" +[build-dependencies] +phf_codegen = "0.10.0" [[bin]] name = "coreutils" path = "src/bin/coreutils.rs" + +[[bin]] +name = "uudoc" +path = "src/bin/uudoc.rs" diff --git a/DEVELOPER_INSTRUCTIONS.md b/DEVELOPER_INSTRUCTIONS.md index 027d4dca1..c007fba7e 100644 --- a/DEVELOPER_INSTRUCTIONS.md +++ b/DEVELOPER_INSTRUCTIONS.md @@ -21,7 +21,7 @@ Running GNU tests At the end you should have uutils, gnu and gnulib checked out next to each other. - Run `cd uutils && ./util/build-gnu.sh && cd ..` to get everything ready (this may take a while) -- Finally, you can run `tests with bash uutils/util/run-gnu-test.sh `. Instead of `` insert the test you want to run, e.g. `tests/misc/wc-proc`. +- Finally, you can run tests with `bash uutils/util/run-gnu-test.sh `. Instead of `` insert the test you want to run, e.g. `tests/misc/wc-proc.sh`. Code Coverage Report Generation @@ -33,7 +33,7 @@ Code coverage report can be generated using [grcov](https://github.com/mozilla/g ### Using Nightly Rust -To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-cc) coverage report +To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report ```bash $ export CARGO_INCREMENTAL=0 diff --git a/GNUmakefile b/GNUmakefile index 367568ca8..281952736 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -26,11 +26,6 @@ BINDIR ?= /bin MANDIR ?= /man/man1 INSTALLDIR_BIN=$(DESTDIR)$(PREFIX)$(BINDIR) -INSTALLDIR_MAN=$(DESTDIR)$(PREFIX)/share/$(MANDIR) -$(shell test -d $(INSTALLDIR_MAN)) -ifneq ($(.SHELLSTATUS),0) -override INSTALLDIR_MAN=$(DESTDIR)$(PREFIX)$(MANDIR) -endif #prefix to apply to coreutils binary and all tool binaries PROG_PREFIX ?= @@ -47,18 +42,19 @@ BUSYBOX_VER := 1.32.1 BUSYBOX_SRC := $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER) ifeq ($(SELINUX_ENABLED),) - SELINUX_ENABLED := 0 - ifneq ($(OS),Windows_NT) - ifeq ($(shell /sbin/selinuxenabled 2>/dev/null ; echo $$?),0) - SELINUX_ENABLED := 1 - endif - endif + SELINUX_ENABLED := 0 + ifneq ($(OS),Windows_NT) + ifeq ($(shell /sbin/selinuxenabled 2>/dev/null ; echo $$?),0) + SELINUX_ENABLED := 1 + endif + endif endif # Possible programs PROGS := \ base32 \ base64 \ + basenc \ basename \ cat \ cksum \ @@ -67,6 +63,7 @@ PROGS := \ csplit \ cut \ date \ + dd \ df \ dircolors \ dirname \ @@ -161,11 +158,11 @@ SELINUX_PROGS := \ runcon ifneq ($(OS),Windows_NT) - PROGS := $(PROGS) $(UNIX_PROGS) + PROGS := $(PROGS) $(UNIX_PROGS) endif ifeq ($(SELINUX_ENABLED),1) - PROGS := $(PROGS) $(SELINUX_PROGS) + PROGS := $(PROGS) $(SELINUX_PROGS) endif UTILS ?= $(PROGS) @@ -279,10 +276,7 @@ endif build-coreutils: ${CARGO} build ${CARGOFLAGS} --features "${EXES}" ${PROFILE_CMD} --no-default-features -build-manpages: - cd $(DOCSDIR) && $(MAKE) man - -build: build-coreutils build-pkgs build-manpages +build: build-coreutils build-pkgs $(foreach test,$(filter-out $(SKIP_UTILS),$(PROGS)),$(eval $(call TEST_BUSYBOX,$(test)))) @@ -316,7 +310,7 @@ busytest: $(BUILDDIR)/busybox $(addprefix test_busybox_,$(filter-out $(SKIP_UTIL endif clean: - $(RM) $(BUILDDIR) + cargo clean cd $(DOCSDIR) && $(MAKE) clean distclean: clean @@ -324,20 +318,16 @@ distclean: clean install: build mkdir -p $(INSTALLDIR_BIN) - mkdir -p $(INSTALLDIR_MAN) ifeq (${MULTICALL}, y) $(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out coreutils, $(INSTALLEES)), \ ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)$(prog) &&) : $(if $(findstring test,$(INSTALLEES)), cd $(INSTALLDIR_BIN) && ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)[) - cat $(DOCSDIR)/_build/man/coreutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)coreutils.1.gz else $(foreach prog, $(INSTALLEES), \ $(INSTALL) $(BUILDDIR)/$(prog) $(INSTALLDIR_BIN)/$(PROG_PREFIX)$(prog);) $(if $(findstring test,$(INSTALLEES)), $(INSTALL) $(BUILDDIR)/test $(INSTALLDIR_BIN)/$(PROG_PREFIX)[) endif - $(foreach man, $(filter $(INSTALLEES), $(basename $(notdir $(wildcard $(DOCSDIR)/_build/man/*)))), \ - cat $(DOCSDIR)/_build/man/$(man).1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)$(man).1.gz &&) : mkdir -p $(DESTDIR)$(PREFIX)/share/zsh/site-functions mkdir -p $(DESTDIR)$(PREFIX)/share/bash-completion/completions mkdir -p $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d @@ -351,12 +341,10 @@ uninstall: ifeq (${MULTICALL}, y) rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)coreutils) endif - rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz) rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS)) rm -f $(INSTALLDIR_BIN)/$(PROG_PREFIX)[ rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(PROG_PREFIX),$(addsuffix .fish,$(PROGS))) - rm -f $(addprefix $(INSTALLDIR_MAN)/$(PROG_PREFIX),$(addsuffix .1.gz,$(PROGS))) -.PHONY: all build build-coreutils build-pkgs build-docs test distclean clean busytest install uninstall +.PHONY: all build build-coreutils build-pkgs test distclean clean busytest install uninstall diff --git a/LICENSE b/LICENSE index 0177c4ab7..49fdbd4cf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) Jordi Boggiano +Copyright (c) Jordi Boggiano and many others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 7e420bb33..0683e42f8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Crates.io](https://img.shields.io/crates/v/coreutils.svg)](https://crates.io/crates/coreutils) [![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ) -[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/coreutils/blob/master/LICENSE) +[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/coreutils/blob/main/LICENSE) [![LOC](https://tokei.rs/b1/github/uutils/coreutils?category=code)](https://github.com/Aaronepower/tokei) [![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils) @@ -15,35 +15,46 @@ uutils is an attempt at writing universal (as in cross-platform) CLI -utilities in [Rust](http://www.rust-lang.org). This repository is intended to -aggregate GNU coreutils rewrites. +utilities in [Rust](http://www.rust-lang.org). + +To install it: + +``` +$ cargo install coreutils +$ ~/.cargo/bin/coreutils +``` ## Why? -Many GNU, Linux and other utilities are useful, and obviously -[some](http://gnuwin32.sourceforge.net) [effort](http://unxutils.sourceforge.net) -has been spent in the past to port them to Windows. However, those projects -are written in platform-specific C, a language considered unsafe compared to Rust, and -have other issues. +uutils aims to work on as many platforms as possible, to be able to use the +same utils on Linux, Mac, Windows and other platforms. This ensures, for +example, that scripts can be easily transferred between platforms. Rust was +chosen not only because it is fast and safe, but is also excellent for +writing cross-platform code. -Rust provides a good, platform-agnostic way of writing systems utilities that are easy -to compile anywhere, and this is as good a way as any to try and learn it. +## Documentation +uutils has both user and developer documentation available: +- [User Manual](https://uutils.github.io/coreutils-docs/user/) +- [Developer Documentation](https://uutils.github.io/coreutils-docs/dev/coreutils/) + +Both can also be generated locally, the instructions for that can be found in the +[coreutils docs](https://github.com/uutils/coreutils-docs) repository. + + ## Requirements * Rust (`cargo`, `rustc`) -* GNU Make (required to build documentation) -* [Sphinx](http://www.sphinx-doc.org/) (for documentation) -* gzip (for installing documentation) +* GNU Make (optional) ### Rust Version uutils follows Rust's release channels and is tested against stable, beta and nightly. -The current oldest supported version of the Rust compiler is `1.47`. +The current oldest supported version of the Rust compiler is `1.56`. On both Windows and Redox, only the nightly version is tested currently. -## Build Instructions +## Building There are currently two methods to build the uutils binaries: either Cargo or GNU Make. @@ -122,7 +133,7 @@ To build only a few of the available utilities: $ make UTILS='UTILITY_1 UTILITY_2' ``` -## Installation Instructions +## Installation ### Cargo @@ -212,7 +223,7 @@ run: cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls ``` -## Un-installation Instructions +## Un-installation Un-installation differs depending on how you have installed uutils. If you used Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use @@ -252,8 +263,9 @@ To uninstall from a custom parent directory: # DESTDIR is also supported $ make PREFIX=/my/path uninstall ``` + -## Test Instructions +## Testing Testing can be done using either Cargo or `make`. @@ -319,7 +331,7 @@ To include tests for unimplemented behavior: $ make UTILS='UTILITY_1 UTILITY_2' SPEC=y test ``` -## Run Busybox Tests +### Run Busybox Tests This testing functionality is only available on *nix operating systems and requires `make`. @@ -342,7 +354,11 @@ To pass an argument like "-v" to the busybox test runtime $ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest ``` -## Comparing with GNU +### Comparing with GNU + +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/coreutils-docs/user/test_coverage.html). ![Evolution over time](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.png?raw=true) @@ -357,7 +373,26 @@ $ bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example Note that it relies on individual utilities (not the multicall binary). -## Contribute +### Improving the GNU compatibility + +The Python script `./util/remaining-gnu-error.py` shows the list of failing tests in the CI. + +To improve the GNU compatibility, the following process is recommended: + +1. Identify a test (the smaller, the better) on a program that you understand or is easy to understand. You can use the `./util/remaining-gnu-error.py` script to help with this decision. +1. Build both the GNU and Rust coreutils using: `bash util/build-gnu.sh` +1. Run the test with `bash util/run-gnu-test.sh ` +1. Start to modify `` to understand what is wrong. Examples: + 1. Add `set -v` to have the bash verbose mode + 1. Add `echo $?` where needed + 1. Bump the content of the output (ex: `cat err`) + 1. ... +1. Or, if the test is simple, extract the relevant information to create a new test case running both GNU & Rust implementation +1. Start to modify the Rust implementation to match the expected behavior +1. Add a test to make sure that we don't regress (our test suite is super quick) + + +## Contributing To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). @@ -371,18 +406,18 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). | basename | df | | | basenc | expr | | | cat | install | | -| chcon | join | | -| chgrp | ls | | -| chmod | more | | -| chown | numfmt | | -| chroot | od (`--strings` and 128-bit data types missing) | | -| cksum | pr | | -| comm | printf | | -| csplit | sort | | -| cut | split | | -| dircolors | tac | | -| dirname | tail | | -| du | test | | +| chcon | ls | | +| chgrp | more | | +| chmod | numfmt | | +| chown | od (`--strings` and 128-bit data types missing) | | +| chroot | pr | | +| cksum | printf | | +| comm | sort | | +| csplit | split | | +| cut | tac | | +| dircolors | tail | | +| dirname | test | | +| du | | | | echo | | | | env | | | | expand | | | @@ -396,16 +431,17 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). | hostid | | | | hostname | | | | id | | | +| join | | | | kill | | | | link | | | | ln | | | | logname | | | -| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | -| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | -| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | -| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | -| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | -| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | | mkdir | | | | mkfifo | | | | mknod | | | diff --git a/build.rs b/build.rs index 261eb5d9a..50b8cfa3f 100644 --- a/build.rs +++ b/build.rs @@ -12,29 +12,29 @@ pub fn main() { println!("cargo:rustc-cfg=build={:?}", profile); } - let env_feature_prefix: &str = "CARGO_FEATURE_"; - let feature_prefix: &str = "feat_"; - let override_prefix: &str = "uu_"; + const ENV_FEATURE_PREFIX: &str = "CARGO_FEATURE_"; + const FEATURE_PREFIX: &str = "feat_"; + const OVERRIDE_PREFIX: &str = "uu_"; let out_dir = env::var("OUT_DIR").unwrap(); // println!("cargo:warning=out_dir={}", out_dir); - let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap().replace("\\", "/"); + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap().replace('\\', "/"); // println!("cargo:warning=manifest_dir={}", manifest_dir); let util_tests_dir = format!("{}/tests/by-util", manifest_dir); // println!("cargo:warning=util_tests_dir={}", util_tests_dir); let mut crates = Vec::new(); for (key, val) in env::vars() { - if val == "1" && key.starts_with(env_feature_prefix) { - let krate = key[env_feature_prefix.len()..].to_lowercase(); + if val == "1" && key.starts_with(ENV_FEATURE_PREFIX) { + let krate = key[ENV_FEATURE_PREFIX.len()..].to_lowercase(); match krate.as_ref() { "default" | "macos" | "unix" | "windows" | "selinux" => continue, // common/standard feature names "nightly" | "test_unimplemented" => continue, // crate-local custom features "test" => continue, // over-ridden with 'uu_test' to avoid collision with rust core crate 'test' - s if s.starts_with(feature_prefix) => continue, // crate feature sets + s if s.starts_with(FEATURE_PREFIX) => continue, // crate feature sets _ => {} // util feature name } - crates.push(krate.to_string()); + crates.push(krate); } } crates.sort(); @@ -43,33 +43,23 @@ pub fn main() { let mut tf = File::create(Path::new(&out_dir).join("test_modules.rs")).unwrap(); mf.write_all( - "type UtilityMap = HashMap<&'static str, (fn(T) -> i32, fn() -> App<'static, 'static>)>;\n\ + "type UtilityMap = phf::Map<&'static str, (fn(T) -> i32, fn() -> Command<'static>)>;\n\ \n\ - fn util_map() -> UtilityMap {\n\ - \t#[allow(unused_mut)]\n\ - \t#[allow(clippy::let_and_return)]\n\ - \tlet mut map = UtilityMap::new();\n\ - " - .as_bytes(), + fn util_map() -> UtilityMap {\n" + .as_bytes(), ) .unwrap(); - for krate in crates { + let mut phf_map = phf_codegen::Map::<&str>::new(); + for krate in &crates { + let map_value = format!("({krate}::uumain, {krate}::uu_app)", krate = krate); match krate.as_ref() { // 'test' is named uu_test to avoid collision with rust core crate 'test'. // It can also be invoked by name '[' for the '[ expr ] syntax'. "uu_test" => { - mf.write_all( - format!( - "\ - \tmap.insert(\"test\", ({krate}::uumain, {krate}::uu_app));\n\ - \t\tmap.insert(\"[\", ({krate}::uumain, {krate}::uu_app));\n\ - ", - krate = krate - ) - .as_bytes(), - ) - .unwrap(); + phf_map.entry("test", &map_value); + phf_map.entry("[", &map_value); + tf.write_all( format!( "#[path=\"{dir}/test_test.rs\"]\nmod test_test;\n", @@ -77,37 +67,25 @@ pub fn main() { ) .as_bytes(), ) - .unwrap() - } - k if k.starts_with(override_prefix) => { - mf.write_all( - format!( - "\tmap.insert(\"{k}\", ({krate}::uumain, {krate}::uu_app));\n", - k = krate[override_prefix.len()..].to_string(), - krate = krate - ) - .as_bytes(), - ) .unwrap(); + } + k if k.starts_with(OVERRIDE_PREFIX) => { + phf_map.entry(&k[OVERRIDE_PREFIX.len()..], &map_value); tf.write_all( format!( "#[path=\"{dir}/test_{k}.rs\"]\nmod test_{k};\n", - k = krate[override_prefix.len()..].to_string(), + k = &krate[OVERRIDE_PREFIX.len()..], dir = util_tests_dir, ) .as_bytes(), ) - .unwrap() + .unwrap(); } "false" | "true" => { - mf.write_all( - format!( - "\tmap.insert(\"{krate}\", (r#{krate}::uumain, r#{krate}::uu_app));\n", - krate = krate - ) - .as_bytes(), - ) - .unwrap(); + phf_map.entry( + krate, + &format!("(r#{krate}::uumain, r#{krate}::uu_app)", krate = krate), + ); tf.write_all( format!( "#[path=\"{dir}/test_{krate}.rs\"]\nmod test_{krate};\n", @@ -116,32 +94,30 @@ pub fn main() { ) .as_bytes(), ) - .unwrap() + .unwrap(); } "hashsum" => { - mf.write_all( - format!( - "\ - \tmap.insert(\"{krate}\", ({krate}::uumain, {krate}::uu_app_custom));\n\ - \t\tmap.insert(\"md5sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"sha1sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"sha224sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"sha256sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"sha384sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"sha512sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"sha3sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"sha3-224sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"sha3-256sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"sha3-384sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"sha3-512sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"shake128sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - \t\tmap.insert(\"shake256sum\", ({krate}::uumain, {krate}::uu_app_common));\n\ - ", - krate = krate - ) - .as_bytes(), - ) - .unwrap(); + phf_map.entry( + krate, + &format!("({krate}::uumain, {krate}::uu_app_custom)", krate = krate), + ); + + let map_value = format!("({krate}::uumain, {krate}::uu_app_common)", krate = krate); + phf_map.entry("md5sum", &map_value); + phf_map.entry("sha1sum", &map_value); + phf_map.entry("sha224sum", &map_value); + phf_map.entry("sha256sum", &map_value); + phf_map.entry("sha384sum", &map_value); + phf_map.entry("sha512sum", &map_value); + phf_map.entry("sha3sum", &map_value); + phf_map.entry("sha3-224sum", &map_value); + phf_map.entry("sha3-256sum", &map_value); + phf_map.entry("sha3-384sum", &map_value); + phf_map.entry("sha3-512sum", &map_value); + phf_map.entry("shake128sum", &map_value); + phf_map.entry("shake256sum", &map_value); + phf_map.entry("b2sum", &map_value); + phf_map.entry("b3sum", &map_value); tf.write_all( format!( "#[path=\"{dir}/test_{krate}.rs\"]\nmod test_{krate};\n", @@ -150,17 +126,10 @@ pub fn main() { ) .as_bytes(), ) - .unwrap() + .unwrap(); } _ => { - mf.write_all( - format!( - "\tmap.insert(\"{krate}\", ({krate}::uumain, {krate}::uu_app));\n", - krate = krate - ) - .as_bytes(), - ) - .unwrap(); + phf_map.entry(krate, &map_value); tf.write_all( format!( "#[path=\"{dir}/test_{krate}.rs\"]\nmod test_{krate};\n", @@ -169,12 +138,12 @@ pub fn main() { ) .as_bytes(), ) - .unwrap() + .unwrap(); } } } - - mf.write_all(b"map\n}\n").unwrap(); + write!(mf, "{}", phf_map.build()).unwrap(); + mf.write_all(b"\n}\n").unwrap(); mf.flush().unwrap(); tf.flush().unwrap(); diff --git a/clippy.toml b/clippy.toml deleted file mode 100644 index dbabdab50..000000000 --- a/clippy.toml +++ /dev/null @@ -1 +0,0 @@ -msrv = "1.47.0" diff --git a/deny.toml b/deny.toml new file mode 100644 index 000000000..dea3503de --- /dev/null +++ b/deny.toml @@ -0,0 +1,95 @@ +# spell-checker:ignore SSLeay RUSTSEC + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +db-path = "~/.cargo/advisory-db" +db-urls = ["https://github.com/rustsec/advisory-db"] +vulnerability = "warn" +unmaintained = "warn" +yanked = "warn" +notice = "warn" +ignore = [ + #"RUSTSEC-0000-0000", +] + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +unlicensed = "deny" +allow = [ + "MIT", + "Apache-2.0", + "ISC", + "BSD-2-Clause", + "BSD-2-Clause-FreeBSD", + "BSD-3-Clause", + "CC0-1.0", + "MPL-2.0", # XXX considered copyleft? +] +copyleft = "deny" +allow-osi-fsf-free = "neither" +default = "deny" +confidence-threshold = 0.8 +exceptions = [ + { allow = ["OpenSSL"], name = "ring" }, +] + +[[licenses.clarify]] +name = "ring" +# SPDX considers OpenSSL to encompass both the OpenSSL and SSLeay licenses +# https://spdx.org/licenses/OpenSSL.html +# ISC - Both BoringSSL and ring use this for their new files +# MIT - "Files in third_party/ have their own licenses, as described therein. The MIT +# license, for third_party/fiat, which, unlike other third_party directories, is +# compiled into non-test libraries, is included below." +# OpenSSL - Obviously +expression = "ISC AND MIT AND OpenSSL" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +multiple-versions = "deny" +wildcards = "allow" +highlight = "all" + +# For each duplicate dependency, indicate the name of the dependency which +# introduces it. +# spell-checker: disable +skip = [ + # blake2d_simd + { name = "arrayvec", version = "=0.7.2" }, + # flimit/unix_socket + { name = "cfg-if", version = "=0.1.10" }, + # ordered-multimap + { name = "hashbrown", version = "=0.9.1" }, + # kernel32-sys + { name = "winapi", version = "=0.2.8" }, + # bindgen 0.59.2 + { name = "clap", version = "=2.34.0" }, + { name = "strsim", version = "=0.8.0" }, + { name = "textwrap", version = "=0.11.0" }, + { name = "cpp_common", version = "=0.4.0" }, + # quickcheck + { name = "env_logger", version = "=0.8.4" }, + # cpp_* + { name = "memchr", version = "=1.0.2" }, + { name = "quote", version = "=0.3.15" }, + { name = "unicode-xid", version = "=0.0.4" }, + # exacl + { name = "nix", version = "=0.21.0" }, +] +# spell-checker: enable + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +unknown-registry = "warn" +unknown-git = "warn" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = [] diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..f2b5c7168 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,3 @@ +book +src/utils +src/SUMMARY.md diff --git a/docs/GNUmakefile b/docs/GNUmakefile deleted file mode 100644 index 49d827da8..000000000 --- a/docs/GNUmakefile +++ /dev/null @@ -1,21 +0,0 @@ -# spell-checker:ignore (vars/env) SPHINXOPTS SPHINXBUILD SPHINXPROJ SOURCEDIR BUILDDIR - -# Minimal makefile for Sphinx documentation - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = uutils -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help GNUmakefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: GNUmakefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/Makefile b/docs/Makefile index f56df90fb..7372b3868 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,4 @@ -UseGNU=gmake $* -all: - @$(UseGNU) -.DEFAULT: - @$(UseGNU) +clean: + rm -rf book + rm -f src/SUMMARY.md + rm -f src/utils/* diff --git a/docs/arch.rst b/docs/arch.rst deleted file mode 100644 index 66b516159..000000000 --- a/docs/arch.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. print machine hardware name - -==== -arch -==== - -.. FIXME: this needs to be autogenerated somehow - --------- -Synopsis --------- - -``arch`` [OPTION]... - ------------ -Description ------------ - -``arch`` is an alias for ``uname -m``. They both print the machine hardware -name. - -An exit code of zero indicates success, whereas anything else means failure. -For this program, a non-zero exit code generally means the user provided -invalid options. - --h, --help print a help menu for this program displaying accepted - options and arguments --v, --version print the version number of this program diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 000000000..75982ab31 --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,9 @@ +[book] +authors = ["uutils contributors"] +language = "en" +multilingual = false +src = "src" +title = "uutils Documentation" + +[output.html] +git-repository-url = "https://github.com/rust-lang/cargo/tree/master/src/doc/src" \ No newline at end of file diff --git a/docs/compiles_table.py b/docs/compiles_table.py index aa1b8703c..e2c4c0a8d 100644 --- a/docs/compiles_table.py +++ b/docs/compiles_table.py @@ -40,6 +40,8 @@ TARGETS = [ "x86_64-linux-android", # Solaris "x86_64-sun-solaris", + # Illumos + "x86_64-unknown-illumos", # WASM "wasm32-wasi", # Redox diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 5a1213627..000000000 --- a/docs/conf.py +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# uutils documentation build configuration file, created by -# sphinx-quickstart on Tue Dec 5 23:20:18 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - -# spell-checker:ignore (words) howto htbp imgmath toctree todos uutilsdoc - -import glob -import os -import re - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ['sphinx.ext.imgmath'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'uutils' -copyright = '2017, uutils developers' -author = 'uutils developers' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# * take version from project "Cargo.toml" -version_file = open(os.path.join("..","Cargo.toml"), "r") -version_file_content = version_file.read() -v = re.search("^\s*version\s*=\s*\"([0-9.]+)\"", version_file_content, re.IGNORECASE | re.MULTILINE) -# The short X.Y version. -version = v.groups()[0] -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = { - '**': [ - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - ] -} - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'uutilsdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'uutils.tex', 'uutils Documentation', - 'uutils developers', 'manual'), -] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [] -for name in glob.glob('*.rst'): - if name != 'index.rst': - desc = '' - with open(name) as f: - desc = f.readline().strip() - if desc.startswith('..'): - desc = desc[2:].strip() - else: - desc = '' - man_pages.append(( - name[:-4], # source file without extension - name[:-4].replace('/', '-'), # output file - desc, - [author], - 1 - )) - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'uutils', 'uutils Documentation', - author, 'uutils', 'A cross-platform implementation of GNU coreutils, written in Rust.', - 'Miscellaneous'), -] diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 7f782b12a..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,24 +0,0 @@ -.. uutils documentation master file, created by - sphinx-quickstart on Tue Dec 5 23:20:18 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -.. - spell-checker:ignore (directives) genindex maxdepth modindex toctree ; (misc) quickstart - -Welcome to uutils' documentation! -================================= - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - arch - uutils - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 6e63e3baa..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,39 +0,0 @@ -@setLocal -@ECHO OFF - -rem spell-checker:ignore (vars/env) BUILDDIR SOURCEDIR SPHINXBUILD SPHINXOPTS SPHINXPROJ - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=uutils - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if ErrorLevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/src/contributing.md b/docs/src/contributing.md new file mode 100644 index 000000000..79ef4d13b --- /dev/null +++ b/docs/src/contributing.md @@ -0,0 +1 @@ +{{ #include ../../CONTRIBUTING.md }} \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 000000000..3ea5d913a --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,20 @@ +# uutils Coreutils Documentation + +uutils is an attempt at writing universal (as in cross-platform) CLI +utilities in [Rust](https://www.rust-lang.org). It is available for +Linux, Windows, Mac and other platforms. + +The API reference for `uucore`, the library of functions shared between +various utils, is hosted at at +[docs.rs](https://docs.rs/uucore/latest/uucore/). + +uutils is licensed under the [MIT License](https://github.com/uutils/coreutils/blob/main/LICENSE). + +## Useful links +* [Releases](https://github.com/uutils/coreutils/releases) +* [Source Code](https://github.com/uutils/coreutils) +* [Issues](https://github.com/uutils/coreutils/issues) +* [Discord](https://discord.gg/wQVJbvJ) + +> Note: This manual is automatically generated from the source code and is +> a work in progress. \ No newline at end of file diff --git a/docs/src/installation.md b/docs/src/installation.md new file mode 100644 index 000000000..885b5ecb0 --- /dev/null +++ b/docs/src/installation.md @@ -0,0 +1,3 @@ +# Installation + +{{#include ../../README.md:installation }} \ No newline at end of file diff --git a/docs/src/multicall.md b/docs/src/multicall.md new file mode 100644 index 000000000..5d7b8b169 --- /dev/null +++ b/docs/src/multicall.md @@ -0,0 +1,17 @@ +# Multi-call binary +uutils includes a multi-call binary from which the utils can be invoked. This +reduces the binary size of the binary and can be useful for portability. + +The first argument of the multi-call binary is the util to run, after which +the regular arguments to the util can be passed. + +```shell +coreutils [util] [util options] +``` + +The `--help` flag will print a list of available utils. + +## Example +``` +coreutils ls -l +``` \ No newline at end of file diff --git a/docs/src/test_coverage.css b/docs/src/test_coverage.css new file mode 100644 index 000000000..37a658695 --- /dev/null +++ b/docs/src/test_coverage.css @@ -0,0 +1,46 @@ +:root { + --PASS: #44AF69; + --ERROR: #F8333C; + --FAIL: #F8333C; + --SKIP: #d3c994; +} +.PASS { + color: var(--PASS); +} +.ERROR { + color: var(--ERROR); +} +.FAIL { + color: var(--FAIL); +} +.SKIP { + color: var(--SKIP); +} +.testSummary { + display: inline-flex; + align-items: center; + justify-content: space-between; + width: 90%; +} +.progress { + width: 80%; + display: flex; + justify-content: right; + align-items: center; +} +.progress-bar { + height: 10px; + width: calc(100% - 15ch); + border-radius: 5px; +} +.result { + font-weight: bold; + width: 7ch; + display: inline-block; +} +.result-line { + margin: 8px; +} +.counts { + margin-right: 10px; +} \ No newline at end of file diff --git a/docs/src/test_coverage.js b/docs/src/test_coverage.js new file mode 100644 index 000000000..e601229af --- /dev/null +++ b/docs/src/test_coverage.js @@ -0,0 +1,82 @@ +// spell-checker:ignore hljs +function progressBar(totals) { + const bar = document.createElement("div"); + bar.className = "progress-bar"; + let totalTests = 0; + for (const [key, value] of Object.entries(totals)) { + totalTests += value; + } + const passPercentage = Math.round(100 * totals["PASS"] / totalTests); + const skipPercentage = passPercentage + Math.round(100 * totals["SKIP"] / totalTests); + + // The ternary expressions are used for some edge-cases where there are no failing test, + // but still a red (or beige) line shows up because of how CSS draws gradients. + bar.style = `background: linear-gradient( + to right, + var(--PASS) ${passPercentage}%` + + ( passPercentage === 100 ? ", var(--PASS)" : + `, var(--SKIP) ${passPercentage}%, + var(--SKIP) ${skipPercentage}%` + ) + + (skipPercentage === 100 ? ")" : ", var(--FAIL) 0)"); + + const progress = document.createElement("div"); + progress.className = "progress" + progress.innerHTML = ` + + ${totals["PASS"]} + / + ${totals["SKIP"]} + / + ${totals["FAIL"] + totals["ERROR"]} + + `; + progress.appendChild(bar); + return progress +} + +function parse_result(parent, obj) { + const totals = { + PASS: 0, + SKIP: 0, + FAIL: 0, + ERROR: 0, + }; + for (const [category, content] of Object.entries(obj)) { + if (typeof content === "string") { + const p = document.createElement("p"); + p.className = "result-line"; + totals[content]++; + p.innerHTML = `${content} ${category}`; + parent.appendChild(p); + } else { + const categoryName = document.createElement("code"); + categoryName.innerHTML = category; + categoryName.className = "hljs"; + + const details = document.createElement("details"); + const subtotals = parse_result(details, content); + for (const [subtotal, count] of Object.entries(subtotals)) { + totals[subtotal] += count; + } + const summaryDiv = document.createElement("div"); + summaryDiv.className = "testSummary"; + summaryDiv.appendChild(categoryName); + summaryDiv.appendChild(progressBar(subtotals)); + + const summary = document.createElement("summary"); + summary.appendChild(summaryDiv); + + details.appendChild(summary); + parent.appendChild(details); + } + } + return totals; +} + +fetch("https://raw.githubusercontent.com/uutils/coreutils-tracking/main/gnu-full-result.json") + .then((r) => r.json()) + .then((obj) => { + let parent = document.getElementById("test-cov"); + parse_result(parent, obj); + }); diff --git a/docs/src/test_coverage.md b/docs/src/test_coverage.md new file mode 100644 index 000000000..bf4c72129 --- /dev/null +++ b/docs/src/test_coverage.md @@ -0,0 +1,19 @@ +# GNU Test Coverage + +uutils is actively tested against the GNU coreutils test suite. The results +below are automatically updated every day. + +## Coverage per category + +Click on the categories to see the names of the tests. Green indicates a passing +test, yellow indicates a skipped test and red means that the test either failed +or resulted in an error. + + + + +
+ +## Progress over time + + diff --git a/docs/theme/favicon.png b/docs/theme/favicon.png new file mode 100644 index 000000000..1cd1f26ec Binary files /dev/null and b/docs/theme/favicon.png differ diff --git a/docs/theme/head.hbs b/docs/theme/head.hbs new file mode 100644 index 000000000..31cc2dad5 --- /dev/null +++ b/docs/theme/head.hbs @@ -0,0 +1,16 @@ + diff --git a/docs/uutils.rst b/docs/uutils.rst deleted file mode 100644 index e3b8c6a1a..000000000 --- a/docs/uutils.rst +++ /dev/null @@ -1,24 +0,0 @@ -.. run core utilities - -====== -uutils -====== - -.. FIXME: this needs to be autogenerated somehow - --------- -Synopsis --------- - -``uutils`` [OPTION]... [PROGRAM] [OPTION]... [ARGUMENTS]... - ------------ -Description ------------ - -``uutils`` is a program that contains other coreutils commands, somewhat -similar to Busybox. - ---help, -h print a help menu for PROGRAM displaying accepted options and - arguments; if PROGRAM was not given, do the same but for this - program diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..9dc8c6919 --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base" + ] +} diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 1de1b6354..7a8986035 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -5,11 +5,9 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use clap::App; -use clap::Arg; -use clap::Shell; +use clap::{Arg, Command}; +use clap_complete::Shell; use std::cmp; -use std::collections::hash_map::HashMap; use std::ffi::OsStr; use std::ffi::OsString; use std::io::{self, Write}; @@ -65,7 +63,7 @@ fn main() { // * prefix/stem may be any string ending in a non-alphanumeric character let util_name = if let Some(util) = utils.keys().find(|util| { binary_as_util.ends_with(*util) - && !(&binary_as_util[..binary_as_util.len() - (*util).len()]) + && !binary_as_util[..binary_as_util.len() - (*util).len()] .ends_with(char::is_alphanumeric) }) { // prefixed util => replace 0th (aka, executable name) argument @@ -89,7 +87,7 @@ fn main() { }; if util == "completion" { - gen_completions(args, utils); + gen_completions(args, &utils); } match utils.get(util) { @@ -134,22 +132,22 @@ fn main() { /// Prints completions for the utility in the first parameter for the shell in the second parameter to stdout fn gen_completions( args: impl Iterator, - util_map: UtilityMap, + util_map: &UtilityMap, ) -> ! { let all_utilities: Vec<_> = std::iter::once("coreutils") .chain(util_map.keys().copied()) .collect(); - let matches = App::new("completion") + let matches = Command::new("completion") .about("Prints completions to stdout") .arg( - Arg::with_name("utility") - .possible_values(&all_utilities) + Arg::new("utility") + .possible_values(all_utilities) .required(true), ) .arg( - Arg::with_name("shell") - .possible_values(&Shell::variants()) + Arg::new("shell") + .possible_values(Shell::possible_values()) .required(true), ) .get_matches_from(std::iter::once(OsString::from("completion")).chain(args)); @@ -157,7 +155,7 @@ fn gen_completions( let utility = matches.value_of("utility").unwrap(); let shell = matches.value_of("shell").unwrap(); - let mut app = if utility == "coreutils" { + let mut command = if utility == "coreutils" { gen_coreutils_app(util_map) } else { util_map.get(utility).unwrap().1() @@ -165,15 +163,15 @@ fn gen_completions( let shell: Shell = shell.parse().unwrap(); let bin_name = std::env::var("PROG_PREFIX").unwrap_or_default() + utility; - app.gen_completions_to(bin_name, shell, &mut io::stdout()); + clap_complete::generate(shell, &mut command, bin_name, &mut io::stdout()); io::stdout().flush().unwrap(); process::exit(0); } -fn gen_coreutils_app(util_map: UtilityMap) -> App<'static, 'static> { - let mut app = App::new("coreutils"); +fn gen_coreutils_app(util_map: &UtilityMap) -> Command<'static> { + let mut command = Command::new("coreutils"); for (_, (_, sub_app)) in util_map { - app = app.subcommand(sub_app()); + command = command.subcommand(sub_app()); } - app + command } diff --git a/src/bin/uudoc.rs b/src/bin/uudoc.rs new file mode 100644 index 000000000..24c347fe7 --- /dev/null +++ b/src/bin/uudoc.rs @@ -0,0 +1,211 @@ +// This file is part 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 tldr + +use clap::Command; +use std::ffi::OsString; +use std::fs::File; +use std::io::Cursor; +use std::io::{self, Read, Seek, Write}; +use zip::ZipArchive; + +include!(concat!(env!("OUT_DIR"), "/uutils_map.rs")); + +fn main() -> io::Result<()> { + println!("Downloading tldr archive"); + let mut zip_reader = ureq::get("https://tldr.sh/assets/tldr.zip") + .call() + .unwrap() + .into_reader(); + let mut buffer = Vec::new(); + zip_reader.read_to_end(&mut buffer).unwrap(); + let mut tldr_zip = ZipArchive::new(Cursor::new(buffer)).unwrap(); + + let utils = util_map::>>(); + match std::fs::create_dir("docs/src/utils/") { + Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()), + x => x, + }?; + + let mut summary = File::create("docs/src/SUMMARY.md")?; + + let _ = write!( + summary, + "# Summary\n\ + \n\ + [Introduction](index.md)\n\ + * [Installation](installation.md)\n\ + * [Contributing](contributing.md)\n\ + * [GNU test coverage](test_coverage.md)\n\ + \n\ + # Reference\n\ + * [Multi-call binary](multicall.md)\n", + ); + + let mut utils = utils.entries().collect::>(); + utils.sort(); + for (&name, (_, command)) in utils { + if name == "[" { + continue; + } + let p = format!("docs/src/utils/{}.md", name); + if let Ok(f) = File::create(&p) { + write_markdown(f, &mut command(), name, &mut tldr_zip)?; + println!("Wrote to '{}'", p); + } else { + println!("Error writing to {}", p); + } + writeln!(summary, "* [{0}](utils/{0}.md)", name)?; + } + Ok(()) +} + +fn write_markdown( + mut w: impl Write, + command: &mut Command, + name: &str, + tldr_zip: &mut zip::ZipArchive, +) -> io::Result<()> { + write!(w, "# {}\n\n", name)?; + write_version(&mut w, command)?; + write_usage(&mut w, command, name)?; + write_description(&mut w, command)?; + write_options(&mut w, command)?; + write_examples(&mut w, name, tldr_zip) +} + +fn write_version(w: &mut impl Write, command: &Command) -> io::Result<()> { + writeln!( + w, + "
version: {}
", + command.render_version().split_once(' ').unwrap().1 + ) +} + +fn write_usage(w: &mut impl Write, command: &mut Command, name: &str) -> io::Result<()> { + writeln!(w, "\n```")?; + let mut usage: String = command + .render_usage() + .lines() + .skip(1) + .map(|l| l.trim()) + .filter(|l| !l.is_empty()) + .collect::>() + .join("\n"); + usage = usage.replace(uucore::execution_phrase(), name); + writeln!(w, "{}", usage)?; + writeln!(w, "```") +} + +fn write_description(w: &mut impl Write, command: &Command) -> io::Result<()> { + if let Some(about) = command.get_long_about().or_else(|| command.get_about()) { + writeln!(w, "{}", about) + } else { + Ok(()) + } +} + +fn write_examples( + w: &mut impl Write, + name: &str, + tldr_zip: &mut zip::ZipArchive, +) -> io::Result<()> { + let content = if let Some(f) = get_zip_content(tldr_zip, &format!("pages/common/{}.md", name)) { + f + } else if let Some(f) = get_zip_content(tldr_zip, &format!("pages/linux/{}.md", name)) { + f + } else { + return Ok(()); + }; + + writeln!(w, "## Examples")?; + writeln!(w)?; + for line in content.lines().skip_while(|l| !l.starts_with('-')) { + if let Some(l) = line.strip_prefix("- ") { + writeln!(w, "{}", l)?; + } else if line.starts_with('`') { + writeln!(w, "```shell\n{}\n```", line.trim_matches('`'))?; + } else if line.is_empty() { + writeln!(w)?; + } else { + println!("Not sure what to do with this line:"); + println!("{}", line); + } + } + writeln!(w)?; + writeln!( + w, + "> The examples are provided by the [tldr-pages project](https://tldr.sh) under the [CC BY 4.0 License](https://github.com/tldr-pages/tldr/blob/main/LICENSE.md)." + )?; + writeln!(w, ">")?; + writeln!( + w, + "> Please note that, as uutils is a work in progress, some examples might fail." + ) +} + +fn get_zip_content(archive: &mut ZipArchive, name: &str) -> Option { + let mut s = String::new(); + archive.by_name(name).ok()?.read_to_string(&mut s).unwrap(); + Some(s) +} + +fn write_options(w: &mut impl Write, command: &Command) -> io::Result<()> { + writeln!(w, "

Options

")?; + write!(w, "
")?; + for arg in command.get_arguments() { + write!(w, "
")?; + let mut first = true; + for l in arg.get_long_and_visible_aliases().unwrap_or_default() { + if !first { + write!(w, ", ")?; + } else { + first = false; + } + write!(w, "")?; + write!(w, "--{}", l)?; + if let Some(names) = arg.get_value_names() { + write!( + w, + "={}", + names + .iter() + .map(|x| format!("<{}>", x)) + .collect::>() + .join(" ") + )?; + } + write!(w, "")?; + } + for s in arg.get_short_and_visible_aliases().unwrap_or_default() { + if !first { + write!(w, ", ")?; + } else { + first = false; + } + write!(w, "")?; + write!(w, "-{}", s)?; + if let Some(names) = arg.get_value_names() { + write!( + w, + " {}", + names + .iter() + .map(|x| format!("<{}>", x)) + .collect::>() + .join(" ") + )?; + } + write!(w, "")?; + } + writeln!(w, "
")?; + writeln!( + w, + "
\n\n{}\n\n
", + arg.get_help().unwrap_or_default().replace('\n', "
") + )?; + } + writeln!(w, "
\n") +} diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index c7fc4f9f9..588520fa3 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_arch" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "arch ~ (uutils) display machine architecture" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/arch" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/arch" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,10 +15,9 @@ edition = "2018" path = "src/arch.rs" [dependencies] -platform-info = "0.1" -clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +platform-info = "0.2" +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } [[bin]] name = "arch" diff --git a/src/uu/arch/LICENSE b/src/uu/arch/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/arch/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index d23a11cc8..502e2d5a0 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -8,13 +8,13 @@ use platform_info::*; -use clap::{crate_version, App}; +use clap::{crate_version, Command}; use uucore::error::{FromIo, UResult}; static ABOUT: &str = "Display machine architecture"; static SUMMARY: &str = "Determine architecture name for current machine."; -#[uucore_procs::gen_uumain] +#[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { uu_app().get_matches_from(args); @@ -23,9 +23,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(()) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help(SUMMARY) + .infer_long_args(true) } diff --git a/src/uu/arch/src/main.rs b/src/uu/arch/src/main.rs index e2668864c..a47da8346 100644 --- a/src/uu/arch/src/main.rs +++ b/src/uu/arch/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_arch); +uucore::bin!(uu_arch); diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index d5fc40024..c5d54133e 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_base32" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/base32" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/base32" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,14 +15,9 @@ edition = "2018" path = "src/base32.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features = ["encoding"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features = ["encoding"] } [[bin]] name = "base32" path = "src/main.rs" - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/base32/LICENSE b/src/uu/base32/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/base32/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index f4b4b49de..4260c472e 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -7,31 +7,28 @@ use std::io::{stdin, Read}; -use clap::App; +use clap::Command; use uucore::{encoding::Format, error::UResult}; pub mod base_common; -static ABOUT: &str = " - With no FILE, or when FILE is -, read standard input. +static ABOUT: &str = "\ +With no FILE, or when FILE is -, read standard input. - The data are encoded as described for the base32 alphabet in RFC - 4648. When decoding, the input may contain newlines in addition - to the bytes of the formal base32 alphabet. Use --ignore-garbage - to attempt to recover from any other non-alphabet bytes in the - encoded stream. +The data are encoded as described for the base32 alphabet in RFC +4648. When decoding, the input may contain newlines in addition +to the bytes of the formal base32 alphabet. Use --ignore-garbage +to attempt to recover from any other non-alphabet bytes in the +encoded stream. "; -fn usage() -> String { - format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) -} +const USAGE: &str = "{} [OPTION]... [FILE]"; -#[uucore_procs::gen_uumain] +#[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format = Format::Base32; - let usage = usage(); - let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?; + let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, USAGE)?; // Create a reference to stdin so we can return a locked stdin from // parse_base_cmd_args @@ -47,6 +44,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ) } -pub fn uu_app() -> App<'static, 'static> { - base_common::base_app(ABOUT) +pub fn uu_app<'a>() -> Command<'a> { + base_common::base_app(ABOUT, USAGE) } diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index b07203507..100c85b1f 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -12,13 +12,13 @@ use std::io::{stdout, Read, Write}; use uucore::display::Quotable; use uucore::encoding::{wrap_print, Data, Format}; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; use std::fs::File; use std::io::{BufReader, Stdin}; use std::path::Path; -use clap::{crate_version, App, Arg}; +use clap::{crate_version, Arg, Command}; pub static BASE_CMD_PARSE_ERROR: i32 = 1; @@ -38,7 +38,7 @@ pub mod options { } impl Config { - pub fn from(options: &clap::ArgMatches) -> UResult { + pub fn from(options: &clap::ArgMatches) -> UResult { let file: Option = match options.values_of(options::FILE) { Some(mut values) => { let name = values.next().unwrap(); @@ -76,7 +76,7 @@ impl Config { }) .transpose()?; - Ok(Config { + Ok(Self { decode: options.is_present(options::DECODE), ignore_garbage: options.is_present(options::IGNORE_GARBAGE), wrap_cols: cols, @@ -86,33 +86,35 @@ impl Config { } pub fn parse_base_cmd_args(args: impl uucore::Args, about: &str, usage: &str) -> UResult { - let app = base_app(about).usage(usage); + let command = base_app(about, usage); let arg_list = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - Config::from(&app.get_matches_from(arg_list)) + Config::from(&command.get_matches_from(arg_list)) } -pub fn base_app<'a>(about: &'a str) -> App<'static, 'a> { - App::new(uucore::util_name()) +pub fn base_app<'a>(about: &'a str, usage: &'a str) -> Command<'a> { + Command::new(uucore::util_name()) .version(crate_version!()) .about(about) + .override_usage(format_usage(usage)) + .infer_long_args(true) // Format arguments. .arg( - Arg::with_name(options::DECODE) - .short("d") + Arg::new(options::DECODE) + .short('d') .long(options::DECODE) .help("decode data"), ) .arg( - Arg::with_name(options::IGNORE_GARBAGE) - .short("i") + Arg::new(options::IGNORE_GARBAGE) + .short('i') .long(options::IGNORE_GARBAGE) .help("when decoding, ignore non-alphabetic characters"), ) .arg( - Arg::with_name(options::WRAP) - .short("w") + Arg::new(options::WRAP) + .short('w') .long(options::WRAP) .takes_value(true) .help( @@ -121,7 +123,7 @@ pub fn base_app<'a>(about: &'a str) -> App<'static, 'a> { ) // "multiple" arguments are used to check whether there is more than one // file passed in. - .arg(Arg::with_name(options::FILE).index(1).multiple(true)) + .arg(Arg::new(options::FILE).index(1).multiple_occurrences(true)) } pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> UResult> { @@ -152,7 +154,7 @@ pub fn handle_input( if !decode { match data.encode() { Ok(s) => { - wrap_print(&data, s); + wrap_print(&data, &s); Ok(()) } Err(_) => Err(USimpleError::new( diff --git a/src/uu/base32/src/main.rs b/src/uu/base32/src/main.rs index 83a0b6607..e7ab9a5fd 100644 --- a/src/uu/base32/src/main.rs +++ b/src/uu/base32/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_base32); +uucore::bin!(uu_base32); diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index c8d71b85a..0169ee77b 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_base64" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/base64" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/base64" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,15 +15,9 @@ edition = "2018" path = "src/base64.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features = ["encoding"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features = ["encoding"] } uu_base32 = { version=">=0.0.8", package="uu_base32", path="../base32"} [[bin]] name = "base64" path = "src/main.rs" - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/base64/LICENSE b/src/uu/base64/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/base64/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index c041d6d69..1bfe29312 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -13,26 +13,23 @@ use uucore::{encoding::Format, error::UResult}; use std::io::{stdin, Read}; -static ABOUT: &str = " - With no FILE, or when FILE is -, read standard input. +static ABOUT: &str = "\ +With no FILE, or when FILE is -, read standard input. - The data are encoded as described for the base64 alphabet in RFC - 3548. When decoding, the input may contain newlines in addition - to the bytes of the formal base64 alphabet. Use --ignore-garbage - to attempt to recover from any other non-alphabet bytes in the - encoded stream. +The data are encoded as described for the base64 alphabet in RFC +3548. When decoding, the input may contain newlines in addition +to the bytes of the formal base64 alphabet. Use --ignore-garbage +to attempt to recover from any other non-alphabet bytes in the +encoded stream. "; -fn usage() -> String { - format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) -} +const USAGE: &str = "{0} [OPTION]... [FILE]"; -#[uucore_procs::gen_uumain] +#[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format = Format::Base64; - let usage = usage(); - let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?; + let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, USAGE)?; // Create a reference to stdin so we can return a locked stdin from // parse_base_cmd_args diff --git a/src/uu/base64/src/main.rs b/src/uu/base64/src/main.rs index cae6cb3c4..ea5728880 100644 --- a/src/uu/base64/src/main.rs +++ b/src/uu/base64/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_base64); +uucore::bin!(uu_base64); diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index b75105790..226f61b20 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_basename" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/basename" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/basename" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,14 +15,9 @@ edition = "2018" path = "src/basename.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } [[bin]] name = "basename" path = "src/main.rs" - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/basename/LICENSE b/src/uu/basename/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/basename/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 464c0e4f1..7b0a0a486 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -7,23 +7,17 @@ // spell-checker:ignore (ToDO) fullname -#[macro_use] -extern crate uucore; - -use clap::{crate_version, App, Arg}; +use clap::{crate_version, Arg, Command}; use std::path::{is_separator, PathBuf}; -use uucore::InvalidEncodingHandling; +use uucore::display::Quotable; +use uucore::error::{UResult, UUsageError}; +use uucore::{format_usage, InvalidEncodingHandling}; static SUMMARY: &str = "Print NAME with any leading directory components removed If specified, also remove a trailing SUFFIX"; -fn usage() -> String { - format!( - "{0} NAME [SUFFIX] - {0} OPTION... NAME...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "{} NAME [SUFFIX] + {} OPTION... NAME..."; pub mod options { pub static MULTIPLE: &str = "multiple"; @@ -32,24 +26,19 @@ pub mod options { pub static ZERO: &str = "zero"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = usage(); // // Argument parsing // - let matches = uu_app().usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); // too few arguments if !matches.is_present(options::NAME) { - crash!( - 1, - "{1}\nTry '{0} --help' for more information.", - uucore::execution_phrase(), - "missing operand" - ); + return Err(UUsageError::new(1, "missing operand".to_string())); } let opt_suffix = matches.is_present(options::SUFFIX); @@ -58,12 +47,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let multiple_paths = opt_suffix || opt_multiple; // too many arguments if !multiple_paths && matches.occurrences_of(options::NAME) > 2 { - crash!( + return Err(UUsageError::new( 1, - "extra operand '{1}'\nTry '{0} --help' for more information.", - uucore::execution_phrase(), - matches.values_of(options::NAME).unwrap().nth(2).unwrap() - ); + format!( + "extra operand {}", + matches + .values_of(options::NAME) + .unwrap() + .nth(2) + .unwrap() + .quote() + ), + )); } let suffix = if opt_suffix { @@ -89,30 +84,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 { print!("{}{}", basename(path, suffix), line_ending); } - 0 + Ok(()) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) .arg( - Arg::with_name(options::MULTIPLE) - .short("a") + Arg::new(options::MULTIPLE) + .short('a') .long(options::MULTIPLE) .help("support multiple arguments and treat each as a NAME"), ) - .arg(Arg::with_name(options::NAME).multiple(true).hidden(true)) .arg( - Arg::with_name(options::SUFFIX) - .short("s") + Arg::new(options::NAME) + .multiple_occurrences(true) + .hide(true), + ) + .arg( + Arg::new(options::SUFFIX) + .short('s') .long(options::SUFFIX) .value_name("SUFFIX") .help("remove a trailing SUFFIX; implies -a"), ) .arg( - Arg::with_name(options::ZERO) - .short("z") + Arg::new(options::ZERO) + .short('z') .long(options::ZERO) .help("end each output line with NUL, not newline"), ) @@ -131,21 +132,15 @@ fn basename(fullname: &str, suffix: &str) -> String { // Convert to path buffer and get last path component let pb = PathBuf::from(path); match pb.components().last() { - Some(c) => strip_suffix(c.as_os_str().to_str().unwrap(), suffix), + Some(c) => { + let name = c.as_os_str().to_str().unwrap(); + if name == suffix { + name.to_string() + } else { + name.strip_suffix(suffix).unwrap_or(name).to_string() + } + } + None => "".to_owned(), } } - -// can be replaced with strip_suffix once MSRV is 1.45 -#[allow(clippy::manual_strip)] -fn strip_suffix(name: &str, suffix: &str) -> String { - if name == suffix { - return name.to_owned(); - } - - if name.ends_with(suffix) { - return name[..name.len() - suffix.len()].to_owned(); - } - - name.to_owned() -} diff --git a/src/uu/basename/src/main.rs b/src/uu/basename/src/main.rs index aa452f750..68d003269 100644 --- a/src/uu/basename/src/main.rs +++ b/src/uu/basename/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_basename); +uucore::bin!(uu_basename); diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index 165d3d3a0..4242e0391 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_basenc" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "basenc ~ (uutils) decode/encode input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/basenc" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/basenc" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,15 +15,10 @@ edition = "2018" path = "src/basenc.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features = ["encoding"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features = ["encoding"] } uu_base32 = { version=">=0.0.8", package="uu_base32", path="../base32"} [[bin]] name = "basenc" path = "src/main.rs" - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/basenc/LICENSE b/src/uu/basenc/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/basenc/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/basenc/src/basenc.rs b/src/uu/basenc/src/basenc.rs index e4b24c351..d47fe123a 100644 --- a/src/uu/basenc/src/basenc.rs +++ b/src/uu/basenc/src/basenc.rs @@ -8,7 +8,7 @@ //spell-checker:ignore (args) lsbf msbf -use clap::{App, Arg}; +use clap::{Arg, Command}; use uu_base32::base_common::{self, Config, BASE_CMD_PARSE_ERROR}; use uucore::{ @@ -19,12 +19,12 @@ use uucore::{ use std::io::{stdin, Read}; -static ABOUT: &str = " - With no FILE, or when FILE is -, read standard input. +static ABOUT: &str = "\ +With no FILE, or when FILE is -, read standard input. - When decoding, the input may contain newlines in addition to the bytes of - the formal alphabet. Use --ignore-garbage to attempt to recover - from any other non-alphabet bytes in the encoded stream. +When decoding, the input may contain newlines in addition to the bytes of +the formal alphabet. Use --ignore-garbage to attempt to recover +from any other non-alphabet bytes in the encoded stream. "; const ENCODINGS: &[(&str, Format)] = &[ @@ -36,26 +36,20 @@ const ENCODINGS: &[(&str, Format)] = &[ ("base2lsbf", Format::Base2Lsbf), ("base2msbf", Format::Base2Msbf), ("z85", Format::Z85), - // common abbreviations. TODO: once we have clap 3.0 we can use `AppSettings::InferLongArgs` to get all abbreviations automatically - ("base2l", Format::Base2Lsbf), - ("base2m", Format::Base2Msbf), ]; -fn usage() -> String { - format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) -} +const USAGE: &str = "{} [OPTION]... [FILE]"; -pub fn uu_app() -> App<'static, 'static> { - let mut app = base_common::base_app(ABOUT); +pub fn uu_app<'a>() -> Command<'a> { + let mut command = base_common::base_app(ABOUT, USAGE); for encoding in ENCODINGS { - app = app.arg(Arg::with_name(encoding.0).long(encoding.0)); + command = command.arg(Arg::new(encoding.0).long(encoding.0)); } - app + command } fn parse_cmd_args(args: impl uucore::Args) -> UResult<(Config, Format)> { - let usage = usage(); - let matches = uu_app().usage(&usage[..]).get_matches_from( + let matches = uu_app().get_matches_from( args.collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(), ); @@ -68,7 +62,7 @@ fn parse_cmd_args(args: impl uucore::Args) -> UResult<(Config, Format)> { Ok((config, format)) } -#[uucore_procs::gen_uumain] +#[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (config, format) = parse_cmd_args(args)?; // Create a reference to stdin so we can return a locked stdin from diff --git a/src/uu/basenc/src/main.rs b/src/uu/basenc/src/main.rs index 9a9a5f4c6..a035e9a68 100644 --- a/src/uu/basenc/src/main.rs +++ b/src/uu/basenc/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_basenc); +uucore::bin!(uu_basenc); diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index b6b0165ef..f76846491 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_cat" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "cat ~ (uutils) concatenate and display input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/cat" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/cat" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,18 +15,14 @@ edition = "2018" path = "src/cat.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } thiserror = "1.0" atty = "0.2" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs", "pipes"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "pipes"] } [target.'cfg(unix)'.dependencies] unix_socket = "0.5.0" -nix = "0.20.0" - -[target.'cfg(windows)'.dependencies] -winapi-util = "0.1.5" +nix = "0.23.1" [[bin]] name = "cat" diff --git a/src/uu/cat/LICENSE b/src/uu/cat/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/cat/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index af84890db..edba1b8d0 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -14,12 +14,13 @@ extern crate unix_socket; // last synced with: cat (GNU coreutils) 8.13 -use clap::{crate_version, App, Arg}; +use clap::{crate_version, Arg, Command}; use std::fs::{metadata, File}; use std::io::{self, Read, Write}; use thiserror::Error; use uucore::display::Quotable; use uucore::error::UResult; +use uucore::fs::FileInformation; #[cfg(unix)] use std::os::unix::io::AsRawFd; @@ -35,10 +36,10 @@ use std::net::Shutdown; use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use unix_socket::UnixStream; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static NAME: &str = "cat"; -static SYNTAX: &str = "[OPTION]... [FILE]..."; +static USAGE: &str = "{} [OPTION]... [FILE]..."; static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output With no FILE, or when FILE is -, read standard input."; @@ -181,7 +182,7 @@ mod options { pub static SHOW_NONPRINTING: &str = "show-nonprinting"; } -#[uucore_procs::gen_uumain] +#[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) @@ -235,67 +236,72 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { show_tabs, squeeze_blank, }; - cat_files(files, &options) + cat_files(&files, &options) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .name(NAME) .version(crate_version!()) - .usage(SYNTAX) + .override_usage(format_usage(USAGE)) .about(SUMMARY) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .infer_long_args(true) .arg( - Arg::with_name(options::SHOW_ALL) - .short("A") + Arg::new(options::FILE) + .hide(true) + .multiple_occurrences(true), + ) + .arg( + Arg::new(options::SHOW_ALL) + .short('A') .long(options::SHOW_ALL) .help("equivalent to -vET"), ) .arg( - Arg::with_name(options::NUMBER_NONBLANK) - .short("b") + Arg::new(options::NUMBER_NONBLANK) + .short('b') .long(options::NUMBER_NONBLANK) .help("number nonempty output lines, overrides -n") .overrides_with(options::NUMBER), ) .arg( - Arg::with_name(options::SHOW_NONPRINTING_ENDS) - .short("e") + Arg::new(options::SHOW_NONPRINTING_ENDS) + .short('e') .help("equivalent to -vE"), ) .arg( - Arg::with_name(options::SHOW_ENDS) - .short("E") + Arg::new(options::SHOW_ENDS) + .short('E') .long(options::SHOW_ENDS) .help("display $ at end of each line"), ) .arg( - Arg::with_name(options::NUMBER) - .short("n") + Arg::new(options::NUMBER) + .short('n') .long(options::NUMBER) .help("number all output lines"), ) .arg( - Arg::with_name(options::SQUEEZE_BLANK) - .short("s") + Arg::new(options::SQUEEZE_BLANK) + .short('s') .long(options::SQUEEZE_BLANK) .help("suppress repeated empty output lines"), ) .arg( - Arg::with_name(options::SHOW_NONPRINTING_TABS) - .short("t") + Arg::new(options::SHOW_NONPRINTING_TABS) + .short('t') .long(options::SHOW_NONPRINTING_TABS) .help("equivalent to -vT"), ) .arg( - Arg::with_name(options::SHOW_TABS) - .short("T") + Arg::new(options::SHOW_TABS) + .short('T') .long(options::SHOW_TABS) .help("display TAB characters at ^I"), ) .arg( - Arg::with_name(options::SHOW_NONPRINTING) - .short("v") + Arg::new(options::SHOW_NONPRINTING) + .short('v') .long(options::SHOW_NONPRINTING) .help("use ^ and M- notation, except for LF (\\n) and TAB (\\t)"), ) @@ -317,18 +323,17 @@ fn cat_path( path: &str, options: &OutputOptions, state: &mut OutputState, - #[cfg(unix)] out_info: &nix::sys::stat::FileStat, - #[cfg(windows)] out_info: &winapi_util::file::Information, + out_info: Option<&FileInformation>, ) -> CatResult<()> { - if path == "-" { - let stdin = io::stdin(); - let mut handle = InputHandle { - reader: stdin, - is_interactive: atty::is(atty::Stream::Stdin), - }; - return cat_handle(&mut handle, options, state); - } match get_input_type(path)? { + InputType::StdIn => { + let stdin = io::stdin(); + let mut handle = InputHandle { + reader: stdin, + is_interactive: atty::is(atty::Stream::Stdin), + }; + cat_handle(&mut handle, options, state) + } InputType::Directory => Err(CatError::IsDirectory), #[cfg(unix)] InputType::Socket => { @@ -342,10 +347,15 @@ fn cat_path( } _ => { let file = File::open(path)?; - #[cfg(any(windows, unix))] - if same_file(out_info, &file) { - return Err(CatError::OutputIsInput); + + if let Some(out_info) = out_info { + if out_info.file_size() != 0 + && FileInformation::from_file(&file).as_ref() == Some(out_info) + { + return Err(CatError::OutputIsInput); + } } + let mut handle = InputHandle { reader: file, is_interactive: false, @@ -355,25 +365,8 @@ fn cat_path( } } -#[cfg(unix)] -fn same_file(a_info: &nix::sys::stat::FileStat, b: &File) -> bool { - let b_info = nix::sys::stat::fstat(b.as_raw_fd()).unwrap(); - b_info.st_size != 0 && b_info.st_dev == a_info.st_dev && b_info.st_ino == a_info.st_ino -} - -#[cfg(windows)] -fn same_file(a_info: &winapi_util::file::Information, b: &File) -> bool { - let b_info = winapi_util::file::information(b).unwrap(); - b_info.file_size() != 0 - && b_info.volume_serial_number() == a_info.volume_serial_number() - && b_info.file_index() == a_info.file_index() -} - -fn cat_files(files: Vec, options: &OutputOptions) -> UResult<()> { - #[cfg(windows)] - let out_info = winapi_util::file::information(&std::io::stdout()).unwrap(); - #[cfg(unix)] - let out_info = nix::sys::stat::fstat(std::io::stdout().as_raw_fd()).unwrap(); +fn cat_files(files: &[String], options: &OutputOptions) -> UResult<()> { + let out_info = FileInformation::from_file(&std::io::stdout()); let mut state = OutputState { line_number: 1, @@ -383,8 +376,8 @@ fn cat_files(files: Vec, options: &OutputOptions) -> UResult<()> { }; let mut error_messages: Vec = Vec::new(); - for path in &files { - if let Err(err) = cat_path(path, options, &mut state, &out_info) { + for path in files { + if let Err(err) = cat_path(path, options, &mut state, out_info.as_ref()) { error_messages.push(format!("{}: {}", path.maybe_quote(), err)); } } @@ -486,7 +479,7 @@ fn write_lines( if !state.at_line_start || !options.squeeze_blank || !state.one_blank_kept { state.one_blank_kept = true; if state.at_line_start && options.number == NumberingMode::All { - write!(&mut writer, "{0:6}\t", state.line_number)?; + write!(writer, "{0:6}\t", state.line_number)?; state.line_number += 1; } writer.write_all(options.end_of_line().as_bytes())?; @@ -505,7 +498,7 @@ fn write_lines( } state.one_blank_kept = false; if state.at_line_start && options.number != NumberingMode::None { - write!(&mut writer, "{0:6}\t", state.line_number)?; + write!(writer, "{0:6}\t", state.line_number)?; state.line_number += 1; } @@ -567,13 +560,12 @@ fn write_tab_to_end(mut in_buf: &[u8], writer: &mut W) -> usize { { Some(p) => { writer.write_all(&in_buf[..p]).unwrap(); - if in_buf[p] == b'\n' { - return count + p; - } else if in_buf[p] == b'\t' { + if in_buf[p] == b'\t' { writer.write_all(b"^I").unwrap(); in_buf = &in_buf[p + 1..]; count += p + 1; } else { + // b'\n' or b'\r' return count + p; } } @@ -596,10 +588,10 @@ fn write_nonprint_to_end(in_buf: &[u8], writer: &mut W, tab: &[u8]) -> 9 => writer.write_all(tab), 0..=8 | 10..=31 => writer.write_all(&[b'^', byte + 64]), 32..=126 => writer.write_all(&[byte]), - 127 => writer.write_all(&[b'^', byte - 64]), + 127 => writer.write_all(&[b'^', b'?']), 128..=159 => writer.write_all(&[b'M', b'-', b'^', byte - 64]), 160..=254 => writer.write_all(&[b'M', b'-', byte - 128]), - _ => writer.write_all(&[b'M', b'-', b'^', 63]), + _ => writer.write_all(&[b'M', b'-', b'^', b'?']), } .unwrap(); count += 1; diff --git a/src/uu/cat/src/main.rs b/src/uu/cat/src/main.rs index 1adab666b..df198c960 100644 --- a/src/uu/cat/src/main.rs +++ b/src/uu/cat/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_cat); +uucore::bin!(uu_cat); diff --git a/src/uu/chcon/Cargo.toml b/src/uu/chcon/Cargo.toml index a359f142f..092f7702e 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "uu_chcon" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "chcon ~ (uutils) change file security context" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chcon" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/chcon" keywords = ["coreutils", "uutils", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -14,9 +14,8 @@ edition = "2018" path = "src/chcon.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } uucore = { version = ">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } -uucore_procs = { version = ">=0.0.6", package="uucore_procs", path="../../uucore_procs" } selinux = { version = "0.2" } fts-sys = { version = "0.2" } thiserror = { version = "1.0" } diff --git a/src/uu/chcon/LICENSE b/src/uu/chcon/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/chcon/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 9664f69f5..e49191ea3 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -2,9 +2,11 @@ #![allow(clippy::upper_case_acronyms)] -use uucore::{display::Quotable, show_error, show_usage_error, show_warning}; +use uucore::error::{UResult, USimpleError, UUsageError}; +use uucore::format_usage; +use uucore::{display::Quotable, show_error, show_warning}; -use clap::{App, Arg}; +use clap::{Arg, Command}; use selinux::{OpaqueSecurityContext, SecurityContext}; use std::borrow::Cow; @@ -21,8 +23,13 @@ use errors::*; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Change the SELinux security context of each FILE to CONTEXT. \n\ With --reference, change the security context of each FILE to that of RFILE."; +const USAGE: &str = "\ + {} [OPTION]... CONTEXT FILE... \n \ + {} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \ + {} [OPTION]... --reference=RFILE FILE..."; pub mod options { + pub static HELP: &str = "help"; pub static VERBOSE: &str = "verbose"; pub static REFERENCE: &str = "reference"; @@ -51,35 +58,24 @@ pub mod options { } } -fn get_usage() -> String { - format!( - "{0} [OPTION]... CONTEXT FILE... \n \ - {0} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \ - {0} [OPTION]... --reference=RFILE FILE...", - uucore::execution_phrase() - ) -} - -pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); - - let config = uu_app().usage(usage.as_ref()); +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let config = uu_app(); let options = match parse_command_line(config, args) { Ok(r) => r, Err(r) => { if let Error::CommandLine(r) = &r { - match r.kind { - clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => { + match r.kind() { + clap::ErrorKind::DisplayHelp | clap::ErrorKind::DisplayVersion => { println!("{}", r); - return libc::EXIT_SUCCESS; + return Ok(()); } _ => {} } } - show_usage_error!("{}.\n", r); - return libc::EXIT_FAILURE; + return Err(UUsageError::new(libc::EXIT_FAILURE, format!("{}.\n", r))); } }; @@ -98,8 +94,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match result { Err(r) => { - show_error!("{}.", report_full_error(&r)); - return libc::EXIT_FAILURE; + return Err(USimpleError::new( + libc::EXIT_FAILURE, + format!("{}.", report_full_error(&r)), + )); } Ok(file_context) => SELinuxSecurityContext::File(file_context), @@ -111,14 +109,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(context) => context, Err(_r) => { - show_error!("Invalid security context {}.", context.quote()); - return libc::EXIT_FAILURE; + return Err(USimpleError::new( + libc::EXIT_FAILURE, + format!("Invalid security context {}.", context.quote()), + )); } }; if SecurityContext::from_c_str(&c_context, false).check() == Some(false) { - show_error!("Invalid security context {}.", context.quote()); - return libc::EXIT_FAILURE; + return Err(USimpleError::new( + libc::EXIT_FAILURE, + format!("Invalid security context {}.", context.quote()), + )); } SELinuxSecurityContext::String(Some(c_context)) @@ -132,8 +134,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(r) => Some(r), Err(r) => { - show_error!("{}.", report_full_error(&r)); - return libc::EXIT_FAILURE; + return Err(USimpleError::new( + libc::EXIT_FAILURE, + format!("{}.", report_full_error(&r)), + )); } } } else { @@ -142,21 +146,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let results = process_files(&options, &context, root_dev_ino); if results.is_empty() { - return libc::EXIT_SUCCESS; + return Ok(()); } for result in &results { show_error!("{}.", report_full_error(result)); } - libc::EXIT_FAILURE + Err(libc::EXIT_FAILURE.into()) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .version(VERSION) .about(ABOUT) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) .arg( - Arg::with_name(options::dereference::DEREFERENCE) + Arg::new(options::HELP) + .long(options::HELP) + .help("Print help information."), + ) + .arg( + Arg::new(options::dereference::DEREFERENCE) .long(options::dereference::DEREFERENCE) .conflicts_with(options::dereference::NO_DEREFERENCE) .help( @@ -165,24 +176,24 @@ pub fn uu_app() -> App<'static, 'static> { ), ) .arg( - Arg::with_name(options::dereference::NO_DEREFERENCE) - .short("h") + Arg::new(options::dereference::NO_DEREFERENCE) + .short('h') .long(options::dereference::NO_DEREFERENCE) .help("Affect symbolic links instead of any referenced file."), ) .arg( - Arg::with_name(options::preserve_root::PRESERVE_ROOT) + Arg::new(options::preserve_root::PRESERVE_ROOT) .long(options::preserve_root::PRESERVE_ROOT) .conflicts_with(options::preserve_root::NO_PRESERVE_ROOT) .help("Fail to operate recursively on '/'."), ) .arg( - Arg::with_name(options::preserve_root::NO_PRESERVE_ROOT) + Arg::new(options::preserve_root::NO_PRESERVE_ROOT) .long(options::preserve_root::NO_PRESERVE_ROOT) .help("Do not treat '/' specially (the default)."), ) .arg( - Arg::with_name(options::REFERENCE) + Arg::new(options::REFERENCE) .long(options::REFERENCE) .takes_value(true) .value_name("RFILE") @@ -190,49 +201,54 @@ pub fn uu_app() -> App<'static, 'static> { .help( "Use security context of RFILE, rather than specifying \ a CONTEXT value.", - ), + ) + .allow_invalid_utf8(true), ) .arg( - Arg::with_name(options::USER) - .short("u") + Arg::new(options::USER) + .short('u') .long(options::USER) .takes_value(true) .value_name("USER") - .help("Set user USER in the target security context."), + .help("Set user USER in the target security context.") + .allow_invalid_utf8(true), ) .arg( - Arg::with_name(options::ROLE) - .short("r") + Arg::new(options::ROLE) + .short('r') .long(options::ROLE) .takes_value(true) .value_name("ROLE") - .help("Set role ROLE in the target security context."), + .help("Set role ROLE in the target security context.") + .allow_invalid_utf8(true), ) .arg( - Arg::with_name(options::TYPE) - .short("t") + Arg::new(options::TYPE) + .short('t') .long(options::TYPE) .takes_value(true) .value_name("TYPE") - .help("Set type TYPE in the target security context."), + .help("Set type TYPE in the target security context.") + .allow_invalid_utf8(true), ) .arg( - Arg::with_name(options::RANGE) - .short("l") + Arg::new(options::RANGE) + .short('l') .long(options::RANGE) .takes_value(true) .value_name("RANGE") - .help("Set range RANGE in the target security context."), + .help("Set range RANGE in the target security context.") + .allow_invalid_utf8(true), ) .arg( - Arg::with_name(options::RECURSIVE) - .short("R") + Arg::new(options::RECURSIVE) + .short('R') .long(options::RECURSIVE) .help("Operate on files and directories recursively."), ) .arg( - Arg::with_name(options::sym_links::FOLLOW_ARG_DIR_SYM_LINK) - .short("H") + Arg::new(options::sym_links::FOLLOW_ARG_DIR_SYM_LINK) + .short('H') .requires(options::RECURSIVE) .overrides_with_all(&[ options::sym_links::FOLLOW_DIR_SYM_LINKS, @@ -244,8 +260,8 @@ pub fn uu_app() -> App<'static, 'static> { ), ) .arg( - Arg::with_name(options::sym_links::FOLLOW_DIR_SYM_LINKS) - .short("L") + Arg::new(options::sym_links::FOLLOW_DIR_SYM_LINKS) + .short('L') .requires(options::RECURSIVE) .overrides_with_all(&[ options::sym_links::FOLLOW_ARG_DIR_SYM_LINK, @@ -257,8 +273,8 @@ pub fn uu_app() -> App<'static, 'static> { ), ) .arg( - Arg::with_name(options::sym_links::NO_FOLLOW_SYM_LINKS) - .short("P") + Arg::new(options::sym_links::NO_FOLLOW_SYM_LINKS) + .short('P') .requires(options::RECURSIVE) .overrides_with_all(&[ options::sym_links::FOLLOW_ARG_DIR_SYM_LINK, @@ -270,12 +286,17 @@ pub fn uu_app() -> App<'static, 'static> { ), ) .arg( - Arg::with_name(options::VERBOSE) - .short("v") + Arg::new(options::VERBOSE) + .short('v') .long(options::VERBOSE) .help("Output a diagnostic for every file processed."), ) - .arg(Arg::with_name("FILE").multiple(true).min_values(1)) + .arg( + Arg::new("FILE") + .multiple_occurrences(true) + .min_values(1) + .allow_invalid_utf8(true), + ) } #[derive(Debug)] @@ -288,8 +309,8 @@ struct Options { files: Vec, } -fn parse_command_line(config: clap::App, args: impl uucore::Args) -> Result { - let matches = config.get_matches_from_safe(args)?; +fn parse_command_line(config: clap::Command, args: impl uucore::Args) -> Result { + let matches = config.try_get_matches_from(args)?; let verbose = matches.is_present(options::VERBOSE); @@ -387,23 +408,21 @@ enum RecursiveMode { impl RecursiveMode { fn is_recursive(self) -> bool { match self { - RecursiveMode::NotRecursive => false, + Self::NotRecursive => false, - RecursiveMode::RecursiveButDoNotFollowSymLinks - | RecursiveMode::RecursiveAndFollowAllDirSymLinks - | RecursiveMode::RecursiveAndFollowArgDirSymLinks => true, + Self::RecursiveButDoNotFollowSymLinks + | Self::RecursiveAndFollowAllDirSymLinks + | Self::RecursiveAndFollowArgDirSymLinks => true, } } fn fts_open_options(self) -> c_int { match self { - RecursiveMode::NotRecursive | RecursiveMode::RecursiveButDoNotFollowSymLinks => { - fts_sys::FTS_PHYSICAL - } + Self::NotRecursive | Self::RecursiveButDoNotFollowSymLinks => fts_sys::FTS_PHYSICAL, - RecursiveMode::RecursiveAndFollowAllDirSymLinks => fts_sys::FTS_LOGICAL, + Self::RecursiveAndFollowAllDirSymLinks => fts_sys::FTS_LOGICAL, - RecursiveMode::RecursiveAndFollowArgDirSymLinks => { + Self::RecursiveAndFollowArgDirSymLinks => { fts_sys::FTS_PHYSICAL | fts_sys::FTS_COMFOLLOW } } @@ -707,7 +726,7 @@ fn root_dev_ino_warn(dir_name: &Path) { // When a program like chgrp performs a recursive traversal that requires traversing symbolic links, // it is *not* a problem. // However, when invoked with "-P -R", it deserves a warning. -// The fts_options parameter records the options that control this aspect of fts's behavior, +// The fts_options parameter records the options that control this aspect of fts behavior, // so test that. fn cycle_warning_required(fts_options: c_int, entry: &fts::EntryRef) -> bool { // When dereferencing no symlinks, or when dereferencing only those listed on the command line @@ -723,7 +742,7 @@ This almost certainly means that you have a corrupted file system.\n\ NOTIFY YOUR SYSTEM MANAGER.\n\ The following directory is part of the cycle {}.", file_name.quote() - ) + ); } #[derive(Debug)] diff --git a/src/uu/chcon/src/errors.rs b/src/uu/chcon/src/errors.rs index 2d8f72e67..8a1678a85 100644 --- a/src/uu/chcon/src/errors.rs +++ b/src/uu/chcon/src/errors.rs @@ -64,10 +64,10 @@ impl Error { pub(crate) fn report_full_error(mut err: &dyn std::error::Error) -> String { let mut desc = String::with_capacity(256); - write!(&mut desc, "{}", err).unwrap(); + write!(desc, "{}", err).unwrap(); while let Some(source) = err.source() { err = source; - write!(&mut desc, ". {}", err).unwrap(); + write!(desc, ". {}", err).unwrap(); } desc } diff --git a/src/uu/chcon/src/main.rs b/src/uu/chcon/src/main.rs index e23b34c5b..d93d7d1da 100644 --- a/src/uu/chcon/src/main.rs +++ b/src/uu/chcon/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_chcon); +uucore::bin!(uu_chcon); diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index acc03472c..952c83e54 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_chgrp" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chgrp" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/chgrp" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,9 +15,8 @@ edition = "2018" path = "src/chgrp.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } [[bin]] name = "chgrp" diff --git a/src/uu/chgrp/LICENSE b/src/uu/chgrp/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/chgrp/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index c70e5e5c7..d7e8baafe 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -10,9 +10,10 @@ use uucore::display::Quotable; pub use uucore::entries; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::format_usage; use uucore::perms::{chown_base, options, IfFrom}; -use clap::{App, Arg, ArgMatches}; +use clap::{Arg, ArgMatches, Command}; use std::fs; use std::os::unix::fs::MetadataExt; @@ -20,12 +21,9 @@ use std::os::unix::fs::MetadataExt; static ABOUT: &str = "Change the group of each FILE to GROUP."; static VERSION: &str = env!("CARGO_PKG_VERSION"); -fn get_usage() -> String { - format!( - "{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "\ + {} [OPTION]... GROUP FILE...\n \ + {} [OPTION]... --reference=RFILE FILE..."; fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option, Option, IfFrom)> { let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) { @@ -51,95 +49,94 @@ fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option, Option, Ok((dest_gid, None, IfFrom::All)) } -#[uucore_procs::gen_uumain] +#[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); - - chown_base( - uu_app().usage(&usage[..]), - args, - options::ARG_GROUP, - parse_gid_and_uid, - true, - ) + chown_base(uu_app(), args, options::ARG_GROUP, parse_gid_and_uid, true) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .version(VERSION) .about(ABOUT) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) .arg( - Arg::with_name(options::verbosity::CHANGES) - .short("c") + Arg::new(options::HELP) + .long(options::HELP) + .help("Print help information.") + ) + .arg( + Arg::new(options::verbosity::CHANGES) + .short('c') .long(options::verbosity::CHANGES) .help("like verbose but report only when a change is made"), ) .arg( - Arg::with_name(options::verbosity::SILENT) - .short("f") + Arg::new(options::verbosity::SILENT) + .short('f') .long(options::verbosity::SILENT), ) .arg( - Arg::with_name(options::verbosity::QUIET) + Arg::new(options::verbosity::QUIET) .long(options::verbosity::QUIET) .help("suppress most error messages"), ) .arg( - Arg::with_name(options::verbosity::VERBOSE) - .short("v") + Arg::new(options::verbosity::VERBOSE) + .short('v') .long(options::verbosity::VERBOSE) .help("output a diagnostic for every file processed"), ) .arg( - Arg::with_name(options::dereference::DEREFERENCE) + Arg::new(options::dereference::DEREFERENCE) .long(options::dereference::DEREFERENCE), ) .arg( - Arg::with_name(options::dereference::NO_DEREFERENCE) - .short("h") + Arg::new(options::dereference::NO_DEREFERENCE) + .short('h') .long(options::dereference::NO_DEREFERENCE) .help( "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", ), ) .arg( - Arg::with_name(options::preserve_root::PRESERVE) + Arg::new(options::preserve_root::PRESERVE) .long(options::preserve_root::PRESERVE) .help("fail to operate recursively on '/'"), ) .arg( - Arg::with_name(options::preserve_root::NO_PRESERVE) + Arg::new(options::preserve_root::NO_PRESERVE) .long(options::preserve_root::NO_PRESERVE) .help("do not treat '/' specially (the default)"), ) .arg( - Arg::with_name(options::REFERENCE) + Arg::new(options::REFERENCE) .long(options::REFERENCE) .value_name("RFILE") .help("use RFILE's group rather than specifying GROUP values") .takes_value(true) - .multiple(false), + .multiple_occurrences(false), ) .arg( - Arg::with_name(options::RECURSIVE) - .short("R") + Arg::new(options::RECURSIVE) + .short('R') .long(options::RECURSIVE) .help("operate on files and directories recursively"), ) .arg( - Arg::with_name(options::traverse::TRAVERSE) - .short(options::traverse::TRAVERSE) + Arg::new(options::traverse::TRAVERSE) + .short(options::traverse::TRAVERSE.chars().next().unwrap()) .help("if a command line argument is a symbolic link to a directory, traverse it"), ) .arg( - Arg::with_name(options::traverse::NO_TRAVERSE) - .short(options::traverse::NO_TRAVERSE) + Arg::new(options::traverse::NO_TRAVERSE) + .short(options::traverse::NO_TRAVERSE.chars().next().unwrap()) .help("do not traverse any symbolic links (default)") .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), ) .arg( - Arg::with_name(options::traverse::EVERY) - .short(options::traverse::EVERY) + Arg::new(options::traverse::EVERY) + .short(options::traverse::EVERY.chars().next().unwrap()) .help("traverse every symbolic link to a directory encountered"), ) } diff --git a/src/uu/chgrp/src/main.rs b/src/uu/chgrp/src/main.rs index ee6f70a8b..7d5330a61 100644 --- a/src/uu/chgrp/src/main.rs +++ b/src/uu/chgrp/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_chgrp); +uucore::bin!(uu_chgrp); diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index 64961e6dd..844c65544 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_chmod" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "chmod ~ (uutils) change mode of FILE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chmod" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/chmod" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,11 +15,9 @@ edition = "2018" path = "src/chmod.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } -libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs", "mode"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } -walkdir = "2.2" +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +libc = "0.2.121" +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode"] } [[bin]] name = "chmod" diff --git a/src/uu/chmod/LICENSE b/src/uu/chmod/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/chmod/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 68c55b4cb..25a37c372 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -7,20 +7,17 @@ // spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's -#[macro_use] -extern crate uucore; - -use clap::{crate_version, App, Arg}; +use clap::{crate_version, Arg, Command}; use std::fs; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::Path; use uucore::display::Quotable; +use uucore::error::{ExitCode, UResult, USimpleError, UUsageError}; use uucore::fs::display_permissions_unix; use uucore::libc::mode_t; #[cfg(not(windows))] use uucore::mode; -use uucore::InvalidEncodingHandling; -use walkdir::WalkDir; +use uucore::{format_usage, show_error, InvalidEncodingHandling}; static ABOUT: &str = "Change the mode of each FILE to MODE. With --reference, change the mode of each FILE to that of RFILE."; @@ -37,20 +34,17 @@ mod options { pub const FILE: &str = "FILE"; } -fn usage() -> String { - format!( - "{0} [OPTION]... MODE[,MODE]... FILE... -or: {0} [OPTION]... OCTAL-MODE FILE... -or: {0} [OPTION]... --reference=RFILE FILE...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "\ + {} [OPTION]... MODE[,MODE]... FILE... + {} [OPTION]... OCTAL-MODE FILE... + {} [OPTION]... --reference=RFILE FILE..."; fn get_long_usage() -> String { String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.") } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -59,25 +53,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE"). let mode_had_minus_prefix = mode::strip_minus_from_mode(&mut args); - let usage = usage(); let after_help = get_long_usage(); - let matches = uu_app() - .usage(&usage[..]) - .after_help(&after_help[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&after_help[..]).get_matches_from(args); let changes = matches.is_present(options::CHANGES); let quiet = matches.is_present(options::QUIET); let verbose = matches.is_present(options::VERBOSE); let preserve_root = matches.is_present(options::PRESERVE_ROOT); let recursive = matches.is_present(options::RECURSIVE); - let fmode = matches - .value_of(options::REFERENCE) - .and_then(|fref| match fs::metadata(fref) { + let fmode = match matches.value_of(options::REFERENCE) { + Some(fref) => match fs::metadata(fref) { Ok(meta) => Some(meta.mode()), - Err(err) => crash!(1, "cannot stat attributes of {}: {}", fref.quote(), err), - }); + Err(err) => { + return Err(USimpleError::new( + 1, + format!("cannot stat attributes of {}: {}", fref.quote(), err), + )) + } + }, + None => None, + }; let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required let cmode = if mode_had_minus_prefix { // clap parsing is finished, now put prefix back @@ -100,7 +96,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; if files.is_empty() { - crash!(1, "missing operand"); + return Err(UUsageError::new(1, "missing operand".to_string())); } let chmoder = Chmoder { @@ -112,71 +108,69 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fmode, cmode, }; - match chmoder.chmod(files) { - Ok(()) => {} - Err(e) => return e, - } - 0 + chmoder.chmod(&files) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) .arg( - Arg::with_name(options::CHANGES) + Arg::new(options::CHANGES) .long(options::CHANGES) - .short("c") + .short('c') .help("like verbose but report only when a change is made"), ) .arg( - Arg::with_name(options::QUIET) + Arg::new(options::QUIET) .long(options::QUIET) .visible_alias("silent") - .short("f") + .short('f') .help("suppress most error messages"), ) .arg( - Arg::with_name(options::VERBOSE) + Arg::new(options::VERBOSE) .long(options::VERBOSE) - .short("v") + .short('v') .help("output a diagnostic for every file processed"), ) .arg( - Arg::with_name(options::NO_PRESERVE_ROOT) + Arg::new(options::NO_PRESERVE_ROOT) .long(options::NO_PRESERVE_ROOT) .help("do not treat '/' specially (the default)"), ) .arg( - Arg::with_name(options::PRESERVE_ROOT) + Arg::new(options::PRESERVE_ROOT) .long(options::PRESERVE_ROOT) .help("fail to operate recursively on '/'"), ) .arg( - Arg::with_name(options::RECURSIVE) + Arg::new(options::RECURSIVE) .long(options::RECURSIVE) - .short("R") + .short('R') .help("change files and directories recursively"), ) .arg( - Arg::with_name(options::REFERENCE) + Arg::new(options::REFERENCE) .long("reference") .takes_value(true) .help("use RFILE's mode instead of MODE values"), ) .arg( - Arg::with_name(options::MODE) - .required_unless(options::REFERENCE) + Arg::new(options::MODE) + .required_unless_present(options::REFERENCE) .takes_value(true), // It would be nice if clap could parse with delimiter, e.g. "g-x,u+x", - // however .multiple(true) cannot be used here because FILE already needs that. - // Only one positional argument with .multiple(true) set is allowed per command + // however .multiple_occurrences(true) cannot be used here because FILE already needs that. + // Only one positional argument with .multiple_occurrences(true) set is allowed per command ) .arg( - Arg::with_name(options::FILE) - .required_unless(options::MODE) - .multiple(true), + Arg::new(options::FILE) + .required_unless_present(options::MODE) + .multiple_occurrences(true), ) } @@ -191,10 +185,10 @@ struct Chmoder { } impl Chmoder { - fn chmod(&self, files: Vec) -> Result<(), i32> { + fn chmod(&self, files: &[String]) -> UResult<()> { let mut r = Ok(()); - for filename in &files { + for filename in files { let filename = &filename[..]; let file = Path::new(filename); if !file.exists() { @@ -204,29 +198,47 @@ impl Chmoder { filename.quote() ); if !self.quiet { - show_error!("cannot operate on dangling symlink {}", filename.quote()); + return Err(USimpleError::new( + 1, + format!("cannot operate on dangling symlink {}", filename.quote()), + )); } } else if !self.quiet { - show_error!( - "cannot access {}: No such file or directory", - filename.quote() - ); + return Err(USimpleError::new( + 1, + format!( + "cannot access {}: No such file or directory", + filename.quote() + ), + )); } - return Err(1); + return Err(ExitCode::new(1)); } if self.recursive && self.preserve_root && filename == "/" { - show_error!( - "it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe", - filename.quote() - ); - return Err(1); + return Err(USimpleError::new( + 1, + format!( + "it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe", + filename.quote() + ) + )); } if !self.recursive { r = self.chmod_file(file).and(r); } else { - for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) { - let file = entry.path(); - r = self.chmod_file(file).and(r); + r = self.walk_dir(file); + } + } + r + } + + fn walk_dir(&self, file_path: &Path) -> UResult<()> { + let mut r = self.chmod_file(file_path); + if !is_symlink(file_path) && file_path.is_dir() { + for dir_entry in file_path.read_dir()? { + let path = dir_entry?.path(); + if !is_symlink(&path) { + r = self.walk_dir(path.as_path()); } } } @@ -234,14 +246,14 @@ impl Chmoder { } #[cfg(windows)] - fn chmod_file(&self, file: &Path) -> Result<(), i32> { + fn chmod_file(&self, file: &Path) -> UResult<()> { // chmod is useless on Windows // it doesn't set any permissions at all // instead it just sets the readonly attribute on the file - Err(0) + Ok(()) } #[cfg(unix)] - fn chmod_file(&self, file: &Path) -> Result<(), i32> { + fn chmod_file(&self, file: &Path) -> UResult<()> { use uucore::mode::get_umask; let fperm = match fs::metadata(file) { @@ -258,11 +270,13 @@ impl Chmoder { } else if err.kind() == std::io::ErrorKind::PermissionDenied { // These two filenames would normally be conditionally // quoted, but GNU's tests expect them to always be quoted - show_error!("{}: Permission denied", file.quote()); + return Err(USimpleError::new( + 1, + format!("{}: Permission denied", file.quote()), + )); } else { - show_error!("{}: {}", file.quote(), err); + return Err(USimpleError::new(1, format!("{}: {}", file.quote(), err))); } - return Err(1); } }; match self.fmode { @@ -296,22 +310,25 @@ impl Chmoder { } Err(f) => { if !self.quiet { - show_error!("{}", f); + return Err(USimpleError::new(1, f)); + } else { + return Err(ExitCode::new(1)); } - return Err(1); } } } self.change_file(fperm, new_mode, file)?; // if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail if (new_mode & !naively_expected_new_mode) != 0 { - show_error!( - "{}: new permissions are {}, not {}", - file.maybe_quote(), - display_permissions_unix(new_mode as mode_t, false), - display_permissions_unix(naively_expected_new_mode as mode_t, false) - ); - return Err(1); + return Err(USimpleError::new( + 1, + format!( + "{}: new permissions are {}, not {}", + file.maybe_quote(), + display_permissions_unix(new_mode as mode_t, false), + display_permissions_unix(naively_expected_new_mode as mode_t, false) + ), + )); } } } diff --git a/src/uu/chmod/src/main.rs b/src/uu/chmod/src/main.rs index adaf887f8..f0ec0c2e9 100644 --- a/src/uu/chmod/src/main.rs +++ b/src/uu/chmod/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_chmod); +uucore::bin!(uu_chmod); diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index 20381c660..7db67752c 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_chown" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chown" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/chown" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,9 +15,8 @@ edition = "2018" path = "src/chown.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } [[bin]] name = "chown" diff --git a/src/uu/chown/LICENSE b/src/uu/chown/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/chown/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index f24c4ec89..3add9df1f 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -9,23 +9,21 @@ use uucore::display::Quotable; pub use uucore::entries::{self, Group, Locate, Passwd}; +use uucore::format_usage; use uucore::perms::{chown_base, options, IfFrom}; use uucore::error::{FromIo, UResult, USimpleError}; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::{crate_version, Arg, ArgMatches, Command}; use std::fs; use std::os::unix::fs::MetadataExt; static ABOUT: &str = "change file owner and group"; -fn get_usage() -> String { - format!( - "{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "\ + {} [OPTION]... [OWNER][:[GROUP]] FILE... + {} [OPTION]... --reference=RFILE FILE..."; fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option, Option, IfFrom)> { let filter = if let Some(spec) = matches.value_of(options::FROM) { @@ -54,12 +52,10 @@ fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option, Optio Ok((dest_gid, dest_uid, filter)) } -#[uucore_procs::gen_uumain] +#[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); - chown_base( - uu_app().usage(&usage[..]), + uu_app(), args, options::ARG_OWNER, parse_gid_uid_and_filter, @@ -67,18 +63,25 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) .arg( - Arg::with_name(options::verbosity::CHANGES) - .short("c") + Arg::new(options::HELP) + .long(options::HELP) + .help("Print help information."), + ) + .arg( + Arg::new(options::verbosity::CHANGES) + .short('c') .long(options::verbosity::CHANGES) .help("like verbose but report only when a change is made"), ) .arg( - Arg::with_name(options::dereference::DEREFERENCE) + Arg::new(options::dereference::DEREFERENCE) .long(options::dereference::DEREFERENCE) .help( "affect the referent of each symbolic link (this is the default), \ @@ -86,8 +89,8 @@ pub fn uu_app() -> App<'static, 'static> { ), ) .arg( - Arg::with_name(options::dereference::NO_DEREFERENCE) - .short("h") + Arg::new(options::dereference::NO_DEREFERENCE) + .short('h') .long(options::dereference::NO_DEREFERENCE) .help( "affect symbolic links instead of any referenced file \ @@ -95,7 +98,7 @@ pub fn uu_app() -> App<'static, 'static> { ), ) .arg( - Arg::with_name(options::FROM) + Arg::new(options::FROM) .long(options::FROM) .help( "change the owner and/or group of each file only if its \ @@ -106,60 +109,60 @@ pub fn uu_app() -> App<'static, 'static> { .value_name("CURRENT_OWNER:CURRENT_GROUP"), ) .arg( - Arg::with_name(options::preserve_root::PRESERVE) + Arg::new(options::preserve_root::PRESERVE) .long(options::preserve_root::PRESERVE) .help("fail to operate recursively on '/'"), ) .arg( - Arg::with_name(options::preserve_root::NO_PRESERVE) + Arg::new(options::preserve_root::NO_PRESERVE) .long(options::preserve_root::NO_PRESERVE) .help("do not treat '/' specially (the default)"), ) .arg( - Arg::with_name(options::verbosity::QUIET) + Arg::new(options::verbosity::QUIET) .long(options::verbosity::QUIET) .help("suppress most error messages"), ) .arg( - Arg::with_name(options::RECURSIVE) - .short("R") + Arg::new(options::RECURSIVE) + .short('R') .long(options::RECURSIVE) .help("operate on files and directories recursively"), ) .arg( - Arg::with_name(options::REFERENCE) + Arg::new(options::REFERENCE) .long(options::REFERENCE) .help("use RFILE's owner and group rather than specifying OWNER:GROUP values") .value_name("RFILE") .min_values(1), ) .arg( - Arg::with_name(options::verbosity::SILENT) - .short("f") + Arg::new(options::verbosity::SILENT) + .short('f') .long(options::verbosity::SILENT), ) .arg( - Arg::with_name(options::traverse::TRAVERSE) - .short(options::traverse::TRAVERSE) + Arg::new(options::traverse::TRAVERSE) + .short(options::traverse::TRAVERSE.chars().next().unwrap()) .help("if a command line argument is a symbolic link to a directory, traverse it") .overrides_with_all(&[options::traverse::EVERY, options::traverse::NO_TRAVERSE]), ) .arg( - Arg::with_name(options::traverse::EVERY) - .short(options::traverse::EVERY) + Arg::new(options::traverse::EVERY) + .short(options::traverse::EVERY.chars().next().unwrap()) .help("traverse every symbolic link to a directory encountered") .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::NO_TRAVERSE]), ) .arg( - Arg::with_name(options::traverse::NO_TRAVERSE) - .short(options::traverse::NO_TRAVERSE) + Arg::new(options::traverse::NO_TRAVERSE) + .short(options::traverse::NO_TRAVERSE.chars().next().unwrap()) .help("do not traverse any symbolic links (default)") .overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]), ) .arg( - Arg::with_name(options::verbosity::VERBOSE) + Arg::new(options::verbosity::VERBOSE) .long(options::verbosity::VERBOSE) - .short("v") + .short('v') .help("output a diagnostic for every file processed"), ) } @@ -183,7 +186,7 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { let uid = if !user.is_empty() { Some(match Passwd::locate(user) { - Ok(u) => u.uid(), // We have been able to get the uid + Ok(u) => u.uid, // We have been able to get the uid Err(_) => // we have NOT been able to find the uid // but we could be in the case where we have user.group @@ -208,7 +211,7 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { Some( Group::locate(group) .map_err(|_| USimpleError::new(1, format!("invalid group: {}", spec.quote())))? - .gid(), + .gid, ) } else { None diff --git a/src/uu/chown/src/main.rs b/src/uu/chown/src/main.rs index b3ed39970..db3a4f1d2 100644 --- a/src/uu/chown/src/main.rs +++ b/src/uu/chown/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_chown); +uucore::bin!(uu_chown); diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index 362e43b59..8976f8256 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_chroot" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chroot" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/chroot" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,9 +15,8 @@ edition = "2018" path = "src/chroot.rs" [dependencies] -clap= "2.33" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries"] } [[bin]] name = "chroot" diff --git a/src/uu/chroot/LICENSE b/src/uu/chroot/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/chroot/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 55097c1bb..713336104 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -7,20 +7,20 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) NEWROOT Userspec pstatus +mod error; -#[macro_use] -extern crate uucore; -use clap::{crate_version, App, Arg}; +use crate::error::ChrootError; +use clap::{crate_version, Arg, Command}; use std::ffi::CString; use std::io::Error; use std::path::Path; -use std::process::Command; -use uucore::display::Quotable; +use std::process; +use uucore::error::{set_exit_code, UResult}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; -use uucore::{entries, InvalidEncodingHandling}; +use uucore::{entries, format_usage, InvalidEncodingHandling}; static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT."; -static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]"; +static USAGE: &str = "{} [OPTION]... NEWROOT [COMMAND [ARG]...]"; mod options { pub const NEWROOT: &str = "newroot"; @@ -31,7 +31,8 @@ mod options { pub const COMMAND: &str = "command"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -44,19 +45,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let newroot: &Path = match matches.value_of(options::NEWROOT) { Some(v) => Path::new(v), - None => crash!( - 1, - "Missing operand: NEWROOT\nTry '{} --help' for more information.", - uucore::execution_phrase() - ), + None => return Err(ChrootError::MissingNewRoot.into()), }; if !newroot.is_dir() { - crash!( - 1, - "cannot change root directory to {}: no such directory", - newroot.quote() - ); + return Err(ChrootError::NoSuchDirectory(format!("{}", newroot.display())).into()); } let commands = match matches.values_of(options::COMMAND) { @@ -82,65 +75,60 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let chroot_args = &command[1..]; // NOTE: Tests can only trigger code beyond this point if they're invoked with root permissions - set_context(newroot, &matches); + set_context(newroot, &matches)?; - let pstatus = Command::new(chroot_command) + let pstatus = match process::Command::new(chroot_command) .args(chroot_args) .status() - .unwrap_or_else(|e| { - // TODO: Exit status: - // 125 if chroot itself fails - // 126 if command is found but cannot be invoked - // 127 if command cannot be found - crash!( - 1, - "failed to run command {}: {}", - command[0].to_string().quote(), - e - ) - }); + { + Ok(status) => status, + Err(e) => return Err(ChrootError::CommandFailed(command[0].to_string(), e).into()), + }; - if pstatus.success() { + let code = if pstatus.success() { 0 } else { pstatus.code().unwrap_or(-1) - } + }; + set_exit_code(code); + Ok(()) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) - .usage(SYNTAX) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) .arg( - Arg::with_name(options::NEWROOT) - .hidden(true) + Arg::new(options::NEWROOT) + .hide(true) .required(true) .index(1), ) .arg( - Arg::with_name(options::USER) - .short("u") + Arg::new(options::USER) + .short('u') .long(options::USER) .help("User (ID or name) to switch before running the program") .value_name("USER"), ) .arg( - Arg::with_name(options::GROUP) - .short("g") + Arg::new(options::GROUP) + .short('g') .long(options::GROUP) .help("Group (ID or name) to switch to") .value_name("GROUP"), ) .arg( - Arg::with_name(options::GROUPS) - .short("G") + Arg::new(options::GROUPS) + .short('G') .long(options::GROUPS) .help("Comma-separated list of groups to switch to") .value_name("GROUP1,GROUP2..."), ) .arg( - Arg::with_name(options::USERSPEC) + Arg::new(options::USERSPEC) .long(options::USERSPEC) .help( "Colon-separated user and group to switch to. \ @@ -150,14 +138,14 @@ pub fn uu_app() -> App<'static, 'static> { .value_name("USER:GROUP"), ) .arg( - Arg::with_name(options::COMMAND) - .hidden(true) - .multiple(true) + Arg::new(options::COMMAND) + .hide(true) + .multiple_occurrences(true) .index(2), ) } -fn set_context(root: &Path, options: &clap::ArgMatches) { +fn set_context(root: &Path, options: &clap::ArgMatches) -> UResult<()> { let userspec_str = options.value_of(options::USERSPEC); let user_str = options.value_of(options::USER).unwrap_or_default(); let group_str = options.value_of(options::GROUP).unwrap_or_default(); @@ -166,7 +154,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { Some(u) => { let s: Vec<&str> = u.split(':').collect(); if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) { - crash!(1, "invalid userspec: {}", u.quote()) + return Err(ChrootError::InvalidUserspec(u.to_string()).into()); }; s } @@ -179,83 +167,79 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { (userspec[0], userspec[1]) }; - enter_chroot(root); + enter_chroot(root)?; - set_groups_from_str(groups_str); - set_main_group(group); - set_user(user); + set_groups_from_str(groups_str)?; + set_main_group(group)?; + set_user(user)?; + Ok(()) } -fn enter_chroot(root: &Path) { +fn enter_chroot(root: &Path) -> UResult<()> { std::env::set_current_dir(root).unwrap(); let err = unsafe { chroot(CString::new(".").unwrap().as_bytes_with_nul().as_ptr() as *const libc::c_char) }; - if err != 0 { - crash!( - 1, - "cannot chroot to {}: {}", - root.quote(), - Error::last_os_error() - ) - }; -} - -fn set_main_group(group: &str) { - if !group.is_empty() { - let group_id = match entries::grp2gid(group) { - Ok(g) => g, - _ => crash!(1, "no such group: {}", group.maybe_quote()), - }; - let err = unsafe { setgid(group_id) }; - if err != 0 { - crash!( - 1, - "cannot set gid to {}: {}", - group_id, - Error::last_os_error() - ) - } + if err == 0 { + Ok(()) + } else { + Err(ChrootError::CannotEnter(format!("{}", root.display()), Error::last_os_error()).into()) } } +fn set_main_group(group: &str) -> UResult<()> { + if !group.is_empty() { + let group_id = match entries::grp2gid(group) { + Ok(g) => g, + _ => return Err(ChrootError::NoSuchGroup(group.to_string()).into()), + }; + let err = unsafe { setgid(group_id) }; + if err != 0 { + return Err( + ChrootError::SetGidFailed(group_id.to_string(), Error::last_os_error()).into(), + ); + } + } + Ok(()) +} + #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -fn set_groups(groups: Vec) -> libc::c_int { +fn set_groups(groups: &[libc::gid_t]) -> libc::c_int { unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) } } #[cfg(target_os = "linux")] -fn set_groups(groups: Vec) -> libc::c_int { +fn set_groups(groups: &[libc::gid_t]) -> libc::c_int { unsafe { setgroups(groups.len() as libc::size_t, groups.as_ptr()) } } -fn set_groups_from_str(groups: &str) { +fn set_groups_from_str(groups: &str) -> UResult<()> { if !groups.is_empty() { - let groups_vec: Vec = groups - .split(',') - .map(|x| match entries::grp2gid(x) { + let mut groups_vec = vec![]; + for group in groups.split(',') { + let gid = match entries::grp2gid(group) { Ok(g) => g, - _ => crash!(1, "no such group: {}", x), - }) - .collect(); - let err = set_groups(groups_vec); + Err(_) => return Err(ChrootError::NoSuchGroup(group.to_string()).into()), + }; + groups_vec.push(gid); + } + let err = set_groups(&groups_vec); if err != 0 { - crash!(1, "cannot set groups: {}", Error::last_os_error()) + return Err(ChrootError::SetGroupsFailed(Error::last_os_error()).into()); } } + Ok(()) } -fn set_user(user: &str) { +fn set_user(user: &str) -> UResult<()> { if !user.is_empty() { let user_id = entries::usr2uid(user).unwrap(); let err = unsafe { setuid(user_id as libc::uid_t) }; if err != 0 { - crash!( - 1, - "cannot set user to {}: {}", - user.maybe_quote(), - Error::last_os_error() - ) + return Err( + ChrootError::SetUserFailed(user.to_string(), Error::last_os_error()).into(), + ); } } + Ok(()) } diff --git a/src/uu/chroot/src/error.rs b/src/uu/chroot/src/error.rs new file mode 100644 index 000000000..69e8ac54b --- /dev/null +++ b/src/uu/chroot/src/error.rs @@ -0,0 +1,81 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +// spell-checker:ignore NEWROOT Userspec userspec +//! Errors returned by chroot. +use std::fmt::Display; +use std::io::Error; +use uucore::display::Quotable; +use uucore::error::UError; + +/// Errors that can happen while executing chroot. +#[derive(Debug)] +pub enum ChrootError { + /// Failed to enter the specified directory. + CannotEnter(String, Error), + + /// Failed to execute the specified command. + CommandFailed(String, Error), + + /// The given user and group specification was invalid. + InvalidUserspec(String), + + /// The new root directory was not given. + MissingNewRoot, + + /// Failed to find the specified group. + NoSuchGroup(String), + + /// The given directory does not exist. + NoSuchDirectory(String), + + /// The call to `setgid()` failed. + SetGidFailed(String, Error), + + /// The call to `setgroups()` failed. + SetGroupsFailed(Error), + + /// The call to `setuid()` failed. + SetUserFailed(String, Error), +} + +impl std::error::Error for ChrootError {} + +impl UError for ChrootError { + // TODO: Exit status: + // 125 if chroot itself fails + // 126 if command is found but cannot be invoked + // 127 if command cannot be found + fn code(&self) -> i32 { + 1 + } +} + +impl Display for ChrootError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::CannotEnter(s, e) => write!(f, "cannot chroot to {}: {}", s.quote(), e,), + Self::CommandFailed(s, e) => { + write!(f, "failed to run command {}: {}", s.to_string().quote(), e,) + } + Self::InvalidUserspec(s) => write!(f, "invalid userspec: {}", s.quote(),), + Self::MissingNewRoot => write!( + f, + "Missing operand: NEWROOT\nTry '{} --help' for more information.", + uucore::execution_phrase(), + ), + Self::NoSuchGroup(s) => write!(f, "no such group: {}", s.maybe_quote(),), + Self::NoSuchDirectory(s) => write!( + f, + "cannot change root directory to {}: no such directory", + s.quote(), + ), + Self::SetGidFailed(s, e) => write!(f, "cannot set gid to {}: {}", s, e), + Self::SetGroupsFailed(e) => write!(f, "cannot set groups: {}", e), + Self::SetUserFailed(s, e) => { + write!(f, "cannot set user to {}: {}", s.maybe_quote(), e) + } + } + } +} diff --git a/src/uu/chroot/src/main.rs b/src/uu/chroot/src/main.rs index 0ca88cfaf..a8ccd8254 100644 --- a/src/uu/chroot/src/main.rs +++ b/src/uu/chroot/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_chroot); +uucore::bin!(uu_chroot); diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index b7f81d74b..50fdccf88 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_cksum" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/cksum" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/cksum" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,15 +15,9 @@ edition = "2018" path = "src/cksum.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } -libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } [[bin]] name = "cksum" path = "src/main.rs" - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/cksum/LICENSE b/src/uu/cksum/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/cksum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 92853a3e8..e901e0820 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -6,23 +6,21 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) fname - -#[macro_use] -extern crate uucore; - -use clap::{crate_version, App, Arg}; +use clap::{crate_version, Arg, Command}; use std::fs::File; use std::io::{self, stdin, BufReader, Read}; use std::path::Path; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult}; use uucore::InvalidEncodingHandling; +use uucore::{format_usage, show}; // NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 const CRC_TABLE_LEN: usize = 256; const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table(); const NAME: &str = "cksum"; -const SYNTAX: &str = "[OPTIONS] [FILE]..."; +const USAGE: &str = "{} [OPTIONS] [FILE]..."; const SUMMARY: &str = "Print CRC and size for each file"; const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] { @@ -82,27 +80,18 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> { let mut crc = 0u32; let mut size = 0usize; - let file; let mut rd: Box = match fname { "-" => Box::new(stdin()), _ => { - let path = &Path::new(fname); - if path.is_dir() { - return Err(std::io::Error::new( - io::ErrorKind::InvalidInput, - "Is a directory", - )); - }; - // Silent the warning as we want to the error message - #[allow(clippy::question_mark)] - if path.metadata().is_err() { - return Err(std::io::Error::new( - io::ErrorKind::NotFound, - "No such file or directory", - )); - }; - file = File::open(&path)?; - Box::new(BufReader::new(file)) + let p = Path::new(fname); + + // Directories should not give an error, but should be interpreted + // as empty files to match GNU semantics. + if p.is_dir() { + Box::new(BufReader::new(io::empty())) as Box + } else { + Box::new(BufReader::new(File::open(p)?)) as Box + } } }; @@ -123,7 +112,8 @@ mod options { pub static FILE: &str = "file"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -136,35 +126,30 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; if files.is_empty() { - match cksum("-") { - Ok((crc, size)) => println!("{} {}", crc, size), - Err(err) => { - show_error!("-: {}", err); - return 2; - } - } - return 0; + let (crc, size) = cksum("-")?; + println!("{} {}", crc, size); + return Ok(()); } - let mut exit_code = 0; for fname in &files { - match cksum(fname.as_ref()) { + match cksum(fname.as_ref()).map_err_context(|| format!("{}", fname.maybe_quote())) { Ok((crc, size)) => println!("{} {} {}", crc, size, fname), - Err(err) => { - show_error!("{}: {}", fname.maybe_quote(), err); - exit_code = 2; - } - } + Err(err) => show!(err), + }; } - - exit_code + Ok(()) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .about(SUMMARY) - .usage(SYNTAX) - .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) + .arg( + Arg::new(options::FILE) + .hide(true) + .multiple_occurrences(true), + ) } diff --git a/src/uu/cksum/src/main.rs b/src/uu/cksum/src/main.rs index b8a8f6b33..8cab56681 100644 --- a/src/uu/cksum/src/main.rs +++ b/src/uu/cksum/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_cksum); +uucore::bin!(uu_cksum); diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index abcbff57b..a6de62a58 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_comm" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "comm ~ (uutils) compare sorted inputs" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/comm" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/comm" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,15 +15,9 @@ edition = "2018" path = "src/comm.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } -libc = "0.2.42" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } [[bin]] name = "comm" path = "src/main.rs" - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/comm/LICENSE b/src/uu/comm/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/comm/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 56af42fd9..2207493d3 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -11,12 +11,15 @@ use std::cmp::Ordering; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; -use uucore::InvalidEncodingHandling; +use uucore::error::FromIo; +use uucore::error::UResult; +use uucore::{format_usage, InvalidEncodingHandling}; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::{crate_version, Arg, ArgMatches, Command}; static ABOUT: &str = "compare two sorted files line by line"; static LONG_HELP: &str = ""; +const USAGE: &str = "{} [OPTION]... FILE1 FILE2"; mod options { pub const COLUMN_1: &str = "1"; @@ -28,10 +31,6 @@ mod options { pub const FILE_2: &str = "FILE2"; } -fn usage() -> String { - format!("{} [OPTION]... FILE1 FILE2", uucore::execution_phrase()) -} - fn mkdelim(col: usize, opts: &ArgMatches) -> String { let mut s = String::new(); let delim = opts.value_of(options::DELIMITER).unwrap(); @@ -128,50 +127,52 @@ fn open_file(name: &str) -> io::Result { } } -pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = usage(); +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = uu_app().usage(&usage[..]).get_matches_from(args); - - let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap(); - let mut f2 = open_file(matches.value_of(options::FILE_2).unwrap()).unwrap(); + let matches = uu_app().get_matches_from(args); + let filename1 = matches.value_of(options::FILE_1).unwrap(); + let filename2 = matches.value_of(options::FILE_2).unwrap(); + let mut f1 = open_file(filename1).map_err_context(|| filename1.to_string())?; + let mut f2 = open_file(filename2).map_err_context(|| filename2.to_string())?; comm(&mut f1, &mut f2, &matches); - - 0 + Ok(()) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) .arg( - Arg::with_name(options::COLUMN_1) - .short(options::COLUMN_1) + Arg::new(options::COLUMN_1) + .short('1') .help("suppress column 1 (lines unique to FILE1)"), ) .arg( - Arg::with_name(options::COLUMN_2) - .short(options::COLUMN_2) + Arg::new(options::COLUMN_2) + .short('2') .help("suppress column 2 (lines unique to FILE2)"), ) .arg( - Arg::with_name(options::COLUMN_3) - .short(options::COLUMN_3) + Arg::new(options::COLUMN_3) + .short('3') .help("suppress column 3 (lines that appear in both files)"), ) .arg( - Arg::with_name(options::DELIMITER) + Arg::new(options::DELIMITER) .long(options::DELIMITER) .help("separate columns with STR") .value_name("STR") .default_value(options::DELIMITER_DEFAULT) .hide_default_value(true), ) - .arg(Arg::with_name(options::FILE_1).required(true)) - .arg(Arg::with_name(options::FILE_2).required(true)) + .arg(Arg::new(options::FILE_1).required(true)) + .arg(Arg::new(options::FILE_2).required(true)) } diff --git a/src/uu/comm/src/main.rs b/src/uu/comm/src/main.rs index 07ac07544..0417389c2 100644 --- a/src/uu/comm/src/main.rs +++ b/src/uu/comm/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_comm); +uucore::bin!(uu_comm); diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 891bf0244..452e1e8cf 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cp" -version = "0.0.8" +version = "0.0.13" authors = [ "Jordy Dickinson ", "Joshua S. Miller ", @@ -10,7 +10,7 @@ license = "MIT" description = "cp ~ (uutils) copy SOURCE to DESTINATION" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/cp" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/cp" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -19,24 +19,23 @@ edition = "2018" path = "src/cp.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } filetime = "0.2" -libc = "0.2.85" -quick-error = "1.2.3" -selinux = { version="0.2.3", optional=true } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +libc = "0.2.121" +quick-error = "2.0.1" +selinux = { version="0.2", optional=true } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] } walkdir = "2.2" [target.'cfg(target_os = "linux")'.dependencies] -ioctl-sys = "0.6" +ioctl-sys = "0.8" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version="0.3", features=["fileapi"] } [target.'cfg(unix)'.dependencies] xattr="0.2.1" -exacl= { version = "0.6.0", optional=true } +exacl= { version = "0.8.0", optional=true } [[bin]] name = "cp" @@ -45,7 +44,3 @@ path = "src/main.rs" [features] feat_selinux = ["selinux"] feat_acl = ["exacl"] - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/cp/LICENSE b/src/uu/cp/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/cp/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 518a2262c..d69bb705b 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -8,7 +8,7 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -// spell-checker:ignore (ToDO) ficlone linkgs lstat nlink nlinks pathbuf reflink strs xattrs +// spell-checker:ignore (ToDO) ficlone linkgs lstat nlink nlinks pathbuf reflink strs xattrs symlinked #[cfg(target_os = "linux")] #[macro_use] @@ -19,6 +19,8 @@ extern crate quick_error; extern crate uucore; use uucore::display::Quotable; +use uucore::format_usage; +use uucore::fs::FileInformation; #[cfg(windows)] use winapi::um::fileapi::CreateFileW; #[cfg(windows)] @@ -26,8 +28,10 @@ use winapi::um::fileapi::GetFileInformationByHandle; use std::borrow::Cow; -use clap::{crate_version, App, Arg, ArgMatches}; +use clap::{crate_version, Arg, ArgMatches, Command}; use filetime::FileTime; +#[cfg(unix)] +use libc::mkfifo; use quick_error::ResultExt; use std::collections::HashSet; use std::env; @@ -41,6 +45,10 @@ use std::fs::OpenOptions; use std::io; use std::io::{stdin, stdout, Write}; use std::mem; +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; +#[cfg(unix)] +use std::os::unix::fs::{FileTypeExt, PermissionsExt}; #[cfg(target_os = "linux")] use std::os::unix::io::AsRawFd; #[cfg(windows)] @@ -53,9 +61,6 @@ use uucore::error::{set_exit_code, ExitCode, UError, UResult}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use walkdir::WalkDir; -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; - #[cfg(target_os = "linux")] ioctl!(write ficlone with 0x94, 9; std::os::raw::c_int); @@ -63,14 +68,14 @@ quick_error! { #[derive(Debug)] pub enum Error { /// Simple io::Error wrapper - IoErr(err: io::Error) { from() cause(err) display("{}", err) } + IoErr(err: io::Error) { from() source(err) display("{}", err)} /// Wrapper for io::Error with path context IoErrContext(err: io::Error, path: String) { display("{}: {}", path, err) context(path: &'a str, err: io::Error) -> (err, path.to_owned()) context(context: String, err: io::Error) -> (err, context) - cause(err) + source(err) } /// General copy error @@ -85,7 +90,7 @@ quick_error! { NotAllFilesCopied {} /// Simple walkdir::Error wrapper - WalkDirErr(err: walkdir::Error) { from() display("{}", err) cause(err) } + WalkDirErr(err: walkdir::Error) { from() display("{}", err) source(err) } /// Simple std::path::StripPrefixError wrapper StripPrefixError(err: StripPrefixError) { from() } @@ -148,7 +153,7 @@ pub type Target = PathBuf; pub type TargetSlice = Path; /// Specifies whether when overwrite files -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] pub enum ClobberMode { Force, RemoveDestination, @@ -156,7 +161,7 @@ pub enum ClobberMode { } /// Specifies whether when overwrite files -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] pub enum OverwriteMode { /// [Default] Always overwrite existing files Clobber(ClobberMode), @@ -229,14 +234,10 @@ static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; static LONG_HELP: &str = ""; static EXIT_ERR: i32 = 1; -fn usage() -> String { - format!( - "{0} [OPTION]... [-T] SOURCE DEST - {0} [OPTION]... SOURCE... DIRECTORY - {0} [OPTION]... -t DIRECTORY SOURCE...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "\ + {} [OPTION]... [-T] SOURCE DEST + {} [OPTION]... SOURCE... DIRECTORY + {} [OPTION]... -t DIRECTORY SOURCE..."; // Argument constants mod options { @@ -295,174 +296,186 @@ static DEFAULT_ATTRIBUTES: &[Attribute] = &[ Attribute::Timestamps, ]; -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + const MODE_ARGS: &[&str] = &[ + options::LINK, + options::REFLINK, + options::SYMBOLIC_LINK, + options::ATTRIBUTES_ONLY, + options::COPY_CONTENTS, + ]; + Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) - .arg(Arg::with_name(options::TARGET_DIRECTORY) - .short("t") + .override_usage(format_usage(USAGE)) + .infer_long_args(true) + .arg(Arg::new(options::TARGET_DIRECTORY) + .short('t') .conflicts_with(options::NO_TARGET_DIRECTORY) .long(options::TARGET_DIRECTORY) .value_name(options::TARGET_DIRECTORY) .takes_value(true) + .validator(|s| { + if Path::new(s).is_dir() { + return Ok(()); + } + Err(format!("'{}' is not a directory", s)) + }) .help("copy all SOURCE arguments into target-directory")) - .arg(Arg::with_name(options::NO_TARGET_DIRECTORY) - .short("T") + .arg(Arg::new(options::NO_TARGET_DIRECTORY) + .short('T') .long(options::NO_TARGET_DIRECTORY) .conflicts_with(options::TARGET_DIRECTORY) .help("Treat DEST as a regular file and not a directory")) - .arg(Arg::with_name(options::INTERACTIVE) - .short("i") + .arg(Arg::new(options::INTERACTIVE) + .short('i') .long(options::INTERACTIVE) - .conflicts_with(options::NO_CLOBBER) + .overrides_with(options::NO_CLOBBER) .help("ask before overwriting files")) - .arg(Arg::with_name(options::LINK) - .short("l") + .arg(Arg::new(options::LINK) + .short('l') .long(options::LINK) - .overrides_with(options::REFLINK) + .overrides_with_all(MODE_ARGS) .help("hard-link files instead of copying")) - .arg(Arg::with_name(options::NO_CLOBBER) - .short("n") + .arg(Arg::new(options::NO_CLOBBER) + .short('n') .long(options::NO_CLOBBER) - .conflicts_with(options::INTERACTIVE) + .overrides_with(options::INTERACTIVE) .help("don't overwrite a file that already exists")) - .arg(Arg::with_name(options::RECURSIVE) - .short("r") + .arg(Arg::new(options::RECURSIVE) + .short('r') .long(options::RECURSIVE) // --archive sets this option .help("copy directories recursively")) - .arg(Arg::with_name(options::RECURSIVE_ALIAS) - .short("R") + .arg(Arg::new(options::RECURSIVE_ALIAS) + .short('R') .help("same as -r")) - .arg(Arg::with_name(options::STRIP_TRAILING_SLASHES) + .arg(Arg::new(options::STRIP_TRAILING_SLASHES) .long(options::STRIP_TRAILING_SLASHES) .help("remove any trailing slashes from each SOURCE argument")) - .arg(Arg::with_name(options::VERBOSE) - .short("v") + .arg(Arg::new(options::VERBOSE) + .short('v') .long(options::VERBOSE) .help("explicitly state what is being done")) - .arg(Arg::with_name(options::SYMBOLIC_LINK) - .short("s") + .arg(Arg::new(options::SYMBOLIC_LINK) + .short('s') .long(options::SYMBOLIC_LINK) - .conflicts_with(options::LINK) - .overrides_with(options::REFLINK) + .overrides_with_all(MODE_ARGS) .help("make symbolic links instead of copying")) - .arg(Arg::with_name(options::FORCE) - .short("f") + .arg(Arg::new(options::FORCE) + .short('f') .long(options::FORCE) .help("if an existing destination file cannot be opened, remove it and \ try again (this option is ignored when the -n option is also used). \ Currently not implemented for Windows.")) - .arg(Arg::with_name(options::REMOVE_DESTINATION) + .arg(Arg::new(options::REMOVE_DESTINATION) .long(options::REMOVE_DESTINATION) - .conflicts_with(options::FORCE) + .overrides_with(options::FORCE) .help("remove each existing destination file before attempting to open it \ - (contrast with --force). On Windows, current only works for writeable files.")) + (contrast with --force). On Windows, currently only works for writeable files.")) .arg(backup_control::arguments::backup()) .arg(backup_control::arguments::backup_no_args()) .arg(backup_control::arguments::suffix()) - .arg(Arg::with_name(options::UPDATE) - .short("u") + .arg(Arg::new(options::UPDATE) + .short('u') .long(options::UPDATE) .help("copy only when the SOURCE file is newer than the destination file \ or when the destination file is missing")) - .arg(Arg::with_name(options::REFLINK) + .arg(Arg::new(options::REFLINK) .long(options::REFLINK) .takes_value(true) .value_name("WHEN") + .overrides_with_all(MODE_ARGS) .help("control clone/CoW copies. See below")) - .arg(Arg::with_name(options::ATTRIBUTES_ONLY) + .arg(Arg::new(options::ATTRIBUTES_ONLY) .long(options::ATTRIBUTES_ONLY) - .conflicts_with(options::COPY_CONTENTS) - .overrides_with(options::REFLINK) + .overrides_with_all(MODE_ARGS) .help("Don't copy the file data, just the attributes")) - .arg(Arg::with_name(options::PRESERVE) + .arg(Arg::new(options::PRESERVE) .long(options::PRESERVE) .takes_value(true) - .multiple(true) - .use_delimiter(true) + .multiple_occurrences(true) + .use_value_delimiter(true) .possible_values(PRESERVABLE_ATTRIBUTES) .min_values(0) .value_name("ATTR_LIST") - .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE]) + .overrides_with_all(&[options::ARCHIVE, options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE]) // -d sets this option // --archive sets this option .help("Preserve the specified attributes (default: mode, ownership (unix only), timestamps), \ if possible additional attributes: context, links, xattr, all")) - .arg(Arg::with_name(options::PRESERVE_DEFAULT_ATTRIBUTES) - .short("-p") + .arg(Arg::new(options::PRESERVE_DEFAULT_ATTRIBUTES) + .short('p') .long(options::PRESERVE_DEFAULT_ATTRIBUTES) - .conflicts_with_all(&[options::PRESERVE, options::NO_PRESERVE, options::ARCHIVE]) + .overrides_with_all(&[options::PRESERVE, options::NO_PRESERVE, options::ARCHIVE]) .help("same as --preserve=mode,ownership(unix only),timestamps")) - .arg(Arg::with_name(options::NO_PRESERVE) + .arg(Arg::new(options::NO_PRESERVE) .long(options::NO_PRESERVE) .takes_value(true) .value_name("ATTR_LIST") - .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::ARCHIVE]) + .overrides_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::ARCHIVE]) .help("don't preserve the specified attributes")) - .arg(Arg::with_name(options::PARENTS) + .arg(Arg::new(options::PARENTS) .long(options::PARENTS) .alias(options::PARENT) .help("use full source file name under DIRECTORY")) - .arg(Arg::with_name(options::NO_DEREFERENCE) - .short("-P") + .arg(Arg::new(options::NO_DEREFERENCE) + .short('P') .long(options::NO_DEREFERENCE) - .conflicts_with(options::DEREFERENCE) + .overrides_with(options::DEREFERENCE) // -d sets this option .help("never follow symbolic links in SOURCE")) - .arg(Arg::with_name(options::DEREFERENCE) - .short("L") + .arg(Arg::new(options::DEREFERENCE) + .short('L') .long(options::DEREFERENCE) - .conflicts_with(options::NO_DEREFERENCE) + .overrides_with(options::NO_DEREFERENCE) .help("always follow symbolic links in SOURCE")) - .arg(Arg::with_name(options::ARCHIVE) - .short("a") + .arg(Arg::new(options::ARCHIVE) + .short('a') .long(options::ARCHIVE) - .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::NO_PRESERVE]) + .overrides_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::NO_PRESERVE]) .help("Same as -dR --preserve=all")) - .arg(Arg::with_name(options::NO_DEREFERENCE_PRESERVE_LINKS) - .short("d") + .arg(Arg::new(options::NO_DEREFERENCE_PRESERVE_LINKS) + .short('d') .help("same as --no-dereference --preserve=links")) - .arg(Arg::with_name(options::ONE_FILE_SYSTEM) - .short("x") + .arg(Arg::new(options::ONE_FILE_SYSTEM) + .short('x') .long(options::ONE_FILE_SYSTEM) .help("stay on this file system")) // TODO: implement the following args - .arg(Arg::with_name(options::COPY_CONTENTS) + .arg(Arg::new(options::COPY_CONTENTS) .long(options::COPY_CONTENTS) - .conflicts_with(options::ATTRIBUTES_ONLY) + .overrides_with(options::ATTRIBUTES_ONLY) .help("NotImplemented: copy contents of special files when recursive")) - .arg(Arg::with_name(options::SPARSE) + .arg(Arg::new(options::SPARSE) .long(options::SPARSE) .takes_value(true) .value_name("WHEN") .help("NotImplemented: control creation of sparse files. See below")) - .arg(Arg::with_name(options::CONTEXT) + .arg(Arg::new(options::CONTEXT) .long(options::CONTEXT) .takes_value(true) .value_name("CTX") .help("NotImplemented: set SELinux security context of destination file to default type")) - .arg(Arg::with_name(options::CLI_SYMBOLIC_LINKS) - .short("H") + .arg(Arg::new(options::CLI_SYMBOLIC_LINKS) + .short('H') .help("NotImplemented: follow command-line symbolic links in SOURCE")) // END TODO - .arg(Arg::with_name(options::PATHS) - .multiple(true)) + .arg(Arg::new(options::PATHS) + .multiple_occurrences(true)) } -#[uucore_procs::gen_uumain] +#[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let matches = uu_app() .after_help(&*format!( "{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP )) - .usage(&usage[..]) - .get_matches_from(args); + .try_get_matches_from(args)?; let options = Options::from_matches(&matches)?; @@ -493,43 +506,43 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } impl ClobberMode { - fn from_matches(matches: &ArgMatches) -> ClobberMode { + fn from_matches(matches: &ArgMatches) -> Self { if matches.is_present(options::FORCE) { - ClobberMode::Force + Self::Force } else if matches.is_present(options::REMOVE_DESTINATION) { - ClobberMode::RemoveDestination + Self::RemoveDestination } else { - ClobberMode::Standard + Self::Standard } } } impl OverwriteMode { - fn from_matches(matches: &ArgMatches) -> OverwriteMode { + fn from_matches(matches: &ArgMatches) -> Self { if matches.is_present(options::INTERACTIVE) { - OverwriteMode::Interactive(ClobberMode::from_matches(matches)) + Self::Interactive(ClobberMode::from_matches(matches)) } else if matches.is_present(options::NO_CLOBBER) { - OverwriteMode::NoClobber + Self::NoClobber } else { - OverwriteMode::Clobber(ClobberMode::from_matches(matches)) + Self::Clobber(ClobberMode::from_matches(matches)) } } } impl CopyMode { - fn from_matches(matches: &ArgMatches) -> CopyMode { + fn from_matches(matches: &ArgMatches) -> Self { if matches.is_present(options::LINK) { - CopyMode::Link + Self::Link } else if matches.is_present(options::SYMBOLIC_LINK) { - CopyMode::SymLink + Self::SymLink } else if matches.is_present(options::SPARSE) { - CopyMode::Sparse + Self::Sparse } else if matches.is_present(options::UPDATE) { - CopyMode::Update + Self::Update } else if matches.is_present(options::ATTRIBUTES_ONLY) { - CopyMode::AttrOnly + Self::AttrOnly } else { - CopyMode::Copy + Self::Copy } } } @@ -537,16 +550,16 @@ impl CopyMode { impl FromStr for Attribute { type Err = Error; - fn from_str(value: &str) -> CopyResult { + fn from_str(value: &str) -> CopyResult { Ok(match &*value.to_lowercase() { - "mode" => Attribute::Mode, + "mode" => Self::Mode, #[cfg(unix)] - "ownership" => Attribute::Ownership, - "timestamps" => Attribute::Timestamps, + "ownership" => Self::Ownership, + "timestamps" => Self::Timestamps, #[cfg(feature = "feat_selinux")] - "context" => Attribute::Context, - "links" => Attribute::Links, - "xattr" => Attribute::Xattr, + "context" => Self::Context, + "links" => Self::Links, + "xattr" => Self::Xattr, _ => { return Err(Error::InvalidArgument(format!( "invalid attribute {}", @@ -575,7 +588,7 @@ fn add_all_attributes() -> Vec { } impl Options { - fn from_matches(matches: &ArgMatches) -> CopyResult { + fn from_matches(matches: &ArgMatches) -> CopyResult { let not_implemented_opts = vec![ options::COPY_CONTENTS, options::SPARSE, @@ -644,7 +657,7 @@ impl Options { // if not executed first. preserve_attributes.sort_unstable(); - let options = Options { + let options = Self { attributes_only: matches.is_present(options::ATTRIBUTES_ONLY), copy_contents: matches.is_present(options::COPY_CONTENTS), copy_mode: CopyMode::from_matches(matches), @@ -701,11 +714,11 @@ 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) -> TargetType { + fn determine(sources: &[Source], target: &TargetSlice) -> Self { if sources.len() > 1 || target.is_dir() { - TargetType::Directory + Self::Directory } else { - TargetType::File + Self::File } } } @@ -739,8 +752,8 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec CopyResult<(Vec, source: &std::path::Path, - dest: std::path::PathBuf, + dest: &std::path::Path, found_hard_link: &mut bool, ) -> CopyResult<()> { // Redox does not currently support hard links @@ -766,8 +779,8 @@ fn preserve_hardlinks( let mut stat = mem::zeroed(); if libc::lstat(src_path.as_ptr(), &mut stat) < 0 { return Err(format!( - "cannot stat {:?}: {}", - src_path, + "cannot stat {}: {}", + source.quote(), std::io::Error::last_os_error() ) .into()); @@ -803,7 +816,7 @@ fn preserve_hardlinks( for hard_link in hard_links.iter() { if hard_link.1 == inode { - std::fs::hard_link(hard_link.0.clone(), dest.clone()).unwrap(); + std::fs::hard_link(hard_link.0.clone(), dest).unwrap(); *found_hard_link = true; } } @@ -838,17 +851,21 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu let mut non_fatal_errors = false; let mut seen_sources = HashSet::with_capacity(sources.len()); + let mut symlinked_files = HashSet::new(); for source in sources { if seen_sources.contains(source) { + // FIXME: compare sources by the actual file they point to, not their path. (e.g. dir/file == dir/../dir/file in most cases) show_warning!("source {} specified more than once", source.quote()); } else { let mut found_hard_link = false; if preserve_hard_links { let dest = construct_dest_path(source, target, &target_type, options)?; - preserve_hardlinks(&mut hard_links, source, dest, &mut found_hard_link).unwrap(); + preserve_hardlinks(&mut hard_links, source, &dest, &mut found_hard_link)?; } if !found_hard_link { - if let Err(error) = copy_source(source, target, &target_type, options) { + if let Err(error) = + copy_source(source, target, &target_type, options, &mut symlinked_files) + { match error { // When using --no-clobber, we don't want to show // an error message @@ -858,7 +875,7 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu } _ => { show_error!("{}", error); - non_fatal_errors = true + non_fatal_errors = true; } } } @@ -909,15 +926,16 @@ fn copy_source( target: &TargetSlice, target_type: &TargetType, options: &Options, + symlinked_files: &mut HashSet, ) -> CopyResult<()> { let source_path = Path::new(&source); if source_path.is_dir() { // Copy as directory - copy_directory(source, target, options) + copy_directory(source, target, options, symlinked_files) } else { // Copy as file let dest = construct_dest_path(source_path, target, target_type, options)?; - copy_file(source_path, dest.as_path(), options) + copy_file(source_path, dest.as_path(), options, symlinked_files) } } @@ -947,14 +965,29 @@ fn adjust_canonicalization(p: &Path) -> Cow { /// /// Any errors encountered copying files in the tree will be logged but /// will not cause a short-circuit. -fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyResult<()> { +fn copy_directory( + root: &Path, + target: &TargetSlice, + options: &Options, + symlinked_files: &mut HashSet, +) -> CopyResult<()> { if !options.recursive { return Err(format!("omitting directory {}", root.quote()).into()); } // if no-dereference is enabled and this is a symlink, copy it as a file if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink() { - return copy_file(root, target, options); + return copy_file(root, target, options, symlinked_files); + } + + // check if root is a prefix of target + if path_has_prefix(target, root)? { + return Err(format!( + "cannot copy a directory, {}, into itself, {}", + root.quote(), + target.quote() + ) + .into()); } let current_dir = @@ -1011,17 +1044,25 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR let local_to_target = target.join(&local_to_root_parent); if is_symlink && !options.dereference { - copy_link(&path, &local_to_target)?; + copy_link(&path, &local_to_target, symlinked_files)?; } else if path.is_dir() && !local_to_target.exists() { - or_continue!(fs::create_dir_all(local_to_target)); + if target.is_file() { + return Err("cannot overwrite non-directory with directory".into()); + } + fs::create_dir_all(local_to_target)?; } else if !path.is_dir() { if preserve_hard_links { let mut found_hard_link = false; let source = path.to_path_buf(); let dest = local_to_target.as_path().to_path_buf(); - preserve_hardlinks(&mut hard_links, &source, dest, &mut found_hard_link).unwrap(); + preserve_hardlinks(&mut hard_links, &source, &dest, &mut found_hard_link)?; if !found_hard_link { - match copy_file(path.as_path(), local_to_target.as_path(), options) { + match copy_file( + path.as_path(), + local_to_target.as_path(), + options, + symlinked_files, + ) { Ok(_) => Ok(()), Err(err) => { if fs::symlink_metadata(&source)?.file_type().is_symlink() { @@ -1036,7 +1077,12 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR }?; } } else { - copy_file(path.as_path(), local_to_target.as_path(), options)?; + copy_file( + path.as_path(), + local_to_target.as_path(), + options, + symlinked_files, + )?; } } } @@ -1047,8 +1093,8 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR impl OverwriteMode { fn verify(&self, path: &Path) -> CopyResult<()> { match *self { - OverwriteMode::NoClobber => Err(Error::NotAllFilesCopied), - OverwriteMode::Interactive(_) => { + Self::NoClobber => Err(Error::NotAllFilesCopied), + Self::Interactive(_) => { if prompt_yes!("{}: overwrite {}? ", uucore::util_name(), path.quote()) { Ok(()) } else { @@ -1058,7 +1104,7 @@ impl OverwriteMode { ))) } } - OverwriteMode::Clobber(_) => Ok(()), + Self::Clobber(_) => Ok(()), } } } @@ -1145,18 +1191,24 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu Ok(()) } -#[cfg(not(windows))] -#[allow(clippy::unnecessary_wraps)] // needed for windows version -fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { - match std::os::unix::fs::symlink(source, dest).context(context) { - Ok(_) => Ok(()), - Err(_) => Ok(()), +fn symlink_file( + source: &Path, + dest: &Path, + context: &str, + symlinked_files: &mut HashSet, +) -> CopyResult<()> { + #[cfg(not(windows))] + { + std::os::unix::fs::symlink(source, dest).context(context)?; } -} - -#[cfg(windows)] -fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { - Ok(std::os::windows::fs::symlink_file(source, dest).context(context)?) + #[cfg(windows)] + { + std::os::windows::fs::symlink_file(source, dest).context(context)?; + } + if let Some(file_info) = FileInformation::from_path(dest, false) { + symlinked_files.insert(file_info); + } + Ok(()) } fn context_for(src: &Path, dest: &Path) -> String { @@ -1183,6 +1235,7 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe } match options.overwrite { + // FIXME: print that the file was removed if --verbose is enabled OverwriteMode::Clobber(ClobberMode::Force) => { if fs::metadata(dest)?.permissions().readonly() { fs::remove_file(dest)?; @@ -1198,7 +1251,8 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe } /// Copy the a file from `source` to `dest`. `source` will be dereferenced if -/// `options.dereference` is set to true. `dest` will always be dereferenced. +/// `options.dereference` is set to true. `dest` will be dereferenced only if +/// the source was not a symlink. /// /// Behavior when copying to existing files is contingent on the /// `options.overwrite` mode. If a file is skipped, the return type @@ -1206,11 +1260,39 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe /// /// The original permissions of `source` will be copied to `dest` /// after a successful copy. -fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { +fn copy_file( + source: &Path, + dest: &Path, + options: &Options, + symlinked_files: &mut HashSet, +) -> CopyResult<()> { if dest.exists() { handle_existing_dest(source, dest, options)?; } + // Fail if dest is a dangling symlink or a symlink this program created previously + if fs::symlink_metadata(dest) + .map(|m| m.file_type().is_symlink()) + .unwrap_or(false) + { + if FileInformation::from_path(dest, false) + .map(|info| symlinked_files.contains(&info)) + .unwrap_or(false) + { + return Err(Error::Error(format!( + "will not copy '{}' through just-created symlink '{}'", + source.display(), + dest.display() + ))); + } + if !dest.exists() { + return Err(Error::Error(format!( + "not writing through dangling symlink '{}'", + dest.display() + ))); + } + } + if options.verbose { println!("{}", context_for(source, dest)); } @@ -1220,13 +1302,32 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { let context = context.as_str(); // canonicalize dest and source so that later steps can work with the paths directly - let dest = canonicalize(dest, MissingHandling::Missing, ResolveMode::Physical).unwrap(); let source = if options.dereference { canonicalize(source, MissingHandling::Missing, ResolveMode::Physical).unwrap() } else { source.to_owned() }; + let source_file_type = fs::symlink_metadata(&source).context(context)?.file_type(); + let source_is_symlink = source_file_type.is_symlink(); + + #[cfg(unix)] + let source_is_fifo = source_file_type.is_fifo(); + #[cfg(not(unix))] + let source_is_fifo = false; + + let dest_already_exists_as_symlink = fs::symlink_metadata(&dest) + .map(|meta| meta.file_type().is_symlink()) + .unwrap_or(false); + + let dest = if !(source_is_symlink && dest_already_exists_as_symlink) { + canonicalize(dest, MissingHandling::Missing, ResolveMode::Physical).unwrap() + } else { + // Don't canonicalize a symlink copied over another symlink, because + // then we'll end up overwriting the destination's target. + dest.to_path_buf() + }; + let dest_permissions = if dest.exists() { dest.symlink_metadata().context(context)?.permissions() } else { @@ -1252,13 +1353,30 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { match options.copy_mode { CopyMode::Link => { + if dest.exists() { + let backup_path = + backup_control::get_backup_path(options.backup, &dest, &options.backup_suffix); + if let Some(backup_path) = backup_path { + backup_dest(&dest, &backup_path)?; + fs::remove_file(&dest)?; + } + } + fs::hard_link(&source, &dest).context(context)?; } CopyMode::Copy => { - copy_helper(&source, &dest, options, context)?; + copy_helper( + &source, + &dest, + options, + context, + source_is_symlink, + source_is_fifo, + symlinked_files, + )?; } CopyMode::SymLink => { - symlink_file(&source, &dest, context)?; + symlink_file(&source, &dest, context, symlinked_files)?; } CopyMode::Sparse => return Err(Error::NotImplemented(options::SPARSE.to_string())), CopyMode::Update => { @@ -1271,10 +1389,26 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { if src_time <= dest_time { return Ok(()); } else { - copy_helper(&source, &dest, options, context)?; + copy_helper( + &source, + &dest, + options, + context, + source_is_symlink, + source_is_fifo, + symlinked_files, + )?; } } else { - copy_helper(&source, &dest, options, context)?; + copy_helper( + &source, + &dest, + options, + context, + source_is_symlink, + source_is_fifo, + symlinked_files, + )?; } } CopyMode::AttrOnly => { @@ -1292,7 +1426,13 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { .map(|meta| !meta.file_type().is_symlink()) .unwrap_or(false) { - fs::set_permissions(&dest, dest_permissions).unwrap(); + // Here, to match GNU semantics, we quietly ignore an error + // if a user does not have the correct ownership to modify + // the permissions of a file. + // + // FWIW, the OS will throw an error later, on the write op, if + // the user does not have permission to write to the file. + fs::set_permissions(&dest, dest_permissions).ok(); } for attribute in &options.preserve_attributes { copy_attribute(&source, &dest, attribute)?; @@ -1302,19 +1442,30 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// copy-on-write scheme if --reflink is specified and the filesystem supports it. -fn copy_helper(source: &Path, dest: &Path, options: &Options, context: &str) -> CopyResult<()> { +fn copy_helper( + source: &Path, + dest: &Path, + options: &Options, + context: &str, + source_is_symlink: bool, + source_is_fifo: bool, + symlinked_files: &mut HashSet, +) -> CopyResult<()> { if options.parents { let parent = dest.parent().unwrap_or(dest); fs::create_dir_all(parent)?; } - let is_symlink = fs::symlink_metadata(&source)?.file_type().is_symlink(); + if source.as_os_str() == "/dev/null" { /* workaround a limitation of fs::copy * https://github.com/rust-lang/rust/issues/79390 */ - File::create(dest)?; - } else if is_symlink { - copy_link(source, dest)?; + File::create(dest).context(dest.display().to_string())?; + } else if source_is_fifo && options.recursive { + #[cfg(unix)] + copy_fifo(dest, options.overwrite)?; + } else if source_is_symlink { + copy_link(source, dest, symlinked_files)?; } else if options.reflink_mode != ReflinkMode::Never { #[cfg(not(any(target_os = "linux", target_os = "macos")))] return Err("--reflink is only supported on linux and macOS" @@ -1332,7 +1483,28 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options, context: &str) -> Ok(()) } -fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> { +// "Copies" a FIFO by creating a new one. This workaround is because Rust's +// built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390). +#[cfg(unix)] +fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> { + if dest.exists() { + overwrite.verify(dest)?; + fs::remove_file(&dest)?; + } + + let name = CString::new(dest.as_os_str().as_bytes()).unwrap(); + let err = unsafe { mkfifo(name.as_ptr(), 0o666) }; + if err == -1 { + return Err(format!("cannot create fifo {}: File exists", dest.quote()).into()); + } + Ok(()) +} + +fn copy_link( + source: &Path, + dest: &Path, + symlinked_files: &mut HashSet, +) -> CopyResult<()> { // Here, we will copy the symlink itself (actually, just recreate it) let link = fs::read_link(&source)?; let dest: Cow<'_, Path> = if dest.is_dir() { @@ -1352,7 +1524,7 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> { } dest.into() }; - symlink_file(&link, &dest, &*context_for(&link, &dest)) + symlink_file(&link, &dest, &*context_for(&link, &dest), symlinked_files) } /// Copies `source` to `dest` using copy-on-write if possible. @@ -1411,7 +1583,6 @@ fn copy_on_write_macos( // Extract paths in a form suitable to be passed to a syscall. // The unwrap() is safe because they come from the command-line and so contain non nul // character. - use std::os::unix::ffi::OsStrExt; let src = CString::new(source.as_os_str().as_bytes()).unwrap(); let dst = CString::new(dest.as_os_str().as_bytes()).unwrap(); @@ -1497,6 +1668,13 @@ pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result { Ok(pathbuf1 == pathbuf2) } +pub fn path_has_prefix(p1: &Path, p2: &Path) -> io::Result { + let pathbuf1 = canonicalize(p1, MissingHandling::Normal, ResolveMode::Logical)?; + let pathbuf2 = canonicalize(p2, MissingHandling::Normal, ResolveMode::Logical)?; + + Ok(pathbuf1.starts_with(pathbuf2)) +} + #[test] fn test_cp_localize_to_target() { assert!( @@ -1507,5 +1685,5 @@ fn test_cp_localize_to_target() { ) .unwrap() == Path::new("target/c.txt") - ) + ); } diff --git a/src/uu/cp/src/main.rs b/src/uu/cp/src/main.rs index acfcfd1b2..920615e70 100644 --- a/src/uu/cp/src/main.rs +++ b/src/uu/cp/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_cp); +uucore::bin!(uu_cp); diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index b76e942ee..5c9ae227a 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_csplit" -version = "0.0.8" +version = "0.0.13" 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" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/ls" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/ls" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,16 +15,11 @@ edition = "2018" path = "src/csplit.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } thiserror = "1.0" regex = "1.0.0" -uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs"] } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs"] } [[bin]] name = "csplit" path = "src/main.rs" - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/csplit/LICENSE b/src/uu/csplit/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/csplit/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 0d99154df..a0b739935 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -1,16 +1,22 @@ #![crate_name = "uu_csplit"] +// spell-checker:ignore rustdoc +#![allow(rustdoc::private_intra_doc_links)] #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg, ArgMatches}; -use regex::Regex; + use std::cmp::Ordering; use std::io::{self, BufReader}; use std::{ fs::{remove_file, File}, io::{BufRead, BufWriter, Write}, }; + +use clap::{crate_version, Arg, ArgMatches, Command}; +use regex::Regex; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult}; +use uucore::{format_usage, InvalidEncodingHandling}; mod csplit_error; mod patterns; @@ -18,10 +24,10 @@ mod split_name; use crate::csplit_error::CsplitError; use crate::split_name::SplitName; -use uucore::InvalidEncodingHandling; static SUMMARY: &str = "split a file into sections determined by context lines"; static LONG_HELP: &str = "Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output."; +const USAGE: &str = "{} [OPTION]... FILE PATTERN..."; mod options { pub const SUFFIX_FORMAT: &str = "suffix-format"; @@ -35,13 +41,6 @@ mod options { pub const PATTERN: &str = "pattern"; } -fn usage() -> String { - format!( - "{0} [OPTION]... FILE PATTERN...", - uucore::execution_phrase() - ) -} - /// Command line options for csplit. pub struct CsplitOptions { split_name: crate::SplitName, @@ -52,13 +51,13 @@ pub struct CsplitOptions { } impl CsplitOptions { - fn new(matches: &ArgMatches) -> CsplitOptions { + fn new(matches: &ArgMatches) -> Self { let keep_files = matches.is_present(options::KEEP_FILES); let quiet = matches.is_present(options::QUIET); let elide_empty_files = matches.is_present(options::ELIDE_EMPTY_FILES); let suppress_matched = matches.is_present(options::SUPPRESS_MATCHED); - CsplitOptions { + Self { split_name: crash_if_err!( 1, SplitName::new( @@ -80,12 +79,12 @@ impl CsplitOptions { /// # Errors /// /// - [`io::Error`] if there is some problem reading/writing from/to a file. -/// - [`::CsplitError::LineOutOfRange`] if the line number pattern is larger than the number of input +/// - [`CsplitError::LineOutOfRange`] if the line number pattern is larger than the number of input /// lines. -/// - [`::CsplitError::LineOutOfRangeOnRepetition`], like previous but after applying the pattern +/// - [`CsplitError::LineOutOfRangeOnRepetition`], like previous but after applying the pattern /// more than once. -/// - [`::CsplitError::MatchNotFound`] if no line matched a regular expression. -/// - [`::CsplitError::MatchNotFoundOnRepetition`], like previous but after applying the pattern +/// - [`CsplitError::MatchNotFound`] if no line matched a regular expression. +/// - [`CsplitError::MatchNotFoundOnRepetition`], like previous but after applying the pattern /// more than once. pub fn csplit( options: &CsplitOptions, @@ -103,9 +102,9 @@ where input_iter.rewind_buffer(); if let Some((_, line)) = input_iter.next() { split_writer.new_writer()?; - split_writer.writeln(line?)?; + split_writer.writeln(&line?)?; for (_, line) in input_iter { - split_writer.writeln(line?)?; + split_writer.writeln(&line?)?; } split_writer.finish_split(); } @@ -240,12 +239,12 @@ impl<'a> SplitWriter<'a> { } /// Writes the line to the current split, appending a newline character. - /// If [`dev_null`] is true, then the line is discarded. + /// If [`self.dev_null`] is true, then the line is discarded. /// /// # Errors /// /// Some [`io::Error`] may occur when attempting to write the line. - fn writeln(&mut self, line: String) -> io::Result<()> { + fn writeln(&mut self, line: &str) -> io::Result<()> { if !self.dev_null { match self.current_writer { Some(ref mut current_writer) => { @@ -261,8 +260,8 @@ impl<'a> SplitWriter<'a> { } /// Perform some operations after completing a split, i.e., either remove it - /// if the [`::ELIDE_EMPTY_FILES_OPT`] option is enabled, or print how much bytes were written - /// to it if [`::QUIET_OPT`] is disabled. + /// if the [`options::ELIDE_EMPTY_FILES`] option is enabled, or print how much bytes were written + /// to it if [`options::QUIET`] is disabled. /// /// # Errors /// @@ -302,7 +301,7 @@ impl<'a> SplitWriter<'a> { /// /// In addition to errors reading/writing from/to a file, if the line number /// `n` is greater than the total available lines, then a - /// [`::CsplitError::LineOutOfRange`] error is returned. + /// [`CsplitError::LineOutOfRange`] error is returned. fn do_to_line( &mut self, pattern_as_str: &str, @@ -338,7 +337,7 @@ impl<'a> SplitWriter<'a> { } Ordering::Greater => (), } - self.writeln(l)?; + self.writeln(&l)?; } self.finish_split(); ret @@ -351,9 +350,9 @@ impl<'a> SplitWriter<'a> { /// # Errors /// /// In addition to errors reading/writing from/to a file, the following errors may be returned: - /// - if no line matched, an [`::CsplitError::MatchNotFound`]. + /// - if no line matched, an [`CsplitError::MatchNotFound`]. /// - if there are not enough lines to accommodate the offset, an - /// [`::CsplitError::LineOutOfRange`]. + /// [`CsplitError::LineOutOfRange`]. fn do_to_match( &mut self, pattern_as_str: &str, @@ -368,7 +367,7 @@ impl<'a> SplitWriter<'a> { // The offset is zero or positive, no need for a buffer on the lines read. // NOTE: drain the buffer of input_iter, no match should be done within. for line in input_iter.drain_buffer() { - self.writeln(line)?; + self.writeln(&line)?; } // retain the matching line input_iter.set_size_of_buffer(1); @@ -385,7 +384,7 @@ impl<'a> SplitWriter<'a> { ); } // a positive offset, some more lines need to be added to the current split - (false, _) => self.writeln(l)?, + (false, _) => self.writeln(&l)?, _ => (), }; offset -= 1; @@ -394,7 +393,7 @@ impl<'a> SplitWriter<'a> { while offset > 0 { match input_iter.next() { Some((_, line)) => { - self.writeln(line?)?; + self.writeln(&line?)?; } None => { self.finish_split(); @@ -408,7 +407,7 @@ impl<'a> SplitWriter<'a> { self.finish_split(); return Ok(()); } - self.writeln(l)?; + self.writeln(&l)?; } } else { // With a negative offset we use a buffer to keep the lines within the offset. @@ -422,7 +421,7 @@ impl<'a> SplitWriter<'a> { let l = line?; if regex.is_match(&l) { for line in input_iter.shrink_buffer_to_size() { - self.writeln(line)?; + self.writeln(&line)?; } if !self.options.suppress_matched { // add 1 to the buffer size to make place for the matched line @@ -439,12 +438,12 @@ impl<'a> SplitWriter<'a> { return Ok(()); } if let Some(line) = input_iter.add_line_to_buffer(ln, l) { - self.writeln(line)?; + self.writeln(&line)?; } } // no match, drain the buffer into the current split for line in input_iter.drain_buffer() { - self.writeln(line)?; + self.writeln(&line)?; } } @@ -472,8 +471,8 @@ impl InputSplitter where I: Iterator)>, { - fn new(iter: I) -> InputSplitter { - InputSplitter { + fn new(iter: I) -> Self { + Self { iter, buffer: Vec::new(), rewind: false, @@ -509,7 +508,7 @@ where self.size = size; } - /// Add a line to the buffer. If the buffer has [`size`] elements, then its head is removed and + /// Add a line to the buffer. If the buffer has [`self.size`] elements, then its head is removed and /// the new line is pushed to the buffer. The removed head is then available in the returned /// option. fn add_line_to_buffer(&mut self, ln: usize, line: String) -> Option { @@ -712,13 +711,13 @@ mod tests { } } -pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = usage(); +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = uu_app().usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); // get the file to split let file_name = matches.value_of(options::FILE).unwrap(); @@ -729,76 +728,80 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap() .map(str::to_string) .collect(); - let patterns = crash_if_err!(1, patterns::get_patterns(&patterns[..])); + let patterns = patterns::get_patterns(&patterns[..])?; let options = CsplitOptions::new(&matches); if file_name == "-" { let stdin = io::stdin(); - crash_if_err!(1, csplit(&options, patterns, stdin.lock())); + Ok(csplit(&options, patterns, stdin.lock())?) } else { - let file = crash_if_err!(1, File::open(file_name)); - let file_metadata = crash_if_err!(1, file.metadata()); + let file = File::open(file_name) + .map_err_context(|| format!("cannot access {}", file_name.quote()))?; + let file_metadata = file + .metadata() + .map_err_context(|| format!("cannot access {}", file_name.quote()))?; if !file_metadata.is_file() { - crash!(1, "{} is not a regular file", file_name.quote()); + return Err(CsplitError::NotRegularFile(file_name.to_string()).into()); } - crash_if_err!(1, csplit(&options, patterns, BufReader::new(file))); - }; - 0 + Ok(csplit(&options, patterns, BufReader::new(file))?) + } } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) .arg( - Arg::with_name(options::SUFFIX_FORMAT) - .short("b") + Arg::new(options::SUFFIX_FORMAT) + .short('b') .long(options::SUFFIX_FORMAT) .value_name("FORMAT") .help("use sprintf FORMAT instead of %02d"), ) .arg( - Arg::with_name(options::PREFIX) - .short("f") + Arg::new(options::PREFIX) + .short('f') .long(options::PREFIX) .value_name("PREFIX") .help("use PREFIX instead of 'xx'"), ) .arg( - Arg::with_name(options::KEEP_FILES) - .short("k") + Arg::new(options::KEEP_FILES) + .short('k') .long(options::KEEP_FILES) .help("do not remove output files on errors"), ) .arg( - Arg::with_name(options::SUPPRESS_MATCHED) + Arg::new(options::SUPPRESS_MATCHED) .long(options::SUPPRESS_MATCHED) .help("suppress the lines matching PATTERN"), ) .arg( - Arg::with_name(options::DIGITS) - .short("n") + Arg::new(options::DIGITS) + .short('n') .long(options::DIGITS) .value_name("DIGITS") .help("use specified number of digits instead of 2"), ) .arg( - Arg::with_name(options::QUIET) - .short("s") + Arg::new(options::QUIET) + .short('s') .long(options::QUIET) .visible_alias("silent") .help("do not print counts of output file sizes"), ) .arg( - Arg::with_name(options::ELIDE_EMPTY_FILES) - .short("z") + Arg::new(options::ELIDE_EMPTY_FILES) + .short('z') .long(options::ELIDE_EMPTY_FILES) .help("remove empty output files"), ) - .arg(Arg::with_name(options::FILE).hidden(true).required(true)) + .arg(Arg::new(options::FILE).hide(true).required(true)) .arg( - Arg::with_name(options::PATTERN) - .hidden(true) - .multiple(true) + Arg::new(options::PATTERN) + .hide(true) + .multiple_occurrences(true) .required(true), ) .after_help(LONG_HELP) diff --git a/src/uu/csplit/src/csplit_error.rs b/src/uu/csplit/src/csplit_error.rs index 1d4823ee2..b81a331a2 100644 --- a/src/uu/csplit/src/csplit_error.rs +++ b/src/uu/csplit/src/csplit_error.rs @@ -2,6 +2,7 @@ use std::io; use thiserror::Error; use uucore::display::Quotable; +use uucore::error::UError; /// Errors thrown by the csplit command #[derive(Debug, Error)] @@ -28,10 +29,18 @@ pub enum CsplitError { SuffixFormatIncorrect, #[error("too many % conversion specifications in suffix")] SuffixFormatTooManyPercents, + #[error("{} is not a regular file", ._0.quote())] + NotRegularFile(String), } impl From for CsplitError { fn from(error: io::Error) -> Self { - CsplitError::IoError(error) + Self::IoError(error) + } +} + +impl UError for CsplitError { + fn code(&self) -> i32 { + 1 } } diff --git a/src/uu/csplit/src/main.rs b/src/uu/csplit/src/main.rs index b0b144e8c..1ada30007 100644 --- a/src/uu/csplit/src/main.rs +++ b/src/uu/csplit/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_csplit); +uucore::bin!(uu_csplit); diff --git a/src/uu/csplit/src/patterns.rs b/src/uu/csplit/src/patterns.rs index 4ab7862ac..0346ed381 100644 --- a/src/uu/csplit/src/patterns.rs +++ b/src/uu/csplit/src/patterns.rs @@ -44,8 +44,8 @@ pub enum ExecutePattern { impl ExecutePattern { pub fn iter(&self) -> ExecutePatternIter { match self { - ExecutePattern::Times(n) => ExecutePatternIter::new(Some(*n)), - ExecutePattern::Always => ExecutePatternIter::new(None), + Self::Times(n) => ExecutePatternIter::new(Some(*n)), + Self::Always => ExecutePatternIter::new(None), } } } @@ -56,8 +56,8 @@ pub struct ExecutePatternIter { } impl ExecutePatternIter { - fn new(max: Option) -> ExecutePatternIter { - ExecutePatternIter { max, cur: 0 } + fn new(max: Option) -> Self { + Self { max, cur: 0 } } } @@ -88,7 +88,7 @@ impl Iterator for ExecutePatternIter { /// /// # Errors /// -/// If a pattern is incorrect, a [`::CsplitError::InvalidPattern`] error is returned, which may be +/// If a pattern is incorrect, a [`CsplitError::InvalidPattern`] error is returned, which may be /// due to, e.g.,: /// - an invalid regular expression; /// - an invalid number for, e.g., the offset. diff --git a/src/uu/csplit/src/split_name.rs b/src/uu/csplit/src/split_name.rs index 44ea2a5af..b1f4f1505 100644 --- a/src/uu/csplit/src/split_name.rs +++ b/src/uu/csplit/src/split_name.rs @@ -29,7 +29,7 @@ impl SplitName { prefix_opt: Option, format_opt: Option, n_digits_opt: Option, - ) -> Result { + ) -> Result { // get the prefix let prefix = prefix_opt.unwrap_or_else(|| "xx".to_string()); // the width for the split offset @@ -231,7 +231,7 @@ impl SplitName { } }; - Ok(SplitName { fn_split_name }) + Ok(Self { fn_split_name }) } /// Returns the filename of the i-th split. diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 991e3c449..3f800a786 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_cut" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/cut" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/cut" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,9 +15,8 @@ edition = "2018" path = "src/cut.rs" [dependencies] -clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } memchr = "2" bstr = "0.2" atty = "0.2" @@ -25,7 +24,3 @@ atty = "0.2" [[bin]] name = "cut" path = "src/main.rs" - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/cut/LICENSE b/src/uu/cut/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/cut/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 35d92b83f..2264f4f26 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -11,21 +11,22 @@ extern crate uucore; use bstr::io::BufReadExt; -use clap::{crate_version, App, Arg}; +use clap::{crate_version, Arg, Command}; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::Path; use uucore::display::Quotable; +use uucore::error::{FromIo, UResult, USimpleError}; use self::searcher::Searcher; use uucore::ranges::Range; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; mod searcher; static NAME: &str = "cut"; -static SYNTAX: &str = - "[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+"; +static USAGE: &str = + "{} [-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+"; static SUMMARY: &str = "Prints specified byte or field columns from each line of stdin or the input files"; static LONG_HELP: &str = " @@ -142,7 +143,7 @@ fn list_to_ranges(list: &str, complement: bool) -> Result, String> { } } -fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> i32 { +fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> { let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; let buf_in = BufReader::new(reader); let mut out = stdout_writer(); @@ -152,7 +153,7 @@ fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> i32 { .map_or("", String::as_str) .as_bytes(); - let res = buf_in.for_byte_record(newline_char, |line| { + let result = buf_in.for_byte_record(newline_char, |line| { let mut print_delim = false; for &Range { low, high } in ranges { if low > line.len() { @@ -171,8 +172,12 @@ fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> i32 { out.write_all(&[newline_char])?; Ok(true) }); - crash_if_err!(1, res); - 0 + + if let Err(e) = result { + return Err(USimpleError::new(1, e.to_string())); + } + + Ok(()) } #[allow(clippy::cognitive_complexity)] @@ -183,7 +188,7 @@ fn cut_fields_delimiter( only_delimited: bool, newline_char: u8, out_delim: &str, -) -> i32 { +) -> UResult<()> { let buf_in = BufReader::new(reader); let mut out = stdout_writer(); let input_delim_len = delim.len(); @@ -246,12 +251,16 @@ fn cut_fields_delimiter( out.write_all(&[newline_char])?; Ok(true) }); - crash_if_err!(1, result); - 0 + + if let Err(e) = result { + return Err(USimpleError::new(1, e.to_string())); + } + + Ok(()) } #[allow(clippy::cognitive_complexity)] -fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32 { +fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> UResult<()> { let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; if let Some(ref o_delim) = opts.out_delimiter { return cut_fields_delimiter( @@ -323,13 +332,16 @@ fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32 out.write_all(&[newline_char])?; Ok(true) }); - crash_if_err!(1, result); - 0 + + if let Err(e) = result { + return Err(USimpleError::new(1, e.to_string())); + } + + Ok(()) } -fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { +fn cut_files(mut filenames: Vec, mode: &Mode) -> UResult<()> { let mut stdin_read = false; - let mut exit_code = 0; if filenames.is_empty() { filenames.push("-".to_owned()); @@ -341,11 +353,11 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { continue; } - exit_code |= match mode { + show_if_err!(match mode { Mode::Bytes(ref ranges, ref opts) => cut_bytes(stdin(), ranges, opts), Mode::Characters(ref ranges, ref opts) => cut_bytes(stdin(), ranges, opts), Mode::Fields(ref ranges, ref opts) => cut_fields(stdin(), ranges, opts), - }; + }); stdin_read = true; } else { @@ -356,28 +368,20 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { continue; } - if path.metadata().is_err() { - show_error!("{}: No such file or directory", filename.maybe_quote()); - continue; - } - - let file = match File::open(&path) { - Ok(f) => f, - Err(e) => { - show_error!("opening {}: {}", filename.quote(), e); - continue; - } - }; - - exit_code |= match mode { - Mode::Bytes(ref ranges, ref opts) => cut_bytes(file, ranges, opts), - Mode::Characters(ref ranges, ref opts) => cut_bytes(file, ranges, opts), - Mode::Fields(ref ranges, ref opts) => cut_fields(file, ranges, opts), - }; + show_if_err!(File::open(&path) + .map_err_context(|| filename.maybe_quote().to_string()) + .and_then(|file| { + match &mode { + Mode::Bytes(ranges, opts) | Mode::Characters(ranges, opts) => { + cut_bytes(file, ranges, opts) + } + Mode::Fields(ranges, opts) => cut_fields(file, ranges, opts), + } + })); } } - exit_code + Ok(()) } mod options { @@ -392,7 +396,8 @@ mod options { pub const FILE: &str = "file"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); @@ -462,12 +467,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { delim = "="; } if delim.chars().count() > 1 { - Err(msg_opt_invalid_should_be!( - "empty or 1 character long", - "a value 2 characters or longer", - "--delimiter", - "-d" - )) + Err("invalid input: The '--delimiter' ('-d') option expects empty or 1 character long, but was provided a value 2 characters or longer".into()) } else { let delim = if delim.is_empty() { "\0".to_owned() @@ -499,13 +499,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }) } (ref b, ref c, ref f) if b.is_some() || c.is_some() || f.is_some() => Err( - msg_expects_no_more_than_one_of!("--fields (-f)", "--chars (-c)", "--bytes (-b)"), + "invalid usage: expects no more than one of --fields (-f), --chars (-c) or --bytes (-b)".into() ), - _ => Err(msg_expects_one_of!( - "--fields (-f)", - "--chars (-c)", - "--bytes (-b)" - )), + _ => Err("invalid usage: expects one of --fields (-f), --chars (-c) or --bytes (-b)".into()), }; let mode_parse = match mode_parse { @@ -514,20 +510,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.is_present(options::DELIMITER) => { - Err(msg_opt_only_usable_if!( - "printing a sequence of fields", - "--delimiter", - "-d" - )) + Err("invalid input: The '--delimiter' ('-d') option only usable if printing a sequence of fields".into()) } Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.is_present(options::ONLY_DELIMITED) => { - Err(msg_opt_only_usable_if!( - "printing a sequence of fields", - "--only-delimited", - "-s" - )) + Err("invalid input: The '--only-delimited' ('-s') option only usable if printing a sequence of fields".into()) } _ => Ok(mode), }, @@ -540,24 +528,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect(); match mode_parse { - Ok(mode) => cut_files(files, mode), - Err(err_msg) => { - show_error!("{}", err_msg); - 1 - } + Ok(mode) => cut_files(files, &mode), + Err(e) => Err(USimpleError::new(1, e)), } } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .name(NAME) .version(crate_version!()) - .usage(SYNTAX) + .override_usage(format_usage(USAGE)) .about(SUMMARY) .after_help(LONG_HELP) + .infer_long_args(true) .arg( - Arg::with_name(options::BYTES) - .short("b") + Arg::new(options::BYTES) + .short('b') .long(options::BYTES) .takes_value(true) .help("filter byte columns from the input source") @@ -566,8 +552,8 @@ pub fn uu_app() -> App<'static, 'static> { .display_order(1), ) .arg( - Arg::with_name(options::CHARACTERS) - .short("c") + Arg::new(options::CHARACTERS) + .short('c') .long(options::CHARACTERS) .help("alias for character mode") .takes_value(true) @@ -576,8 +562,8 @@ pub fn uu_app() -> App<'static, 'static> { .display_order(2), ) .arg( - Arg::with_name(options::DELIMITER) - .short("d") + Arg::new(options::DELIMITER) + .short('d') .long(options::DELIMITER) .help("specify the delimiter character that separates fields in the input source. Defaults to Tab.") .takes_value(true) @@ -585,8 +571,8 @@ pub fn uu_app() -> App<'static, 'static> { .display_order(3), ) .arg( - Arg::with_name(options::FIELDS) - .short("f") + Arg::new(options::FIELDS) + .short('f') .long(options::FIELDS) .help("filter field columns from the input source") .takes_value(true) @@ -595,30 +581,30 @@ pub fn uu_app() -> App<'static, 'static> { .display_order(4), ) .arg( - Arg::with_name(options::COMPLEMENT) + Arg::new(options::COMPLEMENT) .long(options::COMPLEMENT) .help("invert the filter - instead of displaying only the filtered columns, display all but those columns") .takes_value(false) .display_order(5), ) .arg( - Arg::with_name(options::ONLY_DELIMITED) - .short("s") + Arg::new(options::ONLY_DELIMITED) + .short('s') .long(options::ONLY_DELIMITED) .help("in field mode, only print lines which contain the delimiter") .takes_value(false) .display_order(6), ) .arg( - Arg::with_name(options::ZERO_TERMINATED) - .short("z") + Arg::new(options::ZERO_TERMINATED) + .short('z') .long(options::ZERO_TERMINATED) .help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)") .takes_value(false) .display_order(8), ) .arg( - Arg::with_name(options::OUTPUT_DELIMITER) + Arg::new(options::OUTPUT_DELIMITER) .long(options::OUTPUT_DELIMITER) .help("in field mode, replace the delimiter in output lines with this option's argument") .takes_value(true) @@ -626,8 +612,8 @@ pub fn uu_app() -> App<'static, 'static> { .display_order(7), ) .arg( - Arg::with_name(options::FILE) - .hidden(true) - .multiple(true) + Arg::new(options::FILE) + .hide(true) + .multiple_occurrences(true) ) } diff --git a/src/uu/cut/src/main.rs b/src/uu/cut/src/main.rs index 8822335f6..2f000a7f3 100644 --- a/src/uu/cut/src/main.rs +++ b/src/uu/cut/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_cut); +uucore::bin!(uu_cut); diff --git a/src/uu/cut/src/searcher.rs b/src/uu/cut/src/searcher.rs index 5fe4a723b..d8a040451 100644 --- a/src/uu/cut/src/searcher.rs +++ b/src/uu/cut/src/searcher.rs @@ -72,7 +72,7 @@ mod tests { assert_eq!(vec![] as Vec, items); } - fn test_multibyte(line: &[u8], expected: Vec) { + fn test_multibyte(line: &[u8], expected: &[usize]) { let iter = Searcher::new(line, NEEDLE); let items: Vec = iter.collect(); assert_eq!(expected, items); @@ -80,26 +80,26 @@ mod tests { #[test] fn test_multibyte_normal() { - test_multibyte("...ab...ab...".as_bytes(), vec![3, 8]); + test_multibyte("...ab...ab...".as_bytes(), &[3, 8]); } #[test] fn test_multibyte_needle_head_at_end() { - test_multibyte("a".as_bytes(), vec![]); + test_multibyte("a".as_bytes(), &[]); } #[test] fn test_multibyte_starting_needle() { - test_multibyte("ab...ab...".as_bytes(), vec![0, 5]); + test_multibyte("ab...ab...".as_bytes(), &[0, 5]); } #[test] fn test_multibyte_trailing_needle() { - test_multibyte("...ab...ab".as_bytes(), vec![3, 8]); + test_multibyte("...ab...ab".as_bytes(), &[3, 8]); } #[test] fn test_multibyte_first_byte_false_match() { - test_multibyte("aA..aCaC..ab..aD".as_bytes(), vec![10]); + test_multibyte("aA..aCaC..ab..aD".as_bytes(), &[10]); } } diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index b1077fbe1..a73a04d1b 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_date" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "date ~ (uutils) display or set the current time" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/date" +repository = "https://github.com/uutils/coreutils/tree/main/src/date" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -15,10 +15,9 @@ edition = "2018" path = "src/date.rs" [dependencies] -chrono = "0.4.4" -clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.10", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" } +chrono = "^0.4.11" +clap = { version = "3.1", features = ["wrap_help", "cargo"] } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } [target.'cfg(unix)'.dependencies] libc = "0.2" @@ -29,7 +28,3 @@ winapi = { version = "0.3", features = ["minwinbase", "sysinfoapi", "minwindef"] [[bin]] name = "date" path = "src/main.rs" - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/date/LICENSE b/src/uu/date/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/date/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index adcf77024..8946768d5 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -11,14 +11,17 @@ use chrono::{DateTime, FixedOffset, Local, Offset, Utc}; #[cfg(windows)] use chrono::{Datelike, Timelike}; -use clap::{crate_version, App, Arg}; +use clap::{crate_version, Arg, Command}; #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] use libc::{clock_settime, timespec, CLOCK_REALTIME}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; use uucore::display::Quotable; -use uucore::show_error; +#[cfg(not(any(target_os = "macos", target_os = "redox")))] +use uucore::error::FromIo; +use uucore::error::{UResult, USimpleError}; +use uucore::{format_usage, show_error}; #[cfg(windows)] use winapi::{ shared::minwindef::WORD, @@ -35,8 +38,10 @@ const MINUTE: &str = "minute"; const SECOND: &str = "second"; const NS: &str = "ns"; -const NAME: &str = "date"; const ABOUT: &str = "print or set the system date and time"; +const USAGE: &str = "\ + {} [OPTION]... [+FORMAT]... + {} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]"; const OPT_DATE: &str = "date"; const OPT_FORMAT: &str = "format"; @@ -108,11 +113,11 @@ enum Iso8601Format { impl<'a> From<&'a str> for Iso8601Format { fn from(s: &str) -> Self { match s { - HOURS | HOUR => Iso8601Format::Hours, - MINUTES | MINUTE => Iso8601Format::Minutes, - SECONDS | SECOND => Iso8601Format::Seconds, - NS => Iso8601Format::Ns, - DATE => Iso8601Format::Date, + HOURS | HOUR => Self::Hours, + MINUTES | MINUTE => Self::Minutes, + SECONDS | SECOND => Self::Seconds, + NS => Self::Ns, + DATE => Self::Date, // Should be caught by clap _ => panic!("Invalid format: {}", s), } @@ -128,27 +133,25 @@ enum Rfc3339Format { impl<'a> From<&'a str> for Rfc3339Format { fn from(s: &str) -> Self { match s { - DATE => Rfc3339Format::Date, - SECONDS | SECOND => Rfc3339Format::Seconds, - NS => Rfc3339Format::Ns, + DATE => Self::Date, + SECONDS | SECOND => Self::Seconds, + NS => Self::Ns, // Should be caught by clap _ => panic!("Invalid format: {}", s), } } } -pub fn uumain(args: impl uucore::Args) -> i32 { - let syntax = format!( - "{0} [OPTION]... [+FORMAT]... - {0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]", - NAME - ); - let matches = uu_app().usage(&syntax[..]).get_matches_from(args); +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches = uu_app().get_matches_from(args); let format = if let Some(form) = matches.value_of(OPT_FORMAT) { if !form.starts_with('+') { - show_error!("invalid date {}", form.quote()); - return 1; + return Err(USimpleError::new( + 1, + format!("invalid date {}", form.quote()), + )); } let form = form[1..].to_string(); Format::Custom(form) @@ -176,8 +179,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let set_to = match matches.value_of(OPT_SET).map(parse_date) { None => None, Some(Err((input, _err))) => { - show_error!("invalid date {}", input.quote()); - return 1; + return Err(USimpleError::new( + 1, + format!("invalid date {}", input.quote()), + )); } Some(Ok(date)) => Some(date), }; @@ -241,80 +246,80 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let formatted = date.format(format_string).to_string().replace("%f", "%N"); println!("{}", formatted); } - Err((input, _err)) => { - show_error!("invalid date {}", input.quote()); - } + Err((input, _err)) => show_error!("invalid date {}", input.quote()), } } } - 0 + Ok(()) } -pub fn uu_app() -> App<'static, 'static> { - App::new(uucore::util_name()) +pub fn uu_app<'a>() -> Command<'a> { + Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) .arg( - Arg::with_name(OPT_DATE) - .short("d") + Arg::new(OPT_DATE) + .short('d') .long(OPT_DATE) .takes_value(true) .help("display time described by STRING, not 'now'"), ) .arg( - Arg::with_name(OPT_FILE) - .short("f") + Arg::new(OPT_FILE) + .short('f') .long(OPT_FILE) .takes_value(true) .help("like --date; once for each line of DATEFILE"), ) .arg( - Arg::with_name(OPT_ISO_8601) - .short("I") + Arg::new(OPT_ISO_8601) + .short('I') .long(OPT_ISO_8601) .takes_value(true) .help(ISO_8601_HELP_STRING), ) .arg( - Arg::with_name(OPT_RFC_EMAIL) - .short("R") + Arg::new(OPT_RFC_EMAIL) + .short('R') .long(OPT_RFC_EMAIL) .help(RFC_5322_HELP_STRING), ) .arg( - Arg::with_name(OPT_RFC_3339) + Arg::new(OPT_RFC_3339) .long(OPT_RFC_3339) .takes_value(true) .help(RFC_3339_HELP_STRING), ) .arg( - Arg::with_name(OPT_DEBUG) + Arg::new(OPT_DEBUG) .long(OPT_DEBUG) .help("annotate the parsed date, and warn about questionable usage to stderr"), ) .arg( - Arg::with_name(OPT_REFERENCE) - .short("r") + Arg::new(OPT_REFERENCE) + .short('r') .long(OPT_REFERENCE) .takes_value(true) .help("display the last modification time of FILE"), ) .arg( - Arg::with_name(OPT_SET) - .short("s") + Arg::new(OPT_SET) + .short('s') .long(OPT_SET) .takes_value(true) .help(OPT_SET_HELP_STRING), ) .arg( - Arg::with_name(OPT_UNIVERSAL) - .short("u") + Arg::new(OPT_UNIVERSAL) + .short('u') .long(OPT_UNIVERSAL) .alias(OPT_UNIVERSAL_2) .help("print or set Coordinated Universal Time (UTC)"), ) - .arg(Arg::with_name(OPT_FORMAT).multiple(false)) + .arg(Arg::new(OPT_FORMAT).multiple_occurrences(false)) } /// Return the appropriate format string for the given settings. @@ -348,29 +353,33 @@ fn parse_date + Clone>( } #[cfg(not(any(unix, windows)))] -fn set_system_datetime(_date: DateTime) -> i32 { +fn set_system_datetime(_date: DateTime) -> UResult<()> { unimplemented!("setting date not implemented (unsupported target)"); } #[cfg(target_os = "macos")] -fn set_system_datetime(_date: DateTime) -> i32 { - show_error!("setting the date is not supported by macOS"); - 1 +fn set_system_datetime(_date: DateTime) -> UResult<()> { + Err(USimpleError::new( + 1, + "setting the date is not supported by macOS".to_string(), + )) } #[cfg(target_os = "redox")] -fn set_system_datetime(_date: DateTime) -> i32 { - show_error!("setting the date is not supported by Redox"); - 1 +fn set_system_datetime(_date: DateTime) -> UResult<()> { + Err(USimpleError::new( + 1, + "setting the date is not supported by Redox".to_string(), + )) } #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] /// System call to set date (unix). /// See here for more: -/// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html -/// https://linux.die.net/man/3/clock_settime -/// https://www.gnu.org/software/libc/manual/html_node/Time-Types.html -fn set_system_datetime(date: DateTime) -> i32 { +/// `` +/// `` +/// `` +fn set_system_datetime(date: DateTime) -> UResult<()> { let timespec = timespec { tv_sec: date.timestamp() as _, tv_nsec: date.timestamp_subsec_nanos() as _, @@ -379,11 +388,9 @@ fn set_system_datetime(date: DateTime) -> i32 { let result = unsafe { clock_settime(CLOCK_REALTIME, ×pec) }; if result != 0 { - let error = std::io::Error::last_os_error(); - show_error!("cannot set date: {}", error); - error.raw_os_error().unwrap() + Err(std::io::Error::last_os_error().map_err_context(|| "cannot set date".to_string())) } else { - 0 + Ok(()) } } @@ -392,7 +399,7 @@ fn set_system_datetime(date: DateTime) -> i32 { /// See here for more: /// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-setsystemtime /// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime -fn set_system_datetime(date: DateTime) -> i32 { +fn set_system_datetime(date: DateTime) -> UResult<()> { let system_time = SYSTEMTIME { wYear: date.year() as WORD, wMonth: date.month() as WORD, @@ -409,10 +416,8 @@ fn set_system_datetime(date: DateTime) -> i32 { let result = unsafe { SetSystemTime(&system_time) }; if result == 0 { - let error = std::io::Error::last_os_error(); - show_error!("cannot set date: {}", error); - error.raw_os_error().unwrap() + Err(std::io::Error::last_os_error().map_err_context(|| "cannot set date".to_string())) } else { - 0 + Ok(()) } } diff --git a/src/uu/date/src/main.rs b/src/uu/date/src/main.rs index 9064c7f67..7483582de 100644 --- a/src/uu/date/src/main.rs +++ b/src/uu/date/src/main.rs @@ -1 +1 @@ -uucore_procs::main!(uu_date); +uucore::bin!(uu_date); diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index e26c141cb..da2d43184 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "uu_dd" -version = "0.0.8" +version = "0.0.13" authors = ["uutils developers"] license = "MIT" description = "dd ~ (uutils) copy and convert files" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/dd" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/dd" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -16,14 +16,10 @@ path = "src/dd.rs" [dependencies] byte-unit = "4.0" -clap = { version = "2.33", features = [ "wrap_help" ] } +clap = { version = "3.1", features = ["wrap_help", "cargo"] } gcd = "2.0" libc = "0.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } - -[dev-dependencies] -tempfile = "^3" [target.'cfg(target_os = "linux")'.dependencies] signal-hook = "0.3.9" @@ -31,7 +27,3 @@ signal-hook = "0.3.9" [[bin]] name = "dd" path = "src/main.rs" - -[package.metadata.cargo-udeps.ignore] -# Necessary for "make all" -normal = ["uucore_procs"] diff --git a/src/uu/dd/LICENSE b/src/uu/dd/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/dd/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/dd/src/blocks.rs b/src/uu/dd/src/blocks.rs new file mode 100644 index 000000000..3f7c59c27 --- /dev/null +++ b/src/uu/dd/src/blocks.rs @@ -0,0 +1,384 @@ +// * This file is part 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 + +use crate::conversion_tables::ConversionTable; +use crate::datastructures::ConversionMode; +use crate::progress::ReadStat; + +const NEWLINE: u8 = b'\n'; +const SPACE: u8 = b' '; + +/// Split a slice into chunks, padding or truncating as necessary. +/// +/// The slice `buf` is split on newlines, then each block is resized +/// to `cbs` bytes, padding with spaces if necessary. This function +/// expects the input bytes to be ASCII-encoded. +/// +/// If `sync` is true and there has been at least one partial record +/// read from the input (as indicated in `rstat`), then leave an +/// all-spaces block at the end. Otherwise, remove the last block if +/// it is all spaces. +fn block(buf: &[u8], cbs: usize, sync: bool, rstat: &mut ReadStat) -> Vec> { + let mut blocks = buf + .split(|&e| e == NEWLINE) + .map(|split| split.to_vec()) + .fold(Vec::new(), |mut blocks, mut split| { + if split.len() > cbs { + rstat.records_truncated += 1; + } + split.resize(cbs, SPACE); + blocks.push(split); + + blocks + }); + + // If `sync` is true and there has been at least one partial + // record read from the input, then leave the all-spaces block at + // the end. Otherwise, remove it. + if let Some(last) = blocks.last() { + if (!sync || rstat.reads_partial == 0) && last.iter().all(|&e| e == SPACE) { + blocks.pop(); + } + } + + blocks +} + +/// Trims padding from each cbs-length partition of buf +/// as specified by conv=unblock and cbs=N +/// Expects ascii encoded data +fn unblock(buf: &[u8], cbs: usize) -> Vec { + buf.chunks(cbs).fold(Vec::new(), |mut acc, block| { + if let Some(last_char_idx) = block.iter().rposition(|&e| e != SPACE) { + // Include text up to last space. + acc.extend(&block[..=last_char_idx]); + } + + acc.push(NEWLINE); + acc + }) +} + +/// Apply the specified conversion, blocking, and/or unblocking in the right order. +/// +/// The `mode` specifies the combination of conversion, blocking, and +/// unblocking to apply and the order in which to apply it. This +/// function is responsible only for applying the operations. +/// +/// `buf` is the buffer of input bytes to transform. This function +/// mutates this input and also returns a new buffer of bytes +/// representing the result of the transformation. +/// +/// `rstat` maintains a running total of the number of partial and +/// complete blocks read before calling this function. In certain +/// settings of `mode`, this function will update the number of +/// records truncated; that's why `rstat` is borrowed mutably. +pub(crate) fn conv_block_unblock_helper( + mut buf: Vec, + mode: &ConversionMode, + rstat: &mut ReadStat, +) -> Vec { + // TODO This function has a mutable input `buf` but also returns a + // completely new `Vec`; that seems fishy. Could we either make + // the input immutable or make the function not return anything? + + fn apply_conversion(buf: &mut [u8], ct: &ConversionTable) { + for idx in 0..buf.len() { + buf[idx] = ct[buf[idx] as usize]; + } + } + + match mode { + ConversionMode::ConvertOnly(ct) => { + apply_conversion(&mut buf, ct); + buf + } + ConversionMode::BlockThenConvert(ct, cbs, sync) => { + let mut blocks = block(&buf, *cbs, *sync, rstat); + for buf in &mut blocks { + apply_conversion(buf, ct); + } + blocks.into_iter().flatten().collect() + } + ConversionMode::ConvertThenBlock(ct, cbs, sync) => { + apply_conversion(&mut buf, ct); + block(&buf, *cbs, *sync, rstat) + .into_iter() + .flatten() + .collect() + } + ConversionMode::BlockOnly(cbs, sync) => block(&buf, *cbs, *sync, rstat) + .into_iter() + .flatten() + .collect(), + ConversionMode::UnblockThenConvert(ct, cbs) => { + let mut buf = unblock(&buf, *cbs); + apply_conversion(&mut buf, ct); + buf + } + ConversionMode::ConvertThenUnblock(ct, cbs) => { + apply_conversion(&mut buf, ct); + unblock(&buf, *cbs) + } + ConversionMode::UnblockOnly(cbs) => unblock(&buf, *cbs), + } +} + +#[cfg(test)] +mod tests { + + use crate::blocks::{block, unblock}; + use crate::progress::ReadStat; + + const NEWLINE: u8 = b'\n'; + const SPACE: u8 = b' '; + + #[test] + fn block_test_no_nl() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8]; + let res = block(&buf, 4, false, &mut rs); + + assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]); + } + + #[test] + fn block_test_no_nl_short_record() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8]; + let res = block(&buf, 8, false, &mut rs); + + assert_eq!( + res, + vec![vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],] + ); + } + + #[test] + fn block_test_no_nl_trunc() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8, 4u8]; + let res = block(&buf, 4, false, &mut rs); + + // Commented section(s) should be truncated and appear for reference only. + assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8 /*, 4u8*/],]); + assert_eq!(rs.records_truncated, 1); + } + + #[test] + fn block_test_nl_gt_cbs_trunc() { + let mut rs = ReadStat::default(); + let buf = [ + 0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8, + ]; + let res = block(&buf, 4, false, &mut rs); + + assert_eq!( + res, + vec![ + // Commented section(s) should be truncated and appear for reference only. + vec![0u8, 1u8, 2u8, 3u8], + // vec![4u8, SPACE, SPACE, SPACE], + vec![0u8, 1u8, 2u8, 3u8], + // vec![4u8, SPACE, SPACE, SPACE], + vec![5u8, 6u8, 7u8, 8u8], + ] + ); + assert_eq!(rs.records_truncated, 2); + } + + #[test] + fn block_test_surrounded_nl() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8]; + let res = block(&buf, 8, false, &mut rs); + + assert_eq!( + res, + vec![ + vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], + vec![4u8, 5u8, 6u8, 7u8, 8u8, SPACE, SPACE, SPACE], + ] + ); + } + + #[test] + fn block_test_multiple_nl_same_cbs_block() { + let mut rs = ReadStat::default(); + let buf = [ + 0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8, 9u8, + ]; + let res = block(&buf, 8, false, &mut rs); + + assert_eq!( + res, + vec![ + vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], + vec![4u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], + vec![5u8, 6u8, 7u8, 8u8, 9u8, SPACE, SPACE, SPACE], + ] + ); + } + + #[test] + fn block_test_multiple_nl_diff_cbs_block() { + let mut rs = ReadStat::default(); + let buf = [ + 0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, NEWLINE, 8u8, 9u8, + ]; + let res = block(&buf, 8, false, &mut rs); + + assert_eq!( + res, + vec![ + vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], + vec![4u8, 5u8, 6u8, 7u8, SPACE, SPACE, SPACE, SPACE], + vec![8u8, 9u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], + ] + ); + } + + #[test] + fn block_test_end_nl_diff_cbs_block() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE]; + let res = block(&buf, 4, false, &mut rs); + + assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]); + } + + #[test] + fn block_test_end_nl_same_cbs_block() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, NEWLINE]; + let res = block(&buf, 4, false, &mut rs); + + assert_eq!(res, vec![vec![0u8, 1u8, 2u8, SPACE]]); + } + + #[test] + fn block_test_double_end_nl() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, NEWLINE, NEWLINE]; + let res = block(&buf, 4, false, &mut rs); + + assert_eq!( + res, + vec![vec![0u8, 1u8, 2u8, SPACE], vec![SPACE, SPACE, SPACE, SPACE],] + ); + } + + #[test] + fn block_test_start_nl() { + let mut rs = ReadStat::default(); + let buf = [NEWLINE, 0u8, 1u8, 2u8, 3u8]; + let res = block(&buf, 4, false, &mut rs); + + assert_eq!( + res, + vec![vec![SPACE, SPACE, SPACE, SPACE], vec![0u8, 1u8, 2u8, 3u8],] + ); + } + + #[test] + fn block_test_double_surrounded_nl_no_trunc() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8]; + let res = block(&buf, 8, false, &mut rs); + + assert_eq!( + res, + vec![ + vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], + vec![SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], + vec![4u8, 5u8, 6u8, 7u8, SPACE, SPACE, SPACE, SPACE], + ] + ); + } + + #[test] + fn block_test_double_surrounded_nl_double_trunc() { + let mut rs = ReadStat::default(); + let buf = [ + 0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8, + ]; + let res = block(&buf, 4, false, &mut rs); + + assert_eq!( + res, + vec![ + // Commented section(s) should be truncated and appear for reference only. + vec![0u8, 1u8, 2u8, 3u8], + vec![SPACE, SPACE, SPACE, SPACE], + vec![4u8, 5u8, 6u8, 7u8 /*, 8u8*/], + ] + ); + assert_eq!(rs.records_truncated, 1); + } + + #[test] + fn unblock_test_full_cbs() { + let buf = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8]; + let res = unblock(&buf, 8); + + assert_eq!(res, vec![0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, NEWLINE],); + } + + #[test] + fn unblock_test_all_space() { + let buf = [SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE]; + let res = unblock(&buf, 8); + + assert_eq!(res, vec![NEWLINE],); + } + + #[test] + fn unblock_test_decoy_spaces() { + let buf = [0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 7u8]; + let res = unblock(&buf, 8); + + assert_eq!( + res, + vec![0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 7u8, NEWLINE], + ); + } + + #[test] + fn unblock_test_strip_single_cbs() { + let buf = [0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE]; + let res = unblock(&buf, 8); + + assert_eq!(res, vec![0u8, 1u8, 2u8, 3u8, NEWLINE],); + } + + #[test] + fn unblock_test_strip_multi_cbs() { + let buf = vec![ + vec![0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], + vec![0u8, 1u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], + vec![0u8, 1u8, 2u8, SPACE, SPACE, SPACE, SPACE, SPACE], + vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], + ] + .into_iter() + .flatten() + .collect::>(); + + let res = unblock(&buf, 8); + + let exp = vec![ + vec![0u8, NEWLINE], + vec![0u8, 1u8, NEWLINE], + vec![0u8, 1u8, 2u8, NEWLINE], + vec![0u8, 1u8, 2u8, 3u8, NEWLINE], + ] + .into_iter() + .flatten() + .collect::>(); + + assert_eq!(res, exp); + } +} diff --git a/src/uu/dd/src/datastructures.rs b/src/uu/dd/src/datastructures.rs index b4410d210..ffcee4cb1 100644 --- a/src/uu/dd/src/datastructures.rs +++ b/src/uu/dd/src/datastructures.rs @@ -4,59 +4,37 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore ctable, outfile +// spell-checker:ignore ctable, outfile, iseek, oseek + +use std::error::Error; + +use uucore::error::UError; use crate::conversion_tables::*; -use std::error::Error; -use std::time; - -pub struct ProgUpdate { - pub read_stat: ReadStat, - pub write_stat: WriteStat, - pub duration: time::Duration, -} - -#[derive(Clone, Copy, Default)] -pub struct ReadStat { - pub reads_complete: u64, - pub reads_partial: u64, - pub records_truncated: u32, -} -impl std::ops::AddAssign for ReadStat { - fn add_assign(&mut self, other: Self) { - *self = Self { - reads_complete: self.reads_complete + other.reads_complete, - reads_partial: self.reads_partial + other.reads_partial, - records_truncated: self.records_truncated + other.records_truncated, - } - } -} - -#[derive(Clone, Copy)] -pub struct WriteStat { - pub writes_complete: u64, - pub writes_partial: u64, - pub bytes_total: u128, -} -impl std::ops::AddAssign for WriteStat { - fn add_assign(&mut self, other: Self) { - *self = Self { - writes_complete: self.writes_complete + other.writes_complete, - writes_partial: self.writes_partial + other.writes_partial, - bytes_total: self.bytes_total + other.bytes_total, - } - } -} - type Cbs = usize; +/// How to apply conversion, blocking, and/or unblocking. +/// +/// Certain settings of the `conv` parameter to `dd` require a +/// combination of conversion, blocking, or unblocking, applied in a +/// certain order. The variants of this enumeration give the different +/// ways of combining those three operations. +#[derive(Debug, PartialEq)] +pub(crate) enum ConversionMode<'a> { + ConvertOnly(&'a ConversionTable), + BlockOnly(Cbs, bool), + UnblockOnly(Cbs), + BlockThenConvert(&'a ConversionTable, Cbs, bool), + ConvertThenBlock(&'a ConversionTable, Cbs, bool), + UnblockThenConvert(&'a ConversionTable, Cbs), + ConvertThenUnblock(&'a ConversionTable, Cbs), +} + /// Stores all Conv Flags that apply to the input #[derive(Debug, Default, PartialEq)] -pub struct IConvFlags { - pub ctable: Option<&'static ConversionTable>, - pub block: Option, - pub unblock: Option, +pub(crate) struct IConvFlags { + pub mode: Option>, pub swab: bool, pub sync: Option, pub noerror: bool, @@ -114,46 +92,30 @@ pub struct OFlags { pub seek_bytes: bool, } -/// The value of the status cl-option. -/// Controls printing of transfer stats -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum StatusLevel { - Progress, - Noxfer, - None, -} - /// The value of count=N /// Defaults to Reads(N) /// if iflag=count_bytes /// then becomes Bytes(N) #[derive(Debug, PartialEq)] pub enum CountType { - Reads(usize), - Bytes(usize), + Reads(u64), + Bytes(u64), } #[derive(Debug)] pub enum InternalError { WrongInputType, WrongOutputType, - InvalidConvBlockUnblockCase, } impl std::fmt::Display for InternalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::WrongInputType | Self::WrongOutputType => { - write!(f, "Internal dd error: Wrong Input/Output data type") - } - Self::InvalidConvBlockUnblockCase => { - write!(f, "Invalid Conversion, Block, or Unblock data") - } - } + write!(f, "Internal dd error: Wrong Input/Output data type") } } impl Error for InternalError {} +impl UError for InternalError {} pub mod options { pub const INFILE: &str = "if"; @@ -165,6 +127,8 @@ pub mod options { pub const COUNT: &str = "count"; pub const SKIP: &str = "skip"; pub const SEEK: &str = "seek"; + pub const ISEEK: &str = "iseek"; + pub const OSEEK: &str = "oseek"; pub const STATUS: &str = "status"; pub const CONV: &str = "conv"; pub const IFLAG: &str = "iflag"; diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 9f1d28714..354e4b261 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -5,12 +5,7 @@ // 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, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat - -use uucore::InvalidEncodingHandling; - -#[cfg(test)] -mod dd_unit_tests; +// 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 mod datastructures; use datastructures::*; @@ -21,36 +16,35 @@ use parseargs::Matches; mod conversion_tables; use conversion_tables::*; -use byte_unit::Byte; -use clap::{self, crate_version}; -use gcd::Gcd; -#[cfg(target_os = "linux")] -use signal_hook::consts::signal; +mod progress; +use progress::{gen_prog_updater, ProgUpdate, ReadStat, StatusLevel, WriteStat}; + +mod blocks; +use blocks::conv_block_unblock_helper; + use std::cmp; use std::convert::TryInto; use std::env; -use std::error::Error; use std::fs::{File, OpenOptions}; use std::io::{self, Read, Seek, Write}; #[cfg(target_os = "linux")] use std::os::unix::fs::OpenOptionsExt; use std::path::Path; use std::sync::mpsc; -#[cfg(target_os = "linux")] -use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc}; use std::thread; use std::time; +use clap::{crate_version, Arg, ArgMatches, Command}; +use gcd::Gcd; +use uucore::display::Quotable; +use uucore::error::{FromIo, UResult}; +use uucore::{show_error, InvalidEncodingHandling}; + const ABOUT: &str = "copy, and optionally convert, a file system resource"; const BUF_INIT_BYTE: u8 = 0xDD; -const RTN_SUCCESS: i32 = 0; -const RTN_FAILURE: i32 = 1; -const NEWLINE: u8 = b'\n'; -const SPACE: u8 = b' '; struct Input { src: R, - non_ascii: bool, ibs: usize, print_level: Option, count: Option, @@ -59,18 +53,18 @@ struct Input { } impl Input { - fn new(matches: &Matches) -> Result> { + fn new(matches: &Matches) -> UResult { let ibs = parseargs::parse_ibs(matches)?; - let non_ascii = parseargs::parse_input_non_ascii(matches)?; let print_level = parseargs::parse_status_level(matches)?; let cflags = parseargs::parse_conv_flag_input(matches)?; let iflags = parseargs::parse_iflags(matches)?; - let skip = parseargs::parse_skip_amt(&ibs, &iflags, matches)?; + let skip = parseargs::parse_seek_skip_amt(&ibs, iflags.skip_bytes, matches, options::SKIP)?; + let iseek = + parseargs::parse_seek_skip_amt(&ibs, iflags.skip_bytes, matches, options::ISEEK)?; let count = parseargs::parse_count(&iflags, matches)?; - let mut i = Input { + let mut i = Self { src: io::stdin(), - non_ascii, ibs, print_level, count, @@ -78,10 +72,17 @@ impl Input { iflags, }; - if let Some(amt) = skip { - let mut buf = vec![BUF_INIT_BYTE; amt]; - - i.force_fill(&mut buf, amt)?; + // The --skip and --iseek flags are additive. On a stream, they discard bytes. + let amt = skip.unwrap_or(0) + iseek.unwrap_or(0); + if amt > 0 { + if let Err(e) = i.read_skip(amt) { + if let io::ErrorKind::UnexpectedEof = e.kind() { + show_error!("'standard input': cannot skip to specified offset"); + } else { + return io::Result::Err(e) + .map_err_context(|| "I/O error while skipping".to_string()); + } + } } Ok(i) @@ -125,13 +126,14 @@ fn make_linux_iflags(iflags: &IFlags) -> Option { } impl Input { - fn new(matches: &Matches) -> Result> { + fn new(matches: &Matches) -> UResult { let ibs = parseargs::parse_ibs(matches)?; - let non_ascii = parseargs::parse_input_non_ascii(matches)?; let print_level = parseargs::parse_status_level(matches)?; let cflags = parseargs::parse_conv_flag_input(matches)?; let iflags = parseargs::parse_iflags(matches)?; - let skip = parseargs::parse_skip_amt(&ibs, &iflags, matches)?; + let skip = parseargs::parse_seek_skip_amt(&ibs, iflags.skip_bytes, matches, options::SKIP)?; + let iseek = + parseargs::parse_seek_skip_amt(&ibs, iflags.skip_bytes, matches, options::ISEEK)?; let count = parseargs::parse_count(&iflags, matches)?; if let Some(fname) = matches.value_of(options::INFILE) { @@ -144,17 +146,19 @@ impl Input { opts.custom_flags(libc_flags); } - opts.open(fname)? + opts.open(fname) + .map_err_context(|| "failed to open input file".to_string())? }; - if let Some(amt) = skip { - let amt: u64 = amt.try_into()?; - src.seek(io::SeekFrom::Start(amt))?; + // The --skip and --iseek flags are additive. On a file, they seek. + let amt = skip.unwrap_or(0) + iseek.unwrap_or(0); + if amt > 0 { + src.seek(io::SeekFrom::Start(amt)) + .map_err_context(|| "failed to seek in input file".to_string())?; } - let i = Input { + let i = Self { src, - non_ascii, ibs, print_level, count, @@ -196,7 +200,7 @@ impl Input { /// Fills a given buffer. /// Reads in increments of 'self.ibs'. /// The start of each ibs-sized read follows the previous one. - fn fill_consecutive(&mut self, buf: &mut Vec) -> Result> { + fn fill_consecutive(&mut self, buf: &mut Vec) -> std::io::Result { let mut reads_complete = 0; let mut reads_partial = 0; let mut bytes_total = 0; @@ -227,7 +231,7 @@ impl Input { /// Fills a given buffer. /// Reads in increments of 'self.ibs'. /// The start of each ibs-sized read is aligned to multiples of ibs; remaining space is filled with the 'pad' byte. - fn fill_blocks(&mut self, buf: &mut Vec, pad: u8) -> Result> { + fn fill_blocks(&mut self, buf: &mut Vec, pad: u8) -> std::io::Result { let mut reads_complete = 0; let mut reads_partial = 0; let mut base_idx = 0; @@ -259,22 +263,23 @@ impl Input { }) } - /// Force-fills a buffer, ignoring zero-length reads which would otherwise be - /// interpreted as EOF. - /// Note: This will not return unless the source (eventually) produces - /// enough bytes to meet target_len. - fn force_fill(&mut self, buf: &mut [u8], target_len: usize) -> Result> { - let mut base_idx = 0; - while base_idx < target_len { - base_idx += self.read(&mut buf[base_idx..target_len])?; + /// Skips amount_to_read bytes from the Input by copying into a sink + fn read_skip(&mut self, amount_to_read: u64) -> std::io::Result<()> { + let copy_result = io::copy(&mut self.src.by_ref().take(amount_to_read), &mut io::sink()); + if let Ok(n) = copy_result { + if n != amount_to_read { + io::Result::Err(io::Error::new(io::ErrorKind::UnexpectedEof, "")) + } else { + Ok(()) + } + } else { + io::Result::Err(copy_result.unwrap_err()) } - - Ok(base_idx) } } trait OutputTrait: Sized + Write { - fn new(matches: &Matches) -> Result>; + fn new(matches: &Matches) -> UResult; fn fsync(&mut self) -> io::Result<()>; fn fdatasync(&mut self) -> io::Result<()>; } @@ -286,13 +291,25 @@ struct Output { } impl OutputTrait for Output { - fn new(matches: &Matches) -> Result> { + fn new(matches: &Matches) -> UResult { let obs = parseargs::parse_obs(matches)?; let cflags = parseargs::parse_conv_flag_output(matches)?; + let oflags = parseargs::parse_oflags(matches)?; + let seek = parseargs::parse_seek_skip_amt(&obs, oflags.seek_bytes, matches, options::SEEK)?; + let oseek = + parseargs::parse_seek_skip_amt(&obs, oflags.seek_bytes, matches, options::OSEEK)?; - let dst = io::stdout(); + let mut dst = io::stdout(); - Ok(Output { dst, obs, cflags }) + // The --seek and --oseek flags are additive. + let amt = seek.unwrap_or(0) + oseek.unwrap_or(0); + // stdout is not seekable, so we just write null bytes. + if amt > 0 { + io::copy(&mut io::repeat(0u8).take(amt as u64), &mut dst) + .map_err_context(|| String::from("write error"))?; + } + + Ok(Self { dst, obs, cflags }) } fn fsync(&mut self) -> io::Result<()> { @@ -308,22 +325,19 @@ impl Output where Self: OutputTrait, { - fn write_blocks(&mut self, buf: Vec) -> io::Result { + fn write_blocks(&mut self, buf: &[u8]) -> io::Result { let mut writes_complete = 0; let mut writes_partial = 0; let mut bytes_total = 0; for chunk in buf.chunks(self.obs) { - match self.write(chunk)? { - wlen if wlen < chunk.len() => { - writes_partial += 1; - bytes_total += wlen; - } - wlen => { - writes_complete += 1; - bytes_total += wlen; - } + let wlen = self.write(chunk)?; + if wlen < self.obs { + writes_partial += 1; + } else { + writes_complete += 1; } + bytes_total += wlen; } Ok(WriteStat { @@ -333,67 +347,111 @@ where }) } - fn dd_out(mut self, mut i: Input) -> Result<(), Box> { - let mut rstat = ReadStat { - reads_complete: 0, - reads_partial: 0, - records_truncated: 0, - }; - let mut wstat = WriteStat { - writes_complete: 0, - writes_partial: 0, - bytes_total: 0, - }; + /// Print the read/write statistics. + fn print_stats(&self, i: &Input, prog_update: &ProgUpdate) { + match i.print_level { + Some(StatusLevel::None) => {} + Some(StatusLevel::Noxfer) => prog_update.print_io_lines(), + Some(StatusLevel::Progress) | None => prog_update.print_transfer_stats(), + } + } + + /// Flush the output to disk, if configured to do so. + fn sync(&mut self) -> std::io::Result<()> { + if self.cflags.fsync { + self.fsync() + } else if self.cflags.fdatasync { + self.fdatasync() + } else { + // Intentionally do nothing in this case. + Ok(()) + } + } + + /// Copy the given input data to this output, consuming both. + /// + /// This method contains the main loop for the `dd` program. Bytes + /// are read in blocks from `i` and written in blocks to this + /// output. Read/write statistics are reported to stderr as + /// configured by the `status` command-line argument. + /// + /// # Errors + /// + /// If there is a problem reading from the input or writing to + /// this output. + fn dd_out(mut self, mut i: Input) -> std::io::Result<()> { + // The read and write statistics. + // + // These objects are counters, initialized to zero. After each + // iteration of the main loop, each will be incremented by the + // number of blocks read and written, respectively. + let mut rstat = Default::default(); + let mut wstat = Default::default(); + + // The time at which the main loop starts executing. + // + // When `status=progress` is given on the command-line, the + // `dd` program reports its progress every second or so. Part + // of its report includes the throughput in bytes per second, + // which requires knowing how long the process has been + // running. let start = time::Instant::now(); + + // A good buffer size for reading. + // + // This is an educated guess about a good buffer size based on + // the input and output block sizes. let bsize = calc_bsize(i.ibs, self.obs); - let prog_tx = { - let (tx, rx) = mpsc::channel(); - thread::spawn(gen_prog_updater(rx, i.print_level)); - tx - }; + // Start a thread that reports transfer progress. + // + // When `status=progress` is given on the command-line, the + // `dd` program reports its progress every second or so. We + // perform this reporting in a new thread so as not to take + // any CPU time away from the actual reading and writing of + // data. We send a `ProgUpdate` from the transmitter `prog_tx` + // to the receives `rx`, and the receiver prints the transfer + // information. + let (prog_tx, rx) = mpsc::channel(); + thread::spawn(gen_prog_updater(rx, i.print_level)); + // The main read/write loop. + // + // Each iteration reads blocks from the input and writes + // blocks to this output. Read/write statistics are updated on + // each iteration and cumulative statistics are reported to + // the progress reporting thread. while below_count_limit(&i.count, &rstat, &wstat) { - // Read/Write + // Read a block from the input then write the block to the output. + // + // As an optimization, make an educated guess about the + // best buffer size for reading based on the number of + // blocks already read and the number of blocks remaining. let loop_bsize = calc_loop_bsize(&i.count, &rstat, &wstat, i.ibs, bsize); - match read_helper(&mut i, loop_bsize)? { - ( - ReadStat { - reads_complete: 0, - reads_partial: 0, - .. - }, - _, - ) => break, - (rstat_update, buf) => { - let wstat_update = self.write_blocks(buf)?; + let (rstat_update, buf) = read_helper(&mut i, loop_bsize)?; + if rstat_update.is_empty() { + break; + } + let wstat_update = self.write_blocks(&buf)?; - rstat += rstat_update; - wstat += wstat_update; - } - }; - // Update Prog - prog_tx.send(ProgUpdate { - read_stat: rstat, - write_stat: wstat, - duration: start.elapsed(), - })?; + // Update the read/write stats and inform the progress thread. + // + // If the receiver is disconnected, `send()` returns an + // error. Since it is just reporting progress and is not + // crucial to the operation of `dd`, let's just ignore the + // error. + rstat += rstat_update; + wstat += wstat_update; + let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed()); + prog_tx.send(prog_update).unwrap_or(()); } - if self.cflags.fsync { - self.fsync()?; - } else if self.cflags.fdatasync { - self.fdatasync()?; - } + // Flush the output, if configured to do so. + self.sync()?; - match i.print_level { - Some(StatusLevel::Noxfer) | Some(StatusLevel::None) => {} - _ => print_transfer_stats(&ProgUpdate { - read_stat: rstat, - write_stat: wstat, - duration: start.elapsed(), - }), - } + // Print the final read/write statistics. + let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed()); + self.print_stats(&i, &prog_update); Ok(()) } } @@ -439,12 +497,11 @@ fn make_linux_oflags(oflags: &OFlags) -> Option { } impl OutputTrait for Output { - fn new(matches: &Matches) -> Result> { + fn new(matches: &Matches) -> UResult { fn open_dst(path: &Path, cflags: &OConvFlags, oflags: &OFlags) -> Result { let mut opts = OpenOptions::new(); opts.write(true) .create(!cflags.nocreat) - .truncate(!cflags.notrunc) .create_new(cflags.excl) .append(oflags.append); @@ -458,17 +515,32 @@ impl OutputTrait for Output { let obs = parseargs::parse_obs(matches)?; let cflags = parseargs::parse_conv_flag_output(matches)?; let oflags = parseargs::parse_oflags(matches)?; - let seek = parseargs::parse_seek_amt(&obs, &oflags, matches)?; + let seek = parseargs::parse_seek_skip_amt(&obs, oflags.seek_bytes, matches, options::SEEK)?; + let oseek = + parseargs::parse_seek_skip_amt(&obs, oflags.seek_bytes, matches, options::OSEEK)?; if let Some(fname) = matches.value_of(options::OUTFILE) { - let mut dst = open_dst(Path::new(&fname), &cflags, &oflags)?; + let mut dst = open_dst(Path::new(&fname), &cflags, &oflags) + .map_err_context(|| format!("failed to open {}", fname.quote()))?; - if let Some(amt) = seek { - let amt: u64 = amt.try_into()?; - dst.seek(io::SeekFrom::Start(amt))?; + // Seek to the index in the output file, truncating if requested. + // + // Calling `set_len()` may result in an error (for + // example, when calling it on `/dev/null`), but we don't + // want to terminate the process when that happens. + // Instead, we suppress the error by calling + // `Result::ok()`. This matches the behavior of GNU `dd` + // when given the command-line argument `of=/dev/null`. + + // The --seek and --oseek flags are additive. + let i = seek.unwrap_or(0) + oseek.unwrap_or(0); + if !cflags.notrunc { + dst.set_len(i).ok(); } + dst.seek(io::SeekFrom::Start(i)) + .map_err_context(|| "failed to seek in output file".to_string())?; - Ok(Output { dst, obs, cflags }) + Ok(Self { dst, obs, cflags }) } else { // The following error should only occur if someone // mistakenly calls Output::::new() without checking @@ -528,161 +600,8 @@ impl Write for Output { } } -/// Splits the content of buf into cbs-length blocks -/// Appends padding as specified by conv=block and cbs=N -/// Expects ascii encoded data -fn block(buf: Vec, cbs: usize, rstat: &mut ReadStat) -> Vec> { - let mut blocks = buf - .split(|&e| e == NEWLINE) - .map(|split| split.to_vec()) - .fold(Vec::new(), |mut blocks, mut split| { - if split.len() > cbs { - rstat.records_truncated += 1; - } - split.resize(cbs, SPACE); - blocks.push(split); - - blocks - }); - - if let Some(last) = blocks.last() { - if last.iter().all(|&e| e == SPACE) { - blocks.pop(); - } - } - - blocks -} - -/// Trims padding from each cbs-length partition of buf -/// as specified by conv=unblock and cbs=N -/// Expects ascii encoded data -fn unblock(buf: Vec, cbs: usize) -> Vec { - buf.chunks(cbs).fold(Vec::new(), |mut acc, block| { - if let Some(last_char_idx) = block.iter().rposition(|&e| e != SPACE) { - // Include text up to last space. - acc.extend(&block[..=last_char_idx]); - } - - acc.push(NEWLINE); - acc - }) -} - -/// A helper for teasing out which options must be applied and in which order. -/// Some user options, such as the presence of conversion tables, will determine whether the input is assumed to be ascii. The parser sets the Input::non_ascii flag accordingly. -/// Examples: -/// - If conv=ebcdic or conv=ibm is specified then block, unblock or swab must be performed before the conversion happens since the source will start in ascii. -/// - If conv=ascii is specified then block, unblock or swab must be performed after the conversion since the source starts in ebcdic. -/// - If no conversion is specified then the source is assumed to be in ascii. -/// For more info see `info dd` -fn conv_block_unblock_helper( - mut buf: Vec, - i: &mut Input, - rstat: &mut ReadStat, -) -> Result, Box> { - // Local Predicate Fns ------------------------------------------------- - fn should_block_then_conv(i: &Input) -> bool { - !i.non_ascii && i.cflags.block.is_some() - } - fn should_conv_then_block(i: &Input) -> bool { - i.non_ascii && i.cflags.block.is_some() - } - fn should_unblock_then_conv(i: &Input) -> bool { - !i.non_ascii && i.cflags.unblock.is_some() - } - fn should_conv_then_unblock(i: &Input) -> bool { - i.non_ascii && i.cflags.unblock.is_some() - } - fn conv_only(i: &Input) -> bool { - i.cflags.ctable.is_some() && i.cflags.block.is_none() && i.cflags.unblock.is_none() - } - // Local Helper Fns ---------------------------------------------------- - fn apply_conversion(buf: &mut [u8], ct: &ConversionTable) { - for idx in 0..buf.len() { - buf[idx] = ct[buf[idx] as usize]; - } - } - // -------------------------------------------------------------------- - if conv_only(i) { - // no block/unblock - let ct = i.cflags.ctable.unwrap(); - apply_conversion(&mut buf, ct); - - Ok(buf) - } else if should_block_then_conv(i) { - // ascii input so perform the block first - let cbs = i.cflags.block.unwrap(); - - let mut blocks = block(buf, cbs, rstat); - - if let Some(ct) = i.cflags.ctable { - for buf in blocks.iter_mut() { - apply_conversion(buf, ct); - } - } - - let blocks = blocks.into_iter().flatten().collect(); - - Ok(blocks) - } else if should_conv_then_block(i) { - // Non-ascii so perform the conversion first - let cbs = i.cflags.block.unwrap(); - - if let Some(ct) = i.cflags.ctable { - apply_conversion(&mut buf, ct); - } - - let blocks = block(buf, cbs, rstat).into_iter().flatten().collect(); - - Ok(blocks) - } else if should_unblock_then_conv(i) { - // ascii input so perform the unblock first - let cbs = i.cflags.unblock.unwrap(); - - let mut buf = unblock(buf, cbs); - - if let Some(ct) = i.cflags.ctable { - apply_conversion(&mut buf, ct); - } - - Ok(buf) - } else if should_conv_then_unblock(i) { - // Non-ascii input so perform the conversion first - let cbs = i.cflags.unblock.unwrap(); - - if let Some(ct) = i.cflags.ctable { - apply_conversion(&mut buf, ct); - } - - let buf = unblock(buf, cbs); - - Ok(buf) - } else { - // The following error should not happen, as it results from - // insufficient command line data. This case should be caught - // by the parser before making it this far. - // Producing this error is an alternative to risking an unwrap call - // on 'cbs' if the required data is not provided. - Err(Box::new(InternalError::InvalidConvBlockUnblockCase)) - } -} - /// Read helper performs read operations common to all dd reads, and dispatches the buffer to relevant helper functions as dictated by the operations requested by the user. -fn read_helper( - i: &mut Input, - bsize: usize, -) -> Result<(ReadStat, Vec), Box> { - // Local Predicate Fns ----------------------------------------------- - fn is_conv(i: &Input) -> bool { - i.cflags.ctable.is_some() - } - fn is_block(i: &Input) -> bool { - i.cflags.block.is_some() - } - fn is_unblock(i: &Input) -> bool { - i.cflags.unblock.is_some() - } +fn read_helper(i: &mut Input, bsize: usize) -> std::io::Result<(ReadStat, Vec)> { // Local Helper Fns ------------------------------------------------- fn perform_swab(buf: &mut [u8]) { for base in (1..buf.len()).step_by(2) { @@ -705,114 +624,13 @@ fn read_helper( if i.cflags.swab { perform_swab(&mut buf); } - if is_conv(i) || is_block(i) || is_unblock(i) { - let buf = conv_block_unblock_helper(buf, i, &mut rstat)?; - Ok((rstat, buf)) - } else { - Ok((rstat, buf)) - } -} -// Print io lines of a status update: -// + records in -// + records out -fn print_io_lines(update: &ProgUpdate) { - eprintln!( - "{}+{} records in", - update.read_stat.reads_complete, update.read_stat.reads_partial - ); - if update.read_stat.records_truncated > 0 { - eprintln!("{} truncated records", update.read_stat.records_truncated); - } - eprintln!( - "{}+{} records out", - update.write_stat.writes_complete, update.write_stat.writes_partial - ); -} -// Print the progress line of a status update: -// bytes (, ) copied,