1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge branch 'main' into tail_notify

This commit is contained in:
Jan Scheer 2022-04-19 22:14:55 +02:00
commit eb21330ade
No known key found for this signature in database
GPG key ID: C62AD4C29E2B9828
745 changed files with 27822 additions and 14671 deletions

View file

@ -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",
]

1
.clippy.toml Normal file
View file

@ -0,0 +1 @@
msrv = "1.56.0"

View file

@ -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: <https://github.com/foxundermoon/vs-shell-format/blob/bc56a8e367b04bbf7d9947b767dc82516a6155b7/src/shFormat.ts>
# 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: <https://blog.golang.org/gofmt>@@<https://archive.is/wip/9B6FC>
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: <https://github.com/foxundermoon/vs-shell-format/blob/bc56a8e367b04bbf7d9947b767dc82516a6155b7/src/shFormat.ts>
# 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

7
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 5

View file

@ -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: <https://github.com/est31/cargo-udeps/issues/73>
- 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: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
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: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
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: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
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: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
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: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
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: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
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: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
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: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
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: <https://github.com/actions/virtual-environments/issues/4060> , <https://github.com/actions/virtual-environments/pull/4010>
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: <https://github.com/rust-lang/rustup/pull/2783>
# * 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 <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>)
## # note: CODECOV_TOKEN / HAS_CODECOV_TOKEN is not needed for public repositories when using AppVeyor, Azure Pipelines, CircleCI, GitHub Actions, Travis (see <https://docs.codecov.io/docs/about-the-codecov-bash-uploader#section-upload-token>)
## 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

View file

@ -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 <https://github.community/t/how-to-use-env-context/16975/2>)
- main # == env.BRANCH_TARGET ## unfortunately, env context variables are only available in jobs/steps (see <https://github.community/t/how-to-use-env-context/16975/2>)
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

View file

@ -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: <https://github.com/dawidd6/action-download-artifact>
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

1
.rustfmt.toml Normal file
View file

@ -0,0 +1 @@
# * using all default `cargo fmt`/`rustfmt` options

View file

@ -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

2
.vscode/.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Configure GitHub to not mark comments in configuration files as errors
*.json linguist-language=jsonc

26
.vscode/cSpell.json vendored
View file

@ -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": []
}

View file

@ -35,6 +35,7 @@ WASM
XFS
aarch
flac
impls
lzma
# * names
@ -47,6 +48,7 @@ EditorConfig
FreeBSD
Gmail
GNU
Illumos
Irix
MS-DOS
MSDOS

View file

@ -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

View file

@ -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

View file

@ -1,12 +1,13 @@
// spell-checker:ignore (misc) matklad
// see <http://go.microsoft.com/fwlink/?LinkId=827846> 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 <http://go.microsoft.com/fwlink/?LinkId=827846> 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"
]
}

1
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1 @@
{ "cSpell.import": [".vscode/cspell.json"] }

View file

@ -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

1928
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -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 <test>`. Instead of `<test>` 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 <test>`. Instead of `<test>` 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

View file

@ -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

View file

@ -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

114
README.md
View file

@ -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 @@
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR RUNTEST UTILNAME -->
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.
<!-- ANCHOR: installation (this mark is needed for mdbook) -->
## 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
```
<!-- ANCHOR_END: installation (this mark is needed for mdbook) -->
## 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 <your test>`
1. Start to modify `<your test>` 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 | | |

137
build.rs
View file

@ -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<T> = HashMap<&'static str, (fn(T) -> i32, fn() -> App<'static, 'static>)>;\n\
"type UtilityMap<T> = phf::Map<&'static str, (fn(T) -> i32, fn() -> Command<'static>)>;\n\
\n\
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n\
\t#[allow(unused_mut)]\n\
\t#[allow(clippy::let_and_return)]\n\
\tlet mut map = UtilityMap::new();\n\
"
.as_bytes(),
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\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();

View file

@ -1 +0,0 @@
msrv = "1.47.0"

95
deny.toml Normal file
View file

@ -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 = []

3
docs/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
book
src/utils
src/SUMMARY.md

View file

@ -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)

