Merge branch 'master' into master

This commit is contained in:
Sylvestre Ledru 2021-07-04 11:57:59 +02:00 committed by GitHub
commit ae1935c3cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
313 changed files with 11869 additions and 7613 deletions

View file

@ -5,7 +5,9 @@ name: CICD
# spell-checker:ignore (jargon) SHAs deps softprops toolchain # spell-checker:ignore (jargon) SHAs deps softprops toolchain
# spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy # 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 (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 tempfile testsuite uutils # spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend runtest tempfile testsuite uutils
# ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45
env: env:
PROJECT_NAME: coreutils PROJECT_NAME: coreutils
@ -17,6 +19,40 @@ env:
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
code_deps:
name: Style/dependencies
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v2
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# target-specific options
# * CARGO_FEATURES_OPTION
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
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: "`cargo update` testing"
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 ; }
code_format: code_format:
name: Style/format name: Style/format
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
@ -26,18 +62,18 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
shell: bash shell: bash
run: | run: |
## VARs setup ## 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 # target-specific options
# * CARGO_FEATURES_OPTION # * CARGO_FEATURES_OPTION
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
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} outputs CARGO_FEATURES_OPTION
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -48,36 +84,19 @@ jobs:
- name: "`fmt` testing" - name: "`fmt` testing"
shell: bash shell: bash
run: | run: |
# `fmt` testing ## `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> # * 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" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::warning file=\1,line=\2::WARNING: \`cargo fmt\`: style violation/p" ; } 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" - name: "`fmt` testing of tests"
if: success() || failure() # run regardless of prior step success/failure
shell: bash shell: bash
run: | run: |
# `fmt` testing of tests ## `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> # * 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" | sed -E -n "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::warning file=\1,line=\2::WARNING: \`cargo fmt\`: style violation/p" ; } 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 ; }
code_spellcheck: code_lint:
name: Style/spelling name: Style/lint
runs-on: ${{ matrix.job.os }}
strategy:
matrix:
job:
- { os: ubuntu-latest }
steps:
- uses: actions/checkout@v1
- name: Install/setup prerequisites
shell: bash
run: |
sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell -g;
- name: Run `cspell`
shell: bash
run: |
cspell --config .vscode/cSpell.json --no-summary --no-progress "**/*" | sed "s/\(.*\):\(.*\):\(.*\) - \(.*\)/::warning file=\1,line=\2,col=\3::cspell: \4/" || true
code_warnings:
name: Style/warnings
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -87,18 +106,18 @@ jobs:
- { os: macos-latest , features: feat_os_macos } - { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows } - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
shell: bash shell: bash
run: | run: |
## VARs setup ## 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 # target-specific options
# * CARGO_FEATURES_OPTION # * CARGO_FEATURES_OPTION
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
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} outputs CARGO_FEATURES_OPTION
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -106,13 +125,32 @@ jobs:
default: true default: true
profile: minimal # minimal component installation (ie, no documentation) profile: minimal # minimal component installation (ie, no documentation)
components: clippy components: clippy
- name: "`clippy` testing" - name: "`clippy` lint testing"
if: success() || failure() # run regardless of prior step success/failure
shell: bash shell: bash
run: | run: |
# `clippy` testing ## `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> # * 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 ${{ 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:]]+(.*):([0-9]+):([0-9]+).*$/::warning file=\2,line=\3,col=\4::WARNING: \`cargo clippy\`: \1/p;" -e '}' ; } S=$(cargo +nightly clippy --all-targets ${{ 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 ; }
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: min_version:
name: MinRustV # Minimum supported rust version name: MinRustV # Minimum supported rust version
@ -122,7 +160,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }}) - name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -137,33 +175,32 @@ jobs:
use-tool-cache: true use-tool-cache: true
env: env:
RUSTUP_TOOLCHAIN: stable RUSTUP_TOOLCHAIN: stable
- name: Confirm compatible 'Cargo.lock' - name: Confirm MinSRV compatible 'Cargo.lock'
shell: bash shell: bash
run: | run: |
# Confirm compatible 'Cargo.lock' ## 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.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 'Cargo.lock' format; try \`cargo +${{ env.RUST_MIN_SRV }} update\`" ; exit 1 ; } 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: Info - name: Info
shell: bash shell: bash
run: | run: |
# Info ## Info
## environment # environment
echo "## environment" echo "## environment"
echo "CI='${CI}'" echo "CI='${CI}'"
## tooling info display # tooling info display
echo "## tooling" echo "## tooling"
which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
rustup -V rustup -V 2>/dev/null
rustup show active-toolchain rustup show active-toolchain
cargo -V cargo -V
rustc -V rustc -V
cargo-tree tree -V cargo-tree tree -V
## dependencies # dependencies
echo "## dependency list" echo "## dependency list"
cargo fetch --locked --quiet cargo fetch --locked --quiet
## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors
RUSTUP_TOOLCHAIN=stable cargo-tree tree --frozen --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
- name: Test - name: Test
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
@ -172,8 +209,8 @@ jobs:
env: env:
RUSTFLAGS: '-Awarnings' RUSTFLAGS: '-Awarnings'
busybox_test: build_makefile:
name: Busybox test suite name: Build/Makefile
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -181,45 +218,26 @@ jobs:
job: job:
- { os: ubuntu-latest } - { os: ubuntu-latest }
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable
default: true default: true
profile: minimal # minimal component installation (ie, no documentation) profile: minimal # minimal component installation (ie, no documentation)
- name: "prepare busytest" - name: Install/setup prerequisites
shell: bash shell: bash
run: | run: |
make prepare-busytest ## Install/setup prerequisites
- name: "run busybox testsuite" sudo apt-get -y update ; sudo apt-get -y install python3-sphinx ;
- name: "`make build`"
shell: bash shell: bash
run: | run: |
bindir=$(pwd)/target/debug
cd tmp/busybox-*/testsuite
S=$(bindir=$bindir ./runtest) && printf "%s\n" "$S" || { printf "%s\n" "$S" | grep "FAIL:" | sed -e "s/FAIL: /::warning ::Test failure:/g" ; }
makefile_build:
name: Test the build target of the Makefile
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest }
steps:
- uses: actions/checkout@v1
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: "Run make build"
shell: bash
run: |
sudo apt-get -y update ; sudo apt-get -y install python3-sphinx;
make build make build
- name: "`make test`"
shell: bash
run: |
make test
build: build:
name: Build name: Build
@ -231,7 +249,6 @@ jobs:
# { os, target, cargo-options, features, use-cross, toolchain } # { 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: 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: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross }
- { os: ubuntu-16.04 , target: x86_64-unknown-linux-gnu , features: feat_os_unix , 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: 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-gnu , features: feat_os_unix , use-cross: use-cross }
@ -244,11 +261,11 @@ jobs:
- { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly - { os: windows-latest , target: x86_64-pc-windows-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 } - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: Install/setup prerequisites - name: Install/setup prerequisites
shell: bash shell: bash
run: | run: |
## install/setup prerequisites ## Install/setup prerequisites
case '${{ matrix.job.target }}' in case '${{ matrix.job.target }}' in
arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; 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 ;; aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
@ -261,22 +278,20 @@ jobs:
shell: bash shell: bash
run: | run: |
## VARs setup ## 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
TOOLCHAIN="stable" ## default to "stable" toolchain TOOLCHAIN="stable" ## default to "stable" toolchain
# * specify alternate/non-default TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: GH:rust-lang/rust#47048, GH:rust-lang/rust#53454, GH:rust-lang/cargo#6754) # * specify alternate/non-default TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: GH:rust-lang/rust#47048, GH:rust-lang/rust#53454, GH:rust-lang/cargo#6754)
case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac; case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac;
# * use requested TOOLCHAIN if specified # * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
echo set-output name=TOOLCHAIN::${TOOLCHAIN:-<empty>/false} outputs TOOLCHAIN
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
# staging directory # staging directory
STAGING='_staging' STAGING='_staging'
echo set-output name=STAGING::${STAGING} outputs STAGING
echo ::set-output name=STAGING::${STAGING}
# determine EXE suffix # determine EXE suffix
EXE_suffix="" ; case '${{ matrix.job.target }}' in *-pc-windows-*) EXE_suffix=".exe" ;; esac; EXE_suffix="" ; case '${{ matrix.job.target }}' in *-pc-windows-*) EXE_suffix=".exe" ;; esac;
echo set-output name=EXE_suffix::${EXE_suffix} outputs EXE_suffix
echo ::set-output name=EXE_suffix::${EXE_suffix}
# parse commit reference info # parse commit reference info
echo GITHUB_REF=${GITHUB_REF} echo GITHUB_REF=${GITHUB_REF}
echo GITHUB_SHA=${GITHUB_SHA} echo GITHUB_SHA=${GITHUB_SHA}
@ -284,14 +299,7 @@ jobs:
unset REF_BRANCH ; case "${GITHUB_REF}" in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac; unset REF_BRANCH ; case "${GITHUB_REF}" in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac;
unset REF_TAG ; case "${GITHUB_REF}" in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac; unset REF_TAG ; case "${GITHUB_REF}" in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac;
REF_SHAS=${GITHUB_SHA:0:8} REF_SHAS=${GITHUB_SHA:0:8}
echo set-output name=REF_NAME::${REF_NAME} outputs REF_NAME REF_BRANCH REF_TAG REF_SHAS
echo set-output name=REF_BRANCH::${REF_BRANCH}
echo set-output name=REF_TAG::${REF_TAG}
echo set-output name=REF_SHAS::${REF_SHAS}
echo ::set-output name=REF_NAME::${REF_NAME}
echo ::set-output name=REF_BRANCH::${REF_BRANCH}
echo ::set-output name=REF_TAG::${REF_TAG}
echo ::set-output name=REF_SHAS::${REF_SHAS}
# parse target # parse target
unset TARGET_ARCH unset TARGET_ARCH
case '${{ matrix.job.target }}' in case '${{ matrix.job.target }}' in
@ -301,68 +309,50 @@ jobs:
i686-*) TARGET_ARCH=i686 ;; i686-*) TARGET_ARCH=i686 ;;
x86_64-*) TARGET_ARCH=x86_64 ;; x86_64-*) TARGET_ARCH=x86_64 ;;
esac; esac;
echo set-output name=TARGET_ARCH::${TARGET_ARCH}
echo ::set-output name=TARGET_ARCH::${TARGET_ARCH}
unset TARGET_OS ; case '${{ matrix.job.target }}' in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac; unset TARGET_OS ; case '${{ matrix.job.target }}' in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac;
echo set-output name=TARGET_OS::${TARGET_OS} outputs TARGET_ARCH TARGET_OS
echo ::set-output name=TARGET_OS::${TARGET_OS}
# package name # package name
PKG_suffix=".tar.gz" ; case '${{ matrix.job.target }}' in *-pc-windows-*) PKG_suffix=".zip" ;; esac; PKG_suffix=".tar.gz" ; case '${{ matrix.job.target }}' in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }} PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }}
PKG_NAME=${PKG_BASENAME}${PKG_suffix} PKG_NAME=${PKG_BASENAME}${PKG_suffix}
echo set-output name=PKG_suffix::${PKG_suffix} outputs PKG_suffix PKG_BASENAME PKG_NAME
echo set-output name=PKG_BASENAME::${PKG_BASENAME}
echo set-output name=PKG_NAME::${PKG_NAME}
echo ::set-output name=PKG_suffix::${PKG_suffix}
echo ::set-output name=PKG_BASENAME::${PKG_BASENAME}
echo ::set-output name=PKG_NAME::${PKG_NAME}
# deployable tag? (ie, leading "vM" or "M"; M == version number) # deployable tag? (ie, leading "vM" or "M"; M == version number)
unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi
echo set-output name=DEPLOY::${DEPLOY:-<empty>/false} outputs DEPLOY
echo ::set-output name=DEPLOY::${DEPLOY}
# DPKG architecture? # DPKG architecture?
unset DPKG_ARCH unset DPKG_ARCH
case ${{ matrix.job.target }} in case ${{ matrix.job.target }} in
x86_64-*-linux-*) DPKG_ARCH=amd64 ;; x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
*-linux-*) DPKG_ARCH=${TARGET_ARCH} ;; *-linux-*) DPKG_ARCH=${TARGET_ARCH} ;;
esac esac
echo set-output name=DPKG_ARCH::${DPKG_ARCH} outputs DPKG_ARCH
echo ::set-output name=DPKG_ARCH::${DPKG_ARCH}
# DPKG version? # DPKG version?
unset DPKG_VERSION ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DPKG_VERSION=${REF_TAG/#[vV]/} ; fi unset DPKG_VERSION ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DPKG_VERSION=${REF_TAG/#[vV]/} ; fi
echo set-output name=DPKG_VERSION::${DPKG_VERSION} outputs DPKG_VERSION
echo ::set-output name=DPKG_VERSION::${DPKG_VERSION}
# DPKG base name/conflicts? # DPKG base name/conflicts?
DPKG_BASENAME=${PROJECT_NAME} DPKG_BASENAME=${PROJECT_NAME}
DPKG_CONFLICTS=${PROJECT_NAME}-musl DPKG_CONFLICTS=${PROJECT_NAME}-musl
case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac; case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac;
echo set-output name=DPKG_BASENAME::${DPKG_BASENAME} outputs DPKG_BASENAME DPKG_CONFLICTS
echo set-output name=DPKG_CONFLICTS::${DPKG_CONFLICTS}
echo ::set-output name=DPKG_BASENAME::${DPKG_BASENAME}
echo ::set-output name=DPKG_CONFLICTS::${DPKG_CONFLICTS}
# DPKG name # DPKG name
unset DPKG_NAME; unset DPKG_NAME;
if [[ -n $DPKG_ARCH && -n $DPKG_VERSION ]]; then DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" ; fi if [[ -n $DPKG_ARCH && -n $DPKG_VERSION ]]; then DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" ; fi
echo set-output name=DPKG_NAME::${DPKG_NAME} outputs DPKG_NAME
echo ::set-output name=DPKG_NAME::${DPKG_NAME}
# target-specific options # target-specific options
# * CARGO_FEATURES_OPTION # * CARGO_FEATURES_OPTION
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
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} outputs CARGO_FEATURES_OPTION
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
# * CARGO_USE_CROSS (truthy) # * CARGO_USE_CROSS (truthy)
CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac; CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac;
echo set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS:-<empty>/false} outputs CARGO_USE_CROSS
echo ::set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS}
# ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml") # ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml")
if [ -n "${CARGO_USE_CROSS}" ] && [ ! -e "Cross.toml" ] ; then if [ -n "${CARGO_USE_CROSS}" ] && [ ! -e "Cross.toml" ] ; then
printf "[build.env]\npassthrough = [\"CI\"]\n" > Cross.toml printf "[build.env]\npassthrough = [\"CI\"]\n" > Cross.toml
fi fi
# * test only library and/or binaries for arm-type targets # * test only library and/or binaries for arm-type targets
unset CARGO_TEST_OPTIONS ; case '${{ matrix.job.target }}' in aarch64-* | arm-*) CARGO_TEST_OPTIONS="--bins" ;; esac; unset CARGO_TEST_OPTIONS ; case '${{ matrix.job.target }}' in aarch64-* | arm-*) CARGO_TEST_OPTIONS="--bins" ;; esac;
echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} outputs CARGO_TEST_OPTIONS
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
# * executable for `strip`? # * executable for `strip`?
STRIP="strip" STRIP="strip"
case ${{ matrix.job.target }} in case ${{ matrix.job.target }} in
@ -370,12 +360,11 @@ jobs:
arm-*-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; arm-*-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;;
*-pc-windows-msvc) STRIP="" ;; *-pc-windows-msvc) STRIP="" ;;
esac; esac;
echo set-output name=STRIP::${STRIP:-<empty>/false} outputs STRIP
echo ::set-output name=STRIP::${STRIP}
- name: Create all needed build/work directories - name: Create all needed build/work directories
shell: bash shell: bash
run: | run: |
## create build/work space ## Create build/work space
mkdir -p '${{ steps.vars.outputs.STAGING }}' mkdir -p '${{ steps.vars.outputs.STAGING }}'
mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}' mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}'
mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg' mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg'
@ -395,11 +384,12 @@ jobs:
shell: bash shell: bash
run: | run: |
## Dependent VARs setup ## 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 # * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" 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;)" CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
echo set-output name=UTILITY_LIST::${UTILITY_LIST} outputs CARGO_UTILITY_LIST_OPTIONS
echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS}
- name: Install `cargo-tree` # for dependency information - name: Install `cargo-tree` # for dependency information
uses: actions-rs/install@v0.1 uses: actions-rs/install@v0.1
with: with:
@ -411,26 +401,26 @@ jobs:
- name: Info - name: Info
shell: bash shell: bash
run: | run: |
# Info ## Info
## commit info # commit info
echo "## commit" echo "## commit"
echo GITHUB_REF=${GITHUB_REF} echo GITHUB_REF=${GITHUB_REF}
echo GITHUB_SHA=${GITHUB_SHA} echo GITHUB_SHA=${GITHUB_SHA}
## environment # environment
echo "## environment" echo "## environment"
echo "CI='${CI}'" echo "CI='${CI}'"
## tooling info display # tooling info display
echo "## tooling" echo "## tooling"
which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
rustup -V rustup -V 2>/dev/null
rustup show active-toolchain rustup show active-toolchain
cargo -V cargo -V
rustc -V rustc -V
cargo-tree tree -V cargo-tree tree -V
## dependencies # dependencies
echo "## dependency list" echo "## dependency list"
cargo fetch --locked --quiet cargo fetch --locked --quiet
cargo-tree tree --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all --no-dev-dependencies --no-indent | grep -vE "$PWD" | sort --unique cargo-tree tree --locked --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all --no-dev-dependencies --no-indent | grep -vE "$PWD" | sort --unique
- name: Build - name: Build
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
@ -457,7 +447,7 @@ jobs:
- name: Package - name: Package
shell: bash shell: bash
run: | run: |
## package artifact(s) ## Package artifact(s)
# binary # binary
cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/' cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
# `strip` binary (if needed) # `strip` binary (if needed)
@ -498,6 +488,37 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
test_busybox:
name: Tests/BusyBox test suite
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest }
steps:
- uses: actions/checkout@v2
- 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: |
## Run BusyBox test suite
bindir=$(pwd)/target/debug
cd tmp/busybox-*/testsuite
output=$(bindir=$bindir ./runtest 2>&1 || true)
printf "%s\n" "${output}"
n_fails=$(echo "$output" | grep "^FAIL:\s" | wc --lines)
if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi
coverage: coverage:
name: Code Coverage name: Code Coverage
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
@ -510,11 +531,11 @@ jobs:
- { os: macos-latest , features: macos } - { os: macos-latest , features: macos }
- { os: windows-latest , features: windows } - { os: windows-latest , features: windows }
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: Install/setup prerequisites - name: Install/setup prerequisites
shell: bash shell: bash
run: | run: |
## install/setup prerequisites ## Install/setup prerequisites
case '${{ matrix.job.os }}' in case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for testing macos-latest) brew install coreutils ;; # needed for testing
esac esac
@ -524,34 +545,31 @@ jobs:
id: vars id: vars
shell: bash shell: bash
run: | 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; }
# toolchain # toolchain
TOOLCHAIN="nightly-${{ env.RUST_COV_SRV }}" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support TOOLCHAIN="nightly-${{ env.RUST_COV_SRV }}" ## 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 # * 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; case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
# * use requested TOOLCHAIN if specified # * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
echo set-output name=TOOLCHAIN::${TOOLCHAIN} outputs TOOLCHAIN
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
# staging directory # staging directory
STAGING='_staging' STAGING='_staging'
echo set-output name=STAGING::${STAGING} outputs STAGING
echo ::set-output name=STAGING::${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>) ## # 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>) ## # 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 ## unset HAS_CODECOV_TOKEN
## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi ## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi
## echo set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN} ## outputs HAS_CODECOV_TOKEN
## echo ::set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN}
# target-specific options # target-specific options
# * CARGO_FEATURES_OPTION # * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage 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
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} outputs CARGO_FEATURES_OPTION
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
# * CODECOV_FLAGS # * CODECOV_FLAGS
CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' ) CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' )
echo set-output name=CODECOV_FLAGS::${CODECOV_FLAGS} outputs CODECOV_FLAGS
echo ::set-output name=CODECOV_FLAGS::${CODECOV_FLAGS}
- name: rust toolchain ~ install - name: rust toolchain ~ install
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -563,11 +581,11 @@ jobs:
shell: bash shell: bash
run: | run: |
## Dependent VARs setup ## 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 # * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
echo set-output name=UTILITY_LIST::${UTILITY_LIST} outputs CARGO_UTILITY_LIST_OPTIONS
echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS}
- name: Test uucore - name: Test uucore
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
@ -606,12 +624,12 @@ jobs:
with: with:
crate: grcov crate: grcov
version: latest version: latest
use-tool-cache: true use-tool-cache: false
- name: Generate coverage data (via `grcov`) - name: Generate coverage data (via `grcov`)
id: coverage id: coverage
shell: bash shell: bash
run: | run: |
# generate coverage data ## Generate coverage data
COVERAGE_REPORT_DIR="target/debug" COVERAGE_REPORT_DIR="target/debug"
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info" 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 "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?)

135
.github/workflows/FixPR.yml vendored Normal file
View file

@ -0,0 +1,135 @@
name: FixPR
# 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
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>)
types: [ closed ]
jobs:
code_deps:
# Refresh dependencies (ie, 'Cargo.lock') and show updated dependency tree
if: github.event.pull_request.merged == true ## only for PR merges
name: Update/dependencies
runs-on: ${{ matrix.job.os }}
strategy:
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v2
- name: Initialize job 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; }
# surface MSRV from CICD workflow
RUST_MIN_SRV=$(grep -P "^\s+RUST_MIN_SRV:" .github/workflows/CICD.yml | grep -Po "(?<=\x22)\d+[.]\d+(?:[.]\d+)?(?=\x22)" )
outputs RUST_MIN_SRV
- name: Install `rust` toolchain (v${{ steps.vars.outputs.RUST_MIN_SRV }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ steps.vars.outputs.RUST_MIN_SRV }}
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: Install `cargo-tree` # for dependency information
uses: actions-rs/install@v0.1
with:
crate: cargo-tree
version: latest
use-tool-cache: true
env:
RUSTUP_TOOLCHAIN: stable
- name: Ensure updated 'Cargo.lock'
shell: bash
run: |
# Ensure updated '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 || cargo +${{ steps.vars.outputs.RUST_MIN_SRV }} update
- name: Info
shell: bash
run: |
# Info
## environment
echo "## environment"
echo "CI='${CI}'"
## tooling info display
echo "## tooling"
which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
rustup -V 2>/dev/null
rustup show active-toolchain
cargo -V
rustc -V
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
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
uses: EndBug/add-and-commit@v7
with:
branch: ${{ env.BRANCH_TARGET }}
default_author: github_actions
message: "maint ~ refresh 'Cargo.lock'"
add: Cargo.lock
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
code_format:
# Recheck/refresh code formatting
if: github.event.pull_request.merged == true ## only for PR merges
name: Update/format
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v2
- name: Initialize job 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
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
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt
- name: "`cargo fmt`"
shell: bash
run: |
cargo fmt
- name: "`cargo fmt` tests"
shell: bash
run: |
# `cargo fmt` of tests
find tests -name "*.rs" -print0 | xargs -0 cargo fmt --
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
uses: EndBug/add-and-commit@v7
with:
branch: ${{ env.BRANCH_TARGET }}
default_author: github_actions
message: "maint ~ rustfmt (`cargo fmt`)"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,4 +1,6 @@
name: GNU name: GnuTests
# spell-checker:ignore (names) gnulib ; (utils) autopoint gperf pyinotify texinfo ; (vars) XPASS
on: [push, pull_request] on: [push, pull_request]
@ -7,7 +9,6 @@ jobs:
name: Run GNU tests name: Run GNU tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code uutil - name: Checkout code uutil
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
@ -18,7 +19,7 @@ jobs:
repository: 'coreutils/coreutils' repository: 'coreutils/coreutils'
path: 'gnu' path: 'gnu'
ref: v8.32 ref: v8.32
- name: Checkout GNU corelib - name: Checkout GNU coreutils library (gnulib)
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: 'coreutils/gnulib' repository: 'coreutils/gnulib'
@ -32,23 +33,26 @@ jobs:
default: true default: true
profile: minimal # minimal component installation (ie, no documentation) profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt components: rustfmt
- name: Install deps - name: Install dependencies
shell: bash shell: bash
run: | run: |
## Install dependencies
sudo apt-get update 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 python3-sphinx jq
- name: Build binaries - name: Build binaries
shell: bash shell: bash
run: | run: |
## Build binaries
cd uutils cd uutils
bash util/build-gnu.sh bash util/build-gnu.sh
- name: Run GNU tests - name: Run GNU tests
shell: bash shell: bash
run: | run: |
bash uutils/util/run-gnu-test.sh bash uutils/util/run-gnu-test.sh
- name: Extract tests info - name: Extract testing info
shell: bash shell: bash
run: | run: |
## Extract testing info
LOG_FILE=gnu/tests/test-suite.log LOG_FILE=gnu/tests/test-suite.log
if test -f "$LOG_FILE" if test -f "$LOG_FILE"
then then
@ -58,7 +62,13 @@ jobs:
FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\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) 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) ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
echo "::warning ::GNU testsuite = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR" if [[ "$TOTAL" -eq 0 || "$TOTAL" -eq 1 ]]; then
echo "Error in the execution, failing early"
exit 1
fi
output="GNU tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR"
echo "${output}"
if [[ "$FAIL" -gt 0 || "$ERROR" -gt 0 ]]; then echo "::warning ::${output}" ; fi
jq -n \ jq -n \
--arg date "$(date --rfc-email)" \ --arg date "$(date --rfc-email)" \
--arg sha "$GITHUB_SHA" \ --arg sha "$GITHUB_SHA" \
@ -72,12 +82,10 @@ jobs:
else else
echo "::error ::Failed to get summary of test results" echo "::error ::Failed to get summary of test results"
fi fi
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: test-report name: test-report
path: gnu/tests/**/*.log path: gnu/tests/**/*.log
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: gnu-result name: gnu-result

View file

@ -12,6 +12,7 @@ FIFOs
FQDN # fully qualified domain name FQDN # fully qualified domain name
GID # group ID GID # group ID
GIDs GIDs
GNU
GNUEABI GNUEABI
GNUEABIhf GNUEABIhf
JFS JFS
@ -45,6 +46,7 @@ Deno
EditorConfig EditorConfig
FreeBSD FreeBSD
Gmail Gmail
GNU
Irix Irix
MS-DOS MS-DOS
MSDOS MSDOS

View file

@ -78,6 +78,7 @@ symlinks
syscall syscall
syscalls syscalls
tokenize tokenize
toolchain
truthy truthy
unbuffered unbuffered
unescape unescape

View file

@ -58,6 +58,10 @@ Haitao Li
Inokentiy Babushkin Inokentiy Babushkin
Inokentiy Inokentiy
Babushkin Babushkin
Jan Scheer * jhscheer
Jan
Scheer
jhscheer
Jeremiah Peschka Jeremiah Peschka
Jeremiah Jeremiah
Peschka Peschka
@ -97,6 +101,9 @@ Michael Debertol
Michael Gehring Michael Gehring
Michael Michael
Gehring Gehring
Mitchell Mebane
Mitchell
Mebane
Morten Olsen Lysgaard Morten Olsen Lysgaard
Morten Morten
Olsen Olsen

View file

@ -7,6 +7,7 @@ advapi
advapi32-sys advapi32-sys
aho-corasick aho-corasick
backtrace backtrace
blake2b_simd
bstr bstr
byteorder byteorder
chacha chacha
@ -47,16 +48,19 @@ xattr
# * rust/rustc # * rust/rustc
RUSTDOCFLAGS RUSTDOCFLAGS
RUSTFLAGS RUSTFLAGS
bitxor # BitXor trait function
clippy clippy
rustc
rustfmt
rustup
#
bitor # BitOr trait function
bitxor # BitXor trait function
concat concat
fract fract
powi powi
println println
repr repr
rfind rfind
rustc
rustfmt
struct struct
structs structs
substr substr

86
Cargo.lock generated
View file

