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 (names) CodeCOV MacOS MinGW Peltoche rivy
# spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs
# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend 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:
PROJECT_NAME: coreutils
@ -17,6 +19,40 @@ env:
on: [push, pull_request]
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:
name: Style/format
runs-on: ${{ matrix.job.os }}
@ -26,18 +62,18 @@ jobs:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v1
- 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
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
outputs CARGO_FEATURES_OPTION
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
@ -48,36 +84,19 @@ jobs:
- name: "`fmt` testing"
shell: bash
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>
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"
if: success() || failure() # run regardless of prior step success/failure
shell: bash
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>
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:
name: Style/spelling
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
code_lint:
name: Style/lint
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
@ -87,18 +106,18 @@ jobs:
- { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v1
- 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
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
outputs CARGO_FEATURES_OPTION
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
@ -106,13 +125,32 @@ jobs:
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: clippy
- name: "`clippy` testing"
if: success() || failure() # run regardless of prior step success/failure
- name: "`clippy` lint testing"
shell: bash
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>
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:
name: MinRustV # Minimum supported rust version
@ -122,7 +160,7 @@ jobs:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
uses: actions-rs/toolchain@v1
with:
@ -137,33 +175,32 @@ jobs:
use-tool-cache: true
env:
RUSTUP_TOOLCHAIN: stable
- name: Confirm compatible 'Cargo.lock'
- name: Confirm MinSRV compatible 'Cargo.lock'
shell: bash
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 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
shell: bash
run: |
# Info
## environment
## Info
# environment
echo "## environment"
echo "CI='${CI}'"
## tooling info display
# tooling info display
echo "## tooling"
which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
rustup -V
rustup -V 2>/dev/null
rustup show active-toolchain
cargo -V
rustc -V
cargo-tree tree -V
## dependencies
# 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 --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
uses: actions-rs/cargo@v1
with:
@ -172,8 +209,8 @@ jobs:
env:
RUSTFLAGS: '-Awarnings'
busybox_test:
name: Busybox test suite
build_makefile:
name: Build/Makefile
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
@ -181,45 +218,26 @@ jobs:
job:
- { os: ubuntu-latest }
steps:
- uses: actions/checkout@v1
- 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: "prepare busytest"
shell: bash
run: |
make prepare-busytest
- name: "run busybox testsuite"
shell: bash
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"
- name: Install/setup prerequisites
shell: bash
run: |
## Install/setup prerequisites
sudo apt-get -y update ; sudo apt-get -y install python3-sphinx ;
- name: "`make build`"
shell: bash
run: |
make build
- name: "`make test`"
shell: bash
run: |
make test
build:
name: Build
@ -231,7 +249,6 @@ jobs:
# { os, target, cargo-options, features, use-cross, toolchain }
- { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , features: feat_os_unix_gnueabihf , use-cross: use-cross }
- { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross }
- { os: ubuntu-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: 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-msvc , features: feat_os_windows }
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Install/setup prerequisites
shell: bash
run: |
## install/setup prerequisites
## Install/setup prerequisites
case '${{ matrix.job.target }}' in
arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
@ -261,22 +278,20 @@ jobs:
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; }
# 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)
case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac;
# * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
echo set-output name=TOOLCHAIN::${TOOLCHAIN:-<empty>/false}
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
outputs TOOLCHAIN
# staging directory
STAGING='_staging'
echo set-output name=STAGING::${STAGING}
echo ::set-output name=STAGING::${STAGING}
outputs STAGING
# determine EXE suffix
EXE_suffix="" ; case '${{ matrix.job.target }}' in *-pc-windows-*) EXE_suffix=".exe" ;; esac;
echo set-output name=EXE_suffix::${EXE_suffix}
echo ::set-output name=EXE_suffix::${EXE_suffix}
outputs EXE_suffix
# parse commit reference info
echo GITHUB_REF=${GITHUB_REF}
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_TAG ; case "${GITHUB_REF}" in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac;
REF_SHAS=${GITHUB_SHA:0:8}
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}
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}
outputs REF_NAME REF_BRANCH REF_TAG REF_SHAS
# parse target
unset TARGET_ARCH
case '${{ matrix.job.target }}' in
@ -301,68 +309,50 @@ jobs:
i686-*) TARGET_ARCH=i686 ;;
x86_64-*) TARGET_ARCH=x86_64 ;;
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;
echo set-output name=TARGET_OS::${TARGET_OS}
echo ::set-output name=TARGET_OS::${TARGET_OS}
outputs TARGET_ARCH TARGET_OS
# package name
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_NAME=${PKG_BASENAME}${PKG_suffix}
echo set-output name=PKG_suffix::${PKG_suffix}
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}
outputs PKG_suffix PKG_BASENAME PKG_NAME
# deployable tag? (ie, leading "vM" or "M"; M == version number)
unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi
echo set-output name=DEPLOY::${DEPLOY:-<empty>/false}
echo ::set-output name=DEPLOY::${DEPLOY}
outputs DEPLOY
# DPKG architecture?
unset DPKG_ARCH
case ${{ matrix.job.target }} in
x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
*-linux-*) DPKG_ARCH=${TARGET_ARCH} ;;
esac
echo set-output name=DPKG_ARCH::${DPKG_ARCH}
echo ::set-output name=DPKG_ARCH::${DPKG_ARCH}
outputs DPKG_ARCH
# DPKG version?
unset DPKG_VERSION ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DPKG_VERSION=${REF_TAG/#[vV]/} ; fi
echo set-output name=DPKG_VERSION::${DPKG_VERSION}
echo ::set-output name=DPKG_VERSION::${DPKG_VERSION}
outputs DPKG_VERSION
# DPKG base name/conflicts?
DPKG_BASENAME=${PROJECT_NAME}
DPKG_CONFLICTS=${PROJECT_NAME}-musl
case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac;
echo set-output name=DPKG_BASENAME::${DPKG_BASENAME}
echo set-output name=DPKG_CONFLICTS::${DPKG_CONFLICTS}
echo ::set-output name=DPKG_BASENAME::${DPKG_BASENAME}
echo ::set-output name=DPKG_CONFLICTS::${DPKG_CONFLICTS}
outputs DPKG_BASENAME DPKG_CONFLICTS
# DPKG name
unset DPKG_NAME;
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}
echo ::set-output name=DPKG_NAME::${DPKG_NAME}
outputs DPKG_NAME
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
outputs CARGO_FEATURES_OPTION
# * CARGO_USE_CROSS (truthy)
CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac;
echo set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS:-<empty>/false}
echo ::set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS}
outputs CARGO_USE_CROSS
# ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml")
if [ -n "${CARGO_USE_CROSS}" ] && [ ! -e "Cross.toml" ] ; then
printf "[build.env]\npassthrough = [\"CI\"]\n" > Cross.toml
fi
# * 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;
echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
outputs CARGO_TEST_OPTIONS
# * executable for `strip`?
STRIP="strip"
case ${{ matrix.job.target }} in
@ -370,12 +360,11 @@ jobs:
arm-*-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;;
*-pc-windows-msvc) STRIP="" ;;
esac;
echo set-output name=STRIP::${STRIP:-<empty>/false}
echo ::set-output name=STRIP::${STRIP}
outputs STRIP
- name: Create all needed build/work directories
shell: bash
run: |
## create build/work space
## Create build/work space
mkdir -p '${{ steps.vars.outputs.STAGING }}'
mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}'
mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg'
@ -395,11 +384,12 @@ jobs:
shell: bash
run: |
## Dependent VARs setup
outputs() { step_id="dep_vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
echo UTILITY_LIST=${UTILITY_LIST}
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
echo set-output name=UTILITY_LIST::${UTILITY_LIST}
echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS}
outputs CARGO_UTILITY_LIST_OPTIONS
- name: Install `cargo-tree` # for dependency information
uses: actions-rs/install@v0.1
with:
@ -411,26 +401,26 @@ jobs:
- name: Info
shell: bash
run: |
# Info
## commit info
## Info
# commit info
echo "## commit"
echo GITHUB_REF=${GITHUB_REF}
echo GITHUB_SHA=${GITHUB_SHA}
## environment
# environment
echo "## environment"
echo "CI='${CI}'"
## tooling info display
# tooling info display
echo "## tooling"
which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
rustup -V
rustup -V 2>/dev/null
rustup show active-toolchain
cargo -V
rustc -V
cargo-tree tree -V
## dependencies
# dependencies
echo "## dependency list"
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
uses: actions-rs/cargo@v1
with:
@ -457,7 +447,7 @@ jobs:
- name: Package
shell: bash
run: |
## package artifact(s)
## Package artifact(s)
# binary
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)
@ -498,6 +488,37 @@ jobs:
env:
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:
name: Code Coverage
runs-on: ${{ matrix.job.os }}
@ -510,11 +531,11 @@ jobs:
- { os: macos-latest , features: macos }
- { os: windows-latest , features: windows }
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Install/setup prerequisites
shell: bash
run: |
## install/setup prerequisites
## Install/setup prerequisites
case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for testing
esac
@ -524,34 +545,31 @@ jobs:
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; }
# 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
# * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files
case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
# * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
echo set-output name=TOOLCHAIN::${TOOLCHAIN}
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
outputs TOOLCHAIN
# staging directory
STAGING='_staging'
echo set-output name=STAGING::${STAGING}
echo ::set-output name=STAGING::${STAGING}
outputs STAGING
## # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>)
## # note: CODECOV_TOKEN / HAS_CODECOV_TOKEN is not needed for public repositories when using AppVeyor, Azure Pipelines, CircleCI, GitHub Actions, Travis (see <https://docs.codecov.io/docs/about-the-codecov-bash-uploader#section-upload-token>)
## unset HAS_CODECOV_TOKEN
## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi
## echo set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN}
## echo ::set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN}
## outputs HAS_CODECOV_TOKEN
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
outputs CARGO_FEATURES_OPTION
# * CODECOV_FLAGS
CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' )
echo set-output name=CODECOV_FLAGS::${CODECOV_FLAGS}
echo ::set-output name=CODECOV_FLAGS::${CODECOV_FLAGS}
outputs CODECOV_FLAGS
- name: rust toolchain ~ install
uses: actions-rs/toolchain@v1
with:
@ -563,11 +581,11 @@ jobs:
shell: bash
run: |
## Dependent VARs setup
outputs() { step_id="dep_vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
echo set-output name=UTILITY_LIST::${UTILITY_LIST}
echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS}
outputs CARGO_UTILITY_LIST_OPTIONS
- name: Test uucore
uses: actions-rs/cargo@v1
with:
@ -606,12 +624,12 @@ jobs:
with:
crate: grcov
version: latest
use-tool-cache: true
use-tool-cache: false
- name: Generate coverage data (via `grcov`)
id: coverage
shell: bash
run: |
# generate coverage data
## Generate coverage data
COVERAGE_REPORT_DIR="target/debug"
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
# GRCOV_IGNORE_OPTION='--ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?)

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