View file

@ -1,5 +1,4 @@
UseGNU=gmake $*
all:
@$(UseGNU)
.DEFAULT:
@$(UseGNU)
clean:
rm -rf book
rm -f src/SUMMARY.md
rm -f src/utils/*

View file

@ -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

9
docs/book.toml Normal file
View file

@ -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"

View file

@ -40,6 +40,8 @@ TARGETS = [
"x86_64-linux-android",
# Solaris
"x86_64-sun-solaris",
# Illumos
"x86_64-unknown-illumos",
# WASM
"wasm32-wasi",
# Redox

View file

@ -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'),
]

View file

@ -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`

View file

@ -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

1
docs/src/contributing.md Normal file
View file

@ -0,0 +1 @@
{{ #include ../../CONTRIBUTING.md }}

20
docs/src/index.md Normal file
View file

@ -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.

3
docs/src/installation.md Normal file
View file

@ -0,0 +1,3 @@
# Installation
{{#include ../../README.md:installation }}

17
docs/src/multicall.md Normal file
View file

@ -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
```

View file

@ -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;
}

82
docs/src/test_coverage.js Normal file
View file

@ -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 = `
<span class="counts">
<span class="PASS">${totals["PASS"]}</span>
/
<span class="SKIP">${totals["SKIP"]}</span>
/
<span class="FAIL">${totals["FAIL"] + totals["ERROR"]}</span>
</span>
`;
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 = `<span class="result" style="color: var(--${content})">${content}</span> ${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);
});

19
docs/src/test_coverage.md Normal file
View file

@ -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.
<link rel="stylesheet" href="test_coverage.css">
<script src="test_coverage.js"></script>
<div id="test-cov"></div>
## Progress over time
<image src="https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.png?raw=true">

BIN
docs/theme/favicon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

16
docs/theme/head.hbs vendored Normal file
View file

@ -0,0 +1,16 @@
<style>
dd {
margin-bottom: 1em;
}
main {
position: relative;
}
.version {
position: absolute;
top: 1em;
right: 0;
}
dd > p {
margin-top: 0.2em;
}
</style>

View file

@ -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

5
renovate.json Normal file
View file

@ -0,0 +1,5 @@
{
"extends": [
"config:base"
]
}

View file

@ -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<T: uucore::Args>(
args: impl Iterator<Item = OsString>,
util_map: UtilityMap<T>,
util_map: &UtilityMap<T>,
) -> ! {
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<T: uucore::Args>(
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<T: uucore::Args>(
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<T: uucore::Args>(util_map: UtilityMap<T>) -> App<'static, 'static> {
let mut app = App::new("coreutils");
fn gen_coreutils_app<T: uucore::Args>(util_map: &UtilityMap<T>) -> 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
}

211
src/bin/uudoc.rs Normal file
View file

@ -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::<Box<dyn Iterator<Item = OsString>>>();
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::<Vec<_>>();
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<impl Read + Seek>,
) -> 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,
"<div class=\"version\">version: {}</div>",
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::<Vec<_>>()
.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<impl Read + Seek>,
) -> 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<impl Read + Seek>, name: &str) -> Option<String> {
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, "<h2>Options</h2>")?;
write!(w, "<dl>")?;
for arg in command.get_arguments() {
write!(w, "<dt>")?;
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, "<code>")?;
write!(w, "--{}", l)?;
if let Some(names) = arg.get_value_names() {
write!(
w,
"={}",
names
.iter()
.map(|x| format!("&lt;{}&gt;", x))
.collect::<Vec<_>>()
.join(" ")
)?;
}
write!(w, "</code>")?;
}
for s in arg.get_short_and_visible_aliases().unwrap_or_default() {
if !first {
write!(w, ", ")?;
} else {
first = false;
}
write!(w, "<code>")?;
write!(w, "-{}", s)?;
if let Some(names) = arg.get_value_names() {
write!(
w,
" {}",
names
.iter()
.map(|x| format!("&lt;{}&gt;", x))
.collect::<Vec<_>>()
.join(" ")
)?;
}
write!(w, "</code>")?;
}
writeln!(w, "</dt>")?;
writeln!(
w,
"<dd>\n\n{}\n\n</dd>",
arg.get_help().unwrap_or_default().replace('\n', "<br />")
)?;
}
writeln!(w, "</dl>\n")
}

View file

@ -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"

1
src/uu/arch/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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)
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_arch);
uucore::bin!(uu_arch);

View file

@ -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"]

1
src/uu/base32/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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)
}

View file

@ -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<Config> {
pub fn from(options: &clap::ArgMatches) -> UResult<Self> {
let file: Option<String> = 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<Config> {
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<Box<dyn Read + 'a>> {
@ -152,7 +154,7 @@ pub fn handle_input<R: Read>(
if !decode {
match data.encode() {
Ok(s) => {
wrap_print(&data, s);
wrap_print(&data, &s);
Ok(())
}
Err(_) => Err(USimpleError::new(

View file

@ -1 +1 @@
uucore_procs::main!(uu_base32);
uucore::bin!(uu_base32);

View file

@ -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"]

1
src/uu/base64/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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

View file

@ -1 +1 @@
uucore_procs::main!(uu_base64);
uucore::bin!(uu_base64);

View file

@ -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"]

1
src/uu/basename/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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()
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_basename);
uucore::bin!(uu_basename);

View file

@ -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"]

1
src/uu/basenc/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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

View file

@ -1 +1 @@
uucore_procs::main!(uu_basenc);
uucore::bin!(uu_basenc);

View file

@ -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"

1
src/uu/cat/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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<String>, 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<String>, options: &OutputOptions) -> UResult<()> {
};
let mut error_messages: Vec<String> = 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<R: FdReadable>(
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<R: FdReadable>(
}
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<W: Write>(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<W: Write>(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;

View file

@ -1 +1 @@
uucore_procs::main!(uu_cat);
uucore::bin!(uu_cat);

View file

@ -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" }

1
src/uu/chcon/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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<PathBuf>,
}
fn parse_command_line(config: clap::App, args: impl uucore::Args) -> Result<Options> {
let matches = config.get_matches_from_safe(args)?;
fn parse_command_line(config: clap::Command, args: impl uucore::Args) -> Result<Options> {
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)]

View file

@ -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
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_chcon);
uucore::bin!(uu_chcon);

View file

@ -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"

1
src/uu/chgrp/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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<u32>, Option<u32>, 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<u32>, Option<u32>,
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"),
)
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_chgrp);
uucore::bin!(uu_chgrp);

View file

@ -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"

1
src/uu/chmod/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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<String>) -> 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)
),
));
}
}
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_chmod);
uucore::bin!(uu_chmod);

View file

@ -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"

1
src/uu/chown/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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<u32>, Option<u32>, 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<u32>, 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<u32>, Option<u32>)> {
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<u32>, Option<u32>)> {
Some(
Group::locate(group)
.map_err(|_| USimpleError::new(1, format!("invalid group: {}", spec.quote())))?
.gid(),
.gid,
)
} else {
None

View file

@ -1 +1 @@
uucore_procs::main!(uu_chown);
uucore::bin!(uu_chown);

View file

@ -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"

1
src/uu/chroot/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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::gid_t>) -> 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::gid_t>) -> 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<libc::gid_t> = 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(())
}

View file

@ -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)
}
}
}
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_chroot);
uucore::bin!(uu_chroot);

View file

@ -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"]

1
src/uu/cksum/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -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<dyn Read> = 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<dyn Read>
} else {
Box::new(BufReader::new(File::open(p)?)) as Box<dyn Read>
}
}
};
@ -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),
)
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_cksum);
uucore::bin!(uu_cksum);

View file

@ -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"]

Some files were not shown because too many files have changed in this diff Show more