@ -6,16 +6,6 @@ version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "advapi32-sys"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a"
dependencies = [
"winapi 0.2.8",
"winapi-build",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.18"
@ -44,13 +34,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "arrayvec" name = "arrayref"
version = "0.4.12" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
dependencies = [
"nodrop", [[package]]
] name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "atty" name = "atty"
@ -100,11 +93,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]] [[package]]
name = "blake2-rfc" name = "blake2b_simd"
version = "0.2.18" version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
dependencies = [ dependencies = [
"arrayref",
"arrayvec", "arrayvec",
"constant_time_eq", "constant_time_eq",
] ]
@ -183,6 +177,7 @@ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
"strsim", "strsim",
"term_size",
"textwrap", "textwrap",
"unicode-width", "unicode-width",
"vec_map", "vec_map",
@ -224,6 +219,7 @@ version = "0.0.6"
dependencies = [ dependencies = [
"atty", "atty",
"chrono", "chrono",
"clap",
"conv", "conv",
"filetime", "filetime",
"glob 0.3.0", "glob 0.3.0",
@ -700,9 +696,9 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
@ -1383,12 +1379,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.1.9" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
@ -1442,21 +1435,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.6.0" version = "0.6.0"
@ -1501,9 +1479,9 @@ dependencies = [
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.3.0" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -1771,6 +1749,7 @@ dependencies = [
name = "uu_cat" name = "uu_cat"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"atty",
"clap", "clap",
"nix 0.20.0", "nix 0.20.0",
"thiserror", "thiserror",
@ -1783,6 +1762,7 @@ dependencies = [
name = "uu_chgrp" name = "uu_chgrp"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"walkdir", "walkdir",
@ -1871,6 +1851,7 @@ dependencies = [
name = "uu_cut" name = "uu_cut"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"atty",
"bstr", "bstr",
"clap", "clap",
"memchr 2.4.0", "memchr 2.4.0",
@ -1904,6 +1885,7 @@ dependencies = [
name = "uu_dircolors" name = "uu_dircolors"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"glob 0.3.0", "glob 0.3.0",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
@ -1964,6 +1946,7 @@ dependencies = [
name = "uu_expr" name = "uu_expr"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"libc", "libc",
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@ -1991,6 +1974,7 @@ dependencies = [
name = "uu_false" name = "uu_false"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2028,7 +2012,7 @@ dependencies = [
name = "uu_hashsum" name = "uu_hashsum"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"blake2-rfc", "blake2b_simd",
"clap", "clap",
"digest", "digest",
"hex", "hex",
@ -2056,6 +2040,7 @@ dependencies = [
name = "uu_hostid" name = "uu_hostid"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"libc", "libc",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
@ -2215,6 +2200,8 @@ dependencies = [
"nix 0.13.1", "nix 0.13.1",
"redox_syscall 0.1.57", "redox_syscall 0.1.57",
"redox_termios", "redox_termios",
"unicode-segmentation",
"unicode-width",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2258,6 +2245,7 @@ dependencies = [
name = "uu_nohup" name = "uu_nohup"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"atty",
"clap", "clap",
"libc", "libc",
"uucore", "uucore",
@ -2329,6 +2317,7 @@ name = "uu_pr"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap",
"getopts", "getopts",
"itertools 0.10.0", "itertools 0.10.0",
"quick-error 2.0.1", "quick-error 2.0.1",
@ -2351,6 +2340,7 @@ dependencies = [
name = "uu_printf" name = "uu_printf"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"itertools 0.8.2", "itertools 0.8.2",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
@ -2416,6 +2406,7 @@ dependencies = [
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"walkdir", "walkdir",
"winapi 0.3.9",
] ]
[[package]] [[package]]
@ -2483,7 +2474,6 @@ dependencies = [
"ouroboros", "ouroboros",
"rand 0.7.3", "rand 0.7.3",
"rayon", "rayon",
"semver",
"tempfile", "tempfile",
"unicode-width", "unicode-width",
"uucore", "uucore",
@ -2586,6 +2576,7 @@ dependencies = [
name = "uu_test" name = "uu_test"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"libc", "libc",
"redox_syscall 0.1.57", "redox_syscall 0.1.57",
"uucore", "uucore",
@ -2597,8 +2588,8 @@ name = "uu_timeout"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap", "clap",
"getopts",
"libc", "libc",
"nix 0.20.0",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2629,6 +2620,7 @@ dependencies = [
name = "uu_true" name = "uu_true"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2655,6 +2647,7 @@ dependencies = [
name = "uu_tty" name = "uu_tty"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"atty",
"clap", "clap",
"libc", "libc",
"uucore", "uucore",
@ -2746,7 +2739,6 @@ dependencies = [
name = "uu_whoami" name = "uu_whoami"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"advapi32-sys",
"clap", "clap",
"uucore", "uucore",
"uucore_procs", "uucore_procs",

View file

@ -63,6 +63,7 @@ feat_common_core = [
"more", "more",
"mv", "mv",
"nl", "nl",
"numfmt",
"od", "od",
"paste", "paste",
"pr", "pr",
@ -160,7 +161,6 @@ feat_require_unix = [
"mkfifo", "mkfifo",
"mknod", "mknod",
"nice", "nice",
"numfmt",
"nohup", "nohup",
"pathchk", "pathchk",
"stat", "stat",
@ -225,6 +225,7 @@ test = [ "uu_test" ]
[workspace] [workspace]
[dependencies] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
lazy_static = { version="1.3" } lazy_static = { version="1.3" }
textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review
uucore = { version=">=0.0.8", package="uucore", path="src/uucore" } uucore = { version=">=0.0.8", package="uucore", path="src/uucore" }
@ -349,9 +350,9 @@ sha1 = { version="0.6", features=["std"] }
tempfile = "3.2.0" tempfile = "3.2.0"
time = "0.1" time = "0.1"
unindent = "0.1" unindent = "0.1"
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] } uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries", "process"] }
walkdir = "2.2" walkdir = "2.2"
atty = "0.2.14" atty = "0.2"
[target.'cfg(unix)'.dev-dependencies] [target.'cfg(unix)'.dev-dependencies]
rlimit = "0.4.0" rlimit = "0.4.0"

View file

@ -268,11 +268,11 @@ test:
${CARGO} test ${CARGOFLAGS} --features "$(TESTS) $(TEST_SPEC_FEATURE)" --no-default-features $(TEST_NO_FAIL_FAST) ${CARGO} test ${CARGOFLAGS} --features "$(TESTS) $(TEST_SPEC_FEATURE)" --no-default-features $(TEST_NO_FAIL_FAST)
busybox-src: busybox-src:
if [ ! -e $(BUSYBOX_SRC) ]; then \ if [ ! -e "$(BUSYBOX_SRC)" ] ; then \
mkdir -p $(BUSYBOX_ROOT); \ mkdir -p "$(BUSYBOX_ROOT)" ; \
wget https://busybox.net/downloads/busybox-$(BUSYBOX_VER).tar.bz2 -P $(BUSYBOX_ROOT); \ wget "https://busybox.net/downloads/busybox-$(BUSYBOX_VER).tar.bz2" -P "$(BUSYBOX_ROOT)" ; \
tar -C $(BUSYBOX_ROOT) -xf $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER).tar.bz2; \ tar -C "$(BUSYBOX_ROOT)" -xf "$(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER).tar.bz2" ; \
fi; \ fi ;
# This is a busybox-specific config file their test suite wants to parse. # This is a busybox-specific config file their test suite wants to parse.
$(BUILDDIR)/.config: $(BASEDIR)/.busybox-config $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
@ -280,10 +280,12 @@ $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
# Test under the busybox test suite # Test under the busybox test suite
$(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config $(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config
cp $(BUILDDIR)/coreutils $(BUILDDIR)/busybox; \ cp "$(BUILDDIR)/coreutils" "$(BUILDDIR)/busybox"
chmod +x $@; chmod +x $@
prepare-busytest: $(BUILDDIR)/busybox prepare-busytest: $(BUILDDIR)/busybox
# disable inapplicable tests
-( cd "$(BUSYBOX_SRC)/testsuite" ; if [ -e "busybox.tests" ] ; then mv busybox.tests busybox.tests- ; fi ; )
ifeq ($(EXES),) ifeq ($(EXES),)
busytest: busytest:
@ -312,6 +314,11 @@ else
endif endif
$(foreach man, $(filter $(INSTALLEES), $(basename $(notdir $(wildcard $(DOCSDIR)/_build/man/*)))), \ $(foreach man, $(filter $(INSTALLEES), $(basename $(notdir $(wildcard $(DOCSDIR)/_build/man/*)))), \
cat $(DOCSDIR)/_build/man/$(man).1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)$(man).1.gz &&) : cat $(DOCSDIR)/_build/man/$(man).1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)$(man).1.gz &&) :
$(foreach prog, $(INSTALLEES), \
$(BUILDDIR)/coreutils completion $(prog) zsh > $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(PROG_PREFIX)$(prog); \
$(BUILDDIR)/coreutils completion $(prog) bash > $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(PROG_PREFIX)$(prog); \
$(BUILDDIR)/coreutils completion $(prog) fish > $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(PROG_PREFIX)$(prog).fish; \
)
uninstall: uninstall:
ifeq (${MULTICALL}, y) ifeq (${MULTICALL}, y)
@ -319,6 +326,9 @@ ifeq (${MULTICALL}, y)
endif endif
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz) rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz)
rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS))
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))) 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 build-docs test distclean clean busytest install uninstall

View file

@ -134,6 +134,9 @@ $ cargo install --path .
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`). This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
This does not install files necessary for shell completion. For shell completion to work,
use `GNU Make` or see `Manually install shell completions`.
### GNU Make ### GNU Make
To install all available utilities: To install all available utilities:
@ -179,6 +182,10 @@ Set install parent directory (default value is /usr/local):
$ make PREFIX=/my/path install $ make PREFIX=/my/path install
``` ```
Installing with `make` installs shell completions for all installed utilities
for `bash`, `fish` and `zsh`. Completions for `elvish` and `powershell` can also
be generated; See `Manually install shell completions`.
### NixOS ### NixOS
The [standard package set](https://nixos.org/nixpkgs/manual/) of [NixOS](https://nixos.org/) The [standard package set](https://nixos.org/nixpkgs/manual/) of [NixOS](https://nixos.org/)
@ -188,6 +195,23 @@ provides this package out of the box since 18.03:
$ nix-env -iA nixos.uutils-coreutils $ nix-env -iA nixos.uutils-coreutils
``` ```
### Manually install shell completions
The `coreutils` binary can generate completions for the `bash`, `elvish`, `fish`, `powershell`
and `zsh` shells. It prints the result to stdout.
The syntax is:
```bash
cargo run completion <utility> <shell>
```
So, to install completions for `ls` on `bash` to `/usr/local/share/bash-completion/completions/ls`,
run:
```bash
cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
```
## Un-installation Instructions ## Un-installation Instructions
Un-installation differs depending on how you have installed uutils. If you used Un-installation differs depending on how you have installed uutils. If you used
@ -342,22 +366,22 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
| Done | Semi-Done | To Do | | Done | Semi-Done | To Do |
|-----------|-----------|--------| |-----------|-----------|--------|
| arch | cp | chcon | | arch | cp | chcon |
| base32 | expr | csplit | | base32 | date | dd |
| base64 | install | dd | | base64 | df | runcon |
| basename | ls | df | | basename | expr | stty |
| cat | more | numfmt | | cat | install | |
| chgrp | od (`--strings` and 128-bit data types missing) | runcon | | chgrp | join | |
| chmod | printf | stty | | chmod | ls | |
| chown | sort | | | chown | more | |
| chroot | split | | | chroot | numfmt | |
| cksum | tail | | | cksum | od (`--strings` and 128-bit data types missing) | |
| comm | test | | | comm | pr | |
| csplit | date | | | csplit | printf | |
| cut | join | | | cut | sort | |
| dircolors | df | | | dircolors | split | |
| dirname | tac | | | dirname | tac | |
| du | pr | | | du | tail | |
| echo | | | | echo | test | |
| env | | | | env | | |
| expand | | | | expand | | |
| factor | | | | factor | | |
@ -374,12 +398,12 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
| link | | | | link | | |
| ln | | | | ln | | |
| logname | | | | logname | | |
| ~~md5sum~~ (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/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha1sum~~ (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)) | | | ~~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)) | | | ~~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)) | | | ~~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)) | | | ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| mkdir | | | | mkdir | | |
| mkfifo | | | | mkfifo | | |
| mknod | | | | mknod | | |

View file

@ -43,7 +43,7 @@ pub fn main() {
let mut tf = File::create(Path::new(&out_dir).join("test_modules.rs")).unwrap(); let mut tf = File::create(Path::new(&out_dir).join("test_modules.rs")).unwrap();
mf.write_all( mf.write_all(
"type UtilityMap<T> = HashMap<&'static str, fn(T) -> i32>;\n\ "type UtilityMap<T> = HashMap<&'static str, (fn(T) -> i32, fn() -> App<'static, 'static>)>;\n\
\n\ \n\
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n\ fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n\
\tlet mut map = UtilityMap::new();\n\ \tlet mut map = UtilityMap::new();\n\
@ -54,10 +54,33 @@ pub fn main() {
for krate in crates { for krate in crates {
match krate.as_ref() { 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();
tf.write_all(
format!(
"#[path=\"{dir}/test_test.rs\"]\nmod test_test;\n",
dir = util_tests_dir,
)
.as_bytes(),
)
.unwrap()
}
k if k.starts_with(override_prefix) => { k if k.starts_with(override_prefix) => {
mf.write_all( mf.write_all(
format!( format!(
"\tmap.insert(\"{k}\", {krate}::uumain);\n", "\tmap.insert(\"{k}\", ({krate}::uumain, {krate}::uu_app));\n",
k = krate[override_prefix.len()..].to_string(), k = krate[override_prefix.len()..].to_string(),
krate = krate krate = krate
) )
@ -77,7 +100,7 @@ pub fn main() {
"false" | "true" => { "false" | "true" => {
mf.write_all( mf.write_all(
format!( format!(
"\tmap.insert(\"{krate}\", r#{krate}::uumain);\n", "\tmap.insert(\"{krate}\", (r#{krate}::uumain, r#{krate}::uu_app));\n",
krate = krate krate = krate
) )
.as_bytes(), .as_bytes(),
@ -97,20 +120,20 @@ pub fn main() {
mf.write_all( mf.write_all(
format!( format!(
"\ "\
\tmap.insert(\"{krate}\", {krate}::uumain);\n\ \tmap.insert(\"{krate}\", ({krate}::uumain, {krate}::uu_app_custom));\n\
\t\tmap.insert(\"md5sum\", {krate}::uumain);\n\ \t\tmap.insert(\"md5sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"sha1sum\", {krate}::uumain);\n\ \t\tmap.insert(\"sha1sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"sha224sum\", {krate}::uumain);\n\ \t\tmap.insert(\"sha224sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"sha256sum\", {krate}::uumain);\n\ \t\tmap.insert(\"sha256sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"sha384sum\", {krate}::uumain);\n\ \t\tmap.insert(\"sha384sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"sha512sum\", {krate}::uumain);\n\ \t\tmap.insert(\"sha512sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"sha3sum\", {krate}::uumain);\n\ \t\tmap.insert(\"sha3sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"sha3-224sum\", {krate}::uumain);\n\ \t\tmap.insert(\"sha3-224sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"sha3-256sum\", {krate}::uumain);\n\ \t\tmap.insert(\"sha3-256sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"sha3-384sum\", {krate}::uumain);\n\ \t\tmap.insert(\"sha3-384sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"sha3-512sum\", {krate}::uumain);\n\ \t\tmap.insert(\"sha3-512sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"shake128sum\", {krate}::uumain);\n\ \t\tmap.insert(\"shake128sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
\t\tmap.insert(\"shake256sum\", {krate}::uumain);\n\ \t\tmap.insert(\"shake256sum\", ({krate}::uumain, {krate}::uu_app_common));\n\
", ",
krate = krate krate = krate
) )
@ -130,7 +153,7 @@ pub fn main() {
_ => { _ => {
mf.write_all( mf.write_all(
format!( format!(
"\tmap.insert(\"{krate}\", {krate}::uumain);\n", "\tmap.insert(\"{krate}\", ({krate}::uumain, {krate}::uu_app));\n",
krate = krate krate = krate
) )
.as_bytes(), .as_bytes(),

View file

@ -16,7 +16,7 @@ Synopsis
Description Description
----------- -----------
``uutils`` is a program that contains that other coreutils commands, somewhat ``uutils`` is a program that contains other coreutils commands, somewhat
similar to Busybox. similar to Busybox.
--help, -h print a help menu for PROGRAM displaying accepted options and --help, -h print a help menu for PROGRAM displaying accepted options and

View file

@ -5,6 +5,9 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
use clap::App;
use clap::Arg;
use clap::Shell;
use std::cmp; use std::cmp;
use std::collections::hash_map::HashMap; use std::collections::hash_map::HashMap;
use std::ffi::OsString; use std::ffi::OsString;
@ -52,7 +55,7 @@ fn main() {
let binary_as_util = name(&binary); let binary_as_util = name(&binary);
// binary name equals util name? // binary name equals util name?
if let Some(&uumain) = utils.get(binary_as_util) { if let Some(&(uumain, _)) = utils.get(binary_as_util) {
process::exit(uumain((vec![binary.into()].into_iter()).chain(args))); process::exit(uumain((vec![binary.into()].into_iter()).chain(args)));
} }
@ -74,8 +77,12 @@ fn main() {
if let Some(util_os) = util_name { if let Some(util_os) = util_name {
let util = util_os.as_os_str().to_string_lossy(); let util = util_os.as_os_str().to_string_lossy();
if util == "completion" {
gen_completions(args, utils);
}
match utils.get(&util[..]) { match utils.get(&util[..]) {
Some(&uumain) => { Some(&(uumain, _)) => {
process::exit(uumain((vec![util_os].into_iter()).chain(args))); process::exit(uumain((vec![util_os].into_iter()).chain(args)));
} }
None => { None => {
@ -85,7 +92,7 @@ fn main() {
let util = util_os.as_os_str().to_string_lossy(); let util = util_os.as_os_str().to_string_lossy();
match utils.get(&util[..]) { match utils.get(&util[..]) {
Some(&uumain) => { Some(&(uumain, _)) => {
let code = uumain( let code = uumain(
(vec![util_os, OsString::from("--help")].into_iter()) (vec![util_os, OsString::from("--help")].into_iter())
.chain(args), .chain(args),
@ -113,3 +120,50 @@ fn main() {
process::exit(0); process::exit(0);
} }
} }
/// 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>,
) -> ! {
let all_utilities: Vec<_> = std::iter::once("coreutils")
.chain(util_map.keys().copied())
.collect();
let matches = App::new("completion")
.about("Prints completions to stdout")
.arg(
Arg::with_name("utility")
.possible_values(&all_utilities)
.required(true),
)
.arg(
Arg::with_name("shell")
.possible_values(&Shell::variants())
.required(true),
)
.get_matches_from(std::iter::once(OsString::from("completion")).chain(args));
let utility = matches.value_of("utility").unwrap();
let shell = matches.value_of("shell").unwrap();
let mut app = if utility == "coreutils" {
gen_coreutils_app(util_map)
} else {
util_map.get(utility).unwrap().1()
};
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());
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");
for (_, (_, sub_app)) in util_map {
app = app.subcommand(sub_app());
}
app
}

View file

@ -16,7 +16,7 @@ path = "src/arch.rs"
[dependencies] [dependencies]
platform-info = "0.1" platform-info = "0.1"
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -12,18 +12,23 @@ extern crate uucore;
use platform_info::*; use platform_info::*;
use clap::{crate_version, App}; use clap::{crate_version, App};
use uucore::error::{FromIo, UResult};
static ABOUT: &str = "Display machine architecture"; static ABOUT: &str = "Display machine architecture";
static SUMMARY: &str = "Determine architecture name for current machine."; static SUMMARY: &str = "Determine architecture name for current machine.";
pub fn uumain(args: impl uucore::Args) -> i32 { #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uu_app().get_matches_from(args);
let uts = PlatformInfo::new().map_err_context(|| "cannot get system name".to_string())?;
println!("{}", uts.machine().trim());
Ok(())
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.after_help(SUMMARY) .after_help(SUMMARY)
.get_matches_from(args);
let uts = return_if_err!(1, PlatformInfo::new());
println!("{}", uts.machine().trim());
0
} }

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/base32.rs" path = "src/base32.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -10,6 +10,7 @@ extern crate uucore;
use std::io::{stdin, Read}; use std::io::{stdin, Read};
use clap::App;
use uucore::encoding::Format; use uucore::encoding::Format;
pub mod base_common; pub mod base_common;
@ -38,18 +39,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let config_result: Result<base_common::Config, String> = let config_result: Result<base_common::Config, String> =
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));
if config_result.is_err() {
match config_result {
Ok(_) => panic!(),
Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s),
}
}
// Create a reference to stdin so we can return a locked stdin from // Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args // parse_base_cmd_args
let stdin_raw = stdin(); let stdin_raw = stdin();
let config = config_result.unwrap();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw); let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
base_common::handle_input( base_common::handle_input(
@ -63,3 +57,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0 0
} }
pub fn uu_app() -> App<'static, 'static> {
base_common::base_app(executable!(), VERSION, ABOUT)
}

View file

@ -39,7 +39,7 @@ impl Config {
Some(mut values) => { Some(mut values) => {
let name = values.next().unwrap(); let name = values.next().unwrap();
if values.len() != 0 { if values.len() != 0 {
return Err(format!("extra operand {}", name)); return Err(format!("extra operand '{}'", name));
} }
if name == "-" { if name == "-" {
@ -54,15 +54,13 @@ impl Config {
None => None, None => None,
}; };
let cols = match options.value_of(options::WRAP) { let cols = options
Some(num) => match num.parse::<usize>() { .value_of(options::WRAP)
Ok(n) => Some(n), .map(|num| {
Err(e) => { num.parse::<usize>()
return Err(format!("Invalid wrap size: {}: {}", num, e)); .map_err(|e| format!("Invalid wrap size: '{}': {}", num, e))
} })
}, .transpose()?;
None => None,
};
Ok(Config { Ok(Config {
decode: options.is_present(options::DECODE), decode: options.is_present(options::DECODE),
@ -80,10 +78,17 @@ pub fn parse_base_cmd_args(
about: &str, about: &str,
usage: &str, usage: &str,
) -> Result<Config, String> { ) -> Result<Config, String> {
let app = App::new(name) let app = base_app(name, version, about).usage(usage);
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
Config::from(app.get_matches_from(arg_list))
}
pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static, 'a> {
App::new(name)
.version(version) .version(version)
.about(about) .about(about)
.usage(usage)
// Format arguments. // Format arguments.
.arg( .arg(
Arg::with_name(options::DECODE) Arg::with_name(options::DECODE)
@ -108,11 +113,7 @@ pub fn parse_base_cmd_args(
) )
// "multiple" arguments are used to check whether there is more than one // "multiple" arguments are used to check whether there is more than one
// file passed in. // file passed in.
.arg(Arg::with_name(options::FILE).index(1).multiple(true)); .arg(Arg::with_name(options::FILE).index(1).multiple(true))
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
Config::from(app.get_matches_from(arg_list))
} }
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> { pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> {

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/base64.rs" path = "src/base64.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"} uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"}

View file

@ -10,6 +10,7 @@
extern crate uucore; extern crate uucore;
use uu_base32::base_common; use uu_base32::base_common;
pub use uu_base32::uu_app;
use uucore::encoding::Format; use uucore::encoding::Format;
@ -38,18 +39,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let name = executable!(); let name = executable!();
let config_result: Result<base_common::Config, String> = let config_result: Result<base_common::Config, String> =
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));
if config_result.is_err() {
match config_result {
Ok(_) => panic!(),
Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s),
}
}
// Create a reference to stdin so we can return a locked stdin from // Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args // parse_base_cmd_args
let stdin_raw = stdin(); let stdin_raw = stdin();
let config = config_result.unwrap();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw); let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
base_common::handle_input( base_common::handle_input(

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/basename.rs" path = "src/basename.rs"
[dependencies] [dependencies]
clap = "2.33.2" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -40,31 +40,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// //
// Argument parsing // Argument parsing
// //
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
.version(crate_version!())
.about(SUMMARY)
.usage(&usage[..])
.arg(
Arg::with_name(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")
.long(options::SUFFIX)
.value_name("SUFFIX")
.help("remove a trailing SUFFIX; implies -a"),
)
.arg(
Arg::with_name(options::ZERO)
.short("z")
.long(options::ZERO)
.help("end each output line with NUL, not newline"),
)
.get_matches_from(args);
// too few arguments // too few arguments
if !matches.is_present(options::NAME) { if !matches.is_present(options::NAME) {
@ -110,22 +86,41 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let line_ending = if opt_zero { "\0" } else { "\n" }; let line_ending = if opt_zero { "\0" } else { "\n" };
for path in paths { for path in paths {
print!("{}{}", basename(&path, &suffix), line_ending); print!("{}{}", basename(path, suffix), line_ending);
} }
0 0
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
.arg(
Arg::with_name(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")
.long(options::SUFFIX)
.value_name("SUFFIX")
.help("remove a trailing SUFFIX; implies -a"),
)
.arg(
Arg::with_name(options::ZERO)
.short("z")
.long(options::ZERO)
.help("end each output line with NUL, not newline"),
)
}
fn basename(fullname: &str, suffix: &str) -> String { fn basename(fullname: &str, suffix: &str) -> String {
// Remove all platform-specific path separators from the end // Remove all platform-specific path separators from the end
let mut path: String = fullname let path = fullname.trim_end_matches(is_separator);
.chars()
.rev()
.skip_while(|&ch| is_separator(ch))
.collect();
// Undo reverse
path = path.chars().rev().collect();
// Convert to path buffer and get last path component // Convert to path buffer and get last path component
let pb = PathBuf::from(path); let pb = PathBuf::from(path);

View file

@ -15,8 +15,9 @@ edition = "2018"
path = "src/cat.rs" path = "src/cat.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
thiserror = "1.0" thiserror = "1.0"
atty = "0.2"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -20,7 +20,6 @@ use clap::{crate_version, App, Arg};
use std::fs::{metadata, File}; use std::fs::{metadata, File};
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use thiserror::Error; use thiserror::Error;
use uucore::fs::is_stdin_interactive;
/// Linux splice support /// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
@ -170,7 +169,65 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let matches = App::new(executable!()) let matches = uu_app().get_matches_from(args);
let number_mode = if matches.is_present(options::NUMBER_NONBLANK) {
NumberingMode::NonEmpty
} else if matches.is_present(options::NUMBER) {
NumberingMode::All
} else {
NumberingMode::None
};
let show_nonprint = vec![
options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(),
options::SHOW_NONPRINTING.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let show_ends = vec![
options::SHOW_ENDS.to_owned(),
options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let show_tabs = vec![
options::SHOW_ALL.to_owned(),
options::SHOW_TABS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let squeeze_blank = matches.is_present(options::SQUEEZE_BLANK);
let files: Vec<String> = match matches.values_of(options::FILE) {
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
None => vec!["-".to_owned()],
};
let options = OutputOptions {
show_ends,
number: number_mode,
show_nonprint,
show_tabs,
squeeze_blank,
};
let success = cat_files(files, &options).is_ok();
if success {
0
} else {
1
}
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME) .name(NAME)
.version(crate_version!()) .version(crate_version!())
.usage(SYNTAX) .usage(SYNTAX)
@ -230,61 +287,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(options::SHOW_NONPRINTING) .long(options::SHOW_NONPRINTING)
.help("use ^ and M- notation, except for LF (\\n) and TAB (\\t)"), .help("use ^ and M- notation, except for LF (\\n) and TAB (\\t)"),
) )
.get_matches_from(args);
let number_mode = if matches.is_present(options::NUMBER_NONBLANK) {
NumberingMode::NonEmpty
} else if matches.is_present(options::NUMBER) {
NumberingMode::All
} else {
NumberingMode::None
};
let show_nonprint = vec![
options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(),
options::SHOW_NONPRINTING.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let show_ends = vec![
options::SHOW_ENDS.to_owned(),
options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let show_tabs = vec![
options::SHOW_ALL.to_owned(),
options::SHOW_TABS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let squeeze_blank = matches.is_present(options::SQUEEZE_BLANK);
let files: Vec<String> = match matches.values_of(options::FILE) {
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
None => vec!["-".to_owned()],
};
let options = OutputOptions {
show_ends,
number: number_mode,
show_nonprint,
show_tabs,
squeeze_blank,
};
let success = cat_files(files, &options).is_ok();
if success {
0
} else {
1
}
} }
fn cat_handle<R: Read>( fn cat_handle<R: Read>(
@ -295,7 +297,7 @@ fn cat_handle<R: Read>(
if options.can_write_fast() { if options.can_write_fast() {
write_fast(handle) write_fast(handle)
} else { } else {
write_lines(handle, &options, state) write_lines(handle, options, state)
} }
} }
@ -306,9 +308,9 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: stdin.as_raw_fd(), file_descriptor: stdin.as_raw_fd(),
reader: stdin, reader: stdin,
is_interactive: is_stdin_interactive(), is_interactive: atty::is(atty::Stream::Stdin),
}; };
return cat_handle(&mut handle, &options, state); return cat_handle(&mut handle, options, state);
} }
match get_input_type(path)? { match get_input_type(path)? {
InputType::Directory => Err(CatError::IsDirectory), InputType::Directory => Err(CatError::IsDirectory),
@ -322,7 +324,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
reader: socket, reader: socket,
is_interactive: false, is_interactive: false,
}; };
cat_handle(&mut handle, &options, state) cat_handle(&mut handle, options, state)
} }
_ => { _ => {
let file = File::open(path)?; let file = File::open(path)?;
@ -332,7 +334,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
reader: file, reader: file,
is_interactive: false, is_interactive: false,
}; };
cat_handle(&mut handle, &options, state) cat_handle(&mut handle, options, state)
} }
} }
} }
@ -345,7 +347,7 @@ fn cat_files(files: Vec<String>, options: &OutputOptions) -> Result<(), u32> {
}; };
for path in &files { for path in &files {
if let Err(err) = cat_path(path, &options, &mut state) { if let Err(err) = cat_path(path, options, &mut state) {
show_error!("{}: {}", path, err); show_error!("{}: {}", path, err);
error_count += 1; error_count += 1;
} }

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/chgrp.rs" path = "src/chgrp.rs"
[dependencies] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2" walkdir = "2.2"

View file

@ -14,6 +14,8 @@ use uucore::fs::resolve_relative_path;
use uucore::libc::gid_t; use uucore::libc::gid_t;
use uucore::perms::{wrap_chgrp, Verbosity}; use uucore::perms::{wrap_chgrp, Verbosity};
use clap::{App, Arg};
extern crate walkdir; extern crate walkdir;
use walkdir::WalkDir; use walkdir::WalkDir;
@ -24,76 +26,117 @@ use std::os::unix::fs::MetadataExt;
use std::path::Path; use std::path::Path;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static SYNTAX: &str = static ABOUT: &str = "Change the group of each FILE to GROUP.";
"chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE..."; static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "Change the group of each FILE to GROUP.";
pub mod options {
pub mod verbosity {
pub static CHANGES: &str = "changes";
pub static QUIET: &str = "quiet";
pub static SILENT: &str = "silent";
pub static VERBOSE: &str = "verbose";
}
pub mod preserve_root {
pub static PRESERVE: &str = "preserve-root";
pub static NO_PRESERVE: &str = "no-preserve-root";
}
pub mod dereference {
pub static DEREFERENCE: &str = "dereference";
pub static NO_DEREFERENCE: &str = "no-dereference";
}
pub static RECURSIVE: &str = "recursive";
pub mod traverse {
pub static TRAVERSE: &str = "H";
pub static NO_TRAVERSE: &str = "P";
pub static EVERY: &str = "L";
}
pub static REFERENCE: &str = "reference";
pub static ARG_GROUP: &str = "GROUP";
pub static ARG_FILES: &str = "FILE";
}
const FTS_COMFOLLOW: u8 = 1; const FTS_COMFOLLOW: u8 = 1;
const FTS_PHYSICAL: u8 = 1 << 1; const FTS_PHYSICAL: u8 = 1 << 1;
const FTS_LOGICAL: u8 = 1 << 2; const FTS_LOGICAL: u8 = 1 << 2;
fn get_usage() -> String {
format!(
"{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...",
executable!()
)
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
let mut opts = app!(SYNTAX, SUMMARY, ""); let usage = get_usage();
opts.optflag("c",
"changes",
"like verbose but report only when a change is made")
.optflag("f", "silent", "")
.optflag("", "quiet", "suppress most error messages")
.optflag("v",
"verbose",
"output a diagnostic for every file processed")
.optflag("", "dereference", "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself")
.optflag("h", "no-dereference", "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)")
.optflag("",
"no-preserve-root",
"do not treat '/' specially (the default)")
.optflag("", "preserve-root", "fail to operate recursively on '/'")
.optopt("",
"reference",
"use RFILE's owner and group rather than specifying OWNER:GROUP values",
"RFILE")
.optflag("R",
"recursive",
"operate on files and directories recursively")
.optflag("H",
"",
"if a command line argument is a symbolic link to a directory, traverse it")
.optflag("L",
"",
"traverse every symbolic link to a directory encountered")
.optflag("P", "", "do not traverse any symbolic links (default)");
let mut bit_flag = FTS_PHYSICAL; let mut app = uu_app().usage(&usage[..]);
let mut preserve_root = false;
let mut derefer = -1; // we change the positional args based on whether
let flags: &[char] = &['H', 'L', 'P']; // --reference was used.
for opt in &args { let mut reference = false;
match opt.as_str() { let mut help = false;
// If more than one is specified, only the final one takes effect. // stop processing options on --
s if s.contains(flags) => { for arg in args.iter().take_while(|s| *s != "--") {
if let Some(idx) = s.rfind(flags) { if arg.starts_with("--reference=") || arg == "--reference" {
match s.chars().nth(idx).unwrap() { reference = true;
'H' => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL, } else if arg == "--help" {
'L' => bit_flag = FTS_LOGICAL, // we stop processing once we see --help,
'P' => bit_flag = FTS_PHYSICAL, // as it doesn't matter if we've seen reference or not
_ => (), help = true;
} break;
}
}
"--no-preserve-root" => preserve_root = false,
"--preserve-root" => preserve_root = true,
"--dereference" => derefer = 1,
"--no-dereference" => derefer = 0,
_ => (),
} }
} }
let matches = opts.parse(args); if help || !reference {
let recursive = matches.opt_present("recursive"); // add both positional arguments
app = app.arg(
Arg::with_name(options::ARG_GROUP)
.value_name(options::ARG_GROUP)
.required(true)
.takes_value(true)
.multiple(false),
)
}
app = app.arg(
Arg::with_name(options::ARG_FILES)
.value_name(options::ARG_FILES)
.multiple(true)
.takes_value(true)
.required(true)
.min_values(1),
);
let matches = app.get_matches_from(args);
/* Get the list of files */
let files: Vec<String> = matches
.values_of(options::ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let preserve_root = matches.is_present(options::preserve_root::PRESERVE);
let mut derefer = if matches.is_present(options::dereference::DEREFERENCE) {
1
} else if matches.is_present(options::dereference::NO_DEREFERENCE) {
0
} else {
-1
};
let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) {
FTS_COMFOLLOW | FTS_PHYSICAL
} else if matches.is_present(options::traverse::EVERY) {
FTS_LOGICAL
} else {
FTS_PHYSICAL
};
let recursive = matches.is_present(options::RECURSIVE);
if recursive { if recursive {
if bit_flag == FTS_PHYSICAL { if bit_flag == FTS_PHYSICAL {
if derefer == 1 { if derefer == 1 {
@ -106,27 +149,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
bit_flag = FTS_PHYSICAL; bit_flag = FTS_PHYSICAL;
} }
let verbosity = if matches.opt_present("changes") { let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes Verbosity::Changes
} else if matches.opt_present("silent") || matches.opt_present("quiet") { } else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET)
{
Verbosity::Silent Verbosity::Silent
} else if matches.opt_present("verbose") { } else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose Verbosity::Verbose
} else { } else {
Verbosity::Normal Verbosity::Normal
}; };
if matches.free.is_empty() { let dest_gid: u32;
show_usage_error!("missing operand"); if let Some(file) = matches.value_of(options::REFERENCE) {
return 1;
} else if matches.free.len() < 2 && !matches.opt_present("reference") {
show_usage_error!("missing operand after {}", matches.free[0]);
return 1;
}
let dest_gid: gid_t;
let mut files;
if let Some(file) = matches.opt_str("reference") {
match fs::metadata(&file) { match fs::metadata(&file) {
Ok(meta) => { Ok(meta) => {
dest_gid = meta.gid(); dest_gid = meta.gid();
@ -136,19 +172,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
return 1; return 1;
} }
} }
files = matches.free;
} else { } else {
match entries::grp2gid(&matches.free[0]) { let group = matches.value_of(options::ARG_GROUP).unwrap_or_default();
match entries::grp2gid(group) {
Ok(g) => { Ok(g) => {
dest_gid = g; dest_gid = g;
} }
_ => { _ => {
show_error!("invalid group: {}", matches.free[0].as_str()); show_error!("invalid group: {}", group);
return 1; return 1;
} }
} }
files = matches.free;
files.remove(0);
} }
let executor = Chgrper { let executor = Chgrper {
@ -163,6 +197,86 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
executor.exec() executor.exec()
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(VERSION)
.about(ABOUT)
.arg(
Arg::with_name(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")
.long(options::verbosity::SILENT),
)
.arg(
Arg::with_name(options::verbosity::QUIET)
.long(options::verbosity::QUIET)
.help("suppress most error messages"),
)
.arg(
Arg::with_name(options::verbosity::VERBOSE)
.short("v")
.long(options::verbosity::VERBOSE)
.help("output a diagnostic for every file processed"),
)
.arg(
Arg::with_name(options::dereference::DEREFERENCE)
.long(options::dereference::DEREFERENCE),
)
.arg(
Arg::with_name(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)
.long(options::preserve_root::PRESERVE)
.help("fail to operate recursively on '/'"),
)
.arg(
Arg::with_name(options::preserve_root::NO_PRESERVE)
.long(options::preserve_root::NO_PRESERVE)
.help("do not treat '/' specially (the default)"),
)
.arg(
Arg::with_name(options::REFERENCE)
.long(options::REFERENCE)
.value_name("RFILE")
.help("use RFILE's group rather than specifying GROUP values")
.takes_value(true)
.multiple(false),
)
.arg(
Arg::with_name(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)
.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)
.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)
.help("traverse every symbolic link to a directory encountered"),
)
}
struct Chgrper { struct Chgrper {
dest_gid: gid_t, dest_gid: gid_t,
bit_flag: u8, bit_flag: u8,

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/chmod.rs" path = "src/chmod.rs"
[dependencies] [dependencies]
clap = "2.33.3" clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -61,11 +61,64 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let after_help = get_long_usage(); let after_help = get_long_usage();
let matches = App::new(executable!()) let matches = uu_app()
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..]) .usage(&usage[..])
.after_help(&after_help[..]) .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) {
Ok(meta) => Some(meta.mode()),
Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err),
});
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
format!("-{}", modes)
} else {
modes.to_string()
};
let mut files: Vec<String> = matches
.values_of(options::FILE)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let cmode = if fmode.is_some() {
// "--reference" and MODE are mutually exclusive
// if "--reference" was used MODE needs to be interpreted as another FILE
// it wasn't possible to implement this behavior directly with clap
files.push(cmode);
None
} else {
Some(cmode)
};
let chmoder = Chmoder {
changes,
quiet,
verbose,
preserve_root,
recursive,
fmode,
cmode,
};
match chmoder.chmod(files) {
Ok(()) => {}
Err(e) => return e,
}
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg( .arg(
Arg::with_name(options::CHANGES) Arg::with_name(options::CHANGES)
.long(options::CHANGES) .long(options::CHANGES)
@ -120,54 +173,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.required_unless(options::MODE) .required_unless(options::MODE)
.multiple(true), .multiple(true),
) )
.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(|ref fref| match fs::metadata(fref) {
Ok(meta) => Some(meta.mode()),
Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err),
});
let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
let mut cmode = if mode_had_minus_prefix {
// clap parsing is finished, now put prefix back
Some(format!("-{}", modes))
} else {
Some(modes.to_string())
};
let mut files: Vec<String> = matches
.values_of(options::FILE)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
if fmode.is_some() {
// "--reference" and MODE are mutually exclusive
// if "--reference" was used MODE needs to be interpreted as another FILE
// it wasn't possible to implement this behavior directly with clap
files.push(cmode.unwrap());
cmode = None;
}
let chmoder = Chmoder {
changes,
quiet,
verbose,
preserve_root,
recursive,
fmode,
cmode,
};
match chmoder.chmod(files) {
Ok(()) => {}
Err(e) => return e,
}
0
} }
// Iterate 'args' and delete the first occurrence // Iterate 'args' and delete the first occurrence
@ -230,11 +235,11 @@ impl Chmoder {
return Err(1); return Err(1);
} }
if !self.recursive { if !self.recursive {
r = self.chmod_file(&file).and(r); r = self.chmod_file(file).and(r);
} else { } else {
for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) { for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) {
let file = entry.path(); let file = entry.path();
r = self.chmod_file(&file).and(r); r = self.chmod_file(file).and(r);
} }
} }
} }

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/chown.rs" path = "src/chown.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
glob = "0.3.0" glob = "0.3.0"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -73,10 +73,116 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
/* First arg is the owner/group */
let owner = matches.value_of(ARG_OWNER).unwrap();
/* Then the list of files */
let files: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let preserve_root = matches.is_present(options::preserve_root::PRESERVE);
let mut derefer = if matches.is_present(options::dereference::NO_DEREFERENCE) {
1
} else {
0
};
let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) {
FTS_COMFOLLOW | FTS_PHYSICAL
} else if matches.is_present(options::traverse::EVERY) {
FTS_LOGICAL
} else {
FTS_PHYSICAL
};
let recursive = matches.is_present(options::RECURSIVE);
if recursive {
if bit_flag == FTS_PHYSICAL {
if derefer == 1 {
show_error!("-R --dereference requires -H or -L");
return 1;
}
derefer = 0;
}
} else {
bit_flag = FTS_PHYSICAL;
}
let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes
} else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET)
{
Verbosity::Silent
} else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose
} else {
Verbosity::Normal
};
let filter = if let Some(spec) = matches.value_of(options::FROM) {
match parse_spec(spec) {
Ok((Some(uid), None)) => IfFrom::User(uid),
Ok((None, Some(gid))) => IfFrom::Group(gid),
Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid),
Ok((None, None)) => IfFrom::All,
Err(e) => {
show_error!("{}", e);
return 1;
}
}
} else {
IfFrom::All
};
let dest_uid: Option<u32>;
let dest_gid: Option<u32>;
if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) {
Ok(meta) => {
dest_gid = Some(meta.gid());
dest_uid = Some(meta.uid());
}
Err(e) => {
show_error!("failed to get attributes of '{}': {}", file, e);
return 1;
}
}
} else {
match parse_spec(owner) {
Ok((u, g)) => {
dest_uid = u;
dest_gid = g;
}
Err(e) => {
show_error!("{}", e);
return 1;
}
}
}
let executor = Chowner {
bit_flag,
dest_uid,
dest_gid,
verbosity,
recursive,
dereference: derefer != 0,
filter,
preserve_root,
files,
};
executor.exec()
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.usage(&usage[..])
.arg( .arg(
Arg::with_name(options::verbosity::CHANGES) Arg::with_name(options::verbosity::CHANGES)
.short("c") .short("c")
@ -167,110 +273,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.required(true) .required(true)
.min_values(1), .min_values(1),
) )
.get_matches_from(args);
/* First arg is the owner/group */
let owner = matches.value_of(ARG_OWNER).unwrap();
/* Then the list of files */
let files: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let preserve_root = matches.is_present(options::preserve_root::PRESERVE);
let mut derefer = if matches.is_present(options::dereference::NO_DEREFERENCE) {
1
} else {
0
};
let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) {
FTS_COMFOLLOW | FTS_PHYSICAL
} else if matches.is_present(options::traverse::EVERY) {
FTS_LOGICAL
} else {
FTS_PHYSICAL
};
let recursive = matches.is_present(options::RECURSIVE);
if recursive {
if bit_flag == FTS_PHYSICAL {
if derefer == 1 {
show_error!("-R --dereference requires -H or -L");
return 1;
}
derefer = 0;
}
} else {
bit_flag = FTS_PHYSICAL;
}
let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes
} else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET)
{
Verbosity::Silent
} else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose
} else {
Verbosity::Normal
};
let filter = if let Some(spec) = matches.value_of(options::FROM) {
match parse_spec(&spec) {
Ok((Some(uid), None)) => IfFrom::User(uid),
Ok((None, Some(gid))) => IfFrom::Group(gid),
Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid),
Ok((None, None)) => IfFrom::All,
Err(e) => {
show_error!("{}", e);
return 1;
}
}
} else {
IfFrom::All
};
let dest_uid: Option<u32>;
let dest_gid: Option<u32>;
if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) {
Ok(meta) => {
dest_gid = Some(meta.gid());
dest_uid = Some(meta.uid());
}
Err(e) => {
show_error!("failed to get attributes of '{}': {}", file, e);
return 1;
}
}
} else {
match parse_spec(&owner) {
Ok((u, g)) => {
dest_uid = u;
dest_gid = g;
}
Err(e) => {
show_error!("{}", e);
return 1;
}
}
}
let executor = Chowner {
bit_flag,
dest_uid,
dest_gid,
verbosity,
recursive,
dereference: derefer != 0,
filter,
preserve_root,
files,
};
executor.exec()
} }
fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> { fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
@ -278,37 +280,25 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
let usr_only = args.len() == 1 && !args[0].is_empty(); let usr_only = args.len() == 1 && !args[0].is_empty();
let grp_only = args.len() == 2 && args[0].is_empty(); let grp_only = args.len() == 2 && args[0].is_empty();
let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty(); let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty();
let uid = if usr_only || usr_grp {
if usr_only { Some(
Ok(( Passwd::locate(args[0])
Some(match Passwd::locate(args[0]) { .map_err(|_| format!("invalid user: '{}'", spec))?
Ok(v) => v.uid(), .uid(),
_ => return Err(format!("invalid user: {}", spec)), )
}),
None,
))
} else if grp_only {
Ok((
None,
Some(match Group::locate(args[1]) {
Ok(v) => v.gid(),
_ => return Err(format!("invalid group: {}", spec)),
}),
))
} else if usr_grp {
Ok((
Some(match Passwd::locate(args[0]) {
Ok(v) => v.uid(),
_ => return Err(format!("invalid user: {}", spec)),
}),
Some(match Group::locate(args[1]) {
Ok(v) => v.gid(),
_ => return Err(format!("invalid group: {}", spec)),
}),
))
} else { } else {
Ok((None, None)) None
} };
let gid = if grp_only || usr_grp {
Some(
Group::locate(args[1])
.map_err(|_| format!("invalid group: '{}'", spec))?
.gid(),
)
} else {
None
};
Ok((uid, gid))
} }
enum IfFrom { enum IfFrom {
@ -497,3 +487,17 @@ impl Chowner {
} }
} }
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_spec() {
assert_eq!(parse_spec(":"), Ok((None, None)));
assert!(parse_spec("::")
.err()
.unwrap()
.starts_with("invalid group: "));
}
}