View file

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

View file

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

View file

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

View file

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

View file

@ -63,6 +63,7 @@ feat_common_core = [
"more",
"mv",
"nl",
"numfmt",
"od",
"paste",
"pr",
@ -160,7 +161,6 @@ feat_require_unix = [
"mkfifo",
"mknod",
"nice",
"numfmt",
"nohup",
"pathchk",
"stat",
@ -225,6 +225,7 @@ test = [ "uu_test" ]
[workspace]
[dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
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
uucore = { version=">=0.0.8", package="uucore", path="src/uucore" }
@ -349,9 +350,9 @@ sha1 = { version="0.6", features=["std"] }
tempfile = "3.2.0"
time = "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"
atty = "0.2.14"
atty = "0.2"
[target.'cfg(unix)'.dev-dependencies]
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)
busybox-src:
if [ ! -e $(BUSYBOX_SRC) ]; then \
mkdir -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; \
fi; \
if [ ! -e "$(BUSYBOX_SRC)" ] ; then \
mkdir -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" ; \
fi ;
# This is a busybox-specific config file their test suite wants to parse.
$(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
@ -280,10 +280,12 @@ $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
# Test under the busybox test suite
$(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config
cp $(BUILDDIR)/coreutils $(BUILDDIR)/busybox; \
chmod +x $@;
cp "$(BUILDDIR)/coreutils" "$(BUILDDIR)/busybox"
chmod +x $@
prepare-busytest: $(BUILDDIR)/busybox
# disable inapplicable tests
-( cd "$(BUSYBOX_SRC)/testsuite" ; if [ -e "busybox.tests" ] ; then mv busybox.tests busybox.tests- ; fi ; )
ifeq ($(EXES),)
busytest:
@ -312,6 +314,11 @@ else
endif
$(foreach man, $(filter $(INSTALLEES), $(basename $(notdir $(wildcard $(DOCSDIR)/_build/man/*)))), \
cat $(DOCSDIR)/_build/man/$(man).1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)$(man).1.gz &&) :
$(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:
ifeq (${MULTICALL}, y)
@ -319,6 +326,9 @@ ifeq (${MULTICALL}, y)
endif
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz)
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)))
.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 does not install files necessary for shell completion. For shell completion to work,
use `GNU Make` or see `Manually install shell completions`.
### GNU Make
To install all available utilities:
@ -179,6 +182,10 @@ Set install parent directory (default value is /usr/local):
$ 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
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
```
### 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 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 |
|-----------|-----------|--------|
| arch | cp | chcon |
| base32 | expr | csplit |
| base64 | install | dd |
| basename | ls | df |
| cat | more | numfmt |
| chgrp | od (`--strings` and 128-bit data types missing) | runcon |
| chmod | printf | stty |
| chown | sort | |
| chroot | split | |
| cksum | tail | |
| comm | test | |
| csplit | date | |
| cut | join | |
| dircolors | df | |
| base32 | date | dd |
| base64 | df | runcon |
| basename | expr | stty |
| cat | install | |
| chgrp | join | |
| chmod | ls | |
| chown | more | |
| chroot | numfmt | |
| cksum | od (`--strings` and 128-bit data types missing) | |
| comm | pr | |
| csplit | printf | |
| cut | sort | |
| dircolors | split | |
| dirname | tac | |
| du | pr | |
| echo | | |
| du | tail | |
| echo | test | |
| env | | |
| expand | | |
| factor | | |
@ -374,12 +398,12 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
| link | | |
| ln | | |
| logname | | |
| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| mkdir | | |
| mkfifo | | |
| mknod | | |

View file

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

View file

@ -16,7 +16,7 @@ Synopsis
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.
--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
// file that was distributed with this source code.
use clap::App;
use clap::Arg;
use clap::Shell;
use std::cmp;
use std::collections::hash_map::HashMap;
use std::ffi::OsString;
@ -52,7 +55,7 @@ fn main() {
let binary_as_util = name(&binary);
// 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)));
}
@ -74,8 +77,12 @@ fn main() {
if let Some(util_os) = util_name {
let util = util_os.as_os_str().to_string_lossy();
if util == "completion" {
gen_completions(args, utils);
}
match utils.get(&util[..]) {
Some(&uumain) => {
Some(&(uumain, _)) => {
process::exit(uumain((vec![util_os].into_iter()).chain(args)));
}
None => {
@ -85,7 +92,7 @@ fn main() {
let util = util_os.as_os_str().to_string_lossy();
match utils.get(&util[..]) {
Some(&uumain) => {
Some(&(uumain, _)) => {
let code = uumain(
(vec![util_os, OsString::from("--help")].into_iter())
.chain(args),
@ -113,3 +120,50 @@ fn main() {
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]
platform-info = "0.1"
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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 clap::{crate_version, App};
use uucore::error::{FromIo, UResult};
static ABOUT: &str = "Display machine architecture";
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!())
.version(crate_version!())
.about(ABOUT)
.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"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
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 clap::App;
use uucore::encoding::Format;
pub mod base_common;
@ -38,18 +39,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let config_result: Result<base_common::Config, String> =
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
if config_result.is_err() {
match config_result {
Ok(_) => panic!(),
Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s),
}
}
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));
// Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args
let stdin_raw = stdin();
let config = config_result.unwrap();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
base_common::handle_input(
@ -63,3 +57,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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) => {
let name = values.next().unwrap();
if values.len() != 0 {
return Err(format!("extra operand {}", name));
return Err(format!("extra operand '{}'", name));
}
if name == "-" {
@ -54,15 +54,13 @@ impl Config {
None => None,
};
let cols = match options.value_of(options::WRAP) {
Some(num) => match num.parse::<usize>() {
Ok(n) => Some(n),
Err(e) => {
return Err(format!("Invalid wrap size: {}: {}", num, e));
}
},
None => None,
};
let cols = options
.value_of(options::WRAP)
.map(|num| {
num.parse::<usize>()
.map_err(|e| format!("Invalid wrap size: '{}': {}", num, e))
})
.transpose()?;
Ok(Config {
decode: options.is_present(options::DECODE),
@ -80,10 +78,17 @@ pub fn parse_base_cmd_args(
about: &str,
usage: &str,
) -> 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)
.about(about)
.usage(usage)
// Format arguments.
.arg(
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
// file passed in.
.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))
.arg(Arg::with_name(options::FILE).index(1).multiple(true))
}
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"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"}

View file

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

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/basename.rs"
[dependencies]
clap = "2.33.2"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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
//
let matches = App::new(executable!())
.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);
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
// too few arguments
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" };
for path in paths {
print!("{}{}", basename(&path, &suffix), line_ending);
print!("{}{}", basename(path, suffix), line_ending);
}
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 {
// Remove all platform-specific path separators from the end
let mut path: String = fullname
.chars()
.rev()
.skip_while(|&ch| is_separator(ch))
.collect();
// Undo reverse
path = path.chars().rev().collect();
let path = fullname.trim_end_matches(is_separator);
// Convert to path buffer and get last path component
let pb = PathBuf::from(path);

View file

@ -15,8 +15,9 @@ edition = "2018"
path = "src/cat.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
thiserror = "1.0"
atty = "0.2"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
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::io::{self, Read, Write};
use thiserror::Error;
use uucore::fs::is_stdin_interactive;
/// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))]
@ -170,7 +169,65 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::Ignore)
.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)
.version(crate_version!())
.usage(SYNTAX)
@ -230,61 +287,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(options::SHOW_NONPRINTING)
.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>(
@ -295,7 +297,7 @@ fn cat_handle<R: Read>(
if options.can_write_fast() {
write_fast(handle)
} 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"))]
file_descriptor: stdin.as_raw_fd(),
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)? {
InputType::Directory => Err(CatError::IsDirectory),
@ -322,7 +324,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
reader: socket,
is_interactive: false,
};
cat_handle(&mut handle, &options, state)
cat_handle(&mut handle, options, state)
}
_ => {
let file = File::open(path)?;
@ -332,7 +334,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
reader: file,
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 {
if let Err(err) = cat_path(path, &options, &mut state) {
if let Err(err) = cat_path(path, options, &mut state) {
show_error!("{}: {}", path, err);
error_count += 1;
}

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/chgrp.rs"
[dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
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" }
walkdir = "2.2"

View file

@ -14,6 +14,8 @@ use uucore::fs::resolve_relative_path;
use uucore::libc::gid_t;
use uucore::perms::{wrap_chgrp, Verbosity};
use clap::{App, Arg};
extern crate walkdir;
use walkdir::WalkDir;
@ -24,76 +26,117 @@ use std::os::unix::fs::MetadataExt;
use std::path::Path;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str =
"chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE...";
static SUMMARY: &str = "Change the group of each FILE to GROUP.";
static ABOUT: &str = "Change the group of each FILE to GROUP.";
static VERSION: &str = env!("CARGO_PKG_VERSION");
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_PHYSICAL: u8 = 1 << 1;
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 {
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let mut opts = app!(SYNTAX, SUMMARY, "");
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 usage = get_usage();
let mut bit_flag = FTS_PHYSICAL;
let mut preserve_root = false;
let mut derefer = -1;
let flags: &[char] = &['H', 'L', 'P'];
for opt in &args {
match opt.as_str() {
// If more than one is specified, only the final one takes effect.
s if s.contains(flags) => {
if let Some(idx) = s.rfind(flags) {
match s.chars().nth(idx).unwrap() {
'H' => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL,
'L' => bit_flag = FTS_LOGICAL,
'P' => bit_flag = FTS_PHYSICAL,
_ => (),
}
}
}
"--no-preserve-root" => preserve_root = false,
"--preserve-root" => preserve_root = true,
"--dereference" => derefer = 1,
"--no-dereference" => derefer = 0,
_ => (),
let mut app = uu_app().usage(&usage[..]);
// we change the positional args based on whether
// --reference was used.
let mut reference = false;
let mut help = false;
// stop processing options on --
for arg in args.iter().take_while(|s| *s != "--") {
if arg.starts_with("--reference=") || arg == "--reference" {
reference = true;
} else if arg == "--help" {
// we stop processing once we see --help,
// as it doesn't matter if we've seen reference or not
help = true;
break;
}
}
let matches = opts.parse(args);
let recursive = matches.opt_present("recursive");
if help || !reference {
// 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 bit_flag == FTS_PHYSICAL {
if derefer == 1 {
@ -106,27 +149,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
bit_flag = FTS_PHYSICAL;
}
let verbosity = if matches.opt_present("changes") {
let verbosity = if matches.is_present(options::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
} else if matches.opt_present("verbose") {
} else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose
} else {
Verbosity::Normal
};
if matches.free.is_empty() {
show_usage_error!("missing operand");
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") {
let dest_gid: u32;
if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) {
Ok(meta) => {
dest_gid = meta.gid();
@ -136,19 +172,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
return 1;
}
}
files = matches.free;
} else {
match entries::grp2gid(&matches.free[0]) {
let group = matches.value_of(options::ARG_GROUP).unwrap_or_default();
match entries::grp2gid(group) {
Ok(g) => {
dest_gid = g;
}
_ => {
show_error!("invalid group: {}", matches.free[0].as_str());
show_error!("invalid group: {}", group);
return 1;
}
}
files = matches.free;
files.remove(0);
}
let executor = Chgrper {
@ -163,6 +197,86 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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 {
dest_gid: gid_t,
bit_flag: u8,

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/chmod.rs"
[dependencies]
clap = "2.33.3"
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] }
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 after_help = get_long_usage();
let matches = App::new(executable!())
.version(crate_version!())
.about(ABOUT)
let matches = uu_app()
.usage(&usage[..])
.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::with_name(options::CHANGES)
.long(options::CHANGES)
@ -120,54 +173,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.required_unless(options::MODE)
.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
@ -230,11 +235,11 @@ impl Chmoder {
return Err(1);
}
if !self.recursive {
r = self.chmod_file(&file).and(r);
r = self.chmod_file(file).and(r);
} else {
for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) {
let file = entry.path();
r = self.chmod_file(&file).and(r);
r = self.chmod_file(file).and(r);
}
}
}

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/chown.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
glob = "0.3.0"
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" }

View file

@ -73,10 +73,116 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(options::verbosity::CHANGES)
.short("c")
@ -167,110 +273,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.required(true)
.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> {
@ -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 grp_only = args.len() == 2 && args[0].is_empty();
let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty();
if usr_only {
Ok((
Some(match Passwd::locate(args[0]) {
Ok(v) => v.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)),
}),
))
let uid = if usr_only || usr_grp {
Some(
Passwd::locate(args[0])
.map_err(|_| format!("invalid user: '{}'", spec))?
.uid(),
)
} 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 {
@ -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 GROUPS: &str = "groups";
pub const USERSPEC: &str = "userspec";
pub const COMMAND: &str = "command";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
@ -35,11 +36,72 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::ConvertLossy)
.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!())
.about(ABOUT)
.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::with_name(options::USER)
.short("u")
@ -71,59 +133,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
)
.value_name("USER:GROUP"),
)
.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 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)
}
.arg(
Arg::with_name(options::COMMAND)
.hidden(true)
.multiple(true)
.index(2),
)
}
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 groups_str = options.value_of(options::GROUPS).unwrap_or_default();
let userspec = match userspec_str {
Some(ref u) => {
Some(u) => {
let s: Vec<&str> = u.split(':').collect();
if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) {
crash!(1, "invalid userspec: `{}`", u)

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/cksum.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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();
loop {
match rd.read(&mut bytes) {
Ok(num_bytes) => {
let num_bytes = rd.read(&mut bytes)?;
if num_bytes == 0 {
return Ok((crc_final(crc, size), size));
}
@ -170,9 +169,6 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
}
size += num_bytes;
}
Err(err) => return Err(err),
}
}
}
mod options {
@ -184,13 +180,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = App::new(executable!())
.name(NAME)
.version(crate_version!())
.about(SUMMARY)
.usage(SYNTAX)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.get_matches_from(args);
let matches = uu_app().get_matches_from(args);
let files: Vec<String> = match matches.values_of(options::FILE) {
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
@ -221,3 +211,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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) {
match line.chars().last() {
Some('\n') => (),
_ => line.push('\n'),
if !line.ends_with('\n') {
line.push('\n');
}
}
@ -138,10 +137,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::ConvertLossy)
.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!())
.about(ABOUT)
.usage(&usage[..])
.after_help(LONG_HELP)
.arg(
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_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"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
filetime = "0.2"
libc = "0.2.85"
quick-error = "1.2.3"

View file

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

View file

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

View file

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

View file

@ -92,7 +92,7 @@ where
T: BufRead,
{
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);
// consume the rest
@ -711,10 +711,37 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::Ignore)
.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!())
.about(SUMMARY)
.usage(&usage[..])
.arg(
Arg::with_name(options::SUFFIX_FORMAT)
.short("b")
@ -768,29 +795,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.required(true),
)
.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(),
};
if let Some(up_to_match) = captures.name("UPTO") {
let pattern = match Regex::new(up_to_match.as_str()) {
Err(_) => {
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
Ok(reg) => reg,
};
let pattern = Regex::new(up_to_match.as_str())
.map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?;
patterns.push(Pattern::UpToMatch(pattern, offset, execute_ntimes));
} else if let Some(skip_to_match) = captures.name("SKIPTO") {
let pattern = match Regex::new(skip_to_match.as_str()) {
Err(_) => {
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
Ok(reg) => reg,
};
let pattern = Regex::new(skip_to_match.as_str())
.map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?;
patterns.push(Pattern::SkipToMatch(pattern, offset, execute_ntimes));
}
} else if let Ok(line_number) = arg.parse::<usize>() {

View file

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

View file

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

View file

@ -17,7 +17,6 @@ use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
use std::path::Path;
use self::searcher::Searcher;
use uucore::fs::is_stdout_interactive;
use uucore::ranges::Range;
use uucore::InvalidEncodingHandling;
@ -127,7 +126,7 @@ enum Mode {
}
fn stdout_writer() -> Box<dyn Write> {
if is_stdout_interactive() {
if atty::is(atty::Stream::Stdout) {
Box::new(stdout())
} else {
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)
.accept_any();
let matches = 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)
)
.get_matches_from(args);
let matches = uu_app().get_matches_from(args);
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);
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 {
Err(msg_opt_invalid_should_be!(
"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]
chrono = "0.4.4"
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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]]",
NAME
);
let matches = App::new(executable!())
.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 matches = uu_app().usage(&syntax[..]).get_matches_from(args);
let format = if let Some(form) = matches.value_of(OPT_FORMAT) {
if !form.starts_with('+') {
eprintln!("date: invalid date {}", form);
eprintln!("date: invalid date '{}'", form);
return 1;
}
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) {
None => None,
Some(Err((input, _err))) => {
eprintln!("date: invalid date {}", input);
eprintln!("date: invalid date '{}'", input);
return 1;
}
Some(Ok(date)) => Some(date),
@ -305,7 +241,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
println!("{}", formatted);
}
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
}
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.
fn make_format_string(settings: &Settings) -> &str {
match settings.format {

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/df.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
number_prefix = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc", "fsext"] }
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 {
let usage = get_usage();
let matches = App::new(executable!())
.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 matches = uu_app().usage(&usage[..]).get_matches_from(args);
let paths: Vec<String> = matches
.values_of(OPT_PATHS)
@ -511,3 +398,118 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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"
[dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
glob = "0.3.0"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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.
//
// (c) Jian Zeng <anonymousknight96@gmail.com>
// (c) Mitchell Mebane <mitchell.mebane@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
@ -15,6 +16,15 @@ use std::env;
use std::fs::File;
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 SUMMARY: &str = "Output commands to set the LS_COLORS environment variable.";
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 {
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.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);
let usage = get_usage();
if (matches.opt_present("csh")
|| matches.opt_present("c-shell")
|| matches.opt_present("sh")
|| matches.opt_present("bourne-shell"))
&& matches.opt_present("print-database")
let matches = uu_app().usage(&usage[..]).get_matches_from(&args);
let files = matches
.values_of(options::FILE)
.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!(
"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;
}
if matches.opt_present("print-database") {
if !matches.free.is_empty() {
if matches.is_present(options::PRINT_DATABASE) {
if !files.is_empty() {
show_usage_error!(
"extra operand {}\nfile operands cannot be combined with \
"extra operand '{}'\nfile operands cannot be combined with \
--print-database (-p)",
matches.free[0]
files[0]
);
return 1;
}
@ -96,9 +105,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
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;
} else if matches.opt_present("sh") || matches.opt_present("bourne-shell") {
} else if matches.is_present(options::BOURNE_SHELL) {
out_format = OutputFmt::Shell;
}
@ -113,24 +122,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
let result;
if matches.free.is_empty() {
if files.is_empty() {
result = parse(INTERNAL_DB.lines(), out_format, "")
} else {
if matches.free.len() > 1 {
show_usage_error!("extra operand {}", matches.free[1]);
if files.len() > 1 {
show_usage_error!("extra operand '{}'", files[1]);
return 1;
}
match File::open(matches.free[0].as_str()) {
match File::open(files[0]) {
Ok(f) => {
let fin = BufReader::new(f);
result = parse(
fin.lines().filter_map(Result::ok),
out_format,
matches.free[0].as_str(),
)
result = parse(fin.lines().filter_map(Result::ok), out_format, files[0])
}
Err(e) => {
show_error!("{}: {}", matches.free[0], e);
show_error!("{}: {}", files[0], e);
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 {
/// Remove comments and trim whitespace
fn purify(&self) -> &Self;
@ -158,21 +194,25 @@ pub trait StrUtils {
impl StrUtils for str {
fn purify(&self) -> &Self {
let mut line = self;
for (n, c) in self.chars().enumerate() {
if c != '#' {
continue;
for (n, _) in self
.as_bytes()
.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;
}
// Ignore if '#' is at the beginning of line
if n == 0 {
None => {
// n == 0
line = &self[..0];
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()

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/dirname.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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 after_help = get_long_usage();
let matches = App::new(executable!())
.about(ABOUT)
let matches = uu_app()
.usage(&usage[..])
.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);
let separator = if matches.is_present(options::ZERO) {
@ -92,3 +83,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
chrono = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version="0.3", features=[] }
[[bin]]

View file

@ -1,17 +1,19 @@
// This file is part of the uutils coreutils package.
//
// (c) Derek Chiang <derekchiang93@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// * This file is part of the uutils coreutils package.
// *
// * (c) Derek Chiang <derekchiang93@gmail.com>
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use chrono::prelude::DateTime;
use chrono::Local;
use clap::ArgMatches;
use clap::{crate_version, App, Arg};
use std::collections::HashSet;
use std::convert::TryFrom;
use std::env;
use std::fs;
#[cfg(not(windows))]
@ -24,8 +26,12 @@ use std::os::unix::fs::MetadataExt;
use std::os::windows::fs::MetadataExt;
#[cfg(windows)]
use std::os::windows::io::AsRawHandle;
#[cfg(windows)]
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::{Duration, UNIX_EPOCH};
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling;
#[cfg(windows)]
use winapi::shared::minwindef::{DWORD, LPVOID};
@ -42,7 +48,7 @@ mod options {
pub const NULL: &str = "0";
pub const ALL: &str = "all";
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 TOTAL: &str = "c";
pub const MAX_DEPTH: &str = "d";
@ -52,9 +58,13 @@ mod options {
pub const BLOCK_SIZE_1M: &str = "m";
pub const SEPARATE_DIRS: &str = "S";
pub const SUMMARIZE: &str = "s";
pub const THRESHOLD: &str = "threshold";
pub const SI: &str = "si";
pub const TIME: &str = "time";
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";
}
@ -79,6 +89,9 @@ struct Options {
max_depth: Option<usize>,
total: bool,
separate_dirs: bool,
one_file_system: bool,
dereference: bool,
inodes: bool,
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
@ -92,6 +105,7 @@ struct Stat {
is_dir: bool,
size: u64,
blocks: u64,
inodes: u64,
inode: Option<FileInfo>,
created: Option<u64>,
accessed: u64,
@ -99,8 +113,12 @@ struct Stat {
}
impl Stat {
fn new(path: PathBuf) -> Result<Stat> {
let metadata = fs::symlink_metadata(&path)?;
fn new(path: PathBuf, options: &Options) -> Result<Stat> {
let metadata = if options.dereference {
fs::metadata(&path)?
} else {
fs::symlink_metadata(&path)?
};
#[cfg(not(windows))]
let file_info = FileInfo {
@ -113,6 +131,7 @@ impl Stat {
is_dir: metadata.is_dir(),
size: metadata.len(),
blocks: metadata.blocks() as u64,
inodes: 1,
inode: Some(file_info),
created: birth_u64(&metadata),
accessed: metadata.atime() as u64,
@ -130,6 +149,7 @@ impl Stat {
size: metadata.len(),
blocks: size_on_disk / 1024 * 2,
inode: file_info,
inodes: 1,
created: windows_creation_time_to_unix_time(metadata.creation_time()),
accessed: windows_time_to_unix_time(metadata.last_access_time()),
modified: windows_time_to_unix_time(metadata.last_write_time()),
@ -159,7 +179,7 @@ fn birth_u64(meta: &Metadata) -> Option<u64> {
}
#[cfg(windows)]
fn get_size_on_disk(path: &PathBuf) -> u64 {
fn get_size_on_disk(path: &Path) -> u64 {
let mut size_on_disk = 0;
// 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)]
fn get_file_info(path: &PathBuf) -> Option<FileInfo> {
fn get_file_info(path: &Path) -> Option<FileInfo> {
let mut result = None;
let file = match fs::File::open(path) {
@ -223,59 +243,18 @@ fn get_file_info(path: &PathBuf) -> Option<FileInfo> {
result
}
fn unit_string_to_number(s: &str) -> Option<u64> {
let mut offset = 0;
let mut s_chars = s.chars().rev();
let (mut ch, multiple) = match s_chars.next() {
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);
};
fn read_block_size(s: Option<&str>) -> usize {
if let Some(s) = s {
parse_size(s)
.unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::BLOCK_SIZE)))
} else {
for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
let env_size = env::var(env_var).ok();
if let Some(quantity) = translate_to_pure_number(&env_size.as_deref()) {
return quantity;
if let Ok(env_size) = env::var(env_var) {
if let Ok(v) = parse_size(&env_size) {
return v;
}
}
}
if env::var("POSIXLY_CORRECT").is_ok() {
512
} else {
@ -283,6 +262,17 @@ fn read_block_size(s: Option<&str>) -> u64 {
}
}
}
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
}
}
// this takes `my_stat` to avoid having to stat files multiple times.
@ -302,7 +292,7 @@ fn du(
Err(e) => {
safe_writeln!(
stderr(),
"{}: cannot read directory {}: {}",
"{}: cannot read directory '{}': {}",
options.program_name,
my_stat.path.display(),
e
@ -313,20 +303,29 @@ fn du(
for f in read {
match f {
Ok(entry) => match Stat::new(entry.path()) {
Ok(entry) => match Stat::new(entry.path(), options) {
Ok(this_stat) => {
if this_stat.is_dir {
futures.push(du(this_stat, options, depth + 1, inodes));
} else {
if this_stat.inode.is_some() {
let inode = this_stat.inode.unwrap();
if let Some(inode) = this_stat.inode {
if inodes.contains(&inode) {
continue;
}
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.blocks += this_stat.blocks;
my_stat.inodes += 1;
if options.all {
stats.push(this_stat);
}
@ -334,18 +333,11 @@ fn du(
}
Err(error) => match error.kind() {
ErrorKind::PermissionDenied => {
let description = format!(
"cannot access '{}'",
entry
.path()
.as_os_str()
.to_str()
.unwrap_or("<Un-printable path>")
);
let description = format!("cannot access '{}'", entry.path().display());
let error_message = "Permission denied";
show_error_custom_description!(description, "{}", error_message)
}
_ => show_error!("{}", error),
_ => show_error!("cannot access '{}': {}", entry.path().display(), 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 {
my_stat.size += stat.size;
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);
Box::new(stats.into_iter())
@ -412,182 +407,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let matches = App::new(executable!())
.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 matches = uu_app().usage(&usage[..]).get_matches_from(args);
let summarize = matches.is_present(options::SUMMARIZE);
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());
match (max_depth_str, max_depth) {
(Some(ref s), _) if summarize => {
show_error!("summarizing conflicts with --max-depth={}", *s);
(Some(s), _) if summarize => {
show_error!("summarizing conflicts with --max-depth={}", s);
return 1;
}
(Some(ref s), None) => {
show_error!("invalid maximum depth '{}'", *s);
(Some(s), None) => {
show_error!("invalid maximum depth '{}'", s);
return 1;
}
(Some(_), Some(_)) | (None, _) => { /* valid */ }
@ -599,16 +431,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
max_depth,
total: matches.is_present(options::TOTAL),
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) {
Some(_) => matches.values_of(options::FILE).unwrap().collect(),
None => {
vec!["./"] // TODO: gnu `du` doesn't use trailing "/" here
}
None => vec!["."],
};
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) {
1000
@ -628,7 +472,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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") {
Some(s) => match s {
@ -661,23 +511,22 @@ Try '{} --help' for more information.",
let mut grand_total = 0;
for path_string in files {
let path = PathBuf::from(&path_string);
match Stat::new(path) {
match Stat::new(path, &options) {
Ok(stat) => {
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 (_, len) = iter.size_hint();
let len = len.unwrap();
for (index, stat) in iter.enumerate() {
let size = if matches.is_present(options::APPARENT_SIZE)
|| matches.is_present(options::BYTES)
{
stat.size
} 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
};
let size = choose_size(&matches, &stat);
if threshold.map_or(false, |threshold| threshold.should_exclude(size)) {
continue;
}
if matches.is_present(options::TIME) {
let tm = {
let secs = {
@ -690,8 +539,8 @@ Try '{} --help' for more information.",
time
} else {
show_error!(
"Invalid argument {} for --time.
birth and creation arguments are not supported on this platform.",
"Invalid argument '{}' for --time.
'birth' and 'creation' arguments are not supported on this platform.",
s
);
return 1;
@ -744,31 +593,235 @@ Try '{} --help' for more information.",
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)]
mod test_du {
#[allow(unused_imports)]
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]
fn test_read_block_size() {
let test_data = [
(Some("10".to_string()), 10),
(Some("1024".to_string()), 1024),
(Some("K".to_string()), 1024),
(None, 1024),
(Some("BAD_STRING".to_string()), 1024),
];
for it in test_data.iter() {
assert_eq!(read_block_size(it.0.as_deref()), it.1);

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/echo.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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
.collect_str(InvalidEncodingHandling::ConvertLossy)
.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)
// TrailingVarArg specifies the final positional argument is a VarArg
// and it doesn't attempts the parse any further args.
@ -154,22 +173,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.multiple(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<()> {
@ -181,7 +184,7 @@ fn execute(no_newline: bool, escaped: bool, free: Vec<String>) -> io::Result<()>
write!(output, " ")?;
}
if escaped {
let should_stop = print_escaped(&input, &mut output)?;
let should_stop = print_escaped(input, &mut output)?;
if should_stop {
break;
}

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/env.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42"
rust-ini = "0.13.0"
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)
};
let conf = match conf {
Ok(config) => config,
Err(error) => {
let conf = conf.map_err(|error| {
eprintln!("env: error: \"{}\": {}", file, error);
return Err(1);
}
};
1
})?;
for (_, prop) in &conf {
// 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[..])
}
fn create_app() -> App<'static, 'static> {
pub fn uu_app() -> App<'static, 'static> {
App::new(crate_name!())
.version(crate_version!())
.author(crate_authors!())
@ -161,7 +158,7 @@ fn create_app() -> App<'static, 'static> {
}
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 ignore_env = matches.is_present("ignore-environment");
@ -245,7 +242,7 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> {
}
// 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
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
match Command::new(&*prog).args(args).status() {
Ok(exit) => {
if !exit.success() {
return Err(exit.code().unwrap());
}
}
Ok(exit) if !exit.success() => return Err(exit.code().unwrap()),
Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127),
Err(_) => return Err(126),
Ok(_) => (),
}
} else {
// no program provided, so just dump all env vars to stdout

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/expand.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
unicode-width = "0.1.5"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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 std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
use std::iter::repeat;
use std::str::from_utf8;
use unicode_width::UnicodeWidthChar;
@ -90,7 +89,7 @@ impl Options {
})
.max()
.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) {
Some(s) => s.map(|v| v.to_string()).collect(),
@ -109,10 +108,16 @@ impl Options {
pub fn uumain(args: impl uucore::Args) -> i32 {
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!())
.about(ABOUT)
.usage(&usage[..])
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::INITIAL)
@ -139,10 +144,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.hidden(true)
.takes_value(true)
)
.get_matches_from(args);
expand(Options::new(&matches));
0
}
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
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 {
safe_unwrap!(output.write_all(&buf[byte..byte + nbytes]));
}

View file

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

View file

@ -8,13 +8,20 @@
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use uucore::InvalidEncodingHandling;
mod syntax_tree;
mod tokens;
static NAME: &str = "expr";
static VERSION: &str = env!("CARGO_PKG_VERSION");
const VERSION: &str = "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 {
let args = args
@ -37,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
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);
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> {
if maybe_ast.is_err() {
Err(maybe_ast.err().unwrap())
} else {
maybe_ast.ok().unwrap().evaluate()
}
maybe_ast.and_then(|ast| ast.evaluate())
}
fn maybe_handle_help_or_version(args: &[String]) -> bool {
@ -137,5 +140,5 @@ Environment variables:
}
fn print_version() {
println!("{} {}", NAME, VERSION);
println!("{} {}", executable!(), crate_version!());
}

View file

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

View file

@ -78,27 +78,27 @@ pub fn strings_to_tokens(strings: &[String]) -> Result<Vec<(usize, Token)>, Stri
"(" => Token::ParOpen,
")" => 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 {
arity: 2,
@ -117,9 +117,9 @@ pub fn strings_to_tokens(strings: &[String]) -> Result<Vec<(usize, Token)>, Stri
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;
}
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" }
uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }
uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
[dev-dependencies]
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 {
let matches = App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
.arg(Arg::with_name(options::NUMBER).multiple(true))
.get_matches_from(args);
let matches = uu_app().get_matches_from(args);
let stdout = stdout();
let mut w = io::BufWriter::new(stdout.lock());
@ -68,3 +64,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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"
[dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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
// * 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
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
}

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/fmt.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42"
unicode-width = "0.1.5"
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 {
let usage = get_usage();
let matches = App::new(executable!())
.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 matches = uu_app().usage(&usage[..]).get_matches_from(args);
let mut files: Vec<String> = matches
.values_of(ARG_FILES)
@ -331,3 +209,127 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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();
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)
.version(crate_version!())
.usage(SYNTAX)
@ -68,37 +96,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(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>) {
for (i, arg) in args.iter().enumerate() {
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();
v.remove(i);
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) {
for filename in &filenames {
let filename: &str = &filename;
let filename: &str = filename;
let mut stdin_buf;
let mut file_buf;
let buffer = BufReader::new(if filename == "-" {

View file

@ -15,9 +15,9 @@ edition = "2018"
path = "src/groups.rs"
[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" }
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
[[bin]]
name = "groups"

View file

@ -5,59 +5,93 @@
//
// For the full copyright and license information, please view the LICENSE
// 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
#[macro_use]
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};
static ABOUT: &str = "display current group names";
static OPT_USER: &str = "user";
mod options {
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 {
format!("{0} [USERNAME]", executable!())
format!("{0} [OPTION]... [USERNAME]...", executable!())
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let matches = App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(Arg::with_name(OPT_USER))
.get_matches_from(args);
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
match matches.value_of(OPT_USER) {
None => {
let users: Vec<String> = matches
.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!(
"{}",
get_groups()
get_groups_gnu(None)
.unwrap()
.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<_>>()
.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!(
"{}",
"{} : {}",
user,
p.belongs_to()
.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<_>>()
.join(" ")
);
0
} 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]
digest = "0.6.2"
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
hex = "0.2.0"
libc = "0.2.42"
md5 = "0.3.5"
@ -25,7 +25,7 @@ regex-syntax = "0.6.7"
sha1 = "0.6.0"
sha2 = "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_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 md5;
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 {
blake2_rfc::blake2b::Blake2b::new(64)
Self::new()
}
fn input(&mut self, input: &[u8]) {
@ -59,12 +58,12 @@ impl Digest for blake2_rfc::blake2b::Blake2b {
}
fn result(&mut self, out: &mut [u8]) {
let hash_result = &self.clone().finalize();
out.copy_from_slice(&hash_result.as_bytes());
let hash_result = &self.finalize();
out.copy_from_slice(hash_result.as_bytes());
}
fn reset(&mut self) {
*self = blake2_rfc::blake2b::Blake2b::new(64);
*self = Self::new();
}
fn output_bits(&self) -> usize {

View file

@ -19,7 +19,6 @@ mod digest;
use self::digest::Digest;
use blake2_rfc::blake2b::Blake2b;
use clap::{App, Arg, ArgMatches};
use hex::ToHex;
use md5::Context as Md5;
@ -85,9 +84,13 @@ fn detect_algo<'a>(
"sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box<dyn Digest>, 256),
"sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box<dyn Digest>, 384),
"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") {
Some(bits_str) => match (&bits_str).parse::<usize>() {
Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(224) => (
"SHA3-224",
Box::new(Sha3_224::new()) as Box<dyn Digest>,
@ -137,7 +140,7 @@ fn detect_algo<'a>(
512,
),
"shake128sum" => match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() {
Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(bits) => (
"SHAKE128",
Box::new(Shake128::new()) as Box<dyn Digest>,
@ -148,7 +151,7 @@ fn detect_algo<'a>(
None => crash!(1, "--bits required for SHAKE-128"),
},
"shake256sum" => match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() {
Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(bits) => (
"SHAKE256",
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)
}
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") {
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(
"SHA3-224",
Box::new(Sha3_224::new()) as Box<dyn Digest>,
@ -235,7 +238,7 @@ fn detect_algo<'a>(
}
if matches.is_present("shake128") {
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),
Err(err) => crash!(1, "{}", err),
},
@ -244,7 +247,7 @@ fn detect_algo<'a>(
}
if matches.is_present("shake256") {
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),
Err(err) => crash!(1, "{}", err),
},
@ -252,10 +255,8 @@ fn detect_algo<'a>(
}
}
}
if alg.is_none() {
crash!(1, "You must specify hash algorithm!")
};
(name, alg.unwrap(), output_bits)
let alg = alg.unwrap_or_else(|| crash!(1, "You must specify hash algorithm!"));
(name, alg, output_bits)
}
}
}
@ -284,119 +285,7 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 {
// Default binary in Windows, text mode otherwise
let binary_flag_default = cfg!(windows);
let binary_help = format!(
"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));
}
}
let app = uu_app(&binary_name);
// 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
@ -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)]
fn hashsum<'a, I>(mut options: Options, files: I) -> Result<(), i32>
where

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/head.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] }
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
use clap::{crate_version, App, Arg};
@ -35,7 +40,7 @@ mod take;
use lines::zlines;
use take::take_all_but;
fn app<'a>() -> App<'a, 'a> {
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
@ -75,7 +80,7 @@ fn app<'a>() -> App<'a, 'a> {
.arg(
Arg::with_name(options::QUIET_NAME)
.short("q")
.long("--quiet")
.long("quiet")
.visible_alias("silent")
.help("never print headers giving file names")
.overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]),
@ -108,12 +113,7 @@ where
{
match parse::parse_num(src) {
Ok((n, last)) => Ok((closure(n), last)),
Err(reason) => match reason {
parse::ParseError::Syntax => Err(format!("'{}'", src)),
parse::ParseError::Overflow => {
Err(format!("'{}': Value too large for defined datatype", src))
}
},
Err(e) => Err(e.to_string()),
}
}
@ -167,7 +167,7 @@ impl HeadOptions {
///Construct options from matches
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();
@ -176,19 +176,11 @@ impl HeadOptions {
options.zeroed = matches.is_present(options::ZERO_NAME);
let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) {
match parse_mode(v, Modes::Bytes) {
Ok(v) => v,
Err(err) => {
return Err(format!("invalid number of bytes: {}", err));
}
}
parse_mode(v, Modes::Bytes)
.map_err(|err| format!("invalid number of bytes: {}", err))?
} else if let Some(v) = matches.value_of(options::LINES_NAME) {
match parse_mode(v, Modes::Lines) {
Ok(v) => v,
Err(err) => {
return Err(format!("invalid number of lines: {}", err));
}
}
parse_mode(v, Modes::Lines)
.map_err(|err| format!("invalid number of lines: {}", err))?
} else {
(Modes::Lines(10), false)
};
@ -474,7 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let args = match HeadOptions::get_from(args) {
Ok(o) => o,
Err(s) => {
crash!(EXIT_FAILURE, "head: {}", s);
crash!(EXIT_FAILURE, "{}", s);
}
};
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 uucore::parse_size::{parse_size, ParseSizeError};
#[derive(PartialEq, Debug)]
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,
/// the bool specifies whether to read from the end
pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> {
let mut num_start = 0;
let mut chars = src.char_indices();
let (mut chars, all_but_last) = match chars.next() {
Some((_, c)) => {
pub fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> {
let mut size_string = src.trim();
let mut all_but_last = false;
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 == '-' {
num_start += 1;
(chars, true)
all_but_last = true;
}
}
} else {
(src.char_indices(), false)
}
}
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;
}
return Err(ParseSizeError::ParseFailure(src.to_string()));
}
let num = if num_count > 0 {
match src[num_start..=num_end].parse::<usize>() {
Ok(n) => Some(n),
Err(_) => return Err(ParseError::Overflow),
parse_size(size_string).map(|n| (n, all_but_last))
}
} 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)]
mod tests {
use super::*;
@ -195,44 +133,6 @@ mod tests {
Some(Ok(src.iter().map(|s| s.to_string()).collect()))
}
#[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() {
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));

View file

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

View file

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

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/hostname.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42"
hostname = { version = "0.3", features = ["set"] }
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 {
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!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(OPT_DOMAIN)
.short("d")
@ -80,19 +95,6 @@ fn execute(args: impl uucore::Args) -> i32 {
possible",
))
.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 {

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/id.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "process"] }
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
// 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://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(dead_code)]
@ -31,210 +47,346 @@ macro_rules! cstr2cow {
};
}
#[cfg(not(target_os = "linux"))]
mod audit {
use super::libc::{c_int, c_uint, dev_t, pid_t, uid_t};
static ABOUT: &str = "Print user and group information for each specified USER,
or (when USER omitted) for the current user.";
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,
mod options {
pub const OPT_AUDIT: &str = "audit"; // GNU's id does not have this
pub const OPT_CONTEXT: &str = "context";
pub const OPT_EFFECTIVE_USER: &str = "user";
pub const OPT_GROUP: &str = "group";
pub const OPT_GROUPS: &str = "groups";
pub const OPT_HUMAN_READABLE: &str = "human-readable"; // GNU's id does not have this
pub const OPT_NAME: &str = "name";
pub const OPT_PASSWORD: &str = "password"; // GNU's id does not have this
pub const OPT_REAL_ID: &str = "real";
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 {
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 {
let usage = get_usage();
let after_help = get_description();
let matches = App::new(executable!())
.version(crate_version!())
.about(ABOUT)
let matches = uu_app()
.usage(&usage[..])
.arg(
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))
.after_help(&after_help[..])
.get_matches_from(args);
let users: Vec<String> = matches
.values_of(ARG_USERS)
.values_of(options::ARG_USERS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
if matches.is_present(OPT_AUDIT) {
auditid();
return 0;
let mut state = State {
nflag: matches.is_present(options::OPT_NAME),
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
} else {
match Passwd::locate(users[0].as_str()) {
match Passwd::locate(users[i].as_str()) {
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);
let uflag = matches.is_present(OPT_EFFECTIVE_USER);
let gflag = matches.is_present(OPT_GROUP);
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) {
// GNU's `id` does not support the flags: -p/-P/-A.
if matches.is_present(options::OPT_PASSWORD) {
// BSD's `id` ignores all but the first specified user
pline(possible_pw.map(|v| v.uid()));
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) {
pretty(possible_pw);
return 0;
}
if possible_pw.is_some() {
id_print(possible_pw, false, false)
if state.gsflag {
print!(
"{}{}",
groups
.iter()
.map(|&id| {
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 {
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>) {
@ -280,7 +432,7 @@ fn pretty(possible_pw: Option<Passwd>) {
println!(
"groups\t{}",
entries::get_groups()
entries::get_groups_gnu(None)
.unwrap()
.iter()
.map(|&gr| entries::gid2grp(gr).unwrap())
@ -347,30 +499,21 @@ fn auditid() {
println!("asid={}", auditinfo.ai_asid);
}
fn id_print(possible_pw: Option<Passwd>, p_euid: bool, p_egid: bool) {
let (uid, gid) = possible_pw
.map(|p| (p.uid(), p.gid()))
.unwrap_or((getuid(), getgid()));
let groups = match Passwd::locate(uid) {
Ok(p) => p.belongs_to(),
Err(e) => crash!(1, "Could not find uid {}: {}", uid, e),
};
fn id_print(state: &State, groups: Vec<u32>) {
let uid = state.ids.as_ref().unwrap().uid;
let gid = state.ids.as_ref().unwrap().gid;
let euid = state.ids.as_ref().unwrap().euid;
let egid = state.ids.as_ref().unwrap().egid;
print!("uid={}({})", uid, entries::uid2usr(uid).unwrap());
print!(" gid={}({})", gid, entries::gid2grp(gid).unwrap());
let euid = geteuid();
if p_euid && (euid != uid) {
if !state.user_specified && (euid != uid) {
print!(" euid={}({})", euid, entries::uid2usr(euid).unwrap());
}
let egid = getegid();
if p_egid && (egid != gid) {
if !state.user_specified && (egid != gid) {
print!(" egid={}({})", euid, entries::gid2grp(egid).unwrap());
}
println!(
print!(
" groups={}",
groups
.iter()
@ -378,4 +521,49 @@ fn id_print(possible_pw: Option<Passwd>, p_euid: bool, p_egid: bool) {
.collect::<Vec<_>>()
.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"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
filetime = "0.2"
file_diff = "1.0.0"
libc = ">= 0.2"

View file

@ -15,6 +15,7 @@ extern crate uucore;
use clap::{crate_version, App, Arg, ArgMatches};
use file_diff::diff;
use filetime::{set_file_times, FileTime};
use uucore::backup_control::{self, BackupMode};
use uucore::entries::{grp2gid, usr2uid};
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
@ -33,6 +34,7 @@ const DEFAULT_STRIP_PROGRAM: &str = "strip";
pub struct Behavior {
main_function: MainFunction,
specified_mode: Option<u32>,
backup_mode: BackupMode,
suffix: String,
owner: String,
group: String,
@ -42,6 +44,7 @@ pub struct Behavior {
strip: bool,
strip_program: String,
create_leading: bool,
target_dir: Option<String>,
}
#[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_BACKUP: &str = "backup";
static OPT_BACKUP_2: &str = "backup2";
static OPT_BACKUP_NO_ARG: &str = "backup2";
static OPT_DIRECTORY: &str = "directory";
static OPT_IGNORED: &str = "ignored";
static OPT_CREATE_LEADING: &str = "create-leading";
@ -97,21 +100,49 @@ fn get_usage() -> String {
pub fn uumain(args: impl uucore::Args) -> i32 {
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!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(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")
)
.arg(
// TODO implement flag
Arg::with_name(OPT_BACKUP_2)
Arg::with_name(OPT_BACKUP_NO_ARG)
.short("b")
.help("(unimplemented) like --backup but does not accept an argument")
.help("like --backup but does not accept an argument")
)
.arg(
Arg::with_name(OPT_IGNORED)
@ -184,7 +215,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(OPT_SUFFIX)
.short("S")
.long(OPT_SUFFIX)
.help("(unimplemented) override the usual backup suffix")
.help("override the usual backup suffix")
.value_name("SUFFIX")
.takes_value(true)
.min_values(1)
@ -194,7 +225,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(OPT_TARGET_DIRECTORY)
.short("t")
.long(OPT_TARGET_DIRECTORY)
.help("(unimplemented) move all SOURCE arguments into DIRECTORY")
.help("move all SOURCE arguments into DIRECTORY")
.value_name("DIRECTORY")
)
.arg(
@ -227,29 +258,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.value_name("CONTEXT")
)
.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.
@ -262,15 +270,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
///
///
fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
if matches.is_present(OPT_BACKUP) {
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) {
if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
Err("--no-target-directory, -T")
} else if matches.is_present(OPT_PRESERVE_CONTEXT) {
Err("--preserve-context, -P")
@ -299,37 +299,25 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
let considering_dir: bool = MainFunction::Directory == main_function;
let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) {
match matches.value_of(OPT_MODE) {
Some(x) => match mode::parse(x, considering_dir) {
Ok(y) => Some(y),
Err(err) => {
let x = matches.value_of(OPT_MODE).ok_or(1)?;
Some(mode::parse(x, considering_dir).map_err(|err| {
show_error!("Invalid mode string: {}", err);
return Err(1);
}
},
None => {
return Err(1);
}
}
1
})?)
} else {
None
};
let backup_suffix = if matches.is_present(OPT_SUFFIX) {
match matches.value_of(OPT_SUFFIX) {
Some(x) => x,
None => {
return Err(1);
}
}
} else {
"~"
};
let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned());
Ok(Behavior {
main_function,
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(),
group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(),
verbose: matches.is_present(OPT_VERBOSE),
@ -342,6 +330,7 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
.unwrap_or(DEFAULT_STRIP_PROGRAM),
),
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;
continue;
}
@ -404,16 +393,17 @@ fn is_new_file_path(path: &Path) -> bool {
///
/// Returns an integer intended as a program return code.
///
fn standard(paths: Vec<String>, b: Behavior) -> i32 {
let sources = &paths[0..paths.len() - 1]
.iter()
.map(PathBuf::from)
.collect::<Vec<_>>();
fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
let target: PathBuf = b
.target_dir
.clone()
.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()) {
copy_files_into_dir(sources, &target.to_path_buf(), &b)
copy_files_into_dir(sources, &target, &b)
} else {
if let Some(parent) = target.parent() {
if !parent.exists() && b.create_leading {
@ -422,15 +412,15 @@ fn standard(paths: Vec<String>, b: Behavior) -> i32 {
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());
return 1;
}
}
}
if target.is_file() || is_new_file_path(target) {
copy_file_to_file(&sources[0], &target.to_path_buf(), &b)
if target.is_file() || is_new_file_path(&target) {
copy_file_to_file(&sources[0], &target, &b)
} else {
show_error!(
"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
///
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
} else {
0
@ -524,6 +514,28 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if b.compare && !need_copy(from, to, b) {
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" {
/* 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(());
}
@ -631,7 +643,11 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
}
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(())

View file

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

View file

@ -328,8 +328,8 @@ impl<'a> State<'a> {
});
} else {
repr.print_field(key);
repr.print_fields(&line1, self.key, self.max_fields);
repr.print_fields(&line2, other.key, other.max_fields);
repr.print_fields(line1, self.key, self.max_fields);
repr.print_fields(line2, other.key, other.max_fields);
}
println!();
@ -442,7 +442,72 @@ impl<'a> State<'a> {
}
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!())
.about(
"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")
.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 {
@ -611,7 +614,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
let mut state1 = State::new(
FileNum::File1,
&file1,
file1,
&stdin,
settings.key1,
settings.print_unpaired,
@ -619,7 +622,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
let mut state2 = State::new(
FileNum::File2,
&file2,
file2,
&stdin,
settings.key2,
settings.print_unpaired,

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/kill.rs"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["signals"] }
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 usage = format!("{} [OPTIONS]... PID...", executable!());
let matches = App::new(executable!())
.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 matches = uu_app().usage(&usage[..]).get_matches_from(args);
let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) {
Mode::Table
@ -106,12 +75,45 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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>) {
let mut i = 0;
while i < args.len() {
// this is safe because slice is valid when it is referenced
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..];
match val.parse() {
Ok(num) => {
@ -129,33 +131,24 @@ fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) {
}
fn table() {
let mut name_width = 0;
/* Compute the maximum width of a signal name. */
for s in &ALL_SIGNALS {
if s.name.len() > name_width {
name_width = s.name.len()
}
}
let name_width = ALL_SIGNALS.iter().map(|n| n.len()).max().unwrap();
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
print!("{0: >#2} {1: <#8}", idx + 1, signal.name);
//TODO: obtain max signal width here
print!("{0: >#2} {1: <#2$}", idx, signal, name_width + 2);
if (idx + 1) % 7 == 0 {
println!();
}
}
println!()
}
fn print_signal(signal_name_or_value: &str) {
for signal in &ALL_SIGNALS {
if signal.name == signal_name_or_value
|| (format!("SIG{}", signal.name)) == signal_name_or_value
{
println!("{}", signal.value);
for (value, &signal) in ALL_SIGNALS.iter().enumerate() {
if signal == signal_name_or_value || (format!("SIG{}", signal)) == signal_name_or_value {
println!("{}", value);
exit!(EXIT_OK as i32)
} else if signal_name_or_value == signal.value.to_string() {
println!("{}", signal.name);
} else if signal_name_or_value == value.to_string() {
println!("{}", signal);
exit!(EXIT_OK as i32)
}
}
@ -165,8 +158,8 @@ fn print_signal(signal_name_or_value: &str) {
fn print_signals() {
let mut pos = 0;
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
pos += signal.name.len();
print!("{}", signal.name);
pos += signal.len();
print!("{}", signal);
if idx > 0 && pos > 73 {
println!();
pos = 0;

View file

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

View file

@ -32,19 +32,7 @@ pub fn normalize_error_message(e: Error) -> String {
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let matches = App::new(executable!())
.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 matches = uu_app().usage(&usage[..]).get_matches_from(args);
let files: Vec<_> = matches
.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"
[dependencies]
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
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 {
overwrite: OverwriteMode,
backup: BackupMode,
force: bool,
suffix: String,
symbolic: bool,
relative: bool,
@ -54,7 +53,7 @@ pub enum BackupMode {
fn get_usage() -> String {
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... DIRECTORY (3rd form)
{0} [OPTION]... -t DIRECTORY TARGET... (4th form)",
@ -64,7 +63,7 @@ fn get_usage() -> String {
fn get_long_usage() -> String {
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 3rd and 4th forms, create links to each TARGET in DIRECTORY.
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 OPT_B: &str = "b";
static OPT_BACKUP: &str = "backup";
static OPT_FORCE: &str = "force";
static OPT_INTERACTIVE: &str = "interactive";
static OPT_NO_DEREFERENCE: &str = "no-dereference";
static OPT_SYMBOLIC: &str = "symbolic";
static OPT_SUFFIX: &str = "suffix";
static OPT_TARGET_DIRECTORY: &str = "target-directory";
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory";
static OPT_RELATIVE: &str = "relative";
static OPT_VERBOSE: &str = "verbose";
mod options {
pub const B: &str = "b";
pub const BACKUP: &str = "backup";
pub const FORCE: &str = "force";
pub const INTERACTIVE: &str = "interactive";
pub const NO_DEREFERENCE: &str = "no-dereference";
pub const SYMBOLIC: &str = "symbolic";
pub const SUFFIX: &str = "suffix";
pub const TARGET_DIRECTORY: &str = "target-directory";
pub const NO_TARGET_DIRECTORY: &str = "no-target-directory";
pub const RELATIVE: &str = "relative";
pub const VERBOSE: &str = "verbose";
}
static ARG_FILES: &str = "files";
@ -96,109 +97,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let long_usage = get_long_usage();
let matches = App::new(executable!())
.version(crate_version!())
.about(ABOUT)
let matches = uu_app()
.usage(&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);
/* the list of files */
@ -209,18 +110,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.map(PathBuf::from)
.collect();
let overwrite_mode = if matches.is_present(OPT_FORCE) {
let overwrite_mode = if matches.is_present(options::FORCE) {
OverwriteMode::Force
} else if matches.is_present(OPT_INTERACTIVE) {
} else if matches.is_present(options::INTERACTIVE) {
OverwriteMode::Interactive
} else {
OverwriteMode::NoClobber
};
let backup_mode = if matches.is_present(OPT_B) {
let backup_mode = if matches.is_present(options::B) {
BackupMode::ExistingBackup
} else if matches.is_present(OPT_BACKUP) {
match matches.value_of(OPT_BACKUP) {
} else if matches.is_present(options::BACKUP) {
match matches.value_of(options::BACKUP) {
None => BackupMode::ExistingBackup,
Some(mode) => match mode {
"simple" | "never" => BackupMode::SimpleBackup,
@ -234,8 +135,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
BackupMode::NoBackup
};
let backup_suffix = if matches.is_present(OPT_SUFFIX) {
matches.value_of(OPT_SUFFIX).unwrap()
let backup_suffix = if matches.is_present(options::SUFFIX) {
matches.value_of(options::SUFFIX).unwrap()
} else {
"~"
};
@ -243,34 +144,137 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let settings = Settings {
overwrite: overwrite_mode,
backup: backup_mode,
force: matches.is_present(OPT_FORCE),
suffix: backup_suffix.to_string(),
symbolic: matches.is_present(OPT_SYMBOLIC),
relative: matches.is_present(OPT_RELATIVE),
target_dir: matches.value_of(OPT_TARGET_DIRECTORY).map(String::from),
no_target_dir: matches.is_present(OPT_NO_TARGET_DIRECTORY),
no_dereference: matches.is_present(OPT_NO_DEREFERENCE),
verbose: matches.is_present(OPT_VERBOSE),
symbolic: matches.is_present(options::SYMBOLIC),
relative: matches.is_present(options::RELATIVE),
target_dir: matches
.value_of(options::TARGET_DIRECTORY)
.map(String::from),
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)
}
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 {
// Handle cases where we create links in a directory first.
if let Some(ref name) = settings.target_dir {
// 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 files.len() == 1 {
// 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());
if files.len() > 2 || last_file.is_dir() {
// 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;
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
// We need to clean the target
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>> {
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
.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 result: PathBuf = dst_abs
let mut result: PathBuf = dst_abs
.components()
.skip(suffix_pos + 1)
.map(|_| OsStr::new(".."))
.chain(src_iter)
.collect();
if result.as_os_str().is_empty() {
result.push(".");
}
Ok(result.into())
}
fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> {
let mut backup_path = None;
let source: Cow<'_, Path> = if settings.relative {
relative_path(&src, dst)?
relative_path(src, dst)?
} else {
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 {
symlink(&source, dst)?;
} else {

View file

@ -16,7 +16,7 @@ path = "src/logname.rs"
[dependencies]
libc = "0.2.42"
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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();
let usage = get_usage();
let _ = App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
.usage(&usage[..])
.get_matches_from(args);
let _ = uu_app().usage(&usage[..]).get_matches_from(args);
match get_userlogin() {
Some(userlogin) => println!("{}", userlogin),
@ -58,3 +54,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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