View file

@ -28,6 +28,7 @@ mod options {
pub const GROUP: &str = "group"; pub const GROUP: &str = "group";
pub const GROUPS: &str = "groups"; pub const GROUPS: &str = "groups";
pub const USERSPEC: &str = "userspec"; pub const USERSPEC: &str = "userspec";
pub const COMMAND: &str = "command";
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
@ -35,11 +36,72 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
let matches = App::new(executable!()) let matches = uu_app().get_matches_from(args);
let default_shell: &'static str = "/bin/sh";
let default_option: &'static str = "-i";
let user_shell = std::env::var("SHELL");
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.",
NAME
),
};
if !newroot.is_dir() {
crash!(
1,
"cannot change root directory to `{}`: no such directory",
newroot.display()
);
}
let commands = match matches.values_of(options::COMMAND) {
Some(v) => v.collect(),
None => vec![],
};
// TODO: refactor the args and command matching
// See: https://github.com/uutils/coreutils/pull/2365#discussion_r647849967
let command: Vec<&str> = match commands.len() {
1 => {
let shell: &str = match user_shell {
Err(_) => default_shell,
Ok(ref s) => s.as_ref(),
};
vec![shell, default_option]
}
_ => commands,
};
set_context(newroot, &matches);
let pstatus = Command::new(command[0])
.args(&command[1..])
.status()
.unwrap_or_else(|e| crash!(1, "Cannot exec: {}", e));
if pstatus.success() {
0
} else {
pstatus.code().unwrap_or(-1)
}
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.usage(SYNTAX) .usage(SYNTAX)
.arg(Arg::with_name(options::NEWROOT).hidden(true).required(true)) .arg(
Arg::with_name(options::NEWROOT)
.hidden(true)
.required(true)
.index(1),
)
.arg( .arg(
Arg::with_name(options::USER) Arg::with_name(options::USER)
.short("u") .short("u")
@ -71,59 +133,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
) )
.value_name("USER:GROUP"), .value_name("USER:GROUP"),
) )
.get_matches_from(args); .arg(
Arg::with_name(options::COMMAND)
let default_shell: &'static str = "/bin/sh"; .hidden(true)
let default_option: &'static str = "-i"; .multiple(true)
let user_shell = std::env::var("SHELL"); .index(2),
)
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.",
NAME
),
};
if !newroot.is_dir() {
crash!(
1,
"cannot change root directory to `{}`: no such directory",
newroot.display()
);
}
let command: Vec<&str> = match matches.args.len() {
1 => {
let shell: &str = match user_shell {
Err(_) => default_shell,
Ok(ref s) => s.as_ref(),
};
vec![shell, default_option]
}
_ => {
let mut vector: Vec<&str> = Vec::new();
for (&k, v) in matches.args.iter() {
vector.push(k);
vector.push(&v.vals[0].to_str().unwrap());
}
vector
}
};
set_context(&newroot, &matches);
let pstatus = Command::new(command[0])
.args(&command[1..])
.status()
.unwrap_or_else(|e| crash!(1, "Cannot exec: {}", e));
if pstatus.success() {
0
} else {
pstatus.code().unwrap_or(-1)
}
} }
fn set_context(root: &Path, options: &clap::ArgMatches) { fn set_context(root: &Path, options: &clap::ArgMatches) {
@ -132,7 +147,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) {
let group_str = options.value_of(options::GROUP).unwrap_or_default(); let group_str = options.value_of(options::GROUP).unwrap_or_default();
let groups_str = options.value_of(options::GROUPS).unwrap_or_default(); let groups_str = options.value_of(options::GROUPS).unwrap_or_default();
let userspec = match userspec_str { let userspec = match userspec_str {
Some(ref u) => { Some(u) => {
let s: Vec<&str> = u.split(':').collect(); let s: Vec<&str> = u.split(':').collect();
if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) { if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) {
crash!(1, "invalid userspec: `{}`", u) crash!(1, "invalid userspec: `{}`", u)

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/cksum.rs" path = "src/cksum.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -160,8 +160,7 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
let mut bytes = init_byte_array(); let mut bytes = init_byte_array();
loop { loop {
match rd.read(&mut bytes) { let num_bytes = rd.read(&mut bytes)?;
Ok(num_bytes) => {
if num_bytes == 0 { if num_bytes == 0 {
return Ok((crc_final(crc, size), size)); return Ok((crc_final(crc, size), size));
} }
@ -170,9 +169,6 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
} }
size += num_bytes; size += num_bytes;
} }
Err(err) => return Err(err),
}
}
} }
mod options { mod options {
@ -184,13 +180,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let matches = App::new(executable!()) let matches = uu_app().get_matches_from(args);
.name(NAME)
.version(crate_version!())
.about(SUMMARY)
.usage(SYNTAX)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.get_matches_from(args);
let files: Vec<String> = match matches.values_of(options::FILE) { let files: Vec<String> = match matches.values_of(options::FILE) {
Some(v) => v.clone().map(|v| v.to_owned()).collect(), Some(v) => v.clone().map(|v| v.to_owned()).collect(),
@ -221,3 +211,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
exit_code exit_code
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME)
.version(crate_version!())
.about(SUMMARY)
.usage(SYNTAX)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
}

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/comm.rs" path = "src/comm.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -50,9 +50,8 @@ fn mkdelim(col: usize, opts: &ArgMatches) -> String {
} }
fn ensure_nl(line: &mut String) { fn ensure_nl(line: &mut String) {
match line.chars().last() { if !line.ends_with('\n') {
Some('\n') => (), line.push('\n');
_ => line.push('\n'),
} }
} }
@ -138,10 +137,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap();
let mut f2 = open_file(matches.value_of(options::FILE_2).unwrap()).unwrap();
comm(&mut f1, &mut f2, &matches);
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.usage(&usage[..])
.after_help(LONG_HELP) .after_help(LONG_HELP)
.arg( .arg(
Arg::with_name(options::COLUMN_1) Arg::with_name(options::COLUMN_1)
@ -168,12 +177,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
) )
.arg(Arg::with_name(options::FILE_1).required(true)) .arg(Arg::with_name(options::FILE_1).required(true))
.arg(Arg::with_name(options::FILE_2).required(true)) .arg(Arg::with_name(options::FILE_2).required(true))
.get_matches_from(args);
let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap();
let mut f2 = open_file(matches.value_of(options::FILE_2).unwrap()).unwrap();
comm(&mut f1, &mut f2, &matches);
0
} }

View file

@ -19,7 +19,7 @@ edition = "2018"
path = "src/cp.rs" path = "src/cp.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
filetime = "0.2" filetime = "0.2"
libc = "0.2.85" libc = "0.2.85"
quick-error = "1.2.3" quick-error = "1.2.3"

View file

@ -7,37 +7,37 @@
### To Do ### To Do
- [ ] archive
- [ ] attributes-only
- [ ] copy-contents
- [ ] no-dereference-preserve-linkgs
- [ ] dereference
- [ ] no-dereference
- [ ] preserve-default-attributes
- [ ] preserve
- [ ] no-preserve
- [ ] parents
- [ ] reflink
- [ ] sparse
- [ ] strip-trailing-slashes
- [ ] update
- [ ] one-file-system
- [ ] context
- [ ] cli-symbolic-links - [ ] cli-symbolic-links
- [ ] context
- [ ] copy-contents
- [ ] sparse
### Completed ### Completed
- [x] archive
- [x] attributes-only
- [x] backup - [x] backup
- [x] dereference
- [x] force (Not implemented on Windows) - [x] force (Not implemented on Windows)
- [x] interactive - [x] interactive
- [x] link - [x] link
- [x] no-clobber - [x] no-clobber
- [x] no-dereference
- [x] no-dereference-preserve-links
- [x] no-preserve
- [x] no-target-directory - [x] no-target-directory
- [x] one-file-system
- [x] parents
- [x] paths - [x] paths
- [x] preserve
- [x] preserve-default-attributes
- [x] recursive - [x] recursive
- [x] reflink
- [x] remove-destination (On Windows, current only works for writeable files) - [x] remove-destination (On Windows, current only works for writeable files)
- [x] strip-trailing-slashes
- [x] suffix - [x] suffix
- [x] symbolic-link - [x] symbolic-link
- [x] target-directory - [x] target-directory
- [x] update
- [x] verbose - [x] verbose
- [x] version - [x] version

View file

@ -48,7 +48,6 @@ use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr; use std::str::FromStr;
use std::string::ToString; use std::string::ToString;
use uucore::backup_control::{self, BackupMode}; use uucore::backup_control::{self, BackupMode};
use uucore::fs::resolve_relative_path;
use uucore::fs::{canonicalize, CanonicalizeMode}; use uucore::fs::{canonicalize, CanonicalizeMode};
use walkdir::WalkDir; use walkdir::WalkDir;
@ -198,7 +197,6 @@ pub struct Options {
copy_contents: bool, copy_contents: bool,
copy_mode: CopyMode, copy_mode: CopyMode,
dereference: bool, dereference: bool,
no_dereference: bool,
no_target_dir: bool, no_target_dir: bool,
one_file_system: bool, one_file_system: bool,
overwrite: OverwriteMode, overwrite: OverwriteMode,
@ -228,39 +226,41 @@ fn get_usage() -> String {
} }
// Argument constants // Argument constants
static OPT_ARCHIVE: &str = "archive"; mod options {
static OPT_ATTRIBUTES_ONLY: &str = "attributes-only"; pub const ARCHIVE: &str = "archive";
static OPT_BACKUP: &str = "backup"; pub const ATTRIBUTES_ONLY: &str = "attributes-only";
static OPT_BACKUP_NO_ARG: &str = "b"; pub const BACKUP: &str = "backup";
static OPT_CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links"; pub const BACKUP_NO_ARG: &str = "b";
static OPT_CONTEXT: &str = "context"; pub const CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links";
static OPT_COPY_CONTENTS: &str = "copy-contents"; pub const CONTEXT: &str = "context";
static OPT_DEREFERENCE: &str = "dereference"; pub const COPY_CONTENTS: &str = "copy-contents";
static OPT_FORCE: &str = "force"; pub const DEREFERENCE: &str = "dereference";
static OPT_INTERACTIVE: &str = "interactive"; pub const FORCE: &str = "force";
static OPT_LINK: &str = "link"; pub const INTERACTIVE: &str = "interactive";
static OPT_NO_CLOBBER: &str = "no-clobber"; pub const LINK: &str = "link";
static OPT_NO_DEREFERENCE: &str = "no-dereference"; pub const NO_CLOBBER: &str = "no-clobber";
static OPT_NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs"; pub const NO_DEREFERENCE: &str = "no-dereference";
static OPT_NO_PRESERVE: &str = "no-preserve"; pub const NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs";
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; pub const NO_PRESERVE: &str = "no-preserve";
static OPT_ONE_FILE_SYSTEM: &str = "one-file-system"; pub const NO_TARGET_DIRECTORY: &str = "no-target-directory";
static OPT_PARENT: &str = "parent"; pub const ONE_FILE_SYSTEM: &str = "one-file-system";
static OPT_PARENTS: &str = "parents"; pub const PARENT: &str = "parent";
static OPT_PATHS: &str = "paths"; pub const PARENTS: &str = "parents";
static OPT_PRESERVE: &str = "preserve"; pub const PATHS: &str = "paths";
static OPT_PRESERVE_DEFAULT_ATTRIBUTES: &str = "preserve-default-attributes"; pub const PRESERVE: &str = "preserve";
static OPT_RECURSIVE: &str = "recursive"; pub const PRESERVE_DEFAULT_ATTRIBUTES: &str = "preserve-default-attributes";
static OPT_RECURSIVE_ALIAS: &str = "recursive_alias"; pub const RECURSIVE: &str = "recursive";
static OPT_REFLINK: &str = "reflink"; pub const RECURSIVE_ALIAS: &str = "recursive_alias";
static OPT_REMOVE_DESTINATION: &str = "remove-destination"; pub const REFLINK: &str = "reflink";
static OPT_SPARSE: &str = "sparse"; pub const REMOVE_DESTINATION: &str = "remove-destination";
static OPT_STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; pub const SPARSE: &str = "sparse";
static OPT_SUFFIX: &str = "suffix"; pub const STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes";
static OPT_SYMBOLIC_LINK: &str = "symbolic-link"; pub const SUFFIX: &str = "suffix";
static OPT_TARGET_DIRECTORY: &str = "target-directory"; pub const SYMBOLIC_LINK: &str = "symbolic-link";
static OPT_UPDATE: &str = "update"; pub const TARGET_DIRECTORY: &str = "target-directory";
static OPT_VERBOSE: &str = "verbose"; pub const UPDATE: &str = "update";
pub const VERBOSE: &str = "verbose";
}
#[cfg(unix)] #[cfg(unix)]
static PRESERVABLE_ATTRIBUTES: &[&str] = &[ static PRESERVABLE_ATTRIBUTES: &[&str] = &[
@ -290,74 +290,71 @@ static DEFAULT_ATTRIBUTES: &[Attribute] = &[
Attribute::Timestamps, Attribute::Timestamps,
]; ];
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uu_app() -> App<'static, 'static> {
let usage = get_usage(); App::new(executable!())
let matches = App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP)) .arg(Arg::with_name(options::TARGET_DIRECTORY)
.usage(&usage[..])
.arg(Arg::with_name(OPT_TARGET_DIRECTORY)
.short("t") .short("t")
.conflicts_with(OPT_NO_TARGET_DIRECTORY) .conflicts_with(options::NO_TARGET_DIRECTORY)
.long(OPT_TARGET_DIRECTORY) .long(options::TARGET_DIRECTORY)
.value_name(OPT_TARGET_DIRECTORY) .value_name(options::TARGET_DIRECTORY)
.takes_value(true) .takes_value(true)
.help("copy all SOURCE arguments into target-directory")) .help("copy all SOURCE arguments into target-directory"))
.arg(Arg::with_name(OPT_NO_TARGET_DIRECTORY) .arg(Arg::with_name(options::NO_TARGET_DIRECTORY)
.short("T") .short("T")
.long(OPT_NO_TARGET_DIRECTORY) .long(options::NO_TARGET_DIRECTORY)
.conflicts_with(OPT_TARGET_DIRECTORY) .conflicts_with(options::TARGET_DIRECTORY)
.help("Treat DEST as a regular file and not a directory")) .help("Treat DEST as a regular file and not a directory"))
.arg(Arg::with_name(OPT_INTERACTIVE) .arg(Arg::with_name(options::INTERACTIVE)
.short("i") .short("i")
.long(OPT_INTERACTIVE) .long(options::INTERACTIVE)
.conflicts_with(OPT_NO_CLOBBER) .conflicts_with(options::NO_CLOBBER)
.help("ask before overwriting files")) .help("ask before overwriting files"))
.arg(Arg::with_name(OPT_LINK) .arg(Arg::with_name(options::LINK)
.short("l") .short("l")
.long(OPT_LINK) .long(options::LINK)
.overrides_with(OPT_REFLINK) .overrides_with(options::REFLINK)
.help("hard-link files instead of copying")) .help("hard-link files instead of copying"))
.arg(Arg::with_name(OPT_NO_CLOBBER) .arg(Arg::with_name(options::NO_CLOBBER)
.short("n") .short("n")
.long(OPT_NO_CLOBBER) .long(options::NO_CLOBBER)
.conflicts_with(OPT_INTERACTIVE) .conflicts_with(options::INTERACTIVE)
.help("don't overwrite a file that already exists")) .help("don't overwrite a file that already exists"))
.arg(Arg::with_name(OPT_RECURSIVE) .arg(Arg::with_name(options::RECURSIVE)
.short("r") .short("r")
.long(OPT_RECURSIVE) .long(options::RECURSIVE)
// --archive sets this option // --archive sets this option
.help("copy directories recursively")) .help("copy directories recursively"))
.arg(Arg::with_name(OPT_RECURSIVE_ALIAS) .arg(Arg::with_name(options::RECURSIVE_ALIAS)
.short("R") .short("R")
.help("same as -r")) .help("same as -r"))
.arg(Arg::with_name(OPT_STRIP_TRAILING_SLASHES) .arg(Arg::with_name(options::STRIP_TRAILING_SLASHES)
.long(OPT_STRIP_TRAILING_SLASHES) .long(options::STRIP_TRAILING_SLASHES)
.help("remove any trailing slashes from each SOURCE argument")) .help("remove any trailing slashes from each SOURCE argument"))
.arg(Arg::with_name(OPT_VERBOSE) .arg(Arg::with_name(options::VERBOSE)
.short("v") .short("v")
.long(OPT_VERBOSE) .long(options::VERBOSE)
.help("explicitly state what is being done")) .help("explicitly state what is being done"))
.arg(Arg::with_name(OPT_SYMBOLIC_LINK) .arg(Arg::with_name(options::SYMBOLIC_LINK)
.short("s") .short("s")
.long(OPT_SYMBOLIC_LINK) .long(options::SYMBOLIC_LINK)
.conflicts_with(OPT_LINK) .conflicts_with(options::LINK)
.overrides_with(OPT_REFLINK) .overrides_with(options::REFLINK)
.help("make symbolic links instead of copying")) .help("make symbolic links instead of copying"))
.arg(Arg::with_name(OPT_FORCE) .arg(Arg::with_name(options::FORCE)
.short("f") .short("f")
.long(OPT_FORCE) .long(options::FORCE)
.help("if an existing destination file cannot be opened, remove it and \ .help("if an existing destination file cannot be opened, remove it and \
try again (this option is ignored when the -n option is also used). \ try again (this option is ignored when the -n option is also used). \
Currently not implemented for Windows.")) Currently not implemented for Windows."))
.arg(Arg::with_name(OPT_REMOVE_DESTINATION) .arg(Arg::with_name(options::REMOVE_DESTINATION)
.long(OPT_REMOVE_DESTINATION) .long(options::REMOVE_DESTINATION)
.conflicts_with(OPT_FORCE) .conflicts_with(options::FORCE)
.help("remove each existing destination file before attempting to open it \ .help("remove each existing destination file before attempting to open it \
(contrast with --force). On Windows, current only works for writeable files.")) (contrast with --force). On Windows, current only works for writeable files."))
.arg(Arg::with_name(OPT_BACKUP) .arg(Arg::with_name(options::BACKUP)
.long(OPT_BACKUP) .long(options::BACKUP)
.help("make a backup of each existing destination file") .help("make a backup of each existing destination file")
.takes_value(true) .takes_value(true)
.require_equals(true) .require_equals(true)
@ -365,104 +362,116 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.possible_values(backup_control::BACKUP_CONTROL_VALUES) .possible_values(backup_control::BACKUP_CONTROL_VALUES)
.value_name("CONTROL") .value_name("CONTROL")
) )
.arg(Arg::with_name(OPT_BACKUP_NO_ARG) .arg(Arg::with_name(options::BACKUP_NO_ARG)
.short(OPT_BACKUP_NO_ARG) .short(options::BACKUP_NO_ARG)
.help("like --backup but does not accept an argument") .help("like --backup but does not accept an argument")
) )
.arg(Arg::with_name(OPT_SUFFIX) .arg(Arg::with_name(options::SUFFIX)
.short("S") .short("S")
.long(OPT_SUFFIX) .long(options::SUFFIX)
.takes_value(true) .takes_value(true)
.value_name("SUFFIX") .value_name("SUFFIX")
.help("override the usual backup suffix")) .help("override the usual backup suffix"))
.arg(Arg::with_name(OPT_UPDATE) .arg(Arg::with_name(options::UPDATE)
.short("u") .short("u")
.long(OPT_UPDATE) .long(options::UPDATE)
.help("copy only when the SOURCE file is newer than the destination file\ .help("copy only when the SOURCE file is newer than the destination file \
or when the destination file is missing")) or when the destination file is missing"))
.arg(Arg::with_name(OPT_REFLINK) .arg(Arg::with_name(options::REFLINK)
.long(OPT_REFLINK) .long(options::REFLINK)
.takes_value(true) .takes_value(true)
.value_name("WHEN") .value_name("WHEN")
.help("control clone/CoW copies. See below")) .help("control clone/CoW copies. See below"))
.arg(Arg::with_name(OPT_ATTRIBUTES_ONLY) .arg(Arg::with_name(options::ATTRIBUTES_ONLY)
.long(OPT_ATTRIBUTES_ONLY) .long(options::ATTRIBUTES_ONLY)
.conflicts_with(OPT_COPY_CONTENTS) .conflicts_with(options::COPY_CONTENTS)
.overrides_with(OPT_REFLINK) .overrides_with(options::REFLINK)
.help("Don't copy the file data, just the attributes")) .help("Don't copy the file data, just the attributes"))
.arg(Arg::with_name(OPT_PRESERVE) .arg(Arg::with_name(options::PRESERVE)
.long(OPT_PRESERVE) .long(options::PRESERVE)
.takes_value(true) .takes_value(true)
.multiple(true) .multiple(true)
.use_delimiter(true) .use_delimiter(true)
.possible_values(PRESERVABLE_ATTRIBUTES) .possible_values(PRESERVABLE_ATTRIBUTES)
.min_values(0)
.value_name("ATTR_LIST") .value_name("ATTR_LIST")
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE]) .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE])
// -d sets this option // -d sets this option
// --archive sets this option // --archive sets this option
.help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\ .help("Preserve the specified attributes (default: mode (unix only), ownership, timestamps), \
if possible additional attributes: context, links, xattr, all")) if possible additional attributes: context, links, xattr, all"))
.arg(Arg::with_name(OPT_PRESERVE_DEFAULT_ATTRIBUTES) .arg(Arg::with_name(options::PRESERVE_DEFAULT_ATTRIBUTES)
.short("-p") .short("-p")
.long(OPT_PRESERVE_DEFAULT_ATTRIBUTES) .long(options::PRESERVE_DEFAULT_ATTRIBUTES)
.conflicts_with_all(&[OPT_PRESERVE, OPT_NO_PRESERVE, OPT_ARCHIVE]) .conflicts_with_all(&[options::PRESERVE, options::NO_PRESERVE, options::ARCHIVE])
.help("same as --preserve=mode(unix only),ownership,timestamps")) .help("same as --preserve=mode(unix only),ownership,timestamps"))
.arg(Arg::with_name(OPT_NO_PRESERVE) .arg(Arg::with_name(options::NO_PRESERVE)
.long(OPT_NO_PRESERVE) .long(options::NO_PRESERVE)
.takes_value(true) .takes_value(true)
.value_name("ATTR_LIST") .value_name("ATTR_LIST")
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_ARCHIVE]) .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::ARCHIVE])
.help("don't preserve the specified attributes")) .help("don't preserve the specified attributes"))
.arg(Arg::with_name(OPT_PARENTS) .arg(Arg::with_name(options::PARENTS)
.long(OPT_PARENTS) .long(options::PARENTS)
.alias(OPT_PARENT) .alias(options::PARENT)
.help("use full source file name under DIRECTORY")) .help("use full source file name under DIRECTORY"))
.arg(Arg::with_name(OPT_NO_DEREFERENCE) .arg(Arg::with_name(options::NO_DEREFERENCE)
.short("-P") .short("-P")
.long(OPT_NO_DEREFERENCE) .long(options::NO_DEREFERENCE)
.conflicts_with(OPT_DEREFERENCE) .conflicts_with(options::DEREFERENCE)
// -d sets this option // -d sets this option
.help("never follow symbolic links in SOURCE")) .help("never follow symbolic links in SOURCE"))
.arg(Arg::with_name(OPT_DEREFERENCE) .arg(Arg::with_name(options::DEREFERENCE)
.short("L") .short("L")
.long(OPT_DEREFERENCE) .long(options::DEREFERENCE)
.conflicts_with(OPT_NO_DEREFERENCE) .conflicts_with(options::NO_DEREFERENCE)
.help("always follow symbolic links in SOURCE")) .help("always follow symbolic links in SOURCE"))
.arg(Arg::with_name(OPT_ARCHIVE) .arg(Arg::with_name(options::ARCHIVE)
.short("a") .short("a")
.long(OPT_ARCHIVE) .long(options::ARCHIVE)
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE]) .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::NO_PRESERVE])
.help("Same as -dR --preserve=all")) .help("Same as -dR --preserve=all"))
.arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS) .arg(Arg::with_name(options::NO_DEREFERENCE_PRESERVE_LINKS)
.short("d") .short("d")
.help("same as --no-dereference --preserve=links")) .help("same as --no-dereference --preserve=links"))
.arg(Arg::with_name(OPT_ONE_FILE_SYSTEM) .arg(Arg::with_name(options::ONE_FILE_SYSTEM)
.short("x") .short("x")
.long(OPT_ONE_FILE_SYSTEM) .long(options::ONE_FILE_SYSTEM)
.help("stay on this file system")) .help("stay on this file system"))
// TODO: implement the following args // TODO: implement the following args
.arg(Arg::with_name(OPT_COPY_CONTENTS) .arg(Arg::with_name(options::COPY_CONTENTS)
.long(OPT_COPY_CONTENTS) .long(options::COPY_CONTENTS)
.conflicts_with(OPT_ATTRIBUTES_ONLY) .conflicts_with(options::ATTRIBUTES_ONLY)
.help("NotImplemented: copy contents of special files when recursive")) .help("NotImplemented: copy contents of special files when recursive"))
.arg(Arg::with_name(OPT_SPARSE) .arg(Arg::with_name(options::SPARSE)
.long(OPT_SPARSE) .long(options::SPARSE)
.takes_value(true) .takes_value(true)
.value_name("WHEN") .value_name("WHEN")
.help("NotImplemented: control creation of sparse files. See below")) .help("NotImplemented: control creation of sparse files. See below"))
.arg(Arg::with_name(OPT_CONTEXT) .arg(Arg::with_name(options::CONTEXT)
.long(OPT_CONTEXT) .long(options::CONTEXT)
.takes_value(true) .takes_value(true)
.value_name("CTX") .value_name("CTX")
.help("NotImplemented: set SELinux security context of destination file to default type")) .help("NotImplemented: set SELinux security context of destination file to default type"))
.arg(Arg::with_name(OPT_CLI_SYMBOLIC_LINKS) .arg(Arg::with_name(options::CLI_SYMBOLIC_LINKS)
.short("H") .short("H")
.help("NotImplemented: follow command-line symbolic links in SOURCE")) .help("NotImplemented: follow command-line symbolic links in SOURCE"))
// END TODO // END TODO
.arg(Arg::with_name(OPT_PATHS) .arg(Arg::with_name(options::PATHS)
.multiple(true)) .multiple(true))
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let matches = uu_app()
.after_help(&*format!(
"{}\n{}",
LONG_HELP,
backup_control::BACKUP_CONTROL_LONG_HELP
))
.usage(&usage[..])
.get_matches_from(args); .get_matches_from(args);
let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches)); let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches));
@ -473,7 +482,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
let paths: Vec<String> = matches let paths: Vec<String> = matches
.values_of(OPT_PATHS) .values_of(options::PATHS)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default(); .unwrap_or_default();
@ -495,9 +504,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
impl ClobberMode { impl ClobberMode {
fn from_matches(matches: &ArgMatches) -> ClobberMode { fn from_matches(matches: &ArgMatches) -> ClobberMode {
if matches.is_present(OPT_FORCE) { if matches.is_present(options::FORCE) {
ClobberMode::Force ClobberMode::Force
} else if matches.is_present(OPT_REMOVE_DESTINATION) { } else if matches.is_present(options::REMOVE_DESTINATION) {
ClobberMode::RemoveDestination ClobberMode::RemoveDestination
} else { } else {
ClobberMode::Standard ClobberMode::Standard
@ -507,9 +516,9 @@ impl ClobberMode {
impl OverwriteMode { impl OverwriteMode {
fn from_matches(matches: &ArgMatches) -> OverwriteMode { fn from_matches(matches: &ArgMatches) -> OverwriteMode {
if matches.is_present(OPT_INTERACTIVE) { if matches.is_present(options::INTERACTIVE) {
OverwriteMode::Interactive(ClobberMode::from_matches(matches)) OverwriteMode::Interactive(ClobberMode::from_matches(matches))
} else if matches.is_present(OPT_NO_CLOBBER) { } else if matches.is_present(options::NO_CLOBBER) {
OverwriteMode::NoClobber OverwriteMode::NoClobber
} else { } else {
OverwriteMode::Clobber(ClobberMode::from_matches(matches)) OverwriteMode::Clobber(ClobberMode::from_matches(matches))
@ -519,15 +528,15 @@ impl OverwriteMode {
impl CopyMode { impl CopyMode {
fn from_matches(matches: &ArgMatches) -> CopyMode { fn from_matches(matches: &ArgMatches) -> CopyMode {
if matches.is_present(OPT_LINK) { if matches.is_present(options::LINK) {
CopyMode::Link CopyMode::Link
} else if matches.is_present(OPT_SYMBOLIC_LINK) { } else if matches.is_present(options::SYMBOLIC_LINK) {
CopyMode::SymLink CopyMode::SymLink
} else if matches.is_present(OPT_SPARSE) { } else if matches.is_present(options::SPARSE) {
CopyMode::Sparse CopyMode::Sparse
} else if matches.is_present(OPT_UPDATE) { } else if matches.is_present(options::UPDATE) {
CopyMode::Update CopyMode::Update
} else if matches.is_present(OPT_ATTRIBUTES_ONLY) { } else if matches.is_present(options::ATTRIBUTES_ONLY) {
CopyMode::AttrOnly CopyMode::AttrOnly
} else { } else {
CopyMode::Copy CopyMode::Copy
@ -575,13 +584,13 @@ fn add_all_attributes() -> Vec<Attribute> {
impl Options { impl Options {
fn from_matches(matches: &ArgMatches) -> CopyResult<Options> { fn from_matches(matches: &ArgMatches) -> CopyResult<Options> {
let not_implemented_opts = vec![ let not_implemented_opts = vec![
OPT_COPY_CONTENTS, options::COPY_CONTENTS,
OPT_SPARSE, options::SPARSE,
#[cfg(not(any(windows, unix)))] #[cfg(not(any(windows, unix)))]
OPT_ONE_FILE_SYSTEM, options::ONE_FILE_SYSTEM,
OPT_CONTEXT, options::CONTEXT,
#[cfg(windows)] #[cfg(windows)]
OPT_FORCE, options::FORCE,
]; ];
for not_implemented_opt in not_implemented_opts { for not_implemented_opt in not_implemented_opts {
@ -590,27 +599,28 @@ impl Options {
} }
} }
let recursive = matches.is_present(OPT_RECURSIVE) let recursive = matches.is_present(options::RECURSIVE)
|| matches.is_present(OPT_RECURSIVE_ALIAS) || matches.is_present(options::RECURSIVE_ALIAS)
|| matches.is_present(OPT_ARCHIVE); || matches.is_present(options::ARCHIVE);
let backup_mode = backup_control::determine_backup_mode( let backup_mode = backup_control::determine_backup_mode(
matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP), matches.is_present(options::BACKUP_NO_ARG) || matches.is_present(options::BACKUP),
matches.value_of(OPT_BACKUP), matches.value_of(options::BACKUP),
); );
let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)); let backup_suffix =
backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX));
let overwrite = OverwriteMode::from_matches(matches); let overwrite = OverwriteMode::from_matches(matches);
// Parse target directory options // Parse target directory options
let no_target_dir = matches.is_present(OPT_NO_TARGET_DIRECTORY); let no_target_dir = matches.is_present(options::NO_TARGET_DIRECTORY);
let target_dir = matches let target_dir = matches
.value_of(OPT_TARGET_DIRECTORY) .value_of(options::TARGET_DIRECTORY)
.map(ToString::to_string); .map(ToString::to_string);
// Parse attributes to preserve // Parse attributes to preserve
let preserve_attributes: Vec<Attribute> = if matches.is_present(OPT_PRESERVE) { let preserve_attributes: Vec<Attribute> = if matches.is_present(options::PRESERVE) {
match matches.values_of(OPT_PRESERVE) { match matches.values_of(options::PRESERVE) {
None => DEFAULT_ATTRIBUTES.to_vec(), None => DEFAULT_ATTRIBUTES.to_vec(),
Some(attribute_strs) => { Some(attribute_strs) => {
let mut attributes = Vec::new(); let mut attributes = Vec::new();
@ -625,33 +635,34 @@ impl Options {
attributes attributes
} }
} }
} else if matches.is_present(OPT_ARCHIVE) { } else if matches.is_present(options::ARCHIVE) {
// --archive is used. Same as --preserve=all // --archive is used. Same as --preserve=all
add_all_attributes() add_all_attributes()
} else if matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) { } else if matches.is_present(options::NO_DEREFERENCE_PRESERVE_LINKS) {
vec![Attribute::Links] vec![Attribute::Links]
} else if matches.is_present(OPT_PRESERVE_DEFAULT_ATTRIBUTES) { } else if matches.is_present(options::PRESERVE_DEFAULT_ATTRIBUTES) {
DEFAULT_ATTRIBUTES.to_vec() DEFAULT_ATTRIBUTES.to_vec()
} else { } else {
vec![] vec![]
}; };
let options = Options { let options = Options {
attributes_only: matches.is_present(OPT_ATTRIBUTES_ONLY), attributes_only: matches.is_present(options::ATTRIBUTES_ONLY),
copy_contents: matches.is_present(OPT_COPY_CONTENTS), copy_contents: matches.is_present(options::COPY_CONTENTS),
copy_mode: CopyMode::from_matches(matches), copy_mode: CopyMode::from_matches(matches),
dereference: matches.is_present(OPT_DEREFERENCE),
// No dereference is set with -p, -d and --archive // No dereference is set with -p, -d and --archive
no_dereference: matches.is_present(OPT_NO_DEREFERENCE) dereference: !(matches.is_present(options::NO_DEREFERENCE)
|| matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) || matches.is_present(options::NO_DEREFERENCE_PRESERVE_LINKS)
|| matches.is_present(OPT_ARCHIVE), || matches.is_present(options::ARCHIVE)
one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM), || recursive)
parents: matches.is_present(OPT_PARENTS), || matches.is_present(options::DEREFERENCE),
update: matches.is_present(OPT_UPDATE), one_file_system: matches.is_present(options::ONE_FILE_SYSTEM),
verbose: matches.is_present(OPT_VERBOSE), parents: matches.is_present(options::PARENTS),
strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES), update: matches.is_present(options::UPDATE),
verbose: matches.is_present(options::VERBOSE),
strip_trailing_slashes: matches.is_present(options::STRIP_TRAILING_SLASHES),
reflink_mode: { reflink_mode: {
if let Some(reflink) = matches.value_of(OPT_REFLINK) { if let Some(reflink) = matches.value_of(options::REFLINK) {
match reflink { match reflink {
"always" => ReflinkMode::Always, "always" => ReflinkMode::Always,
"auto" => ReflinkMode::Auto, "auto" => ReflinkMode::Auto,
@ -664,8 +675,15 @@ impl Options {
} }
} }
} else { } else {
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
ReflinkMode::Auto
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
ReflinkMode::Never ReflinkMode::Never
} }
}
}, },
backup: backup_mode, backup: backup_mode,
backup_suffix, backup_suffix,
@ -709,27 +727,26 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
return Err(format!("extra operand {:?}", paths[2]).into()); return Err(format!("extra operand {:?}", paths[2]).into());
} }
let (mut sources, target) = match options.target_dir { let target = match options.target_dir {
Some(ref target) => { Some(ref target) => {
// All path args are sources, and the target dir was // All path args are sources, and the target dir was
// specified separately // specified separately
(paths, PathBuf::from(target)) PathBuf::from(target)
} }
None => { None => {
// If there was no explicit target-dir, then use the last // If there was no explicit target-dir, then use the last
// path_arg // path_arg
let target = paths.pop().unwrap(); paths.pop().unwrap()
(paths, target)
} }
}; };
if options.strip_trailing_slashes { if options.strip_trailing_slashes {
for source in sources.iter_mut() { for source in paths.iter_mut() {
*source = source.components().as_path().to_owned() *source = source.components().as_path().to_owned()
} }
} }
Ok((sources, target)) Ok((paths, target))
} }
fn preserve_hardlinks( fn preserve_hardlinks(
@ -937,7 +954,15 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR
return Err(format!("omitting directory '{}'", root.display()).into()); return Err(format!("omitting directory '{}'", root.display()).into());
} }
let root_path = Path::new(&root).canonicalize()?; // if no-dereference is enabled and this is a symlink, copy it as a file
if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink() {
return copy_file(root, target, options);
}
let current_dir =
env::current_dir().unwrap_or_else(|e| crash!(1, "failed to get current directory {}", e));
let root_path = current_dir.join(root);
let root_parent = if target.exists() { let root_parent = if target.exists() {
root_path.parent() root_path.parent()
@ -958,18 +983,13 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR
#[cfg(any(windows, target_os = "redox"))] #[cfg(any(windows, target_os = "redox"))]
let mut hard_links: Vec<(String, u64)> = vec![]; let mut hard_links: Vec<(String, u64)> = vec![];
for path in WalkDir::new(root).same_file_system(options.one_file_system) { for path in WalkDir::new(root)
.same_file_system(options.one_file_system)
.follow_links(options.dereference)
{
let p = or_continue!(path); let p = or_continue!(path);
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink(); let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
let path = if (options.no_dereference || options.dereference) && is_symlink { let path = current_dir.join(&p.path());
// we are dealing with a symlink. Don't follow it
match env::current_dir() {
Ok(cwd) => cwd.join(resolve_relative_path(p.path())),
Err(e) => crash!(1, "failed to get current directory {}", e),
}
} else {
or_continue!(p.path().canonicalize())
};
let local_to_root_parent = match root_parent { let local_to_root_parent = match root_parent {
Some(parent) => { Some(parent) => {
@ -992,9 +1012,10 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR
}; };
let local_to_target = target.join(&local_to_root_parent); let local_to_target = target.join(&local_to_root_parent);
if is_symlink && !options.dereference {
if path.is_dir() && !local_to_target.exists() { copy_link(&path, &local_to_target)?;
or_continue!(fs::create_dir_all(local_to_target.clone())); } else if path.is_dir() && !local_to_target.exists() {
or_continue!(fs::create_dir_all(local_to_target));
} else if !path.is_dir() { } else if !path.is_dir() {
if preserve_hard_links { if preserve_hard_links {
let mut found_hard_link = false; let mut found_hard_link = false;
@ -1088,7 +1109,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
} }
#[cfg(not(windows))] #[cfg(not(windows))]
#[allow(clippy::unnecessary_unwrap)] // needed for windows version #[allow(clippy::unnecessary_wraps)] // needed for windows version
fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
match std::os::unix::fs::symlink(source, dest).context(context) { match std::os::unix::fs::symlink(source, dest).context(context) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -1177,7 +1198,7 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
CopyMode::SymLink => { CopyMode::SymLink => {
symlink_file(source, dest, &*context_for(source, dest))?; symlink_file(source, dest, &*context_for(source, dest))?;
} }
CopyMode::Sparse => return Err(Error::NotImplemented(OPT_SPARSE.to_string())), CopyMode::Sparse => return Err(Error::NotImplemented(options::SPARSE.to_string())),
CopyMode::Update => { CopyMode::Update => {
if dest.exists() { if dest.exists() {
let src_metadata = fs::metadata(source)?; let src_metadata = fs::metadata(source)?;
@ -1212,18 +1233,46 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
/// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
/// copy-on-write scheme if --reflink is specified and the filesystem supports it. /// copy-on-write scheme if --reflink is specified and the filesystem supports it.
fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
if options.reflink_mode != ReflinkMode::Never { if options.parents {
let parent = dest.parent().unwrap_or(dest);
fs::create_dir_all(parent)?;
}
let is_symlink = fs::symlink_metadata(&source)?.file_type().is_symlink();
if source.to_string_lossy() == "/dev/null" {
/* workaround a limitation of fs::copy
* https://github.com/rust-lang/rust/issues/79390
*/
File::create(dest)?;
} else if !options.dereference && is_symlink {
copy_link(source, dest)?;
} else if options.reflink_mode != ReflinkMode::Never {
#[cfg(not(any(target_os = "linux", target_os = "macos")))] #[cfg(not(any(target_os = "linux", target_os = "macos")))]
return Err("--reflink is only supported on linux and macOS" return Err("--reflink is only supported on linux and macOS"
.to_string() .to_string()
.into()); .into());
#[cfg(any(target_os = "linux", target_os = "macos"))]
if is_symlink {
assert!(options.dereference);
let real_path = std::fs::read_link(source)?;
#[cfg(target_os = "macos")]
copy_on_write_macos(&real_path, dest, options.reflink_mode)?;
#[cfg(target_os = "linux")]
copy_on_write_linux(&real_path, dest, options.reflink_mode)?;
} else {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
copy_on_write_macos(source, dest, options.reflink_mode)?; copy_on_write_macos(source, dest, options.reflink_mode)?;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
copy_on_write_linux(source, dest, options.reflink_mode)?; copy_on_write_linux(source, dest, options.reflink_mode)?;
} else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() { }
} else {
fs::copy(source, dest).context(&*context_for(source, dest))?;
}
Ok(())
}
fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> {
// Here, we will copy the symlink itself (actually, just recreate it) // Here, we will copy the symlink itself (actually, just recreate it)
let link = fs::read_link(&source)?; let link = fs::read_link(&source)?;
let dest: Cow<'_, Path> = if dest.is_dir() { let dest: Cow<'_, Path> = if dest.is_dir() {
@ -1231,28 +1280,19 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
Some(name) => dest.join(name).into(), Some(name) => dest.join(name).into(),
None => crash!( None => crash!(
EXIT_ERR, EXIT_ERR,
"cannot stat {}: No such file or directory", "cannot stat '{}': No such file or directory",
source.display() source.display()
), ),
} }
} else { } else {
// we always need to remove the file to be able to create a symlink,
// even if it is writeable.
if dest.exists() {
fs::remove_file(dest)?;
}
dest.into() dest.into()
}; };
symlink_file(&link, &dest, &*context_for(&link, &dest))?; symlink_file(&link, &dest, &*context_for(&link, &dest))
} else if source.to_string_lossy() == "/dev/null" {
/* workaround a limitation of fs::copy
* https://github.com/rust-lang/rust/issues/79390
*/
File::create(dest)?;
} else {
if options.parents {
let parent = dest.parent().unwrap_or(dest);
fs::create_dir_all(parent)?;
}
fs::copy(source, dest).context(&*context_for(source, dest))?;
}
Ok(())
} }
/// Copies `source` to `dest` using copy-on-write if possible. /// Copies `source` to `dest` using copy-on-write if possible.
@ -1271,15 +1311,15 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
ReflinkMode::Always => unsafe { ReflinkMode::Always => unsafe {
let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32); let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32);
if result != 0 { if result != 0 {
return Err(format!( Err(format!(
"failed to clone {:?} from {:?}: {}", "failed to clone {:?} from {:?}: {}",
source, source,
dest, dest,
std::io::Error::last_os_error() std::io::Error::last_os_error()
) )
.into()); .into())
} else { } else {
return Ok(()); Ok(())
} }
}, },
ReflinkMode::Auto => unsafe { ReflinkMode::Auto => unsafe {
@ -1287,11 +1327,10 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
if result != 0 { if result != 0 {
fs::copy(source, dest).context(&*context_for(source, dest))?; fs::copy(source, dest).context(&*context_for(source, dest))?;
} }
Ok(())
}, },
ReflinkMode::Never => unreachable!(), ReflinkMode::Never => unreachable!(),
} }
Ok(())
} }
/// Copies `source` to `dest` using copy-on-write if possible. /// Copies `source` to `dest` using copy-on-write if possible.
@ -1392,9 +1431,9 @@ pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result<bool> {
fn test_cp_localize_to_target() { fn test_cp_localize_to_target() {
assert!( assert!(
localize_to_target( localize_to_target(
&Path::new("a/source/"), Path::new("a/source/"),
&Path::new("a/source/c.txt"), Path::new("a/source/c.txt"),
&Path::new("target/") Path::new("target/")
) )
.unwrap() .unwrap()
== Path::new("target/c.txt") == Path::new("target/c.txt")

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/csplit.rs" path = "src/csplit.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
thiserror = "1.0" thiserror = "1.0"
regex = "1.0.0" regex = "1.0.0"
glob = "0.2.11" glob = "0.2.11"

View file

@ -92,7 +92,7 @@ where
T: BufRead, T: BufRead,
{ {
let mut input_iter = InputSplitter::new(input.lines().enumerate()); let mut input_iter = InputSplitter::new(input.lines().enumerate());
let mut split_writer = SplitWriter::new(&options); let mut split_writer = SplitWriter::new(options);
let ret = do_csplit(&mut split_writer, patterns, &mut input_iter); let ret = do_csplit(&mut split_writer, patterns, &mut input_iter);
// consume the rest // consume the rest
@ -711,10 +711,37 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
// get the file to split
let file_name = matches.value_of(options::FILE).unwrap();
// get the patterns to split on
let patterns: Vec<String> = matches
.values_of(options::PATTERN)
.unwrap()
.map(str::to_string)
.collect();
let patterns = return_if_err!(1, patterns::get_patterns(&patterns[..]));
let options = CsplitOptions::new(&matches);
if file_name == "-" {
let stdin = io::stdin();
crash_if_err!(1, csplit(&options, patterns, stdin.lock()));
} else {
let file = return_if_err!(1, File::open(file_name));
let file_metadata = return_if_err!(1, file.metadata());
if !file_metadata.is_file() {
crash!(1, "'{}' is not a regular file", file_name);
}
crash_if_err!(1, csplit(&options, patterns, BufReader::new(file)));
};
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(SUMMARY) .about(SUMMARY)
.usage(&usage[..])
.arg( .arg(
Arg::with_name(options::SUFFIX_FORMAT) Arg::with_name(options::SUFFIX_FORMAT)
.short("b") .short("b")
@ -768,29 +795,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.required(true), .required(true),
) )
.after_help(LONG_HELP) .after_help(LONG_HELP)
.get_matches_from(args);
// get the file to split
let file_name = matches.value_of(options::FILE).unwrap();
// get the patterns to split on
let patterns: Vec<String> = matches
.values_of(options::PATTERN)
.unwrap()
.map(str::to_string)
.collect();
let patterns = return_if_err!(1, patterns::get_patterns(&patterns[..]));
let options = CsplitOptions::new(&matches);
if file_name == "-" {
let stdin = io::stdin();
crash_if_err!(1, csplit(&options, patterns, stdin.lock()));
} else {
let file = return_if_err!(1, File::open(file_name));
let file_metadata = return_if_err!(1, file.metadata());
if !file_metadata.is_file() {
crash!(1, "'{}' is not a regular file", file_name);
}
crash_if_err!(1, csplit(&options, patterns, BufReader::new(file)));
};
0
} }

View file

@ -133,20 +133,12 @@ fn extract_patterns(args: &[String]) -> Result<Vec<Pattern>, CsplitError> {
Some(m) => m.as_str().parse().unwrap(), Some(m) => m.as_str().parse().unwrap(),
}; };
if let Some(up_to_match) = captures.name("UPTO") { if let Some(up_to_match) = captures.name("UPTO") {
let pattern = match Regex::new(up_to_match.as_str()) { let pattern = Regex::new(up_to_match.as_str())
Err(_) => { .map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?;
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
Ok(reg) => reg,
};
patterns.push(Pattern::UpToMatch(pattern, offset, execute_ntimes)); patterns.push(Pattern::UpToMatch(pattern, offset, execute_ntimes));
} else if let Some(skip_to_match) = captures.name("SKIPTO") { } else if let Some(skip_to_match) = captures.name("SKIPTO") {
let pattern = match Regex::new(skip_to_match.as_str()) { let pattern = Regex::new(skip_to_match.as_str())
Err(_) => { .map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?;
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
Ok(reg) => reg,
};
patterns.push(Pattern::SkipToMatch(pattern, offset, execute_ntimes)); patterns.push(Pattern::SkipToMatch(pattern, offset, execute_ntimes));
} }
} else if let Ok(line_number) = arg.parse::<usize>() { } else if let Ok(line_number) = arg.parse::<usize>() {

View file

@ -33,13 +33,13 @@ impl SplitName {
// get the prefix // get the prefix
let prefix = prefix_opt.unwrap_or_else(|| "xx".to_string()); let prefix = prefix_opt.unwrap_or_else(|| "xx".to_string());
// the width for the split offset // the width for the split offset
let n_digits = match n_digits_opt { let n_digits = n_digits_opt
None => 2, .map(|opt| {
Some(opt) => match opt.parse::<usize>() { opt.parse::<usize>()
Ok(digits) => digits, .map_err(|_| CsplitError::InvalidNumber(opt))
Err(_) => return Err(CsplitError::InvalidNumber(opt)), })
}, .transpose()?
}; .unwrap_or(2);
// translate the custom format into a function // translate the custom format into a function
let fn_split_name: Box<dyn Fn(usize) -> String> = match format_opt { let fn_split_name: Box<dyn Fn(usize) -> String> = match format_opt {
None => Box::new(move |n: usize| -> String { None => Box::new(move |n: usize| -> String {

View file

@ -15,11 +15,12 @@ edition = "2018"
path = "src/cut.rs" path = "src/cut.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
memchr = "2" memchr = "2"
bstr = "0.2" bstr = "0.2"
atty = "0.2"
[[bin]] [[bin]]
name = "cut" name = "cut"

View file

@ -17,7 +17,6 @@ use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
use std::path::Path; use std::path::Path;
use self::searcher::Searcher; use self::searcher::Searcher;
use uucore::fs::is_stdout_interactive;
use uucore::ranges::Range; use uucore::ranges::Range;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
@ -127,7 +126,7 @@ enum Mode {
} }
fn stdout_writer() -> Box<dyn Write> { fn stdout_writer() -> Box<dyn Write> {
if is_stdout_interactive() { if atty::is(atty::Stream::Stdout) {
Box::new(stdout()) Box::new(stdout())
} else { } else {
Box::new(BufWriter::new(stdout())) as Box<dyn Write> Box::new(BufWriter::new(stdout())) as Box<dyn Write>
@ -397,88 +396,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let matches = App::new(executable!()) let matches = uu_app().get_matches_from(args);
.name(NAME)
.version(crate_version!())
.usage(SYNTAX)
.about(SUMMARY)
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::BYTES)
.short("b")
.long(options::BYTES)
.takes_value(true)
.help("filter byte columns from the input source")
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(1),
)
.arg(
Arg::with_name(options::CHARACTERS)
.short("c")
.long(options::CHARACTERS)
.help("alias for character mode")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(2),
)
.arg(
Arg::with_name(options::DELIMITER)
.short("d")
.long(options::DELIMITER)
.help("specify the delimiter character that separates fields in the input source. Defaults to Tab.")
.takes_value(true)
.value_name("DELIM")
.display_order(3),
)
.arg(
Arg::with_name(options::FIELDS)
.short("f")
.long(options::FIELDS)
.help("filter field columns from the input source")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(4),
)
.arg(
Arg::with_name(options::COMPLEMENT)
.long(options::COMPLEMENT)
.help("invert the filter - instead of displaying only the filtered columns, display all but those columns")
.takes_value(false)
.display_order(5),
)
.arg(
Arg::with_name(options::ONLY_DELIMITED)
.short("s")
.long(options::ONLY_DELIMITED)
.help("in field mode, only print lines which contain the delimiter")
.takes_value(false)
.display_order(6),
)
.arg(
Arg::with_name(options::ZERO_TERMINATED)
.short("z")
.long(options::ZERO_TERMINATED)
.help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)")
.takes_value(false)
.display_order(8),
)
.arg(
Arg::with_name(options::OUTPUT_DELIMITER)
.long(options::OUTPUT_DELIMITER)
.help("in field mode, replace the delimiter in output lines with this option's argument")
.takes_value(true)
.value_name("NEW_DELIM")
.display_order(7),
)
.arg(
Arg::with_name(options::FILE)
.hidden(true)
.multiple(true)
)
.get_matches_from(args);
let complement = matches.is_present(options::COMPLEMENT); let complement = matches.is_present(options::COMPLEMENT);
@ -532,7 +450,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let zero_terminated = matches.is_present(options::ZERO_TERMINATED); let zero_terminated = matches.is_present(options::ZERO_TERMINATED);
match matches.value_of(options::DELIMITER) { match matches.value_of(options::DELIMITER) {
Some(delim) => { Some(mut delim) => {
// GNU's `cut` supports `-d=` to set the delimiter to `=`.
// Clap parsing is limited in this situation, see:
// https://github.com/uutils/coreutils/issues/2424#issuecomment-863825242
// Since clap parsing handles `-d=` as delimiter explicitly set to "" and
// an empty delimiter is not accepted by GNU's `cut` (and makes no sense),
// we can use this as basis for a simple workaround:
if delim.is_empty() {
delim = "=";
}
if delim.chars().count() > 1 { if delim.chars().count() > 1 {
Err(msg_opt_invalid_should_be!( Err(msg_opt_invalid_should_be!(
"empty or 1 character long", "empty or 1 character long",
@ -619,3 +546,87 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME)
.version(crate_version!())
.usage(SYNTAX)
.about(SUMMARY)
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::BYTES)
.short("b")
.long(options::BYTES)
.takes_value(true)
.help("filter byte columns from the input source")
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(1),
)
.arg(
Arg::with_name(options::CHARACTERS)
.short("c")
.long(options::CHARACTERS)
.help("alias for character mode")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(2),
)
.arg(
Arg::with_name(options::DELIMITER)
.short("d")
.long(options::DELIMITER)
.help("specify the delimiter character that separates fields in the input source. Defaults to Tab.")
.takes_value(true)
.value_name("DELIM")
.display_order(3),
)
.arg(
Arg::with_name(options::FIELDS)
.short("f")
.long(options::FIELDS)
.help("filter field columns from the input source")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(4),
)
.arg(
Arg::with_name(options::COMPLEMENT)
.long(options::COMPLEMENT)
.help("invert the filter - instead of displaying only the filtered columns, display all but those columns")
.takes_value(false)
.display_order(5),
)
.arg(
Arg::with_name(options::ONLY_DELIMITED)
.short("s")
.long(options::ONLY_DELIMITED)
.help("in field mode, only print lines which contain the delimiter")
.takes_value(false)
.display_order(6),
)
.arg(
Arg::with_name(options::ZERO_TERMINATED)
.short("z")
.long(options::ZERO_TERMINATED)
.help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)")
.takes_value(false)
.display_order(8),
)
.arg(
Arg::with_name(options::OUTPUT_DELIMITER)
.long(options::OUTPUT_DELIMITER)
.help("in field mode, replace the delimiter in output lines with this option's argument")
.takes_value(true)
.value_name("NEW_DELIM")
.display_order(7),
)
.arg(
Arg::with_name(options::FILE)
.hidden(true)
.multiple(true)
)
}

View file

@ -16,7 +16,7 @@ path = "src/date.rs"
[dependencies] [dependencies]
chrono = "0.4.4" chrono = "0.4.4"
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -142,75 +142,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
{0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]", {0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]",
NAME NAME
); );
let matches = App::new(executable!()) let matches = uu_app().usage(&syntax[..]).get_matches_from(args);
.version(crate_version!())
.about(ABOUT)
.usage(&syntax[..])
.arg(
Arg::with_name(OPT_DATE)
.short("d")
.long(OPT_DATE)
.takes_value(true)
.help("display time described by STRING, not 'now'"),
)
.arg(
Arg::with_name(OPT_FILE)
.short("f")
.long(OPT_FILE)
.takes_value(true)
.help("like --date; once for each line of DATEFILE"),
)
.arg(
Arg::with_name(OPT_ISO_8601)
.short("I")
.long(OPT_ISO_8601)
.takes_value(true)
.help(ISO_8601_HELP_STRING),
)
.arg(
Arg::with_name(OPT_RFC_EMAIL)
.short("R")
.long(OPT_RFC_EMAIL)
.help(RFC_5322_HELP_STRING),
)
.arg(
Arg::with_name(OPT_RFC_3339)
.long(OPT_RFC_3339)
.takes_value(true)
.help(RFC_3339_HELP_STRING),
)
.arg(
Arg::with_name(OPT_DEBUG)
.long(OPT_DEBUG)
.help("annotate the parsed date, and warn about questionable usage to stderr"),
)
.arg(
Arg::with_name(OPT_REFERENCE)
.short("r")
.long(OPT_REFERENCE)
.takes_value(true)
.help("display the last modification time of FILE"),
)
.arg(
Arg::with_name(OPT_SET)
.short("s")
.long(OPT_SET)
.takes_value(true)
.help(OPT_SET_HELP_STRING),
)
.arg(
Arg::with_name(OPT_UNIVERSAL)
.short("u")
.long(OPT_UNIVERSAL)
.alias(OPT_UNIVERSAL_2)
.help("print or set Coordinated Universal Time (UTC)"),
)
.arg(Arg::with_name(OPT_FORMAT).multiple(false))
.get_matches_from(args);
let format = if let Some(form) = matches.value_of(OPT_FORMAT) { let format = if let Some(form) = matches.value_of(OPT_FORMAT) {
if !form.starts_with('+') { if !form.starts_with('+') {
eprintln!("date: invalid date {}", form); eprintln!("date: invalid date '{}'", form);
return 1; return 1;
} }
let form = form[1..].to_string(); let form = form[1..].to_string();
@ -239,7 +175,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let set_to = match matches.value_of(OPT_SET).map(parse_date) { let set_to = match matches.value_of(OPT_SET).map(parse_date) {
None => None, None => None,
Some(Err((input, _err))) => { Some(Err((input, _err))) => {
eprintln!("date: invalid date {}", input); eprintln!("date: invalid date '{}'", input);
return 1; return 1;
} }
Some(Ok(date)) => Some(date), Some(Ok(date)) => Some(date),
@ -305,7 +241,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
println!("{}", formatted); println!("{}", formatted);
} }
Err((input, _err)) => { Err((input, _err)) => {
println!("date: invalid date {}", input); println!("date: invalid date '{}'", input);
} }
} }
} }
@ -314,6 +250,72 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0 0
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(OPT_DATE)
.short("d")
.long(OPT_DATE)
.takes_value(true)
.help("display time described by STRING, not 'now'"),
)
.arg(
Arg::with_name(OPT_FILE)
.short("f")
.long(OPT_FILE)
.takes_value(true)
.help("like --date; once for each line of DATEFILE"),
)
.arg(
Arg::with_name(OPT_ISO_8601)
.short("I")
.long(OPT_ISO_8601)
.takes_value(true)
.help(ISO_8601_HELP_STRING),
)
.arg(
Arg::with_name(OPT_RFC_EMAIL)
.short("R")
.long(OPT_RFC_EMAIL)
.help(RFC_5322_HELP_STRING),
)
.arg(
Arg::with_name(OPT_RFC_3339)
.long(OPT_RFC_3339)
.takes_value(true)
.help(RFC_3339_HELP_STRING),
)
.arg(
Arg::with_name(OPT_DEBUG)
.long(OPT_DEBUG)
.help("annotate the parsed date, and warn about questionable usage to stderr"),
)
.arg(
Arg::with_name(OPT_REFERENCE)
.short("r")
.long(OPT_REFERENCE)
.takes_value(true)
.help("display the last modification time of FILE"),
)
.arg(
Arg::with_name(OPT_SET)
.short("s")
.long(OPT_SET)
.takes_value(true)
.help(OPT_SET_HELP_STRING),
)
.arg(
Arg::with_name(OPT_UNIVERSAL)
.short("u")
.long(OPT_UNIVERSAL)
.alias(OPT_UNIVERSAL_2)
.help("print or set Coordinated Universal Time (UTC)"),
)
.arg(Arg::with_name(OPT_FORMAT).multiple(false))
}
/// Return the appropriate format string for the given settings. /// Return the appropriate format string for the given settings.
fn make_format_string(settings: &Settings) -> &str { fn make_format_string(settings: &Settings) -> &str {
match settings.format { match settings.format {

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/df.rs" path = "src/df.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
number_prefix = "0.4" number_prefix = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc", "fsext"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc", "fsext"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -258,120 +258,7 @@ fn use_size(free_size: u64, total_size: u64) -> String {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(OPT_ALL)
.short("a")
.long("all")
.help("include dummy file systems"),
)
.arg(
Arg::with_name(OPT_BLOCKSIZE)
.short("B")
.long("block-size")
.takes_value(true)
.help(
"scale sizes by SIZE before printing them; e.g.\
'-BM' prints sizes in units of 1,048,576 bytes",
),
)
.arg(
Arg::with_name(OPT_DIRECT)
.long("direct")
.help("show statistics for a file instead of mount point"),
)
.arg(
Arg::with_name(OPT_TOTAL)
.long("total")
.help("produce a grand total"),
)
.arg(
Arg::with_name(OPT_HUMAN_READABLE)
.short("h")
.long("human-readable")
.conflicts_with(OPT_HUMAN_READABLE_2)
.help("print sizes in human readable format (e.g., 1K 234M 2G)"),
)
.arg(
Arg::with_name(OPT_HUMAN_READABLE_2)
.short("H")
.long("si")
.conflicts_with(OPT_HUMAN_READABLE)
.help("likewise, but use powers of 1000 not 1024"),
)
.arg(
Arg::with_name(OPT_INODES)
.short("i")
.long("inodes")
.help("list inode information instead of block usage"),
)
.arg(
Arg::with_name(OPT_KILO)
.short("k")
.help("like --block-size=1K"),
)
.arg(
Arg::with_name(OPT_LOCAL)
.short("l")
.long("local")
.help("limit listing to local file systems"),
)
.arg(
Arg::with_name(OPT_NO_SYNC)
.long("no-sync")
.conflicts_with(OPT_SYNC)
.help("do not invoke sync before getting usage info (default)"),
)
.arg(
Arg::with_name(OPT_OUTPUT)
.long("output")
.takes_value(true)
.use_delimiter(true)
.help(
"use the output format defined by FIELD_LIST,\
or print all fields if FIELD_LIST is omitted.",
),
)
.arg(
Arg::with_name(OPT_PORTABILITY)
.short("P")
.long("portability")
.help("use the POSIX output format"),
)
.arg(
Arg::with_name(OPT_SYNC)
.long("sync")
.conflicts_with(OPT_NO_SYNC)
.help("invoke sync before getting usage info"),
)
.arg(
Arg::with_name(OPT_TYPE)
.short("t")
.long("type")
.takes_value(true)
.use_delimiter(true)
.help("limit listing to file systems of type TYPE"),
)
.arg(
Arg::with_name(OPT_PRINT_TYPE)
.short("T")
.long("print-type")
.help("print file system type"),
)
.arg(
Arg::with_name(OPT_EXCLUDE_TYPE)
.short("x")
.long("exclude-type")
.takes_value(true)
.use_delimiter(true)
.help("limit listing to file systems not of type TYPE"),
)
.arg(Arg::with_name(OPT_PATHS).multiple(true))
.help("Filesystem(s) to list")
.get_matches_from(args);
let paths: Vec<String> = matches let paths: Vec<String> = matches
.values_of(OPT_PATHS) .values_of(OPT_PATHS)
@ -511,3 +398,118 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
EXIT_OK EXIT_OK
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(OPT_ALL)
.short("a")
.long("all")
.help("include dummy file systems"),
)
.arg(
Arg::with_name(OPT_BLOCKSIZE)
.short("B")
.long("block-size")
.takes_value(true)
.help(
"scale sizes by SIZE before printing them; e.g.\
'-BM' prints sizes in units of 1,048,576 bytes",
),
)
.arg(
Arg::with_name(OPT_DIRECT)
.long("direct")
.help("show statistics for a file instead of mount point"),
)
.arg(
Arg::with_name(OPT_TOTAL)
.long("total")
.help("produce a grand total"),
)
.arg(
Arg::with_name(OPT_HUMAN_READABLE)
.short("h")
.long("human-readable")
.conflicts_with(OPT_HUMAN_READABLE_2)
.help("print sizes in human readable format (e.g., 1K 234M 2G)"),
)
.arg(
Arg::with_name(OPT_HUMAN_READABLE_2)
.short("H")
.long("si")
.conflicts_with(OPT_HUMAN_READABLE)
.help("likewise, but use powers of 1000 not 1024"),
)
.arg(
Arg::with_name(OPT_INODES)
.short("i")
.long("inodes")
.help("list inode information instead of block usage"),
)
.arg(
Arg::with_name(OPT_KILO)
.short("k")
.help("like --block-size=1K"),
)
.arg(
Arg::with_name(OPT_LOCAL)
.short("l")
.long("local")
.help("limit listing to local file systems"),
)
.arg(
Arg::with_name(OPT_NO_SYNC)
.long("no-sync")
.conflicts_with(OPT_SYNC)
.help("do not invoke sync before getting usage info (default)"),
)
.arg(
Arg::with_name(OPT_OUTPUT)
.long("output")
.takes_value(true)
.use_delimiter(true)
.help(
"use the output format defined by FIELD_LIST,\
or print all fields if FIELD_LIST is omitted.",
),
)
.arg(
Arg::with_name(OPT_PORTABILITY)
.short("P")
.long("portability")
.help("use the POSIX output format"),
)
.arg(
Arg::with_name(OPT_SYNC)
.long("sync")
.conflicts_with(OPT_NO_SYNC)
.help("invoke sync before getting usage info"),
)
.arg(
Arg::with_name(OPT_TYPE)
.short("t")
.long("type")
.takes_value(true)
.use_delimiter(true)
.help("limit listing to file systems of type TYPE"),
)
.arg(
Arg::with_name(OPT_PRINT_TYPE)
.short("T")
.long("print-type")
.help("print file system type"),
)
.arg(
Arg::with_name(OPT_EXCLUDE_TYPE)
.short("x")
.long("exclude-type")
.takes_value(true)
.use_delimiter(true)
.help("limit listing to file systems not of type TYPE"),
)
.arg(Arg::with_name(OPT_PATHS).multiple(true))
.help("Filesystem(s) to list")
}

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/dircolors.rs" path = "src/dircolors.rs"
[dependencies] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
glob = "0.3.0" glob = "0.3.0"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -1,6 +1,7 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Jian Zeng <anonymousknight96@gmail.com> // (c) Jian Zeng <anonymousknight96@gmail.com>
// (c) Mitchell Mebane <mitchell.mebane@gmail.com>
// //
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
@ -15,6 +16,15 @@ use std::env;
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use clap::{crate_version, App, Arg};
mod options {
pub const BOURNE_SHELL: &str = "bourne-shell";
pub const C_SHELL: &str = "c-shell";
pub const PRINT_DATABASE: &str = "print-database";
pub const FILE: &str = "FILE";
}
static SYNTAX: &str = "[OPTION]... [FILE]"; static SYNTAX: &str = "[OPTION]... [FILE]";
static SUMMARY: &str = "Output commands to set the LS_COLORS environment variable."; static SUMMARY: &str = "Output commands to set the LS_COLORS environment variable.";
static LONG_HELP: &str = " static LONG_HELP: &str = "
@ -52,28 +62,27 @@ pub fn guess_syntax() -> OutputFmt {
} }
} }
fn get_usage() -> String {
format!("{0} {1}", executable!(), SYNTAX)
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args let args = args
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP) let usage = get_usage();
.optflag("b", "sh", "output Bourne shell code to set LS_COLORS")
.optflag(
"",
"bourne-shell",
"output Bourne shell code to set LS_COLORS",
)
.optflag("c", "csh", "output C shell code to set LS_COLORS")
.optflag("", "c-shell", "output C shell code to set LS_COLORS")
.optflag("p", "print-database", "print the byte counts")
.parse(args);
if (matches.opt_present("csh") let matches = uu_app().usage(&usage[..]).get_matches_from(&args);
|| matches.opt_present("c-shell")
|| matches.opt_present("sh") let files = matches
|| matches.opt_present("bourne-shell")) .values_of(options::FILE)
&& matches.opt_present("print-database") .map_or(vec![], |file_values| file_values.collect());
// clap provides .conflicts_with / .conflicts_with_all, but we want to
// manually handle conflicts so we can match the output of GNU coreutils
if (matches.is_present(options::C_SHELL) || matches.is_present(options::BOURNE_SHELL))
&& matches.is_present(options::PRINT_DATABASE)
{ {
show_usage_error!( show_usage_error!(
"the options to output dircolors' internal database and\nto select a shell \ "the options to output dircolors' internal database and\nto select a shell \
@ -82,12 +91,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
return 1; return 1;
} }
if matches.opt_present("print-database") { if matches.is_present(options::PRINT_DATABASE) {
if !matches.free.is_empty() { if !files.is_empty() {
show_usage_error!( show_usage_error!(
"extra operand {}\nfile operands cannot be combined with \ "extra operand '{}'\nfile operands cannot be combined with \
--print-database (-p)", --print-database (-p)",
matches.free[0] files[0]
); );
return 1; return 1;
} }
@ -96,9 +105,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
let mut out_format = OutputFmt::Unknown; let mut out_format = OutputFmt::Unknown;
if matches.opt_present("csh") || matches.opt_present("c-shell") { if matches.is_present(options::C_SHELL) {
out_format = OutputFmt::CShell; out_format = OutputFmt::CShell;
} else if matches.opt_present("sh") || matches.opt_present("bourne-shell") { } else if matches.is_present(options::BOURNE_SHELL) {
out_format = OutputFmt::Shell; out_format = OutputFmt::Shell;
} }
@ -113,24 +122,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
let result; let result;
if matches.free.is_empty() { if files.is_empty() {
result = parse(INTERNAL_DB.lines(), out_format, "") result = parse(INTERNAL_DB.lines(), out_format, "")
} else { } else {
if matches.free.len() > 1 { if files.len() > 1 {
show_usage_error!("extra operand {}", matches.free[1]); show_usage_error!("extra operand '{}'", files[1]);
return 1; return 1;
} }
match File::open(matches.free[0].as_str()) { match File::open(files[0]) {
Ok(f) => { Ok(f) => {
let fin = BufReader::new(f); let fin = BufReader::new(f);
result = parse( result = parse(fin.lines().filter_map(Result::ok), out_format, files[0])
fin.lines().filter_map(Result::ok),
out_format,
matches.free[0].as_str(),
)
} }
Err(e) => { Err(e) => {
show_error!("{}: {}", matches.free[0], e); show_error!("{}: {}", files[0], e);
return 1; return 1;
} }
} }
@ -147,6 +152,37 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::BOURNE_SHELL)
.long("sh")
.short("b")
.visible_alias("bourne-shell")
.help("output Bourne shell code to set LS_COLORS")
.display_order(1),
)
.arg(
Arg::with_name(options::C_SHELL)
.long("csh")
.short("c")
.visible_alias("c-shell")
.help("output C shell code to set LS_COLORS")
.display_order(2),
)
.arg(
Arg::with_name(options::PRINT_DATABASE)
.long("print-database")
.short("p")
.help("print the byte counts")
.display_order(3),
)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
}
pub trait StrUtils { pub trait StrUtils {
/// Remove comments and trim whitespace /// Remove comments and trim whitespace
fn purify(&self) -> &Self; fn purify(&self) -> &Self;
@ -158,21 +194,25 @@ pub trait StrUtils {
impl StrUtils for str { impl StrUtils for str {
fn purify(&self) -> &Self { fn purify(&self) -> &Self {
let mut line = self; let mut line = self;
for (n, c) in self.chars().enumerate() { for (n, _) in self
if c != '#' { .as_bytes()
continue; .iter()
.enumerate()
.filter(|(_, c)| **c == b'#')
{
// Ignore the content after '#'
// only if it is preceded by at least one whitespace
match self[..n].chars().last() {
Some(c) if c.is_whitespace() => {
line = &self[..n - c.len_utf8()];
break;
} }
None => {
// Ignore if '#' is at the beginning of line // n == 0
if n == 0 {
line = &self[..0]; line = &self[..0];
break; break;
} }
_ => (),
// Ignore the content after '#'
// only if it is preceded by at least one whitespace
if self.chars().nth(n - 1).unwrap().is_whitespace() {
line = &self[..n];
} }
} }
line.trim() line.trim()

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/dirname.rs" path = "src/dirname.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -38,18 +38,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let after_help = get_long_usage(); let after_help = get_long_usage();
let matches = App::new(executable!()) let matches = uu_app()
.about(ABOUT)
.usage(&usage[..]) .usage(&usage[..])
.after_help(&after_help[..]) .after_help(&after_help[..])
.version(crate_version!())
.arg(
Arg::with_name(options::ZERO)
.long(options::ZERO)
.short("z")
.help("separate output with NUL rather than newline"),
)
.arg(Arg::with_name(options::DIR).hidden(true).multiple(true))
.get_matches_from(args); .get_matches_from(args);
let separator = if matches.is_present(options::ZERO) { let separator = if matches.is_present(options::ZERO) {
@ -92,3 +83,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0 0
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.about(ABOUT)
.version(crate_version!())
.arg(
Arg::with_name(options::ZERO)
.long(options::ZERO)
.short("z")
.help("separate output with NUL rather than newline"),
)
.arg(Arg::with_name(options::DIR).hidden(true).multiple(true))
}

View file

@ -15,10 +15,12 @@ edition = "2018"
path = "src/du.rs" path = "src/du.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
chrono = "0.4" chrono = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version="0.3", features=[] } winapi = { version="0.3", features=[] }
[[bin]] [[bin]]

View file

@ -1,17 +1,19 @@
// This file is part of the uutils coreutils package. // * This file is part of the uutils coreutils package.
// // *
// (c) Derek Chiang <derekchiang93@gmail.com> // * (c) Derek Chiang <derekchiang93@gmail.com>
// // *
// For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // * file that was distributed with this source code.
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use chrono::prelude::DateTime; use chrono::prelude::DateTime;
use chrono::Local; use chrono::Local;
use clap::ArgMatches;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom;
use std::env; use std::env;
use std::fs; use std::fs;
#[cfg(not(windows))] #[cfg(not(windows))]
@ -24,8 +26,12 @@ use std::os::unix::fs::MetadataExt;
use std::os::windows::fs::MetadataExt; use std::os::windows::fs::MetadataExt;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::io::AsRawHandle; use std::os::windows::io::AsRawHandle;
#[cfg(windows)]
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use std::time::{Duration, UNIX_EPOCH}; use std::time::{Duration, UNIX_EPOCH};
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
#[cfg(windows)] #[cfg(windows)]
use winapi::shared::minwindef::{DWORD, LPVOID}; use winapi::shared::minwindef::{DWORD, LPVOID};
@ -42,7 +48,7 @@ mod options {
pub const NULL: &str = "0"; pub const NULL: &str = "0";
pub const ALL: &str = "all"; pub const ALL: &str = "all";
pub const APPARENT_SIZE: &str = "apparent-size"; pub const APPARENT_SIZE: &str = "apparent-size";
pub const BLOCK_SIZE: &str = "B"; pub const BLOCK_SIZE: &str = "block-size";
pub const BYTES: &str = "b"; pub const BYTES: &str = "b";
pub const TOTAL: &str = "c"; pub const TOTAL: &str = "c";
pub const MAX_DEPTH: &str = "d"; pub const MAX_DEPTH: &str = "d";
@ -52,9 +58,13 @@ mod options {
pub const BLOCK_SIZE_1M: &str = "m"; pub const BLOCK_SIZE_1M: &str = "m";
pub const SEPARATE_DIRS: &str = "S"; pub const SEPARATE_DIRS: &str = "S";
pub const SUMMARIZE: &str = "s"; pub const SUMMARIZE: &str = "s";
pub const THRESHOLD: &str = "threshold";
pub const SI: &str = "si"; pub const SI: &str = "si";
pub const TIME: &str = "time"; pub const TIME: &str = "time";
pub const TIME_STYLE: &str = "time-style"; pub const TIME_STYLE: &str = "time-style";
pub const ONE_FILE_SYSTEM: &str = "one-file-system";
pub const DEREFERENCE: &str = "dereference";
pub const INODES: &str = "inodes";
pub const FILE: &str = "FILE"; pub const FILE: &str = "FILE";
} }
@ -79,6 +89,9 @@ struct Options {
max_depth: Option<usize>, max_depth: Option<usize>,
total: bool, total: bool,
separate_dirs: bool, separate_dirs: bool,
one_file_system: bool,
dereference: bool,
inodes: bool,
} }
#[derive(PartialEq, Eq, Hash, Clone, Copy)] #[derive(PartialEq, Eq, Hash, Clone, Copy)]
@ -92,6 +105,7 @@ struct Stat {
is_dir: bool, is_dir: bool,
size: u64, size: u64,
blocks: u64, blocks: u64,
inodes: u64,
inode: Option<FileInfo>, inode: Option<FileInfo>,
created: Option<u64>, created: Option<u64>,
accessed: u64, accessed: u64,
@ -99,8 +113,12 @@ struct Stat {
} }
impl Stat { impl Stat {
fn new(path: PathBuf) -> Result<Stat> { fn new(path: PathBuf, options: &Options) -> Result<Stat> {
let metadata = fs::symlink_metadata(&path)?; let metadata = if options.dereference {
fs::metadata(&path)?
} else {
fs::symlink_metadata(&path)?
};
#[cfg(not(windows))] #[cfg(not(windows))]
let file_info = FileInfo { let file_info = FileInfo {
@ -113,6 +131,7 @@ impl Stat {
is_dir: metadata.is_dir(), is_dir: metadata.is_dir(),
size: metadata.len(), size: metadata.len(),
blocks: metadata.blocks() as u64, blocks: metadata.blocks() as u64,
inodes: 1,
inode: Some(file_info), inode: Some(file_info),
created: birth_u64(&metadata), created: birth_u64(&metadata),
accessed: metadata.atime() as u64, accessed: metadata.atime() as u64,
@ -130,6 +149,7 @@ impl Stat {
size: metadata.len(), size: metadata.len(),
blocks: size_on_disk / 1024 * 2, blocks: size_on_disk / 1024 * 2,
inode: file_info, inode: file_info,
inodes: 1,
created: windows_creation_time_to_unix_time(metadata.creation_time()), created: windows_creation_time_to_unix_time(metadata.creation_time()),
accessed: windows_time_to_unix_time(metadata.last_access_time()), accessed: windows_time_to_unix_time(metadata.last_access_time()),
modified: windows_time_to_unix_time(metadata.last_write_time()), modified: windows_time_to_unix_time(metadata.last_write_time()),
@ -159,7 +179,7 @@ fn birth_u64(meta: &Metadata) -> Option<u64> {
} }
#[cfg(windows)] #[cfg(windows)]
fn get_size_on_disk(path: &PathBuf) -> u64 { fn get_size_on_disk(path: &Path) -> u64 {
let mut size_on_disk = 0; let mut size_on_disk = 0;
// bind file so it stays in scope until end of function // bind file so it stays in scope until end of function
@ -191,7 +211,7 @@ fn get_size_on_disk(path: &PathBuf) -> u64 {
} }
#[cfg(windows)] #[cfg(windows)]
fn get_file_info(path: &PathBuf) -> Option<FileInfo> { fn get_file_info(path: &Path) -> Option<FileInfo> {
let mut result = None; let mut result = None;
let file = match fs::File::open(path) { let file = match fs::File::open(path) {
@ -223,65 +243,35 @@ fn get_file_info(path: &PathBuf) -> Option<FileInfo> {
result result
} }
fn unit_string_to_number(s: &str) -> Option<u64> { fn read_block_size(s: Option<&str>) -> usize {
let mut offset = 0; if let Some(s) = s {
let mut s_chars = s.chars().rev(); parse_size(s)
.unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::BLOCK_SIZE)))
let (mut ch, multiple) = match s_chars.next() { } else {
Some('B') | Some('b') => ('B', 1000u64),
Some(ch) => (ch, 1024u64),
None => return None,
};
if ch == 'B' {
ch = s_chars.next()?;
offset += 1;
}
ch = ch.to_ascii_uppercase();
let unit = UNITS
.iter()
.rev()
.find(|&&(unit_ch, _)| unit_ch == ch)
.map(|&(_, val)| {
// we found a match, so increment offset
offset += 1;
val
})
.or_else(|| if multiple == 1024 { Some(0) } else { None })?;
let number = s[..s.len() - offset].parse::<u64>().ok()?;
Some(number * multiple.pow(unit))
}
fn translate_to_pure_number(s: &Option<&str>) -> Option<u64> {
match *s {
Some(ref s) => unit_string_to_number(s),
None => None,
}
}
fn read_block_size(s: Option<&str>) -> u64 {
match translate_to_pure_number(&s) {
Some(v) => v,
None => {
if let Some(value) = s {
show_error!("invalid --block-size argument '{}'", value);
};
for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
let env_size = env::var(env_var).ok(); if let Ok(env_size) = env::var(env_var) {
if let Some(quantity) = translate_to_pure_number(&env_size.as_deref()) { if let Ok(v) = parse_size(&env_size) {
return quantity; return v;
}
} }
} }
if env::var("POSIXLY_CORRECT").is_ok() { if env::var("POSIXLY_CORRECT").is_ok() {
512 512
} else { } else {
1024 1024
} }
} }
}
fn choose_size(matches: &ArgMatches, stat: &Stat) -> u64 {
if matches.is_present(options::INODES) {
stat.inodes
} else if matches.is_present(options::APPARENT_SIZE) || matches.is_present(options::BYTES) {
stat.size
} else {
// The st_blocks field indicates the number of blocks allocated to the file, 512-byte units.
// See: http://linux.die.net/man/2/stat
stat.blocks * 512
} }
} }
@ -302,7 +292,7 @@ fn du(
Err(e) => { Err(e) => {
safe_writeln!( safe_writeln!(
stderr(), stderr(),
"{}: cannot read directory {}: {}", "{}: cannot read directory '{}': {}",
options.program_name, options.program_name,
my_stat.path.display(), my_stat.path.display(),
e e
@ -313,20 +303,29 @@ fn du(
for f in read { for f in read {
match f { match f {
Ok(entry) => match Stat::new(entry.path()) { Ok(entry) => match Stat::new(entry.path(), options) {
Ok(this_stat) => { Ok(this_stat) => {
if this_stat.is_dir { if let Some(inode) = this_stat.inode {
futures.push(du(this_stat, options, depth + 1, inodes));
} else {
if this_stat.inode.is_some() {
let inode = this_stat.inode.unwrap();
if inodes.contains(&inode) { if inodes.contains(&inode) {
continue; continue;
} }
inodes.insert(inode); inodes.insert(inode);
} }
if this_stat.is_dir {
if options.one_file_system {
if let (Some(this_inode), Some(my_inode)) =
(this_stat.inode, my_stat.inode)
{
if this_inode.dev_id != my_inode.dev_id {
continue;
}
}
}
futures.push(du(this_stat, options, depth + 1, inodes));
} else {
my_stat.size += this_stat.size; my_stat.size += this_stat.size;
my_stat.blocks += this_stat.blocks; my_stat.blocks += this_stat.blocks;
my_stat.inodes += 1;
if options.all { if options.all {
stats.push(this_stat); stats.push(this_stat);
} }
@ -334,18 +333,11 @@ fn du(
} }
Err(error) => match error.kind() { Err(error) => match error.kind() {
ErrorKind::PermissionDenied => { ErrorKind::PermissionDenied => {
let description = format!( let description = format!("cannot access '{}'", entry.path().display());
"cannot access '{}'",
entry
.path()
.as_os_str()
.to_str()
.unwrap_or("<Un-printable path>")
);
let error_message = "Permission denied"; let error_message = "Permission denied";
show_error_custom_description!(description, "{}", error_message) show_error_custom_description!(description, "{}", error_message)
} }
_ => show_error!("{}", error), _ => show_error!("cannot access '{}': {}", entry.path().display(), error),
}, },
}, },
Err(error) => show_error!("{}", error), Err(error) => show_error!("{}", error),
@ -353,12 +345,15 @@ fn du(
} }
} }
stats.extend(futures.into_iter().flatten().rev().filter(|stat| { stats.extend(futures.into_iter().flatten().filter(|stat| {
if !options.separate_dirs && stat.path.parent().unwrap() == my_stat.path { if !options.separate_dirs && stat.path.parent().unwrap() == my_stat.path {
my_stat.size += stat.size; my_stat.size += stat.size;
my_stat.blocks += stat.blocks; my_stat.blocks += stat.blocks;
my_stat.inodes += stat.inodes;
} }
options.max_depth == None || depth < options.max_depth.unwrap() options
.max_depth
.map_or(true, |max_depth| depth < max_depth)
})); }));
stats.push(my_stat); stats.push(my_stat);
Box::new(stats.into_iter()) Box::new(stats.into_iter())
@ -412,182 +407,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
.version(crate_version!())
.about(SUMMARY)
.usage(&usage[..])
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::ALL)
.short("a")
.long(options::ALL)
.help("write counts for all files, not just directories"),
)
.arg(
Arg::with_name(options::APPARENT_SIZE)
.long(options::APPARENT_SIZE)
.help(
"print apparent sizes, rather than disk usage \
although the apparent size is usually smaller, it may be larger due to holes \
in ('sparse') files, internal fragmentation, indirect blocks, and the like"
)
)
.arg(
Arg::with_name(options::BLOCK_SIZE)
.short("B")
.long("block-size")
.value_name("SIZE")
.help(
"scale sizes by SIZE before printing them. \
E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below."
)
)
.arg(
Arg::with_name(options::BYTES)
.short("b")
.long("bytes")
.help("equivalent to '--apparent-size --block-size=1'")
)
.arg(
Arg::with_name(options::TOTAL)
.long("total")
.short("c")
.help("produce a grand total")
)
.arg(
Arg::with_name(options::MAX_DEPTH)
.short("d")
.long("max-depth")
.value_name("N")
.help(
"print the total for a directory (or file, with --all) \
only if it is N or fewer levels below the command \
line argument; --max-depth=0 is the same as --summarize"
)
)
.arg(
Arg::with_name(options::HUMAN_READABLE)
.long("human-readable")
.short("h")
.help("print sizes in human readable format (e.g., 1K 234M 2G)")
)
.arg(
Arg::with_name("inodes")
.long("inodes")
.help(
"list inode usage information instead of block usage like --block-size=1K"
)
)
.arg(
Arg::with_name(options::BLOCK_SIZE_1K)
.short("k")
.help("like --block-size=1K")
)
.arg(
Arg::with_name(options::COUNT_LINKS)
.short("l")
.long("count-links")
.help("count sizes many times if hard linked")
)
// .arg(
// Arg::with_name("dereference")
// .short("L")
// .long("dereference")
// .help("dereference all symbolic links")
// )
// .arg(
// Arg::with_name("no-dereference")
// .short("P")
// .long("no-dereference")
// .help("don't follow any symbolic links (this is the default)")
// )
.arg(
Arg::with_name(options::BLOCK_SIZE_1M)
.short("m")
.help("like --block-size=1M")
)
.arg(
Arg::with_name(options::NULL)
.short("0")
.long("null")
.help("end each output line with 0 byte rather than newline")
)
.arg(
Arg::with_name(options::SEPARATE_DIRS)
.short("S")
.long("separate-dirs")
.help("do not include size of subdirectories")
)
.arg(
Arg::with_name(options::SUMMARIZE)
.short("s")
.long("summarize")
.help("display only a total for each argument")
)
.arg(
Arg::with_name(options::SI)
.long(options::SI)
.help("like -h, but use powers of 1000 not 1024")
)
// .arg(
// Arg::with_name("one-file-system")
// .short("x")
// .long("one-file-system")
// .help("skip directories on different file systems")
// )
// .arg(
// Arg::with_name("")
// .short("x")
// .long("exclude-from")
// .value_name("FILE")
// .help("exclude files that match any pattern in FILE")
// )
// .arg(
// Arg::with_name("exclude")
// .long("exclude")
// .value_name("PATTERN")
// .help("exclude files that match PATTERN")
// )
.arg(
Arg::with_name(options::TIME)
.long(options::TIME)
.value_name("WORD")
.require_equals(true)
.min_values(0)
.possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"])
.help(
"show time of the last modification of any file in the \
directory, or any of its subdirectories. If WORD is given, show time as WORD instead \
of modification time: atime, access, use, ctime, status, birth or creation"
)
)
.arg(
Arg::with_name(options::TIME_STYLE)
.long(options::TIME_STYLE)
.value_name("STYLE")
.help(
"show times using style STYLE: \
full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'"
)
)
.arg(
Arg::with_name(options::FILE)
.hidden(true)
.multiple(true)
)
.get_matches_from(args);
let summarize = matches.is_present(options::SUMMARIZE); let summarize = matches.is_present(options::SUMMARIZE);
let max_depth_str = matches.value_of(options::MAX_DEPTH); let max_depth_str = matches.value_of(options::MAX_DEPTH);
let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::<usize>().ok()); let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::<usize>().ok());
match (max_depth_str, max_depth) { match (max_depth_str, max_depth) {
(Some(ref s), _) if summarize => { (Some(s), _) if summarize => {
show_error!("summarizing conflicts with --max-depth={}", *s); show_error!("summarizing conflicts with --max-depth={}", s);
return 1; return 1;
} }
(Some(ref s), None) => { (Some(s), None) => {
show_error!("invalid maximum depth '{}'", *s); show_error!("invalid maximum depth '{}'", s);
return 1; return 1;
} }
(Some(_), Some(_)) | (None, _) => { /* valid */ } (Some(_), Some(_)) | (None, _) => { /* valid */ }
@ -599,16 +431,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
max_depth, max_depth,
total: matches.is_present(options::TOTAL), total: matches.is_present(options::TOTAL),
separate_dirs: matches.is_present(options::SEPARATE_DIRS), separate_dirs: matches.is_present(options::SEPARATE_DIRS),
one_file_system: matches.is_present(options::ONE_FILE_SYSTEM),
dereference: matches.is_present(options::DEREFERENCE),
inodes: matches.is_present(options::INODES),
}; };
let files = match matches.value_of(options::FILE) { let files = match matches.value_of(options::FILE) {
Some(_) => matches.values_of(options::FILE).unwrap().collect(), Some(_) => matches.values_of(options::FILE).unwrap().collect(),
None => { None => vec!["."],
vec!["./"] // TODO: gnu `du` doesn't use trailing "/" here
}
}; };
let block_size = read_block_size(matches.value_of(options::BLOCK_SIZE)); if options.inodes
&& (matches.is_present(options::APPARENT_SIZE) || matches.is_present(options::BYTES))
{
show_warning!("options --apparent-size and -b are ineffective with --inodes")
}
let block_size = u64::try_from(read_block_size(matches.value_of(options::BLOCK_SIZE))).unwrap();
let threshold = matches.value_of(options::THRESHOLD).map(|s| {
Threshold::from_str(s)
.unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::THRESHOLD)))
});
let multiplier: u64 = if matches.is_present(options::SI) { let multiplier: u64 = if matches.is_present(options::SI) {
1000 1000
@ -628,7 +472,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
convert_size_other convert_size_other
} }
}; };
let convert_size = |size| convert_size_fn(size, multiplier, block_size); let convert_size = |size: u64| {
if options.inodes {
size.to_string()
} else {
convert_size_fn(size, multiplier, block_size)
}
};
let time_format_str = match matches.value_of("time-style") { let time_format_str = match matches.value_of("time-style") {
Some(s) => match s { Some(s) => match s {
@ -661,23 +511,22 @@ Try '{} --help' for more information.",
let mut grand_total = 0; let mut grand_total = 0;
for path_string in files { for path_string in files {
let path = PathBuf::from(&path_string); let path = PathBuf::from(&path_string);
match Stat::new(path) { match Stat::new(path, &options) {
Ok(stat) => { Ok(stat) => {
let mut inodes: HashSet<FileInfo> = HashSet::new(); let mut inodes: HashSet<FileInfo> = HashSet::new();
if let Some(inode) = stat.inode {
inodes.insert(inode);
}
let iter = du(stat, &options, 0, &mut inodes); let iter = du(stat, &options, 0, &mut inodes);
let (_, len) = iter.size_hint(); let (_, len) = iter.size_hint();
let len = len.unwrap(); let len = len.unwrap();
for (index, stat) in iter.enumerate() { for (index, stat) in iter.enumerate() {
let size = if matches.is_present(options::APPARENT_SIZE) let size = choose_size(&matches, &stat);
|| matches.is_present(options::BYTES)
{ if threshold.map_or(false, |threshold| threshold.should_exclude(size)) {
stat.size continue;
} else { }
// C's stat is such that each block is assume to be 512 bytes
// See: http://linux.die.net/man/2/stat
stat.blocks * 512
};
if matches.is_present(options::TIME) { if matches.is_present(options::TIME) {
let tm = { let tm = {
let secs = { let secs = {
@ -690,8 +539,8 @@ Try '{} --help' for more information.",
time time
} else { } else {
show_error!( show_error!(
"Invalid argument {} for --time. "Invalid argument '{}' for --time.
birth and creation arguments are not supported on this platform.", 'birth' and 'creation' arguments are not supported on this platform.",
s s
); );
return 1; return 1;
@ -744,31 +593,235 @@ Try '{} --help' for more information.",
0 0
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::ALL)
.short("a")
.long(options::ALL)
.help("write counts for all files, not just directories"),
)
.arg(
Arg::with_name(options::APPARENT_SIZE)
.long(options::APPARENT_SIZE)
.help(
"print apparent sizes, rather than disk usage \
although the apparent size is usually smaller, it may be larger due to holes \
in ('sparse') files, internal fragmentation, indirect blocks, and the like"
)
.alias("app") // The GNU test suite uses this alias
)
.arg(
Arg::with_name(options::BLOCK_SIZE)
.short("B")
.long(options::BLOCK_SIZE)
.value_name("SIZE")
.help(
"scale sizes by SIZE before printing them. \
E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below."
)
)
.arg(
Arg::with_name(options::BYTES)
.short("b")
.long("bytes")
.help("equivalent to '--apparent-size --block-size=1'")
)
.arg(
Arg::with_name(options::TOTAL)
.long("total")
.short("c")
.help("produce a grand total")
)
.arg(
Arg::with_name(options::MAX_DEPTH)
.short("d")
.long("max-depth")
.value_name("N")
.help(
"print the total for a directory (or file, with --all) \
only if it is N or fewer levels below the command \
line argument; --max-depth=0 is the same as --summarize"
)
)
.arg(
Arg::with_name(options::HUMAN_READABLE)
.long("human-readable")
.short("h")
.help("print sizes in human readable format (e.g., 1K 234M 2G)")
)
.arg(
Arg::with_name(options::INODES)
.long(options::INODES)
.help(
"list inode usage information instead of block usage like --block-size=1K"
)
)
.arg(
Arg::with_name(options::BLOCK_SIZE_1K)
.short("k")
.help("like --block-size=1K")
)
.arg(
Arg::with_name(options::COUNT_LINKS)
.short("l")
.long("count-links")
.help("count sizes many times if hard linked")
)
.arg(
Arg::with_name(options::DEREFERENCE)
.short("L")
.long(options::DEREFERENCE)
.help("dereference all symbolic links")
)
// .arg(
// Arg::with_name("no-dereference")
// .short("P")
// .long("no-dereference")
// .help("don't follow any symbolic links (this is the default)")
// )
.arg(
Arg::with_name(options::BLOCK_SIZE_1M)
.short("m")
.help("like --block-size=1M")
)
.arg(
Arg::with_name(options::NULL)
.short("0")
.long("null")
.help("end each output line with 0 byte rather than newline")
)
.arg(
Arg::with_name(options::SEPARATE_DIRS)
.short("S")
.long("separate-dirs")
.help("do not include size of subdirectories")
)
.arg(
Arg::with_name(options::SUMMARIZE)
.short("s")
.long("summarize")
.help("display only a total for each argument")
)
.arg(
Arg::with_name(options::SI)
.long(options::SI)
.help("like -h, but use powers of 1000 not 1024")
)
.arg(
Arg::with_name(options::ONE_FILE_SYSTEM)
.short("x")
.long(options::ONE_FILE_SYSTEM)
.help("skip directories on different file systems")
)
.arg(
Arg::with_name(options::THRESHOLD)
.short("t")
.long(options::THRESHOLD)
.alias("th")
.value_name("SIZE")
.number_of_values(1)
.allow_hyphen_values(true)
.help("exclude entries smaller than SIZE if positive, \
or entries greater than SIZE if negative")
)
// .arg(
// Arg::with_name("")
// .short("x")
// .long("exclude-from")
// .value_name("FILE")
// .help("exclude files that match any pattern in FILE")
// )
// .arg(
// Arg::with_name("exclude")
// .long("exclude")
// .value_name("PATTERN")
// .help("exclude files that match PATTERN")
// )
.arg(
Arg::with_name(options::TIME)
.long(options::TIME)
.value_name("WORD")
.require_equals(true)
.min_values(0)
.possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"])
.help(
"show time of the last modification of any file in the \
directory, or any of its subdirectories. If WORD is given, show time as WORD instead \
of modification time: atime, access, use, ctime, status, birth or creation"
)
)
.arg(
Arg::with_name(options::TIME_STYLE)
.long(options::TIME_STYLE)
.value_name("STYLE")
.help(
"show times using style STYLE: \
full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'"
)
)
.arg(
Arg::with_name(options::FILE)
.hidden(true)
.multiple(true)
)
}
#[derive(Clone, Copy)]
enum Threshold {
Lower(u64),
Upper(u64),
}
impl FromStr for Threshold {
type Err = ParseSizeError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let offset = if s.starts_with(&['-', '+'][..]) { 1 } else { 0 };
let size = u64::try_from(parse_size(&s[offset..])?).unwrap();
if s.starts_with('-') {
Ok(Threshold::Upper(size))
} else {
Ok(Threshold::Lower(size))
}
}
}
impl Threshold {
fn should_exclude(&self, size: u64) -> bool {
match *self {
Threshold::Upper(threshold) => size > threshold,
Threshold::Lower(threshold) => size < threshold,
}
}
}
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
// NOTE:
// GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection
// GNU's du does distinguish between "invalid (suffix in) argument"
match error {
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
}
}
#[cfg(test)] #[cfg(test)]
mod test_du { mod test_du {
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; use super::*;
#[test]
fn test_translate_to_pure_number() {
let test_data = [
(Some("10".to_string()), Some(10)),
(Some("10K".to_string()), Some(10 * 1024)),
(Some("5M".to_string()), Some(5 * 1024 * 1024)),
(Some("900KB".to_string()), Some(900 * 1000)),
(Some("BAD_STRING".to_string()), None),
];
for it in test_data.iter() {
assert_eq!(translate_to_pure_number(&it.0.as_deref()), it.1);
}
}
#[test] #[test]
fn test_read_block_size() { fn test_read_block_size() {
let test_data = [ let test_data = [
(Some("10".to_string()), 10), (Some("1024".to_string()), 1024),
(Some("K".to_string()), 1024),
(None, 1024), (None, 1024),
(Some("BAD_STRING".to_string()), 1024),
]; ];
for it in test_data.iter() { for it in test_data.iter() {
assert_eq!(read_block_size(it.0.as_deref()), it.1); assert_eq!(read_block_size(it.0.as_deref()), it.1);

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/echo.rs" path = "src/echo.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -117,7 +117,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
let matches = App::new(executable!()) let matches = uu_app().get_matches_from(args);
let no_newline = matches.is_present(options::NO_NEWLINE);
let escaped = matches.is_present(options::ENABLE_BACKSLASH_ESCAPE);
let values: Vec<String> = match matches.values_of(options::STRING) {
Some(s) => s.map(|s| s.to_string()).collect(),
None => vec!["".to_string()],
};
match execute(no_newline, escaped, values) {
Ok(_) => 0,
Err(f) => {
show_error!("{}", f);
1
}
}
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME) .name(NAME)
// TrailingVarArg specifies the final positional argument is a VarArg // TrailingVarArg specifies the final positional argument is a VarArg
// and it doesn't attempts the parse any further args. // and it doesn't attempts the parse any further args.
@ -154,22 +173,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.multiple(true) .multiple(true)
.allow_hyphen_values(true), .allow_hyphen_values(true),
) )
.get_matches_from(args);
let no_newline = matches.is_present(options::NO_NEWLINE);
let escaped = matches.is_present(options::ENABLE_BACKSLASH_ESCAPE);
let values: Vec<String> = match matches.values_of(options::STRING) {
Some(s) => s.map(|s| s.to_string()).collect(),
None => vec!["".to_string()],
};
match execute(no_newline, escaped, values) {
Ok(_) => 0,
Err(f) => {
show_error!("{}", f);
1
}
}
} }
fn execute(no_newline: bool, escaped: bool, free: Vec<String>) -> io::Result<()> { fn execute(no_newline: bool, escaped: bool, free: Vec<String>) -> io::Result<()> {
@ -181,7 +184,7 @@ fn execute(no_newline: bool, escaped: bool, free: Vec<String>) -> io::Result<()>
write!(output, " ")?; write!(output, " ")?;
} }
if escaped { if escaped {
let should_stop = print_escaped(&input, &mut output)?; let should_stop = print_escaped(input, &mut output)?;
if should_stop { if should_stop {
break; break;
} }

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/env.rs" path = "src/env.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
rust-ini = "0.13.0" rust-ini = "0.13.0"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }

22
src/uu/env/src/env.rs vendored
View file

@ -82,13 +82,10 @@ fn load_config_file(opts: &mut Options) -> Result<(), i32> {
Ini::load_from_file(file) Ini::load_from_file(file)
}; };
let conf = match conf { let conf = conf.map_err(|error| {
Ok(config) => config,
Err(error) => {
eprintln!("env: error: \"{}\": {}", file, error); eprintln!("env: error: \"{}\": {}", file, error);
return Err(1); 1
} })?;
};
for (_, prop) in &conf { for (_, prop) in &conf {
// ignore all INI section lines (treat them as comments) // ignore all INI section lines (treat them as comments)
@ -117,7 +114,7 @@ fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b
(progname, &args[..]) (progname, &args[..])
} }
fn create_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(crate_name!()) App::new(crate_name!())
.version(crate_version!()) .version(crate_version!())
.author(crate_authors!()) .author(crate_authors!())
@ -161,7 +158,7 @@ fn create_app() -> App<'static, 'static> {
} }
fn run_env(args: impl uucore::Args) -> Result<(), i32> { fn run_env(args: impl uucore::Args) -> Result<(), i32> {
let app = create_app(); let app = uu_app();
let matches = app.get_matches_from(args); let matches = app.get_matches_from(args);
let ignore_env = matches.is_present("ignore-environment"); let ignore_env = matches.is_present("ignore-environment");
@ -245,7 +242,7 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> {
} }
// set specified env vars // set specified env vars
for &(ref name, ref val) in &opts.sets { for &(name, val) in &opts.sets {
// FIXME: set_var() panics if name is an empty string // FIXME: set_var() panics if name is an empty string
env::set_var(name, val); env::set_var(name, val);
} }
@ -256,13 +253,10 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> {
// FIXME: this should just use execvp() (no fork()) on Unix-like systems // FIXME: this should just use execvp() (no fork()) on Unix-like systems
match Command::new(&*prog).args(args).status() { match Command::new(&*prog).args(args).status() {
Ok(exit) => { Ok(exit) if !exit.success() => return Err(exit.code().unwrap()),
if !exit.success() {
return Err(exit.code().unwrap());
}
}
Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127), Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127),
Err(_) => return Err(126), Err(_) => return Err(126),
Ok(_) => (),
} }
} else { } else {
// no program provided, so just dump all env vars to stdout // no program provided, so just dump all env vars to stdout

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/expand.rs" path = "src/expand.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
unicode-width = "0.1.5" unicode-width = "0.1.5"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -15,7 +15,6 @@ extern crate uucore;
use clap::{crate_version, App, Arg, ArgMatches}; use clap::{crate_version, App, Arg, ArgMatches};
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
use std::iter::repeat;
use std::str::from_utf8; use std::str::from_utf8;
use unicode_width::UnicodeWidthChar; use unicode_width::UnicodeWidthChar;
@ -90,7 +89,7 @@ impl Options {
}) })
.max() .max()
.unwrap(); // length of tabstops is guaranteed >= 1 .unwrap(); // length of tabstops is guaranteed >= 1
let tspaces = repeat(' ').take(nspaces).collect(); let tspaces = " ".repeat(nspaces);
let files: Vec<String> = match matches.values_of(options::FILES) { let files: Vec<String> = match matches.values_of(options::FILES) {
Some(s) => s.map(|v| v.to_string()).collect(), Some(s) => s.map(|v| v.to_string()).collect(),
@ -109,10 +108,16 @@ impl Options {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
expand(Options::new(&matches));
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.usage(&usage[..])
.after_help(LONG_HELP) .after_help(LONG_HELP)
.arg( .arg(
Arg::with_name(options::INITIAL) Arg::with_name(options::INITIAL)
@ -139,10 +144,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.hidden(true) .hidden(true)
.takes_value(true) .takes_value(true)
) )
.get_matches_from(args);
expand(Options::new(&matches));
0
} }
fn open(path: String) -> BufReader<Box<dyn Read + 'static>> { fn open(path: String) -> BufReader<Box<dyn Read + 'static>> {
@ -236,7 +237,7 @@ fn expand(options: Options) {
// now dump out either spaces if we're expanding, or a literal tab if we're not // now dump out either spaces if we're expanding, or a literal tab if we're not
if init || !options.iflag { if init || !options.iflag {
safe_unwrap!(output.write_all(&options.tspaces[..nts].as_bytes())); safe_unwrap!(output.write_all(options.tspaces[..nts].as_bytes()));
} else { } else {
safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); safe_unwrap!(output.write_all(&buf[byte..byte + nbytes]));
} }

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/expr.rs" path = "src/expr.rs"
[dependencies] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
num-bigint = "0.4.0" num-bigint = "0.4.0"
num-traits = "0.2.14" num-traits = "0.2.14"

View file

@ -8,13 +8,20 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
mod syntax_tree; mod syntax_tree;
mod tokens; mod tokens;
static NAME: &str = "expr"; const VERSION: &str = "version";
static VERSION: &str = env!("CARGO_PKG_VERSION"); const HELP: &str = "help";
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.arg(Arg::with_name(VERSION).long(VERSION))
.arg(Arg::with_name(HELP).long(HELP))
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args let args = args
@ -37,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
fn process_expr(token_strings: &[String]) -> Result<String, String> { fn process_expr(token_strings: &[String]) -> Result<String, String> {
let maybe_tokens = tokens::strings_to_tokens(&token_strings); let maybe_tokens = tokens::strings_to_tokens(token_strings);
let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens); let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens);
evaluate_ast(maybe_ast) evaluate_ast(maybe_ast)
} }
@ -56,11 +63,7 @@ fn print_expr_error(expr_error: &str) -> ! {
} }
fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> { fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
if maybe_ast.is_err() { maybe_ast.and_then(|ast| ast.evaluate())
Err(maybe_ast.err().unwrap())
} else {
maybe_ast.ok().unwrap().evaluate()
}
} }
fn maybe_handle_help_or_version(args: &[String]) -> bool { fn maybe_handle_help_or_version(args: &[String]) -> bool {
@ -137,5 +140,5 @@ Environment variables:
} }
fn print_version() { fn print_version() {
println!("{} {}", NAME, VERSION); println!("{} {}", executable!(), crate_version!());
} }

View file

@ -160,10 +160,8 @@ impl AstNode {
if let AstNode::Node { operands, .. } = self { if let AstNode::Node { operands, .. } = self {
let mut out = Vec::with_capacity(operands.len()); let mut out = Vec::with_capacity(operands.len());
for operand in operands { for operand in operands {
match operand.evaluate() { let value = operand.evaluate()?;
Ok(value) => out.push(value), out.push(value);
Err(reason) => return Err(reason),
}
} }
Ok(out) Ok(out)
} else { } else {
@ -175,23 +173,14 @@ impl AstNode {
pub fn tokens_to_ast( pub fn tokens_to_ast(
maybe_tokens: Result<Vec<(usize, Token)>, String>, maybe_tokens: Result<Vec<(usize, Token)>, String>,
) -> Result<Box<AstNode>, String> { ) -> Result<Box<AstNode>, String> {
if maybe_tokens.is_err() { maybe_tokens.and_then(|tokens| {
Err(maybe_tokens.err().unwrap())
} else {
let tokens = maybe_tokens.ok().unwrap();
let mut out_stack: TokenStack = Vec::new(); let mut out_stack: TokenStack = Vec::new();
let mut op_stack: TokenStack = Vec::new(); let mut op_stack: TokenStack = Vec::new();
for (token_idx, token) in tokens { for (token_idx, token) in tokens {
if let Err(reason) = push_token_to_either_stack(token_idx, &token, &mut out_stack, &mut op_stack)?;
push_token_to_either_stack(token_idx, &token, &mut out_stack, &mut op_stack)
{
return Err(reason);
}
}
if let Err(reason) = move_rest_of_ops_to_out(&mut out_stack, &mut op_stack) {
return Err(reason);
} }
move_rest_of_ops_to_out(&mut out_stack, &mut op_stack)?;
assert!(op_stack.is_empty()); assert!(op_stack.is_empty());
maybe_dump_rpn(&out_stack); maybe_dump_rpn(&out_stack);
@ -205,7 +194,7 @@ pub fn tokens_to_ast(
maybe_dump_ast(&result); maybe_dump_ast(&result);
result result
} }
} })
} }
fn maybe_dump_ast(result: &Result<Box<AstNode>, String>) { fn maybe_dump_ast(result: &Result<Box<AstNode>, String>) {
@ -261,10 +250,8 @@ fn maybe_ast_node(
) -> Result<Box<AstNode>, String> { ) -> Result<Box<AstNode>, String> {
let mut operands = Vec::with_capacity(arity); let mut operands = Vec::with_capacity(arity);
for _ in 0..arity { for _ in 0..arity {
match ast_from_rpn(rpn) { let operand = ast_from_rpn(rpn)?;
Err(reason) => return Err(reason), operands.push(operand);
Ok(operand) => operands.push(operand),
}
} }
operands.reverse(); operands.reverse();
Ok(AstNode::new_node(token_idx, op_type, operands)) Ok(AstNode::new_node(token_idx, op_type, operands))
@ -408,10 +395,12 @@ fn move_till_match_paren(
op_stack: &mut TokenStack, op_stack: &mut TokenStack,
) -> Result<(), String> { ) -> Result<(), String> {
loop { loop {
match op_stack.pop() { let op = op_stack
None => return Err("syntax error (Mismatched close-parenthesis)".to_string()), .pop()
Some((_, Token::ParOpen)) => return Ok(()), .ok_or_else(|| "syntax error (Mismatched close-parenthesis)".to_string())?;
Some(other) => out_stack.push(other), match op {
(_, Token::ParOpen) => return Ok(()),
other => out_stack.push(other),
} }
} }
} }
@ -471,22 +460,17 @@ fn infix_operator_and(values: &[String]) -> String {
fn operator_match(values: &[String]) -> Result<String, String> { fn operator_match(values: &[String]) -> Result<String, String> {
assert!(values.len() == 2); assert!(values.len() == 2);
let re = match Regex::with_options(&values[1], RegexOptions::REGEX_OPTION_NONE, Syntax::grep()) let re = Regex::with_options(&values[1], RegexOptions::REGEX_OPTION_NONE, Syntax::grep())
{ .map_err(|err| err.description().to_string())?;
Ok(m) => m, Ok(if re.captures_len() > 0 {
Err(err) => return Err(err.description().to_string()), re.captures(&values[0])
}; .map(|captures| captures.at(1).unwrap())
if re.captures_len() > 0 { .unwrap_or("")
Ok(match re.captures(&values[0]) { .to_string()
Some(captures) => captures.at(1).unwrap().to_string(),
None => "".to_string(),
})
} else { } else {
Ok(match re.find(&values[0]) { re.find(&values[0])
Some((start, end)) => (end - start).to_string(), .map_or("0".to_string(), |(start, end)| (end - start).to_string())
None => "0".to_string(),
}) })
}
} }
fn prefix_operator_length(values: &[String]) -> String { fn prefix_operator_length(values: &[String]) -> String {

View file

@ -78,27 +78,27 @@ pub fn strings_to_tokens(strings: &[String]) -> Result<Vec<(usize, Token)>, Stri
"(" => Token::ParOpen, "(" => Token::ParOpen,
")" => Token::ParClose, ")" => Token::ParClose,
"^" => Token::new_infix_op(&s, false, 7), "^" => Token::new_infix_op(s, false, 7),
":" => Token::new_infix_op(&s, true, 6), ":" => Token::new_infix_op(s, true, 6),
"*" => Token::new_infix_op(&s, true, 5), "*" => Token::new_infix_op(s, true, 5),
"/" => Token::new_infix_op(&s, true, 5), "/" => Token::new_infix_op(s, true, 5),
"%" => Token::new_infix_op(&s, true, 5), "%" => Token::new_infix_op(s, true, 5),
"+" => Token::new_infix_op(&s, true, 4), "+" => Token::new_infix_op(s, true, 4),
"-" => Token::new_infix_op(&s, true, 4), "-" => Token::new_infix_op(s, true, 4),
"=" => Token::new_infix_op(&s, true, 3), "=" => Token::new_infix_op(s, true, 3),
"!=" => Token::new_infix_op(&s, true, 3), "!=" => Token::new_infix_op(s, true, 3),
"<" => Token::new_infix_op(&s, true, 3), "<" => Token::new_infix_op(s, true, 3),
">" => Token::new_infix_op(&s, true, 3), ">" => Token::new_infix_op(s, true, 3),
"<=" => Token::new_infix_op(&s, true, 3), "<=" => Token::new_infix_op(s, true, 3),
">=" => Token::new_infix_op(&s, true, 3), ">=" => Token::new_infix_op(s, true, 3),
"&" => Token::new_infix_op(&s, true, 2), "&" => Token::new_infix_op(s, true, 2),
"|" => Token::new_infix_op(&s, true, 1), "|" => Token::new_infix_op(s, true, 1),
"match" => Token::PrefixOp { "match" => Token::PrefixOp {
arity: 2, arity: 2,
@ -117,9 +117,9 @@ pub fn strings_to_tokens(strings: &[String]) -> Result<Vec<(usize, Token)>, Stri
value: s.clone(), value: s.clone(),
}, },
_ => Token::new_value(&s), _ => Token::new_value(s),
}; };
push_token_if_not_escaped(&mut tokens_acc, tok_idx, token_if_not_escaped, &s); push_token_if_not_escaped(&mut tokens_acc, tok_idx, token_if_not_escaped, s);
tok_idx += 1; tok_idx += 1;
} }
maybe_dump_tokens_acc(&tokens_acc); maybe_dump_tokens_acc(&tokens_acc);

View file

@ -21,7 +21,7 @@ rand = { version = "0.7", features = ["small_rng"] }
smallvec = { version = "0.6.14, < 1.0" } smallvec = { version = "0.6.14, < 1.0" }
uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" } uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }
uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" } uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
[dev-dependencies] [dev-dependencies]
paste = "0.1.18" paste = "0.1.18"

View file

@ -36,11 +36,7 @@ fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box<dy
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = App::new(executable!()) let matches = uu_app().get_matches_from(args);
.version(crate_version!())
.about(SUMMARY)
.arg(Arg::with_name(options::NUMBER).multiple(true))
.get_matches_from(args);
let stdout = stdout(); let stdout = stdout();
let mut w = io::BufWriter::new(stdout.lock()); let mut w = io::BufWriter::new(stdout.lock());
@ -68,3 +64,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0 0
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
.arg(Arg::with_name(options::NUMBER).multiple(true))
}

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/false.rs" path = "src/false.rs"
[dependencies] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -5,6 +5,14 @@
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
pub fn uumain(_: impl uucore::Args) -> i32 { use clap::App;
use uucore::executable;
pub fn uumain(args: impl uucore::Args) -> i32 {
uu_app().get_matches_from(args);
1 1
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
}

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/fmt.rs" path = "src/fmt.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
unicode-width = "0.1.5" unicode-width = "0.1.5"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }

View file

@ -77,129 +77,7 @@ pub struct FmtOptions {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(OPT_CROWN_MARGIN)
.short("c")
.long(OPT_CROWN_MARGIN)
.help(
"First and second line of paragraph
may have different indentations, in which
case the first line's indentation is preserved,
and each subsequent line's indentation matches the second line.",
),
)
.arg(
Arg::with_name(OPT_TAGGED_PARAGRAPH)
.short("t")
.long("tagged-paragraph")
.help(
"Like -c, except that the first and second line of a paragraph *must*
have different indentation or they are treated as separate paragraphs.",
),
)
.arg(
Arg::with_name(OPT_PRESERVE_HEADERS)
.short("m")
.long("preserve-headers")
.help(
"Attempt to detect and preserve mail headers in the input.
Be careful when combining this flag with -p.",
),
)
.arg(
Arg::with_name(OPT_SPLIT_ONLY)
.short("s")
.long("split-only")
.help("Split lines only, do not reflow."),
)
.arg(
Arg::with_name(OPT_UNIFORM_SPACING)
.short("u")
.long("uniform-spacing")
.help(
"Insert exactly one
space between words, and two between sentences.
Sentence breaks in the input are detected as [?!.]
followed by two spaces or a newline; other punctuation
is not interpreted as a sentence break.",
),
)
.arg(
Arg::with_name(OPT_PREFIX)
.short("p")
.long("prefix")
.help(
"Reformat only lines
beginning with PREFIX, reattaching PREFIX to reformatted lines.
Unless -x is specified, leading whitespace will be ignored
when matching PREFIX.",
)
.value_name("PREFIX"),
)
.arg(
Arg::with_name(OPT_SKIP_PREFIX)
.short("P")
.long("skip-prefix")
.help(
"Do not reformat lines
beginning with PSKIP. Unless -X is specified, leading whitespace
will be ignored when matching PSKIP",
)
.value_name("PSKIP"),
)
.arg(
Arg::with_name(OPT_EXACT_PREFIX)
.short("x")
.long("exact-prefix")
.help(
"PREFIX must match at the
beginning of the line with no preceding whitespace.",
),
)
.arg(
Arg::with_name(OPT_EXACT_SKIP_PREFIX)
.short("X")
.long("exact-skip-prefix")
.help(
"PSKIP must match at the
beginning of the line with no preceding whitespace.",
),
)
.arg(
Arg::with_name(OPT_WIDTH)
.short("w")
.long("width")
.help("Fill output lines up to a maximum of WIDTH columns, default 79.")
.value_name("WIDTH"),
)
.arg(
Arg::with_name(OPT_GOAL)
.short("g")
.long("goal")
.help("Goal width, default ~0.94*WIDTH. Must be less than WIDTH.")
.value_name("GOAL"),
)
.arg(Arg::with_name(OPT_QUICK).short("q").long("quick").help(
"Break lines more quickly at the
expense of a potentially more ragged appearance.",
))
.arg(
Arg::with_name(OPT_TAB_WIDTH)
.short("T")
.long("tab-width")
.help(
"Treat tabs as TABWIDTH spaces for
determining line length, default 8. Note that this is used only for
calculating line lengths; tabs are preserved in the output.",
)
.value_name("TABWIDTH"),
)
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
.get_matches_from(args);
let mut files: Vec<String> = matches let mut files: Vec<String> = matches
.values_of(ARG_FILES) .values_of(ARG_FILES)
@ -331,3 +209,127 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0 0
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(OPT_CROWN_MARGIN)
.short("c")
.long(OPT_CROWN_MARGIN)
.help(
"First and second line of paragraph \
may have different indentations, in which \
case the first line's indentation is preserved, \
and each subsequent line's indentation matches the second line.",
),
)
.arg(
Arg::with_name(OPT_TAGGED_PARAGRAPH)
.short("t")
.long("tagged-paragraph")
.help(
"Like -c, except that the first and second line of a paragraph *must* \
have different indentation or they are treated as separate paragraphs.",
),
)
.arg(
Arg::with_name(OPT_PRESERVE_HEADERS)
.short("m")
.long("preserve-headers")
.help(
"Attempt to detect and preserve mail headers in the input. \
Be careful when combining this flag with -p.",
),
)
.arg(
Arg::with_name(OPT_SPLIT_ONLY)
.short("s")
.long("split-only")
.help("Split lines only, do not reflow."),
)
.arg(
Arg::with_name(OPT_UNIFORM_SPACING)
.short("u")
.long("uniform-spacing")
.help(
"Insert exactly one \
space between words, and two between sentences. \
Sentence breaks in the input are detected as [?!.] \
followed by two spaces or a newline; other punctuation \
is not interpreted as a sentence break.",
),
)
.arg(
Arg::with_name(OPT_PREFIX)
.short("p")
.long("prefix")
.help(
"Reformat only lines \
beginning with PREFIX, reattaching PREFIX to reformatted lines. \
Unless -x is specified, leading whitespace will be ignored \
when matching PREFIX.",
)
.value_name("PREFIX"),
)
.arg(
Arg::with_name(OPT_SKIP_PREFIX)
.short("P")
.long("skip-prefix")
.help(
"Do not reformat lines \
beginning with PSKIP. Unless -X is specified, leading whitespace \
will be ignored when matching PSKIP",
)
.value_name("PSKIP"),
)
.arg(
Arg::with_name(OPT_EXACT_PREFIX)
.short("x")
.long("exact-prefix")
.help(
"PREFIX must match at the \
beginning of the line with no preceding whitespace.",
),
)
.arg(
Arg::with_name(OPT_EXACT_SKIP_PREFIX)
.short("X")
.long("exact-skip-prefix")
.help(
"PSKIP must match at the \
beginning of the line with no preceding whitespace.",
),
)
.arg(
Arg::with_name(OPT_WIDTH)
.short("w")
.long("width")
.help("Fill output lines up to a maximum of WIDTH columns, default 79.")
.value_name("WIDTH"),
)
.arg(
Arg::with_name(OPT_GOAL)
.short("g")
.long("goal")
.help("Goal width, default ~0.94*WIDTH. Must be less than WIDTH.")
.value_name("GOAL"),
)
.arg(Arg::with_name(OPT_QUICK).short("q").long("quick").help(
"Break lines more quickly at the \
expense of a potentially more ragged appearance.",
))
.arg(
Arg::with_name(OPT_TAB_WIDTH)
.short("T")
.long("tab-width")
.help(
"Treat tabs as TABWIDTH spaces for \
determining line length, default 8. Note that this is used only for \
calculating line lengths; tabs are preserved in the output.",
)
.value_name("TABWIDTH"),
)
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
}

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/fold.rs" path = "src/fold.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -36,7 +36,35 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.accept_any(); .accept_any();
let (args, obs_width) = handle_obsolete(&args[..]); let (args, obs_width) = handle_obsolete(&args[..]);
let matches = App::new(executable!()) let matches = uu_app().get_matches_from(args);
let bytes = matches.is_present(options::BYTES);
let spaces = matches.is_present(options::SPACES);
let poss_width = match matches.value_of(options::WIDTH) {
Some(v) => Some(v.to_owned()),
None => obs_width,
};
let width = match poss_width {
Some(inp_width) => match inp_width.parse::<usize>() {
Ok(width) => width,
Err(e) => crash!(1, "illegal width value (\"{}\"): {}", inp_width, e),
},
None => 80,
};
let files = match matches.values_of(options::FILE) {
Some(v) => v.map(|v| v.to_owned()).collect(),
None => vec!["-".to_owned()],
};
fold(files, bytes, spaces, width);
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME) .name(NAME)
.version(crate_version!()) .version(crate_version!())
.usage(SYNTAX) .usage(SYNTAX)
@ -68,37 +96,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(true), .takes_value(true),
) )
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) .arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.get_matches_from(args);
let bytes = matches.is_present(options::BYTES);
let spaces = matches.is_present(options::SPACES);
let poss_width = match matches.value_of(options::WIDTH) {
Some(v) => Some(v.to_owned()),
None => obs_width,
};
let width = match poss_width {
Some(inp_width) => match inp_width.parse::<usize>() {
Ok(width) => width,
Err(e) => crash!(1, "illegal width value (\"{}\"): {}", inp_width, e),
},
None => 80,
};
let files = match matches.values_of(options::FILE) {
Some(v) => v.map(|v| v.to_owned()).collect(),
None => vec!["-".to_owned()],
};
fold(files, bytes, spaces, width);
0
} }
fn handle_obsolete(args: &[String]) -> (Vec<String>, Option<String>) { fn handle_obsolete(args: &[String]) -> (Vec<String>, Option<String>) {
for (i, arg) in args.iter().enumerate() { for (i, arg) in args.iter().enumerate() {
let slice = &arg; let slice = &arg;
if slice.starts_with('-') && slice.len() > 1 && slice.chars().nth(1).unwrap().is_digit(10) { if slice.starts_with('-') && slice.chars().nth(1).map_or(false, |c| c.is_digit(10)) {
let mut v = args.to_vec(); let mut v = args.to_vec();
v.remove(i); v.remove(i);
return (v, Some(slice[1..].to_owned())); return (v, Some(slice[1..].to_owned()));
@ -109,7 +112,7 @@ fn handle_obsolete(args: &[String]) -> (Vec<String>, Option<String>) {
fn fold(filenames: Vec<String>, bytes: bool, spaces: bool, width: usize) { fn fold(filenames: Vec<String>, bytes: bool, spaces: bool, width: usize) {
for filename in &filenames { for filename in &filenames {
let filename: &str = &filename; let filename: &str = filename;
let mut stdin_buf; let mut stdin_buf;
let mut file_buf; let mut file_buf;
let buffer = BufReader::new(if filename == "-" { let buffer = BufReader::new(if filename == "-" {

View file

@ -15,9 +15,9 @@ edition = "2018"
path = "src/groups.rs" path = "src/groups.rs"
[dependencies] [dependencies]
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "process"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
[[bin]] [[bin]]
name = "groups" name = "groups"

View file

@ -5,59 +5,93 @@
// //
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
//
// ============================================================================
// Test suite summary for GNU coreutils 8.32.162-4eda
// ============================================================================
// PASS: tests/misc/groups-dash.sh
// PASS: tests/misc/groups-process-all.sh
// PASS: tests/misc/groups-version.sh
// spell-checker:ignore (ToDO) passwd // spell-checker:ignore (ToDO) passwd
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use uucore::entries::{get_groups, gid2grp, Locate, Passwd}; use uucore::entries::{get_groups_gnu, gid2grp, Locate, Passwd};
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
static ABOUT: &str = "display current group names"; mod options {
static OPT_USER: &str = "user"; pub const USERS: &str = "USERNAME";
}
static ABOUT: &str = "Print group memberships for each USERNAME or, \
if no USERNAME is specified, for\nthe current process \
(which may differ if the groups database has changed).";
fn get_usage() -> String { fn get_usage() -> String {
format!("{0} [USERNAME]", executable!()) format!("{0} [OPTION]... [USERNAME]...", executable!())
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(Arg::with_name(OPT_USER))
.get_matches_from(args);
match matches.value_of(OPT_USER) { let users: Vec<String> = matches
None => { .values_of(options::USERS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let mut exit_code = 0;
if users.is_empty() {
println!( println!(
"{}", "{}",
get_groups() get_groups_gnu(None)
.unwrap() .unwrap()
.iter() .iter()
.map(|&g| gid2grp(g).unwrap()) .map(|&gid| gid2grp(gid).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", gid);
exit_code = 1;
gid.to_string()
}))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" ") .join(" ")
); );
0 return exit_code;
} }
Some(user) => {
if let Ok(p) = Passwd::locate(user) { for user in users {
if let Ok(p) = Passwd::locate(user.as_str()) {
println!( println!(
"{}", "{} : {}",
user,
p.belongs_to() p.belongs_to()
.iter() .iter()
.map(|&g| gid2grp(g).unwrap()) .map(|&gid| gid2grp(gid).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", gid);
exit_code = 1;
gid.to_string()
}))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" ") .join(" ")
); );
0
} else { } else {
crash!(1, "unknown user {}", user); show_error!("'{}': no such user", user);
} exit_code = 1;
} }
} }
exit_code
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(options::USERS)
.multiple(true)
.takes_value(true)
.value_name(options::USERS),
)
} }

View file

@ -0,0 +1,9 @@
## Benchmarking hashsum
### To bench blake2
Taken from: https://github.com/uutils/coreutils/pull/2296
With a large file:
$ hyperfine "./target/release/coreutils hashsum --b2sum large-file" "b2sum large-file"

View file

@ -16,7 +16,7 @@ path = "src/hashsum.rs"
[dependencies] [dependencies]
digest = "0.6.2" digest = "0.6.2"
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
hex = "0.2.0" hex = "0.2.0"
libc = "0.2.42" libc = "0.2.42"
md5 = "0.3.5" md5 = "0.3.5"
@ -25,7 +25,7 @@ regex-syntax = "0.6.7"
sha1 = "0.6.0" sha1 = "0.6.0"
sha2 = "0.6.0" sha2 = "0.6.0"
sha3 = "0.6.0" sha3 = "0.6.0"
blake2-rfc = "0.2.18" blake2b_simd = "0.5.11"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -1,4 +1,3 @@
extern crate blake2_rfc;
extern crate digest; extern crate digest;
extern crate md5; extern crate md5;
extern crate sha1; extern crate sha1;
@ -49,9 +48,9 @@ impl Digest for md5::Context {
} }
} }
impl Digest for blake2_rfc::blake2b::Blake2b { impl Digest for blake2b_simd::State {
fn new() -> Self { fn new() -> Self {
blake2_rfc::blake2b::Blake2b::new(64) Self::new()
} }
fn input(&mut self, input: &[u8]) { fn input(&mut self, input: &[u8]) {
@ -59,12 +58,12 @@ impl Digest for blake2_rfc::blake2b::Blake2b {
} }
fn result(&mut self, out: &mut [u8]) { fn result(&mut self, out: &mut [u8]) {
let hash_result = &self.clone().finalize(); let hash_result = &self.finalize();
out.copy_from_slice(&hash_result.as_bytes()); out.copy_from_slice(hash_result.as_bytes());
} }
fn reset(&mut self) { fn reset(&mut self) {
*self = blake2_rfc::blake2b::Blake2b::new(64); *self = Self::new();
} }
fn output_bits(&self) -> usize { fn output_bits(&self) -> usize {

View file

@ -19,7 +19,6 @@ mod digest;
use self::digest::Digest; use self::digest::Digest;
use blake2_rfc::blake2b::Blake2b;
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use hex::ToHex; use hex::ToHex;
use md5::Context as Md5; use md5::Context as Md5;
@ -85,9 +84,13 @@ fn detect_algo<'a>(
"sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box<dyn Digest>, 256), "sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box<dyn Digest>, 256),
"sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box<dyn Digest>, 384), "sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box<dyn Digest>, 384),
"sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512), "sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512),
"b2sum" => ("BLAKE2", Box::new(Blake2b::new(64)) as Box<dyn Digest>, 512), "b2sum" => (
"BLAKE2",
Box::new(blake2b_simd::State::new()) as Box<dyn Digest>,
512,
),
"sha3sum" => match matches.value_of("bits") { "sha3sum" => match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(224) => ( Ok(224) => (
"SHA3-224", "SHA3-224",
Box::new(Sha3_224::new()) as Box<dyn Digest>, Box::new(Sha3_224::new()) as Box<dyn Digest>,
@ -137,7 +140,7 @@ fn detect_algo<'a>(
512, 512,
), ),
"shake128sum" => match matches.value_of("bits") { "shake128sum" => match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(bits) => ( Ok(bits) => (
"SHAKE128", "SHAKE128",
Box::new(Shake128::new()) as Box<dyn Digest>, Box::new(Shake128::new()) as Box<dyn Digest>,
@ -148,7 +151,7 @@ fn detect_algo<'a>(
None => crash!(1, "--bits required for SHAKE-128"), None => crash!(1, "--bits required for SHAKE-128"),
}, },
"shake256sum" => match matches.value_of("bits") { "shake256sum" => match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(bits) => ( Ok(bits) => (
"SHAKE256", "SHAKE256",
Box::new(Shake256::new()) as Box<dyn Digest>, Box::new(Shake256::new()) as Box<dyn Digest>,
@ -187,11 +190,11 @@ fn detect_algo<'a>(
set_or_crash("SHA512", Box::new(Sha512::new()), 512) set_or_crash("SHA512", Box::new(Sha512::new()), 512)
} }
if matches.is_present("b2sum") { if matches.is_present("b2sum") {
set_or_crash("BLAKE2", Box::new(Blake2b::new(64)), 512) set_or_crash("BLAKE2", Box::new(blake2b_simd::State::new()), 512)
} }
if matches.is_present("sha3") { if matches.is_present("sha3") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(224) => set_or_crash( Ok(224) => set_or_crash(
"SHA3-224", "SHA3-224",
Box::new(Sha3_224::new()) as Box<dyn Digest>, Box::new(Sha3_224::new()) as Box<dyn Digest>,
@ -235,7 +238,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("shake128") { if matches.is_present("shake128") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits), Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits),
Err(err) => crash!(1, "{}", err), Err(err) => crash!(1, "{}", err),
}, },
@ -244,7 +247,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("shake256") { if matches.is_present("shake256") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits), Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits),
Err(err) => crash!(1, "{}", err), Err(err) => crash!(1, "{}", err),
}, },
@ -252,10 +255,8 @@ fn detect_algo<'a>(
} }
} }
} }
if alg.is_none() { let alg = alg.unwrap_or_else(|| crash!(1, "You must specify hash algorithm!"));
crash!(1, "You must specify hash algorithm!") (name, alg, output_bits)
};
(name, alg.unwrap(), output_bits)
} }
} }
} }
@ -284,119 +285,7 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 {
// Default binary in Windows, text mode otherwise // Default binary in Windows, text mode otherwise
let binary_flag_default = cfg!(windows); let binary_flag_default = cfg!(windows);
let binary_help = format!( let app = uu_app(&binary_name);
"read in binary mode{}",
if binary_flag_default {
" (default)"
} else {
""
}
);
let text_help = format!(
"read in text mode{}",
if binary_flag_default {
""
} else {
" (default)"
}
);
let mut app = App::new(executable!())
.version(crate_version!())
.about("Compute and check message digests.")
.arg(
Arg::with_name("binary")
.short("b")
.long("binary")
.help(&binary_help),
)
.arg(
Arg::with_name("check")
.short("c")
.long("check")
.help("read hashsums from the FILEs and check them"),
)
.arg(
Arg::with_name("tag")
.long("tag")
.help("create a BSD-style checksum"),
)
.arg(
Arg::with_name("text")
.short("t")
.long("text")
.help(&text_help)
.conflicts_with("binary"),
)
.arg(
Arg::with_name("quiet")
.short("q")
.long("quiet")
.help("don't print OK for each successfully verified file"),
)
.arg(
Arg::with_name("status")
.short("s")
.long("status")
.help("don't output anything, status code shows success"),
)
.arg(
Arg::with_name("strict")
.long("strict")
.help("exit non-zero for improperly formatted checksum lines"),
)
.arg(
Arg::with_name("warn")
.short("w")
.long("warn")
.help("warn about improperly formatted checksum lines"),
)
// Needed for variable-length output sums (e.g. SHAKE)
.arg(
Arg::with_name("bits")
.long("bits")
.help("set the size of the output (only for SHAKE)")
.takes_value(true)
.value_name("BITS")
// XXX: should we actually use validators? they're not particularly efficient
.validator(is_valid_bit_num),
)
.arg(
Arg::with_name("FILE")
.index(1)
.multiple(true)
.value_name("FILE"),
);
if !is_custom_binary(&binary_name) {
let algorithms = &[
("md5", "work with MD5"),
("sha1", "work with SHA1"),
("sha224", "work with SHA224"),
("sha256", "work with SHA256"),
("sha384", "work with SHA384"),
("sha512", "work with SHA512"),
("sha3", "work with SHA3"),
("sha3-224", "work with SHA3-224"),
("sha3-256", "work with SHA3-256"),
("sha3-384", "work with SHA3-384"),
("sha3-512", "work with SHA3-512"),
(
"shake128",
"work with SHAKE128 using BITS for the output size",
),
(
"shake256",
"work with SHAKE256 using BITS for the output size",
),
("b2sum", "work with BLAKE2"),
];
for (name, desc) in algorithms {
app = app.arg(Arg::with_name(name).long(name).help(desc));
}
}
// FIXME: this should use get_matches_from_safe() and crash!(), but at the moment that just // FIXME: this should use get_matches_from_safe() and crash!(), but at the moment that just
// causes "error: " to be printed twice (once from crash!() and once from clap). With // causes "error: " to be printed twice (once from crash!() and once from clap). With
@ -444,6 +333,124 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 {
} }
} }
pub fn uu_app_common() -> App<'static, 'static> {
#[cfg(windows)]
const BINARY_HELP: &str = "read in binary mode (default)";
#[cfg(not(windows))]
const BINARY_HELP: &str = "read in binary mode";
#[cfg(windows)]
const TEXT_HELP: &str = "read in text mode";
#[cfg(not(windows))]
const TEXT_HELP: &str = "read in text mode (default)";
App::new(executable!())
.version(crate_version!())
.about("Compute and check message digests.")
.arg(
Arg::with_name("binary")
.short("b")
.long("binary")
.help(BINARY_HELP),
)
.arg(
Arg::with_name("check")
.short("c")
.long("check")
.help("read hashsums from the FILEs and check them"),
)
.arg(
Arg::with_name("tag")
.long("tag")
.help("create a BSD-style checksum"),
)
.arg(
Arg::with_name("text")
.short("t")
.long("text")
.help(TEXT_HELP)
.conflicts_with("binary"),
)
.arg(
Arg::with_name("quiet")
.short("q")
.long("quiet")
.help("don't print OK for each successfully verified file"),
)
.arg(
Arg::with_name("status")
.short("s")
.long("status")
.help("don't output anything, status code shows success"),
)
.arg(
Arg::with_name("strict")
.long("strict")
.help("exit non-zero for improperly formatted checksum lines"),
)
.arg(
Arg::with_name("warn")
.short("w")
.long("warn")
.help("warn about improperly formatted checksum lines"),
)
// Needed for variable-length output sums (e.g. SHAKE)
.arg(
Arg::with_name("bits")
.long("bits")
.help("set the size of the output (only for SHAKE)")
.takes_value(true)
.value_name("BITS")
// XXX: should we actually use validators? they're not particularly efficient
.validator(is_valid_bit_num),
)
.arg(
Arg::with_name("FILE")
.index(1)
.multiple(true)
.value_name("FILE"),
)
}
pub fn uu_app_custom() -> App<'static, 'static> {
let mut app = uu_app_common();
let algorithms = &[
("md5", "work with MD5"),
("sha1", "work with SHA1"),
("sha224", "work with SHA224"),
("sha256", "work with SHA256"),
("sha384", "work with SHA384"),
("sha512", "work with SHA512"),
("sha3", "work with SHA3"),
("sha3-224", "work with SHA3-224"),
("sha3-256", "work with SHA3-256"),
("sha3-384", "work with SHA3-384"),
("sha3-512", "work with SHA3-512"),
(
"shake128",
"work with SHAKE128 using BITS for the output size",
),
(
"shake256",
"work with SHAKE256 using BITS for the output size",
),
("b2sum", "work with BLAKE2"),
];
for (name, desc) in algorithms {
app = app.arg(Arg::with_name(name).long(name).help(desc));
}
app
}
// hashsum is handled differently in build.rs, therefore this is not the same
// as in other utilities.
fn uu_app(binary_name: &str) -> App<'static, 'static> {
if !is_custom_binary(binary_name) {
uu_app_custom()
} else {
uu_app_common()
}
}
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn hashsum<'a, I>(mut options: Options, files: I) -> Result<(), i32> fn hashsum<'a, I>(mut options: Options, files: I) -> Result<(), i32>
where where

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/head.rs" path = "src/head.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -1,3 +1,8 @@
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// spell-checker:ignore (vars) zlines // spell-checker:ignore (vars) zlines
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
@ -35,7 +40,7 @@ mod take;
use lines::zlines; use lines::zlines;
use take::take_all_but; use take::take_all_but;
fn app<'a>() -> App<'a, 'a> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
@ -75,7 +80,7 @@ fn app<'a>() -> App<'a, 'a> {
.arg( .arg(
Arg::with_name(options::QUIET_NAME) Arg::with_name(options::QUIET_NAME)
.short("q") .short("q")
.long("--quiet") .long("quiet")
.visible_alias("silent") .visible_alias("silent")
.help("never print headers giving file names") .help("never print headers giving file names")
.overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]), .overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]),
@ -108,12 +113,7 @@ where
{ {
match parse::parse_num(src) { match parse::parse_num(src) {
Ok((n, last)) => Ok((closure(n), last)), Ok((n, last)) => Ok((closure(n), last)),
Err(reason) => match reason { Err(e) => Err(e.to_string()),
parse::ParseError::Syntax => Err(format!("'{}'", src)),
parse::ParseError::Overflow => {
Err(format!("'{}': Value too large for defined datatype", src))
}
},
} }
} }
@ -167,7 +167,7 @@ impl HeadOptions {
///Construct options from matches ///Construct options from matches
pub fn get_from(args: impl uucore::Args) -> Result<Self, String> { pub fn get_from(args: impl uucore::Args) -> Result<Self, String> {
let matches = app().get_matches_from(arg_iterate(args)?); let matches = uu_app().get_matches_from(arg_iterate(args)?);
let mut options = HeadOptions::new(); let mut options = HeadOptions::new();
@ -176,19 +176,11 @@ impl HeadOptions {
options.zeroed = matches.is_present(options::ZERO_NAME); options.zeroed = matches.is_present(options::ZERO_NAME);
let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) { let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) {
match parse_mode(v, Modes::Bytes) { parse_mode(v, Modes::Bytes)
Ok(v) => v, .map_err(|err| format!("invalid number of bytes: {}", err))?
Err(err) => {
return Err(format!("invalid number of bytes: {}", err));
}
}
} else if let Some(v) = matches.value_of(options::LINES_NAME) { } else if let Some(v) = matches.value_of(options::LINES_NAME) {
match parse_mode(v, Modes::Lines) { parse_mode(v, Modes::Lines)
Ok(v) => v, .map_err(|err| format!("invalid number of lines: {}", err))?
Err(err) => {
return Err(format!("invalid number of lines: {}", err));
}
}
} else { } else {
(Modes::Lines(10), false) (Modes::Lines(10), false)
}; };
@ -474,7 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let args = match HeadOptions::get_from(args) { let args = match HeadOptions::get_from(args) {
Ok(o) => o, Ok(o) => o,
Err(s) => { Err(s) => {
crash!(EXIT_FAILURE, "head: {}", s); crash!(EXIT_FAILURE, "{}", s);
} }
}; };
match uu_head(&args) { match uu_head(&args) {

View file

@ -1,5 +1,10 @@
use std::convert::TryFrom; // * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
use std::ffi::OsString; use std::ffi::OsString;
use uucore::parse_size::{parse_size, ParseSizeError};
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum ParseError { pub enum ParseError {
@ -92,92 +97,25 @@ pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>
} }
/// Parses an -c or -n argument, /// Parses an -c or -n argument,
/// the bool specifies whether to read from the end /// the bool specifies whether to read from the end
pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> { pub fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> {
let mut num_start = 0; let mut size_string = src.trim();
let mut chars = src.char_indices(); let mut all_but_last = false;
let (mut chars, all_but_last) = match chars.next() {
Some((_, c)) => { if let Some(c) = size_string.chars().next() {
if c == '+' || c == '-' {
// head: '+' is not documented (8.32 man pages)
size_string = &size_string[1..];
if c == '-' { if c == '-' {
num_start += 1; all_but_last = true;
(chars, true) }
}
} else { } else {
(src.char_indices(), false) return Err(ParseSizeError::ParseFailure(src.to_string()));
}
}
None => return Err(ParseError::Syntax),
};
let mut num_end = 0usize;
let mut last_char = 0 as char;
let mut num_count = 0usize;
for (n, c) in &mut chars {
if c.is_numeric() {
num_end = n;
num_count += 1;
} else {
last_char = c;
break;
}
} }
let num = if num_count > 0 { parse_size(size_string).map(|n| (n, all_but_last))
match src[num_start..=num_end].parse::<usize>() {
Ok(n) => Some(n),
Err(_) => return Err(ParseError::Overflow),
}
} else {
None
};
if last_char == 0 as char {
if let Some(n) = num {
Ok((n, all_but_last))
} else {
Err(ParseError::Syntax)
}
} else {
let base: u128 = match chars.next() {
Some((_, c)) => {
let b = match c {
'B' if last_char != 'b' => 1000,
'i' if last_char != 'b' => {
if let Some((_, 'B')) = chars.next() {
1024
} else {
return Err(ParseError::Syntax);
}
}
_ => return Err(ParseError::Syntax),
};
if chars.next().is_some() {
return Err(ParseError::Syntax);
} else {
b
}
}
None => 1024,
};
let mul = match last_char.to_lowercase().next().unwrap() {
'b' => 512,
'k' => base.pow(1),
'm' => base.pow(2),
'g' => base.pow(3),
't' => base.pow(4),
'p' => base.pow(5),
'e' => base.pow(6),
'z' => base.pow(7),
'y' => base.pow(8),
_ => return Err(ParseError::Syntax),
};
let mul = match usize::try_from(mul) {
Ok(n) => n,
Err(_) => return Err(ParseError::Overflow),
};
match num.unwrap_or(1).checked_mul(mul) {
Some(n) => Ok((n, all_but_last)),
None => Err(ParseError::Overflow),
}
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -195,44 +133,6 @@ mod tests {
Some(Ok(src.iter().map(|s| s.to_string()).collect())) Some(Ok(src.iter().map(|s| s.to_string()).collect()))
} }
#[test] #[test]
#[cfg(not(target_pointer_width = "128"))]
fn test_parse_overflow_x64() {
assert_eq!(parse_num("1Y"), Err(ParseError::Overflow));
assert_eq!(parse_num("1Z"), Err(ParseError::Overflow));
assert_eq!(parse_num("100E"), Err(ParseError::Overflow));
assert_eq!(parse_num("100000P"), Err(ParseError::Overflow));
assert_eq!(parse_num("1000000000T"), Err(ParseError::Overflow));
assert_eq!(
parse_num("10000000000000000000000"),
Err(ParseError::Overflow)
);
}
#[test]
#[cfg(target_pointer_width = "32")]
fn test_parse_overflow_x32() {
assert_eq!(parse_num("1T"), Err(ParseError::Overflow));
assert_eq!(parse_num("1000G"), Err(ParseError::Overflow));
}
#[test]
fn test_parse_bad_syntax() {
assert_eq!(parse_num("5MiB nonsense"), Err(ParseError::Syntax));
assert_eq!(parse_num("Nonsense string"), Err(ParseError::Syntax));
assert_eq!(parse_num("5mib"), Err(ParseError::Syntax));
assert_eq!(parse_num("biB"), Err(ParseError::Syntax));
assert_eq!(parse_num("-"), Err(ParseError::Syntax));
assert_eq!(parse_num(""), Err(ParseError::Syntax));
}
#[test]
fn test_parse_numbers() {
assert_eq!(parse_num("k"), Ok((1024, false)));
assert_eq!(parse_num("MiB"), Ok((1024 * 1024, false)));
assert_eq!(parse_num("-5"), Ok((5, true)));
assert_eq!(parse_num("b"), Ok((512, false)));
assert_eq!(parse_num("-2GiB"), Ok((2 * 1024 * 1024 * 1024, true)));
assert_eq!(parse_num("5M"), Ok((5 * 1024 * 1024, false)));
assert_eq!(parse_num("5MB"), Ok((5 * 1000 * 1000, false)));
}
#[test]
fn test_parse_numbers_obsolete() { fn test_parse_numbers_obsolete() {
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"])); assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"])); assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/hostid.rs" path = "src/hostid.rs"
[dependencies] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -10,12 +10,10 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{crate_version, App};
use libc::c_long; use libc::c_long;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str = "[options]"; static SYNTAX: &str = "[options]";
static SUMMARY: &str = "";
static LONG_HELP: &str = "";
// currently rust libc interface doesn't include gethostid // currently rust libc interface doesn't include gethostid
extern "C" { extern "C" {
@ -23,14 +21,17 @@ extern "C" {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
app!(SYNTAX, SUMMARY, LONG_HELP).parse( uu_app().get_matches_from(args);
args.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(),
);
hostid(); hostid();
0 0
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.usage(SYNTAX)
}
fn hostid() { fn hostid() {
/* /*
* POSIX says gethostid returns a "32-bit identifier" but is silent * POSIX says gethostid returns a "32-bit identifier" but is silent

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/hostname.rs" path = "src/hostname.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
hostname = { version = "0.3", features = ["set"] } hostname = { version = "0.3", features = ["set"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["wide"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["wide"] }

View file

@ -52,10 +52,25 @@ fn get_usage() -> String {
} }
fn execute(args: impl uucore::Args) -> i32 { fn execute(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
match matches.value_of(OPT_HOST) {
None => display_hostname(&matches),
Some(host) => {
if let Err(err) = hostname::set(host) {
show_error!("{}", err);
1
} else {
0
}
}
}
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.usage(&usage[..])
.arg( .arg(
Arg::with_name(OPT_DOMAIN) Arg::with_name(OPT_DOMAIN)
.short("d") .short("d")
@ -80,19 +95,6 @@ fn execute(args: impl uucore::Args) -> i32 {
possible", possible",
)) ))
.arg(Arg::with_name(OPT_HOST)) .arg(Arg::with_name(OPT_HOST))
.get_matches_from(args);
match matches.value_of(OPT_HOST) {
None => display_hostname(&matches),
Some(host) => {
if let Err(err) = hostname::set(host) {
show_error!("{}", err);
1
} else {
0
}
}
}
} }
fn display_hostname(matches: &ArgMatches) -> i32 { fn display_hostname(matches: &ArgMatches) -> i32 {

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/id.rs" path = "src/id.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "process"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "process"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -6,11 +6,27 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// //
// Synced with: // This was originally based on BSD's `id`
// (noticeable in functionality, usage text, options text, etc.)
// and synced with:
// http://ftp-archive.freebsd.org/mirror/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/shellutils/src/id.c // http://ftp-archive.freebsd.org/mirror/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/shellutils/src/id.c
// http://www.opensource.apple.com/source/shell_cmds/shell_cmds-118/id/id.c // http://www.opensource.apple.com/source/shell_cmds/shell_cmds-118/id/id.c
//
// * This was partially rewritten in order for stdout/stderr/exit_code
// to be conform with GNU coreutils (8.32) test suite for `id`.
//
// * This supports multiple users (a feature that was introduced in coreutils 8.31)
//
// * This passes GNU's coreutils Test suite (8.32)
// for "tests/id/uid.sh" and "tests/id/zero/sh".
//
// * Option '--zero' does not exist for BSD's `id`, therefore '--zero' is only
// allowed together with other options that are available on GNU's `id`.
//
// * Help text based on BSD's `id` manpage and GNU's `id` manpage.
//
// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag // spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(dead_code)] #![allow(dead_code)]
@ -31,210 +47,346 @@ macro_rules! cstr2cow {
}; };
} }
#[cfg(not(target_os = "linux"))] static ABOUT: &str = "Print user and group information for each specified USER,
mod audit { or (when USER omitted) for the current user.";
use super::libc::{c_int, c_uint, dev_t, pid_t, uid_t};
pub type au_id_t = uid_t; mod options {
pub type au_asid_t = pid_t; pub const OPT_AUDIT: &str = "audit"; // GNU's id does not have this
pub type au_event_t = c_uint; pub const OPT_CONTEXT: &str = "context";
pub type au_emod_t = c_uint; pub const OPT_EFFECTIVE_USER: &str = "user";
pub type au_class_t = c_int; pub const OPT_GROUP: &str = "group";
pub type au_flag_t = u64; pub const OPT_GROUPS: &str = "groups";
pub const OPT_HUMAN_READABLE: &str = "human-readable"; // GNU's id does not have this
#[repr(C)] pub const OPT_NAME: &str = "name";
pub struct au_mask { pub const OPT_PASSWORD: &str = "password"; // GNU's id does not have this
pub am_success: c_uint, pub const OPT_REAL_ID: &str = "real";
pub am_failure: c_uint, pub const OPT_ZERO: &str = "zero"; // BSD's id does not have this
} pub const ARG_USERS: &str = "USER";
pub type au_mask_t = au_mask;
#[repr(C)]
pub struct au_tid_addr {
pub port: dev_t,
}
pub type au_tid_addr_t = au_tid_addr;
#[repr(C)]
pub struct c_auditinfo_addr {
pub ai_auid: au_id_t, // Audit user ID
pub ai_mask: au_mask_t, // Audit masks.
pub ai_termid: au_tid_addr_t, // Terminal ID.
pub ai_asid: au_asid_t, // Audit session ID.
pub ai_flags: au_flag_t, // Audit session flags
}
pub type c_auditinfo_addr_t = c_auditinfo_addr;
extern "C" {
pub fn getaudit(auditinfo_addr: *mut c_auditinfo_addr_t) -> c_int;
}
} }
static ABOUT: &str = "Display user and group information for the specified USER,\n or (when USER omitted) for the current user.";
static OPT_AUDIT: &str = "audit";
static OPT_EFFECTIVE_USER: &str = "effective-user";
static OPT_GROUP: &str = "group";
static OPT_GROUPS: &str = "groups";
static OPT_HUMAN_READABLE: &str = "human-readable";
static OPT_NAME: &str = "name";
static OPT_PASSWORD: &str = "password";
static OPT_REAL_ID: &str = "real-id";
static ARG_USERS: &str = "users";
fn get_usage() -> String { fn get_usage() -> String {
format!("{0} [OPTION]... [USER]", executable!()) format!("{0} [OPTION]... [USER]...", executable!())
}
fn get_description() -> String {
String::from(
"The id utility displays the user and group names and numeric IDs, of the \
calling process, to the standard output. If the real and effective IDs are \
different, both are displayed, otherwise only the real ID is displayed.\n\n\
If a user (login name or user ID) is specified, the user and group IDs of \
that user are displayed. In this case, the real and effective IDs are \
assumed to be the same.",
)
}
struct Ids {
uid: u32, // user id
gid: u32, // group id
euid: u32, // effective uid
egid: u32, // effective gid
}
struct State {
nflag: bool, // --name
uflag: bool, // --user
gflag: bool, // --group
gsflag: bool, // --groups
rflag: bool, // --real
zflag: bool, // --zero
ids: Option<Ids>,
// The behavior for calling GNU's `id` and calling GNU's `id $USER` is similar but different.
// * The SELinux context is only displayed without a specified user.
// * The `getgroups` system call is only used without a specified user, this causes
// the order of the displayed groups to be different between `id` and `id $USER`.
//
// Example:
// $ strace -e getgroups id -G $USER
// 1000 10 975 968
// +++ exited with 0 +++
// $ strace -e getgroups id -G
// getgroups(0, NULL) = 4
// getgroups(4, [10, 968, 975, 1000]) = 4
// 1000 10 968 975
// +++ exited with 0 +++
user_specified: bool,
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let after_help = get_description();
let matches = App::new(executable!()) let matches = uu_app()
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..]) .usage(&usage[..])
.arg( .after_help(&after_help[..])
Arg::with_name(OPT_AUDIT)
.short("A")
.help("Display the process audit (not available on Linux)"),
)
.arg(
Arg::with_name(OPT_EFFECTIVE_USER)
.short("u")
.long("user")
.help("Display the effective user ID as a number"),
)
.arg(
Arg::with_name(OPT_GROUP)
.short("g")
.long(OPT_GROUP)
.help("Display the effective group ID as a number"),
)
.arg(
Arg::with_name(OPT_GROUPS)
.short("G")
.long(OPT_GROUPS)
.help("Display the different group IDs"),
)
.arg(
Arg::with_name(OPT_HUMAN_READABLE)
.short("p")
.help("Make the output human-readable"),
)
.arg(
Arg::with_name(OPT_NAME)
.short("n")
.help("Display the name of the user or group ID for the -G, -g and -u options"),
)
.arg(
Arg::with_name(OPT_PASSWORD)
.short("P")
.help("Display the id as a password file entry"),
)
.arg(
Arg::with_name(OPT_REAL_ID)
.short("r")
.help("Display the real ID for the -g and -u options"),
)
.arg(Arg::with_name(ARG_USERS).multiple(true).takes_value(true))
.get_matches_from(args); .get_matches_from(args);
let users: Vec<String> = matches let users: Vec<String> = matches
.values_of(ARG_USERS) .values_of(options::ARG_USERS)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default(); .unwrap_or_default();
if matches.is_present(OPT_AUDIT) { let mut state = State {
auditid(); nflag: matches.is_present(options::OPT_NAME),
return 0; uflag: matches.is_present(options::OPT_EFFECTIVE_USER),
gflag: matches.is_present(options::OPT_GROUP),
gsflag: matches.is_present(options::OPT_GROUPS),
rflag: matches.is_present(options::OPT_REAL_ID),
zflag: matches.is_present(options::OPT_ZERO),
user_specified: !users.is_empty(),
ids: None,
};
let default_format = {
// "default format" is when none of '-ugG' was used
!(state.uflag || state.gflag || state.gsflag)
};
if (state.nflag || state.rflag) && default_format {
crash!(1, "cannot print only names or real IDs in default format");
}
if (state.zflag) && default_format {
// NOTE: GNU test suite "id/zero.sh" needs this stderr output:
crash!(1, "option --zero not permitted in default format");
} }
let possible_pw = if users.is_empty() { let delimiter = {
if state.zflag {
"\0".to_string()
} else {
" ".to_string()
}
};
let line_ending = {
if state.zflag {
'\0'
} else {
'\n'
}
};
let mut exit_code = 0;
for i in 0..=users.len() {
let possible_pw = if !state.user_specified {
None None
} else { } else {
match Passwd::locate(users[0].as_str()) { match Passwd::locate(users[i].as_str()) {
Ok(p) => Some(p), Ok(p) => Some(p),
Err(_) => crash!(1, "No such user/group: {}", users[0]), Err(_) => {
show_error!("'{}': no such user", users[i]);
exit_code = 1;
if i + 1 >= users.len() {
break;
} else {
continue;
}
}
} }
}; };
let nflag = matches.is_present(OPT_NAME); // GNU's `id` does not support the flags: -p/-P/-A.
let uflag = matches.is_present(OPT_EFFECTIVE_USER); if matches.is_present(options::OPT_PASSWORD) {
let gflag = matches.is_present(OPT_GROUP); // BSD's `id` ignores all but the first specified user
let rflag = matches.is_present(OPT_REAL_ID);
if gflag {
let id = possible_pw
.map(|p| p.gid())
.unwrap_or(if rflag { getgid() } else { getegid() });
println!(
"{}",
if nflag {
entries::gid2grp(id).unwrap_or_else(|_| id.to_string())
} else {
id.to_string()
}
);
return 0;
}
if uflag {
let id = possible_pw
.map(|p| p.uid())
.unwrap_or(if rflag { getuid() } else { geteuid() });
println!(
"{}",
if nflag {
entries::uid2usr(id).unwrap_or_else(|_| id.to_string())
} else {
id.to_string()
}
);
return 0;
}
if matches.is_present(OPT_GROUPS) {
println!(
"{}",
if nflag {
possible_pw
.map(|p| p.belongs_to())
.unwrap_or_else(|| entries::get_groups().unwrap())
.iter()
.map(|&id| entries::gid2grp(id).unwrap())
.collect::<Vec<_>>()
.join(" ")
} else {
possible_pw
.map(|p| p.belongs_to())
.unwrap_or_else(|| entries::get_groups().unwrap())
.iter()
.map(|&id| id.to_string())
.collect::<Vec<_>>()
.join(" ")
}
);
return 0;
}
if matches.is_present(OPT_PASSWORD) {
pline(possible_pw.map(|v| v.uid())); pline(possible_pw.map(|v| v.uid()));
return 0; return exit_code;
};
if matches.is_present(options::OPT_HUMAN_READABLE) {
// BSD's `id` ignores all but the first specified user
pretty(possible_pw);
return exit_code;
}
if matches.is_present(options::OPT_AUDIT) {
// BSD's `id` ignores specified users
auditid();
return exit_code;
}
let (uid, gid) = possible_pw.map(|p| (p.uid(), p.gid())).unwrap_or((
if state.rflag { getuid() } else { geteuid() },
if state.rflag { getgid() } else { getegid() },
));
state.ids = Some(Ids {
uid,
gid,
euid: geteuid(),
egid: getegid(),
});
if state.gflag {
print!(
"{}",
if state.nflag {
entries::gid2grp(gid).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", gid);
exit_code = 1;
gid.to_string()
})
} else {
gid.to_string()
}
);
}
if state.uflag {
print!(
"{}",
if state.nflag {
entries::uid2usr(uid).unwrap_or_else(|_| {
show_error!("cannot find name for user ID {}", uid);
exit_code = 1;
uid.to_string()
})
} else {
uid.to_string()
}
);
}
let groups = entries::get_groups_gnu(Some(gid)).unwrap();
let groups = if state.user_specified {
possible_pw.map(|p| p.belongs_to()).unwrap()
} else {
groups.clone()
}; };
if matches.is_present(OPT_HUMAN_READABLE) { if state.gsflag {
pretty(possible_pw); print!(
return 0; "{}{}",
} groups
.iter()
if possible_pw.is_some() { .map(|&id| {
id_print(possible_pw, false, false) if state.nflag {
entries::gid2grp(id).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", id);
exit_code = 1;
id.to_string()
})
} else { } else {
id_print(possible_pw, true, true) id.to_string()
}
})
.collect::<Vec<_>>()
.join(&delimiter),
// NOTE: this is necessary to pass GNU's "tests/id/zero.sh":
if state.zflag && state.user_specified && users.len() > 1 {
"\0"
} else {
""
}
);
} }
0 if default_format {
id_print(&state, groups);
}
print!("{}", line_ending);
if i + 1 >= users.len() {
break;
}
}
exit_code
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(options::OPT_AUDIT)
.short("A")
.conflicts_with_all(&[
options::OPT_GROUP,
options::OPT_EFFECTIVE_USER,
options::OPT_HUMAN_READABLE,
options::OPT_PASSWORD,
options::OPT_GROUPS,
options::OPT_ZERO,
])
.help(
"Display the process audit user ID and other process audit properties,\n\
which requires privilege (not available on Linux).",
),
)
.arg(
Arg::with_name(options::OPT_EFFECTIVE_USER)
.short("u")
.long(options::OPT_EFFECTIVE_USER)
.conflicts_with(options::OPT_GROUP)
.help("Display only the effective user ID as a number."),
)
.arg(
Arg::with_name(options::OPT_GROUP)
.short("g")
.long(options::OPT_GROUP)
.help("Display only the effective group ID as a number"),
)
.arg(
Arg::with_name(options::OPT_GROUPS)
.short("G")
.long(options::OPT_GROUPS)
.conflicts_with_all(&[
options::OPT_GROUP,
options::OPT_EFFECTIVE_USER,
options::OPT_HUMAN_READABLE,
options::OPT_PASSWORD,
options::OPT_AUDIT,
])
.help(
"Display only the different group IDs as white-space separated numbers, \
in no particular order.",
),
)
.arg(
Arg::with_name(options::OPT_HUMAN_READABLE)
.short("p")
.help("Make the output human-readable. Each display is on a separate line."),
)
.arg(
Arg::with_name(options::OPT_NAME)
.short("n")
.long(options::OPT_NAME)
.help(
"Display the name of the user or group ID for the -G, -g and -u options \
instead of the number.\nIf any of the ID numbers cannot be mapped into \
names, the number will be displayed as usual.",
),
)
.arg(
Arg::with_name(options::OPT_PASSWORD)
.short("P")
.help("Display the id as a password file entry."),
)
.arg(
Arg::with_name(options::OPT_REAL_ID)
.short("r")
.long(options::OPT_REAL_ID)
.help(
"Display the real ID for the -G, -g and -u options instead of \
the effective ID.",
),
)
.arg(
Arg::with_name(options::OPT_ZERO)
.short("z")
.long(options::OPT_ZERO)
.help(
"delimit entries with NUL characters, not whitespace;\n\
not permitted in default format",
),
)
.arg(
Arg::with_name(options::OPT_CONTEXT)
.short("Z")
.long(options::OPT_CONTEXT)
.help("NotImplemented: print only the security context of the process"),
)
.arg(
Arg::with_name(options::ARG_USERS)
.multiple(true)
.takes_value(true)
.value_name(options::ARG_USERS),
)
} }
fn pretty(possible_pw: Option<Passwd>) { fn pretty(possible_pw: Option<Passwd>) {
@ -280,7 +432,7 @@ fn pretty(possible_pw: Option<Passwd>) {
println!( println!(
"groups\t{}", "groups\t{}",
entries::get_groups() entries::get_groups_gnu(None)
.unwrap() .unwrap()
.iter() .iter()
.map(|&gr| entries::gid2grp(gr).unwrap()) .map(|&gr| entries::gid2grp(gr).unwrap())
@ -347,30 +499,21 @@ fn auditid() {
println!("asid={}", auditinfo.ai_asid); println!("asid={}", auditinfo.ai_asid);
} }
fn id_print(possible_pw: Option<Passwd>, p_euid: bool, p_egid: bool) { fn id_print(state: &State, groups: Vec<u32>) {
let (uid, gid) = possible_pw let uid = state.ids.as_ref().unwrap().uid;
.map(|p| (p.uid(), p.gid())) let gid = state.ids.as_ref().unwrap().gid;
.unwrap_or((getuid(), getgid())); let euid = state.ids.as_ref().unwrap().euid;
let egid = state.ids.as_ref().unwrap().egid;
let groups = match Passwd::locate(uid) {
Ok(p) => p.belongs_to(),
Err(e) => crash!(1, "Could not find uid {}: {}", uid, e),
};
print!("uid={}({})", uid, entries::uid2usr(uid).unwrap()); print!("uid={}({})", uid, entries::uid2usr(uid).unwrap());
print!(" gid={}({})", gid, entries::gid2grp(gid).unwrap()); print!(" gid={}({})", gid, entries::gid2grp(gid).unwrap());
if !state.user_specified && (euid != uid) {
let euid = geteuid();
if p_euid && (euid != uid) {
print!(" euid={}({})", euid, entries::uid2usr(euid).unwrap()); print!(" euid={}({})", euid, entries::uid2usr(euid).unwrap());
} }
if !state.user_specified && (egid != gid) {
let egid = getegid();
if p_egid && (egid != gid) {
print!(" egid={}({})", euid, entries::gid2grp(egid).unwrap()); print!(" egid={}({})", euid, entries::gid2grp(egid).unwrap());
} }
print!(
println!(
" groups={}", " groups={}",
groups groups
.iter() .iter()
@ -378,4 +521,49 @@ fn id_print(possible_pw: Option<Passwd>, p_euid: bool, p_egid: bool) {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(",") .join(",")
); );
// NOTE: (SELinux NotImplemented) placeholder:
// if !state.user_specified {
// // print SElinux context (does not depend on "-Z")
// print!(" context={}", get_selinux_contexts().join(":"));
// }
}
#[cfg(not(target_os = "linux"))]
mod audit {
use super::libc::{c_int, c_uint, dev_t, pid_t, uid_t};
pub type au_id_t = uid_t;
pub type au_asid_t = pid_t;
pub type au_event_t = c_uint;
pub type au_emod_t = c_uint;
pub type au_class_t = c_int;
pub type au_flag_t = u64;
#[repr(C)]
pub struct au_mask {
pub am_success: c_uint,
pub am_failure: c_uint,
}
pub type au_mask_t = au_mask;
#[repr(C)]
pub struct au_tid_addr {
pub port: dev_t,
}
pub type au_tid_addr_t = au_tid_addr;
#[repr(C)]
pub struct c_auditinfo_addr {
pub ai_auid: au_id_t, // Audit user ID
pub ai_mask: au_mask_t, // Audit masks.
pub ai_termid: au_tid_addr_t, // Terminal ID.
pub ai_asid: au_asid_t, // Audit session ID.
pub ai_flags: au_flag_t, // Audit session flags
}
pub type c_auditinfo_addr_t = c_auditinfo_addr;
extern "C" {
pub fn getaudit(auditinfo_addr: *mut c_auditinfo_addr_t) -> c_int;
}
} }

View file

@ -18,7 +18,7 @@ edition = "2018"
path = "src/install.rs" path = "src/install.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
filetime = "0.2" filetime = "0.2"
file_diff = "1.0.0" file_diff = "1.0.0"
libc = ">= 0.2" libc = ">= 0.2"

View file

@ -15,6 +15,7 @@ extern crate uucore;
use clap::{crate_version, App, Arg, ArgMatches}; use clap::{crate_version, App, Arg, ArgMatches};
use file_diff::diff; use file_diff::diff;
use filetime::{set_file_times, FileTime}; use filetime::{set_file_times, FileTime};
use uucore::backup_control::{self, BackupMode};
use uucore::entries::{grp2gid, usr2uid}; use uucore::entries::{grp2gid, usr2uid};
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity}; use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
@ -33,6 +34,7 @@ const DEFAULT_STRIP_PROGRAM: &str = "strip";
pub struct Behavior { pub struct Behavior {
main_function: MainFunction, main_function: MainFunction,
specified_mode: Option<u32>, specified_mode: Option<u32>,
backup_mode: BackupMode,
suffix: String, suffix: String,
owner: String, owner: String,
group: String, group: String,
@ -42,6 +44,7 @@ pub struct Behavior {
strip: bool, strip: bool,
strip_program: String, strip_program: String,
create_leading: bool, create_leading: bool,
target_dir: Option<String>,
} }
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
@ -67,7 +70,7 @@ static ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing
static OPT_COMPARE: &str = "compare"; static OPT_COMPARE: &str = "compare";
static OPT_BACKUP: &str = "backup"; static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_2: &str = "backup2"; static OPT_BACKUP_NO_ARG: &str = "backup2";
static OPT_DIRECTORY: &str = "directory"; static OPT_DIRECTORY: &str = "directory";
static OPT_IGNORED: &str = "ignored"; static OPT_IGNORED: &str = "ignored";
static OPT_CREATE_LEADING: &str = "create-leading"; static OPT_CREATE_LEADING: &str = "create-leading";
@ -97,21 +100,49 @@ fn get_usage() -> String {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
let paths: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
if let Err(s) = check_unimplemented(&matches) {
show_error!("Unimplemented feature: {}", s);
return 2;
}
let behavior = match behavior(&matches) {
Ok(x) => x,
Err(ret) => {
return ret;
}
};
match behavior.main_function {
MainFunction::Directory => directory(paths, behavior),
MainFunction::Standard => standard(paths, behavior),
}
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.usage(&usage[..])
.arg( .arg(
Arg::with_name(OPT_BACKUP) Arg::with_name(OPT_BACKUP)
.long(OPT_BACKUP) .long(OPT_BACKUP)
.help("(unimplemented) make a backup of each existing destination file") .help("make a backup of each existing destination file")
.takes_value(true)
.require_equals(true)
.min_values(0)
.value_name("CONTROL") .value_name("CONTROL")
) )
.arg( .arg(
// TODO implement flag // TODO implement flag
Arg::with_name(OPT_BACKUP_2) Arg::with_name(OPT_BACKUP_NO_ARG)
.short("b") .short("b")
.help("(unimplemented) like --backup but does not accept an argument") .help("like --backup but does not accept an argument")
) )
.arg( .arg(
Arg::with_name(OPT_IGNORED) Arg::with_name(OPT_IGNORED)
@ -184,7 +215,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(OPT_SUFFIX) Arg::with_name(OPT_SUFFIX)
.short("S") .short("S")
.long(OPT_SUFFIX) .long(OPT_SUFFIX)
.help("(unimplemented) override the usual backup suffix") .help("override the usual backup suffix")
.value_name("SUFFIX") .value_name("SUFFIX")
.takes_value(true) .takes_value(true)
.min_values(1) .min_values(1)
@ -194,7 +225,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(OPT_TARGET_DIRECTORY) Arg::with_name(OPT_TARGET_DIRECTORY)
.short("t") .short("t")
.long(OPT_TARGET_DIRECTORY) .long(OPT_TARGET_DIRECTORY)
.help("(unimplemented) move all SOURCE arguments into DIRECTORY") .help("move all SOURCE arguments into DIRECTORY")
.value_name("DIRECTORY") .value_name("DIRECTORY")
) )
.arg( .arg(
@ -227,29 +258,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.value_name("CONTEXT") .value_name("CONTEXT")
) )
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1)) .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1))
.get_matches_from(args);
let paths: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
if let Err(s) = check_unimplemented(&matches) {
show_error!("Unimplemented feature: {}", s);
return 2;
}
let behavior = match behavior(&matches) {
Ok(x) => x,
Err(ret) => {
return ret;
}
};
match behavior.main_function {
MainFunction::Directory => directory(paths, behavior),
MainFunction::Standard => standard(paths, behavior),
}
} }
/// Check for unimplemented command line arguments. /// Check for unimplemented command line arguments.
@ -262,15 +270,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
/// ///
/// ///
fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> { fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
if matches.is_present(OPT_BACKUP) { if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
Err("--backup")
} else if matches.is_present(OPT_BACKUP_2) {
Err("-b")
} else if matches.is_present(OPT_SUFFIX) {
Err("--suffix, -S")
} else if matches.is_present(OPT_TARGET_DIRECTORY) {
Err("--target-directory, -t")
} else if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
Err("--no-target-directory, -T") Err("--no-target-directory, -T")
} else if matches.is_present(OPT_PRESERVE_CONTEXT) { } else if matches.is_present(OPT_PRESERVE_CONTEXT) {
Err("--preserve-context, -P") Err("--preserve-context, -P")
@ -299,37 +299,25 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
let considering_dir: bool = MainFunction::Directory == main_function; let considering_dir: bool = MainFunction::Directory == main_function;
let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) { let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) {
match matches.value_of(OPT_MODE) { let x = matches.value_of(OPT_MODE).ok_or(1)?;
Some(x) => match mode::parse(x, considering_dir) { Some(mode::parse(x, considering_dir).map_err(|err| {
Ok(y) => Some(y),
Err(err) => {
show_error!("Invalid mode string: {}", err); show_error!("Invalid mode string: {}", err);
return Err(1); 1
} })?)
},
None => {
return Err(1);
}
}
} else { } else {
None None
}; };
let backup_suffix = if matches.is_present(OPT_SUFFIX) { let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned());
match matches.value_of(OPT_SUFFIX) {
Some(x) => x,
None => {
return Err(1);
}
}
} else {
"~"
};
Ok(Behavior { Ok(Behavior {
main_function, main_function,
specified_mode, specified_mode,
suffix: backup_suffix.to_string(), backup_mode: backup_control::determine_backup_mode(
matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP),
),
suffix: backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)),
owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(), owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(),
group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(), group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(),
verbose: matches.is_present(OPT_VERBOSE), verbose: matches.is_present(OPT_VERBOSE),
@ -342,6 +330,7 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
.unwrap_or(DEFAULT_STRIP_PROGRAM), .unwrap_or(DEFAULT_STRIP_PROGRAM),
), ),
create_leading: matches.is_present(OPT_CREATE_LEADING), create_leading: matches.is_present(OPT_CREATE_LEADING),
target_dir,
}) })
} }
@ -379,7 +368,7 @@ fn directory(paths: Vec<String>, b: Behavior) -> i32 {
} }
} }
if mode::chmod(&path, b.mode()).is_err() { if mode::chmod(path, b.mode()).is_err() {
all_successful = false; all_successful = false;
continue; continue;
} }
@ -404,16 +393,17 @@ fn is_new_file_path(path: &Path) -> bool {
/// ///
/// Returns an integer intended as a program return code. /// Returns an integer intended as a program return code.
/// ///
fn standard(paths: Vec<String>, b: Behavior) -> i32 { fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
let sources = &paths[0..paths.len() - 1] let target: PathBuf = b
.iter() .target_dir
.map(PathBuf::from) .clone()
.collect::<Vec<_>>(); .unwrap_or_else(|| paths.pop().unwrap())
.into();
let target = Path::new(paths.last().unwrap()); let sources = &paths.iter().map(PathBuf::from).collect::<Vec<_>>();
if sources.len() > 1 || (target.exists() && target.is_dir()) { if sources.len() > 1 || (target.exists() && target.is_dir()) {
copy_files_into_dir(sources, &target.to_path_buf(), &b) copy_files_into_dir(sources, &target, &b)
} else { } else {
if let Some(parent) = target.parent() { if let Some(parent) = target.parent() {
if !parent.exists() && b.create_leading { if !parent.exists() && b.create_leading {
@ -422,15 +412,15 @@ fn standard(paths: Vec<String>, b: Behavior) -> i32 {
return 1; return 1;
} }
if mode::chmod(&parent, b.mode()).is_err() { if mode::chmod(parent, b.mode()).is_err() {
show_error!("failed to chmod {}", parent.display()); show_error!("failed to chmod {}", parent.display());
return 1; return 1;
} }
} }
} }
if target.is_file() || is_new_file_path(target) { if target.is_file() || is_new_file_path(&target) {
copy_file_to_file(&sources[0], &target.to_path_buf(), &b) copy_file_to_file(&sources[0], &target, &b)
} else { } else {
show_error!( show_error!(
"invalid target {}: No such file or directory", "invalid target {}: No such file or directory",
@ -501,7 +491,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3
/// _target_ must be a non-directory /// _target_ must be a non-directory
/// ///
fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 { fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 {
if copy(file, &target, b).is_err() { if copy(file, target, b).is_err() {
1 1
} else { } else {
0 0
@ -524,6 +514,28 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if b.compare && !need_copy(from, to, b) { if b.compare && !need_copy(from, to, b) {
return Ok(()); return Ok(());
} }
// Declare the path here as we may need it for the verbose output below.
let mut backup_path = None;
// Perform backup, if any, before overwriting 'to'
//
// The codes actually making use of the backup process don't seem to agree
// on how best to approach the issue. (mv and ln, for example)
if to.exists() {
backup_path = backup_control::get_backup_path(b.backup_mode, to, &b.suffix);
if let Some(ref backup_path) = backup_path {
// TODO!!
if let Err(err) = fs::rename(to, backup_path) {
show_error!(
"install: cannot backup file '{}' to '{}': {}",
to.display(),
backup_path.display(),
err
);
return Err(());
}
}
}
if from.to_string_lossy() == "/dev/null" { if from.to_string_lossy() == "/dev/null" {
/* workaround a limitation of fs::copy /* workaround a limitation of fs::copy
@ -563,7 +575,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
} }
} }
if mode::chmod(&to, b.mode()).is_err() { if mode::chmod(to, b.mode()).is_err() {
return Err(()); return Err(());
} }
@ -631,7 +643,11 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
} }
if b.verbose { if b.verbose {
show_error!("'{}' -> '{}'", from.display(), to.display()); print!("'{}' -> '{}'", from.display(), to.display());
match backup_path {
Some(path) => println!(" (backup: '{}')", path.display()),
None => println!(),
}
} }
Ok(()) Ok(())

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/join.rs" path = "src/join.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -328,8 +328,8 @@ impl<'a> State<'a> {
}); });
} else { } else {
repr.print_field(key); repr.print_field(key);
repr.print_fields(&line1, self.key, self.max_fields); repr.print_fields(line1, self.key, self.max_fields);
repr.print_fields(&line2, other.key, other.max_fields); repr.print_fields(line2, other.key, other.max_fields);
} }
println!(); println!();
@ -442,7 +442,72 @@ impl<'a> State<'a> {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = App::new(NAME) let matches = uu_app().get_matches_from(args);
let keys = parse_field_number_option(matches.value_of("j"));
let key1 = parse_field_number_option(matches.value_of("1"));
let key2 = parse_field_number_option(matches.value_of("2"));
let mut settings: Settings = Default::default();
if let Some(value) = matches.value_of("v") {
settings.print_unpaired = parse_file_number(value);
settings.print_joined = false;
} else if let Some(value) = matches.value_of("a") {
settings.print_unpaired = parse_file_number(value);
}
settings.ignore_case = matches.is_present("i");
settings.key1 = get_field_number(keys, key1);
settings.key2 = get_field_number(keys, key2);
if let Some(value) = matches.value_of("t") {
settings.separator = match value.len() {
0 => Sep::Line,
1 => Sep::Char(value.chars().next().unwrap()),
_ => crash!(1, "multi-character tab {}", value),
};
}
if let Some(format) = matches.value_of("o") {
if format == "auto" {
settings.autoformat = true;
} else {
settings.format = format
.split(|c| c == ' ' || c == ',' || c == '\t')
.map(Spec::parse)
.collect();
}
}
if let Some(empty) = matches.value_of("e") {
settings.empty = empty.to_string();
}
if matches.is_present("nocheck-order") {
settings.check_order = CheckOrder::Disabled;
}
if matches.is_present("check-order") {
settings.check_order = CheckOrder::Enabled;
}
if matches.is_present("header") {
settings.headers = true;
}
let file1 = matches.value_of("file1").unwrap();
let file2 = matches.value_of("file2").unwrap();
if file1 == "-" && file2 == "-" {
crash!(1, "both files cannot be standard input");
}
exec(file1, file2, &settings)
}
pub fn uu_app() -> App<'static, 'static> {
App::new(NAME)
.version(crate_version!()) .version(crate_version!())
.about( .about(
"For each pair of input lines with identical join fields, write a line to "For each pair of input lines with identical join fields, write a line to
@ -542,68 +607,6 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2",
.value_name("FILE2") .value_name("FILE2")
.hidden(true), .hidden(true),
) )
.get_matches_from(args);
let keys = parse_field_number_option(matches.value_of("j"));
let key1 = parse_field_number_option(matches.value_of("1"));
let key2 = parse_field_number_option(matches.value_of("2"));
let mut settings: Settings = Default::default();
if let Some(value) = matches.value_of("v") {
settings.print_unpaired = parse_file_number(value);
settings.print_joined = false;
} else if let Some(value) = matches.value_of("a") {
settings.print_unpaired = parse_file_number(value);
}
settings.ignore_case = matches.is_present("i");
settings.key1 = get_field_number(keys, key1);
settings.key2 = get_field_number(keys, key2);
if let Some(value) = matches.value_of("t") {
settings.separator = match value.len() {
0 => Sep::Line,
1 => Sep::Char(value.chars().next().unwrap()),
_ => crash!(1, "multi-character tab {}", value),
};
}
if let Some(format) = matches.value_of("o") {
if format == "auto" {
settings.autoformat = true;
} else {
settings.format = format
.split(|c| c == ' ' || c == ',' || c == '\t')
.map(Spec::parse)
.collect();
}
}
if let Some(empty) = matches.value_of("e") {
settings.empty = empty.to_string();
}
if matches.is_present("nocheck-order") {
settings.check_order = CheckOrder::Disabled;
}
if matches.is_present("check-order") {
settings.check_order = CheckOrder::Enabled;
}
if matches.is_present("header") {
settings.headers = true;
}
let file1 = matches.value_of("file1").unwrap();
let file2 = matches.value_of("file2").unwrap();
if file1 == "-" && file2 == "-" {
crash!(1, "both files cannot be standard input");
}
exec(file1, file2, &settings)
} }
fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 { fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
@ -611,7 +614,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
let mut state1 = State::new( let mut state1 = State::new(
FileNum::File1, FileNum::File1,
&file1, file1,
&stdin, &stdin,
settings.key1, settings.key1,
settings.print_unpaired, settings.print_unpaired,
@ -619,7 +622,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
let mut state2 = State::new( let mut state2 = State::new(
FileNum::File2, FileNum::File2,
&file2, file2,
&stdin, &stdin,
settings.key2, settings.key2,
settings.print_unpaired, settings.print_unpaired,

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/kill.rs" path = "src/kill.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["signals"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["signals"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -43,38 +43,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let (args, obs_signal) = handle_obsolete(args); let (args, obs_signal) = handle_obsolete(args);
let usage = format!("{} [OPTIONS]... PID...", executable!()); let usage = format!("{} [OPTIONS]... PID...", executable!());
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(options::LIST)
.short("l")
.long(options::LIST)
.help("Lists signals")
.conflicts_with(options::TABLE)
.conflicts_with(options::TABLE_OLD),
)
.arg(
Arg::with_name(options::TABLE)
.short("t")
.long(options::TABLE)
.help("Lists table of signals"),
)
.arg(Arg::with_name(options::TABLE_OLD).short("L").hidden(true))
.arg(
Arg::with_name(options::SIGNAL)
.short("s")
.long(options::SIGNAL)
.help("Sends given signal")
.takes_value(true),
)
.arg(
Arg::with_name(options::PIDS_OR_SIGNALS)
.hidden(true)
.multiple(true),
)
.get_matches_from(args);
let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) { let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) {
Mode::Table Mode::Table
@ -106,12 +75,45 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
EXIT_OK EXIT_OK
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(options::LIST)
.short("l")
.long(options::LIST)
.help("Lists signals")
.conflicts_with(options::TABLE)
.conflicts_with(options::TABLE_OLD),
)
.arg(
Arg::with_name(options::TABLE)
.short("t")
.long(options::TABLE)
.help("Lists table of signals"),
)
.arg(Arg::with_name(options::TABLE_OLD).short("L").hidden(true))
.arg(
Arg::with_name(options::SIGNAL)
.short("s")
.long(options::SIGNAL)
.help("Sends given signal")
.takes_value(true),
)
.arg(
Arg::with_name(options::PIDS_OR_SIGNALS)
.hidden(true)
.multiple(true),
)
}
fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) { fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) {
let mut i = 0; let mut i = 0;
while i < args.len() { while i < args.len() {
// this is safe because slice is valid when it is referenced // this is safe because slice is valid when it is referenced
let slice = &args[i].clone(); let slice = &args[i].clone();
if slice.starts_with('-') && slice.len() > 1 && slice.chars().nth(1).unwrap().is_digit(10) { if slice.starts_with('-') && slice.chars().nth(1).map_or(false, |c| c.is_digit(10)) {
let val = &slice[1..]; let val = &slice[1..];
match val.parse() { match val.parse() {
Ok(num) => { Ok(num) => {
@ -129,33 +131,24 @@ fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) {
} }
fn table() { fn table() {
let mut name_width = 0; let name_width = ALL_SIGNALS.iter().map(|n| n.len()).max().unwrap();
/* Compute the maximum width of a signal name. */
for s in &ALL_SIGNALS {
if s.name.len() > name_width {
name_width = s.name.len()
}
}
for (idx, signal) in ALL_SIGNALS.iter().enumerate() { for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
print!("{0: >#2} {1: <#8}", idx + 1, signal.name); print!("{0: >#2} {1: <#2$}", idx, signal, name_width + 2);
//TODO: obtain max signal width here
if (idx + 1) % 7 == 0 { if (idx + 1) % 7 == 0 {
println!(); println!();
} }
} }
println!()
} }
fn print_signal(signal_name_or_value: &str) { fn print_signal(signal_name_or_value: &str) {
for signal in &ALL_SIGNALS { for (value, &signal) in ALL_SIGNALS.iter().enumerate() {
if signal.name == signal_name_or_value if signal == signal_name_or_value || (format!("SIG{}", signal)) == signal_name_or_value {
|| (format!("SIG{}", signal.name)) == signal_name_or_value println!("{}", value);
{
println!("{}", signal.value);
exit!(EXIT_OK as i32) exit!(EXIT_OK as i32)
} else if signal_name_or_value == signal.value.to_string() { } else if signal_name_or_value == value.to_string() {
println!("{}", signal.name); println!("{}", signal);
exit!(EXIT_OK as i32) exit!(EXIT_OK as i32)
} }
} }
@ -165,8 +158,8 @@ fn print_signal(signal_name_or_value: &str) {
fn print_signals() { fn print_signals() {
let mut pos = 0; let mut pos = 0;
for (idx, signal) in ALL_SIGNALS.iter().enumerate() { for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
pos += signal.name.len(); pos += signal.len();
print!("{}", signal.name); print!("{}", signal);
if idx > 0 && pos > 73 { if idx > 0 && pos > 73 {
println!(); println!();
pos = 0; pos = 0;

View file

@ -18,7 +18,7 @@ path = "src/link.rs"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
[[bin]] [[bin]]
name = "link" name = "link"

View file

@ -32,19 +32,7 @@ pub fn normalize_error_message(e: Error) -> String {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let matches = App::new(executable!()) let matches = uu_app().usage(&usage[..]).get_matches_from(args);
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(options::FILES)
.hidden(true)
.required(true)
.min_values(2)
.max_values(2)
.takes_value(true),
)
.get_matches_from(args);
let files: Vec<_> = matches let files: Vec<_> = matches
.values_of_os(options::FILES) .values_of_os(options::FILES)
@ -61,3 +49,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(options::FILES)
.hidden(true)
.required(true)
.min_values(2)
.max_values(2)
.takes_value(true),
)
}

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/ln.rs" path = "src/ln.rs"
[dependencies] [dependencies]
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -27,7 +27,6 @@ use uucore::fs::{canonicalize, CanonicalizeMode};
pub struct Settings { pub struct Settings {
overwrite: OverwriteMode, overwrite: OverwriteMode,
backup: BackupMode, backup: BackupMode,
force: bool,
suffix: String, suffix: String,
symbolic: bool, symbolic: bool,
relative: bool, relative: bool,
@ -54,7 +53,7 @@ pub enum BackupMode {
fn get_usage() -> String { fn get_usage() -> String {
format!( format!(
"{0} [OPTION]... [-T] TARGET LINK_executable!() (1st form) "{0} [OPTION]... [-T] TARGET LINK_NAME (1st form)
{0} [OPTION]... TARGET (2nd form) {0} [OPTION]... TARGET (2nd form)
{0} [OPTION]... TARGET... DIRECTORY (3rd form) {0} [OPTION]... TARGET... DIRECTORY (3rd form)
{0} [OPTION]... -t DIRECTORY TARGET... (4th form)", {0} [OPTION]... -t DIRECTORY TARGET... (4th form)",
@ -64,7 +63,7 @@ fn get_usage() -> String {
fn get_long_usage() -> String { fn get_long_usage() -> String {
String::from( String::from(
" In the 1st form, create a link to TARGET with the name LINK_executable!(). " In the 1st form, create a link to TARGET with the name LINK_NAME.
In the 2nd form, create a link to TARGET in the current directory. In the 2nd form, create a link to TARGET in the current directory.
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY. In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.
Create hard links by default, symbolic links with --symbolic. Create hard links by default, symbolic links with --symbolic.
@ -78,17 +77,19 @@ fn get_long_usage() -> String {
static ABOUT: &str = "change file owner and group"; static ABOUT: &str = "change file owner and group";
static OPT_B: &str = "b"; mod options {
static OPT_BACKUP: &str = "backup"; pub const B: &str = "b";
static OPT_FORCE: &str = "force"; pub const BACKUP: &str = "backup";
static OPT_INTERACTIVE: &str = "interactive"; pub const FORCE: &str = "force";
static OPT_NO_DEREFERENCE: &str = "no-dereference"; pub const INTERACTIVE: &str = "interactive";
static OPT_SYMBOLIC: &str = "symbolic"; pub const NO_DEREFERENCE: &str = "no-dereference";
static OPT_SUFFIX: &str = "suffix"; pub const SYMBOLIC: &str = "symbolic";
static OPT_TARGET_DIRECTORY: &str = "target-directory"; pub const SUFFIX: &str = "suffix";
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; pub const TARGET_DIRECTORY: &str = "target-directory";
static OPT_RELATIVE: &str = "relative"; pub const NO_TARGET_DIRECTORY: &str = "no-target-directory";
static OPT_VERBOSE: &str = "verbose"; pub const RELATIVE: &str = "relative";
pub const VERBOSE: &str = "verbose";
}
static ARG_FILES: &str = "files"; static ARG_FILES: &str = "files";
@ -96,109 +97,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let long_usage = get_long_usage(); let long_usage = get_long_usage();
let matches = App::new(executable!()) let matches = uu_app()
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..]) .usage(&usage[..])
.after_help(&long_usage[..]) .after_help(&long_usage[..])
.arg(Arg::with_name(OPT_B).short(OPT_B).help(
"make a backup of each file that would otherwise be overwritten or \
removed",
))
.arg(
Arg::with_name(OPT_BACKUP)
.long(OPT_BACKUP)
.help(
"make a backup of each file that would otherwise be overwritten \
or removed",
)
.takes_value(true)
.possible_value("simple")
.possible_value("never")
.possible_value("numbered")
.possible_value("t")
.possible_value("existing")
.possible_value("nil")
.possible_value("none")
.possible_value("off")
.value_name("METHOD"),
)
// TODO: opts.arg(
// Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \
// to make hard links to directories");
.arg(
Arg::with_name(OPT_FORCE)
.short("f")
.long(OPT_FORCE)
.help("remove existing destination files"),
)
.arg(
Arg::with_name(OPT_INTERACTIVE)
.short("i")
.long(OPT_INTERACTIVE)
.help("prompt whether to remove existing destination files"),
)
.arg(
Arg::with_name(OPT_NO_DEREFERENCE)
.short("n")
.long(OPT_NO_DEREFERENCE)
.help(
"treat LINK_executable!() as a normal file if it is a \
symbolic link to a directory",
),
)
// TODO: opts.arg(
// Arg::with_name(("L", "logical", "dereference TARGETs that are symbolic links");
//
// TODO: opts.arg(
// Arg::with_name(("P", "physical", "make hard links directly to symbolic links");
.arg(
Arg::with_name(OPT_SYMBOLIC)
.short("s")
.long("symbolic")
.help("make symbolic links instead of hard links"),
)
.arg(
Arg::with_name(OPT_SUFFIX)
.short("S")
.long(OPT_SUFFIX)
.help("override the usual backup suffix")
.value_name("SUFFIX")
.takes_value(true),
)
.arg(
Arg::with_name(OPT_TARGET_DIRECTORY)
.short("t")
.long(OPT_TARGET_DIRECTORY)
.help("specify the DIRECTORY in which to create the links")
.value_name("DIRECTORY")
.conflicts_with(OPT_NO_TARGET_DIRECTORY),
)
.arg(
Arg::with_name(OPT_NO_TARGET_DIRECTORY)
.short("T")
.long(OPT_NO_TARGET_DIRECTORY)
.help("treat LINK_executable!() as a normal file always"),
)
.arg(
Arg::with_name(OPT_RELATIVE)
.short("r")
.long(OPT_RELATIVE)
.help("create symbolic links relative to link location"),
)
.arg(
Arg::with_name(OPT_VERBOSE)
.short("v")
.long(OPT_VERBOSE)
.help("print name of each linked file"),
)
.arg(
Arg::with_name(ARG_FILES)
.multiple(true)
.takes_value(true)
.required(true)
.min_values(1),
)
.get_matches_from(args); .get_matches_from(args);
/* the list of files */ /* the list of files */
@ -209,18 +110,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.map(PathBuf::from) .map(PathBuf::from)
.collect(); .collect();
let overwrite_mode = if matches.is_present(OPT_FORCE) { let overwrite_mode = if matches.is_present(options::FORCE) {
OverwriteMode::Force OverwriteMode::Force
} else if matches.is_present(OPT_INTERACTIVE) { } else if matches.is_present(options::INTERACTIVE) {
OverwriteMode::Interactive OverwriteMode::Interactive
} else { } else {
OverwriteMode::NoClobber OverwriteMode::NoClobber
}; };
let backup_mode = if matches.is_present(OPT_B) { let backup_mode = if matches.is_present(options::B) {
BackupMode::ExistingBackup BackupMode::ExistingBackup
} else if matches.is_present(OPT_BACKUP) { } else if matches.is_present(options::BACKUP) {
match matches.value_of(OPT_BACKUP) { match matches.value_of(options::BACKUP) {
None => BackupMode::ExistingBackup, None => BackupMode::ExistingBackup,
Some(mode) => match mode { Some(mode) => match mode {
"simple" | "never" => BackupMode::SimpleBackup, "simple" | "never" => BackupMode::SimpleBackup,
@ -234,8 +135,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
BackupMode::NoBackup BackupMode::NoBackup
}; };
let backup_suffix = if matches.is_present(OPT_SUFFIX) { let backup_suffix = if matches.is_present(options::SUFFIX) {
matches.value_of(OPT_SUFFIX).unwrap() matches.value_of(options::SUFFIX).unwrap()
} else { } else {
"~" "~"
}; };
@ -243,34 +144,137 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let settings = Settings { let settings = Settings {
overwrite: overwrite_mode, overwrite: overwrite_mode,
backup: backup_mode, backup: backup_mode,
force: matches.is_present(OPT_FORCE),
suffix: backup_suffix.to_string(), suffix: backup_suffix.to_string(),
symbolic: matches.is_present(OPT_SYMBOLIC), symbolic: matches.is_present(options::SYMBOLIC),
relative: matches.is_present(OPT_RELATIVE), relative: matches.is_present(options::RELATIVE),
target_dir: matches.value_of(OPT_TARGET_DIRECTORY).map(String::from), target_dir: matches
no_target_dir: matches.is_present(OPT_NO_TARGET_DIRECTORY), .value_of(options::TARGET_DIRECTORY)
no_dereference: matches.is_present(OPT_NO_DEREFERENCE), .map(String::from),
verbose: matches.is_present(OPT_VERBOSE), no_target_dir: matches.is_present(options::NO_TARGET_DIRECTORY),
no_dereference: matches.is_present(options::NO_DEREFERENCE),
verbose: matches.is_present(options::VERBOSE),
}; };
exec(&paths[..], &settings) exec(&paths[..], &settings)
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(Arg::with_name(options::B).short(options::B).help(
"make a backup of each file that would otherwise be overwritten or \
removed",
))
.arg(
Arg::with_name(options::BACKUP)
.long(options::BACKUP)
.help(
"make a backup of each file that would otherwise be overwritten \
or removed",
)
.takes_value(true)
.possible_values(&[
"simple", "never", "numbered", "t", "existing", "nil", "none", "off",
])
.value_name("METHOD"),
)
// TODO: opts.arg(
// Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \
// to make hard links to directories");
.arg(
Arg::with_name(options::FORCE)
.short("f")
.long(options::FORCE)
.help("remove existing destination files"),
)
.arg(
Arg::with_name(options::INTERACTIVE)
.short("i")
.long(options::INTERACTIVE)
.help("prompt whether to remove existing destination files"),
)
.arg(
Arg::with_name(options::NO_DEREFERENCE)
.short("n")
.long(options::NO_DEREFERENCE)
.help(
"treat LINK_NAME as a normal file if it is a \
symbolic link to a directory",
),
)
// TODO: opts.arg(
// Arg::with_name(("L", "logical", "dereference TARGETs that are symbolic links");
//
// TODO: opts.arg(
// Arg::with_name(("P", "physical", "make hard links directly to symbolic links");
.arg(
Arg::with_name(options::SYMBOLIC)
.short("s")
.long("symbolic")
.help("make symbolic links instead of hard links")
// override added for https://github.com/uutils/coreutils/issues/2359
.overrides_with(options::SYMBOLIC),
)
.arg(
Arg::with_name(options::SUFFIX)
.short("S")
.long(options::SUFFIX)
.help("override the usual backup suffix")
.value_name("SUFFIX")
.takes_value(true),
)
.arg(
Arg::with_name(options::TARGET_DIRECTORY)
.short("t")
.long(options::TARGET_DIRECTORY)
.help("specify the DIRECTORY in which to create the links")
.value_name("DIRECTORY")
.conflicts_with(options::NO_TARGET_DIRECTORY),
)
.arg(
Arg::with_name(options::NO_TARGET_DIRECTORY)
.short("T")
.long(options::NO_TARGET_DIRECTORY)
.help("treat LINK_NAME as a normal file always"),
)
.arg(
Arg::with_name(options::RELATIVE)
.short("r")
.long(options::RELATIVE)
.help("create symbolic links relative to link location")
.requires(options::SYMBOLIC),
)
.arg(
Arg::with_name(options::VERBOSE)
.short("v")
.long(options::VERBOSE)
.help("print name of each linked file"),
)
.arg(
Arg::with_name(ARG_FILES)
.multiple(true)
.takes_value(true)
.required(true)
.min_values(1),
)
}
fn exec(files: &[PathBuf], settings: &Settings) -> i32 { fn exec(files: &[PathBuf], settings: &Settings) -> i32 {
// Handle cases where we create links in a directory first. // Handle cases where we create links in a directory first.
if let Some(ref name) = settings.target_dir { if let Some(ref name) = settings.target_dir {
// 4th form: a directory is specified by -t. // 4th form: a directory is specified by -t.
return link_files_in_dir(files, &PathBuf::from(name), &settings); return link_files_in_dir(files, &PathBuf::from(name), settings);
} }
if !settings.no_target_dir { if !settings.no_target_dir {
if files.len() == 1 { if files.len() == 1 {
// 2nd form: the target directory is the current directory. // 2nd form: the target directory is the current directory.
return link_files_in_dir(files, &PathBuf::from("."), &settings); return link_files_in_dir(files, &PathBuf::from("."), settings);
} }
let last_file = &PathBuf::from(files.last().unwrap()); let last_file = &PathBuf::from(files.last().unwrap());
if files.len() > 2 || last_file.is_dir() { if files.len() > 2 || last_file.is_dir() {
// 3rd form: create links in the last argument. // 3rd form: create links in the last argument.
return link_files_in_dir(&files[0..files.len() - 1], last_file, &settings); return link_files_in_dir(&files[0..files.len() - 1], last_file, settings);
} }
} }
@ -310,7 +314,8 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
let mut all_successful = true; let mut all_successful = true;
for srcpath in files.iter() { for srcpath in files.iter() {
let targetpath = if settings.no_dereference && settings.force { let targetpath =
if settings.no_dereference && matches!(settings.overwrite, OverwriteMode::Force) {
// In that case, we don't want to do link resolution // In that case, we don't want to do link resolution
// We need to clean the target // We need to clean the target
if is_symlink(target_dir) { if is_symlink(target_dir) {
@ -371,7 +376,8 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> { fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> {
let src_abs = canonicalize(src, CanonicalizeMode::Normal)?; let src_abs = canonicalize(src, CanonicalizeMode::Normal)?;
let dst_abs = canonicalize(dst, CanonicalizeMode::Normal)?; let mut dst_abs = canonicalize(dst.parent().unwrap(), CanonicalizeMode::Normal)?;
dst_abs.push(dst.components().last().unwrap());
let suffix_pos = src_abs let suffix_pos = src_abs
.components() .components()
.zip(dst_abs.components()) .zip(dst_abs.components())
@ -380,19 +386,22 @@ fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> {
let src_iter = src_abs.components().skip(suffix_pos).map(|x| x.as_os_str()); let src_iter = src_abs.components().skip(suffix_pos).map(|x| x.as_os_str());
let result: PathBuf = dst_abs let mut result: PathBuf = dst_abs
.components() .components()
.skip(suffix_pos + 1) .skip(suffix_pos + 1)
.map(|_| OsStr::new("..")) .map(|_| OsStr::new(".."))
.chain(src_iter) .chain(src_iter)
.collect(); .collect();
if result.as_os_str().is_empty() {
result.push(".");
}
Ok(result.into()) Ok(result.into())
} }
fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> { fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> {
let mut backup_path = None; let mut backup_path = None;
let source: Cow<'_, Path> = if settings.relative { let source: Cow<'_, Path> = if settings.relative {
relative_path(&src, dst)? relative_path(src, dst)?
} else { } else {
src.into() src.into()
}; };
@ -421,10 +430,6 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> {
} }
} }
if settings.no_dereference && settings.force && dst.exists() {
fs::remove_file(dst)?;
}
if settings.symbolic { if settings.symbolic {
symlink(&source, dst)?; symlink(&source, dst)?;
} else { } else {

View file

@ -16,7 +16,7 @@ path = "src/logname.rs"
[dependencies] [dependencies]
libc = "0.2.42" libc = "0.2.42"
clap = "2.33" clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -45,11 +45,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.accept_any(); .accept_any();
let usage = get_usage(); let usage = get_usage();
let _ = App::new(executable!()) let _ = uu_app().usage(&usage[..]).get_matches_from(args);
.version(crate_version!())
.about(SUMMARY)
.usage(&usage[..])
.get_matches_from(args);
match get_userlogin() { match get_userlogin() {
Some(userlogin) => println!("{}", userlogin), Some(userlogin) => println!("{}", userlogin),
@ -58,3 +54,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0 0
} }
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
}

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