mirror of
https://github.com/uutils/coreutils
synced 2024-12-14 23:32:39 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
ae1935c3cb
313 changed files with 11869 additions and 7613 deletions
318
.github/workflows/CICD.yml
vendored
318
.github/workflows/CICD.yml
vendored
|
@ -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"
|
||||
- name: Install/setup prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
make prepare-busytest
|
||||
- name: "run busybox testsuite"
|
||||
## Install/setup prerequisites
|
||||
sudo apt-get -y update ; sudo apt-get -y install python3-sphinx ;
|
||||
- name: "`make build`"
|
||||
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"
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get -y update ; sudo apt-get -y install python3-sphinx;
|
||||
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
135
.github/workflows/FixPR.yml
vendored
Normal 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 }}
|
|
@ -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: |
|
||||
cd uutils
|
||||
bash util/build-gnu.sh
|
||||
## 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
|
|
@ -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
|
||||
|
|
|
@ -78,6 +78,7 @@ symlinks
|
|||
syscall
|
||||
syscalls
|
||||
tokenize
|
||||
toolchain
|
||||
truthy
|
||||
unbuffered
|
||||
unescape
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
86
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
24
GNUmakefile
24
GNUmakefile
|
@ -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
|
||||
|
|
66
README.md
66
README.md
|
@ -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 | | |
|
||||
|
|
59
build.rs
59
build.rs
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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: "));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -160,18 +160,14 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
|
|||
|
||||
let mut bytes = init_byte_array();
|
||||
loop {
|
||||
match rd.read(&mut bytes) {
|
||||
Ok(num_bytes) => {
|
||||
if num_bytes == 0 {
|
||||
return Ok((crc_final(crc, size), size));
|
||||
}
|
||||
for &b in bytes[..num_bytes].iter() {
|
||||
crc = crc_update(crc, b);
|
||||
}
|
||||
size += num_bytes;
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
let num_bytes = rd.read(&mut bytes)?;
|
||||
if num_bytes == 0 {
|
||||
return Ok((crc_final(crc, size), size));
|
||||
}
|
||||
for &b in bytes[..num_bytes].iter() {
|
||||
crc = crc_update(crc, b);
|
||||
}
|
||||
size += num_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
.help("copy only when the SOURCE file is newer than the destination file\
|
||||
.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),\
|
||||
.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,7 +675,14 @@ impl Options {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
ReflinkMode::Never
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
ReflinkMode::Auto
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
{
|
||||
ReflinkMode::Never
|
||||
}
|
||||
}
|
||||
},
|
||||
backup: backup_mode,
|
||||
|
@ -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,49 +1233,68 @@ 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 {
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
return Err("--reflink is only supported on linux and macOS"
|
||||
.to_string()
|
||||
.into());
|
||||
|
||||
#[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() {
|
||||
// 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() {
|
||||
match source.file_name() {
|
||||
Some(name) => dest.join(name).into(),
|
||||
None => crash!(
|
||||
EXIT_ERR,
|
||||
"cannot stat ‘{}’: No such file or directory",
|
||||
source.display()
|
||||
),
|
||||
}
|
||||
} else {
|
||||
dest.into()
|
||||
};
|
||||
symlink_file(&link, &dest, &*context_for(&link, &dest))?;
|
||||
} else if source.to_string_lossy() == "/dev/null" {
|
||||
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.parents {
|
||||
let parent = dest.parent().unwrap_or(dest);
|
||||
fs::create_dir_all(parent)?;
|
||||
} 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 {
|
||||
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() {
|
||||
match source.file_name() {
|
||||
Some(name) => dest.join(name).into(),
|
||||
None => crash!(
|
||||
EXIT_ERR,
|
||||
"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))
|
||||
}
|
||||
|
||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
|
||||
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
// Ignore if '#' is at the beginning of line
|
||||
if n == 0 {
|
||||
line = &self[..0];
|
||||
break;
|
||||
}
|
||||
|
||||
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
|
||||
if self.chars().nth(n - 1).unwrap().is_whitespace() {
|
||||
line = &self[..n];
|
||||
match self[..n].chars().last() {
|
||||
Some(c) if c.is_whitespace() => {
|
||||
line = &self[..n - c.len_utf8()];
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
// n == 0
|
||||
line = &self[..0];
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
line.trim()
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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,65 +243,35 @@ 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);
|
||||
};
|
||||
|
||||
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;
|
||||
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"] {
|
||||
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 {
|
||||
1024
|
||||
}
|
||||
}
|
||||
if env::var("POSIXLY_CORRECT").is_ok() {
|
||||
512
|
||||
} else {
|
||||
1024
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_size(matches: &ArgMatches, stat: &Stat) -> u64 {
|
||||
if matches.is_present(options::INODES) {
|
||||
stat.inodes
|
||||
} else if matches.is_present(options::APPARENT_SIZE) || matches.is_present(options::BYTES) {
|
||||
stat.size
|
||||
} else {
|
||||
// The st_blocks field indicates the number of blocks allocated to the file, 512-byte units.
|
||||
// See: http://linux.die.net/man/2/stat
|
||||
stat.blocks * 512
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,7 +292,7 @@ fn du(
|
|||
Err(e) => {
|
||||
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 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 {
|
||||
if this_stat.inode.is_some() {
|
||||
let inode = this_stat.inode.unwrap();
|
||||
if inodes.contains(&inode) {
|
||||
continue;
|
||||
}
|
||||
inodes.insert(inode);
|
||||
}
|
||||
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);
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
2
src/uu/env/Cargo.toml
vendored
2
src/uu/env/Cargo.toml
vendored
|
@ -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" }
|
||||
|
|
24
src/uu/env/src/env.rs
vendored
24
src/uu/env/src/env.rs
vendored
|
@ -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) => {
|
||||
eprintln!("env: error: \"{}\": {}", file, error);
|
||||
return Err(1);
|
||||
}
|
||||
};
|
||||
let conf = conf.map_err(|error| {
|
||||
eprintln!("env: error: \"{}\": {}", file, error);
|
||||
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
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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]));
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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!());
|
||||
}
|
||||
|
|
|
@ -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,22 +460,17 @@ 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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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!())
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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 == "-" {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 data‐base 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_gnu(None)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.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(" ")
|
||||
);
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
for user in users {
|
||||
if let Ok(p) = Passwd::locate(user.as_str()) {
|
||||
println!(
|
||||
"{}",
|
||||
get_groups()
|
||||
.unwrap()
|
||||
"{} : {}",
|
||||
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
|
||||
}
|
||||
Some(user) => {
|
||||
if let Ok(p) = Passwd::locate(user) {
|
||||
println!(
|
||||
"{}",
|
||||
p.belongs_to()
|
||||
.iter()
|
||||
.map(|&g| gid2grp(g).unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
);
|
||||
0
|
||||
} else {
|
||||
crash!(1, "unknown user {}", user);
|
||||
}
|
||||
} else {
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
|
9
src/uu/hashsum/BENCHMARKING.md
Normal file
9
src/uu/hashsum/BENCHMARKING.md
Normal 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"
|
||||
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
} else {
|
||||
(src.char_indices(), false)
|
||||
all_but_last = true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
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),
|
||||
}
|
||||
} 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),
|
||||
}
|
||||
}
|
||||
parse_size(size_string).map(|n| (n, all_but_last))
|
||||
}
|
||||
|
||||
#[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"]));
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
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;
|
||||
}
|
||||
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";
|
||||
}
|
||||
|
||||
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() {
|
||||
None
|
||||
} else {
|
||||
match Passwd::locate(users[0].as_str()) {
|
||||
Ok(p) => Some(p),
|
||||
Err(_) => crash!(1, "No such user/group: {}", users[0]),
|
||||
let delimiter = {
|
||||
if state.zflag {
|
||||
"\0".to_string()
|
||||
} else {
|
||||
" ".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
pline(possible_pw.map(|v| v.uid()));
|
||||
return 0;
|
||||
let line_ending = {
|
||||
if state.zflag {
|
||||
'\0'
|
||||
} else {
|
||||
'\n'
|
||||
}
|
||||
};
|
||||
let mut exit_code = 0;
|
||||
|
||||
if matches.is_present(OPT_HUMAN_READABLE) {
|
||||
pretty(possible_pw);
|
||||
return 0;
|
||||
for i in 0..=users.len() {
|
||||
let possible_pw = if !state.user_specified {
|
||||
None
|
||||
} else {
|
||||
match Passwd::locate(users[i].as_str()) {
|
||||
Ok(p) => Some(p),
|
||||
Err(_) => {
|
||||
show_error!("'{}': no such user", users[i]);
|
||||
exit_code = 1;
|
||||
if i + 1 >= users.len() {
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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 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 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.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 {
|
||||
""
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if default_format {
|
||||
id_print(&state, groups);
|
||||
}
|
||||
print!("{}", line_ending);
|
||||
|
||||
if i + 1 >= users.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if possible_pw.is_some() {
|
||||
id_print(possible_pw, false, false)
|
||||
} else {
|
||||
id_print(possible_pw, true, true)
|
||||
}
|
||||
exit_code
|
||||
}
|
||||
|
||||
0
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) => {
|
||||
show_error!("Invalid mode string: {}", err);
|
||||
return Err(1);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
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);
|
||||
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(())
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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,47 +314,48 @@ 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 {
|
||||
// In that case, we don't want to do link resolution
|
||||
// We need to clean the target
|
||||
if is_symlink(target_dir) {
|
||||
if target_dir.is_file() {
|
||||
if let Err(e) = fs::remove_file(target_dir) {
|
||||
show_error!("Could not update {}: {}", target_dir.display(), e)
|
||||
};
|
||||
}
|
||||
if target_dir.is_dir() {
|
||||
// Not sure why but on Windows, the symlink can be
|
||||
// considered as a dir
|
||||
// See test_ln::test_symlink_no_deref_dir
|
||||
if let Err(e) = fs::remove_dir(target_dir) {
|
||||
show_error!("Could not update {}: {}", target_dir.display(), e)
|
||||
};
|
||||
}
|
||||
}
|
||||
target_dir.to_path_buf()
|
||||
} else {
|
||||
match srcpath.as_os_str().to_str() {
|
||||
Some(name) => {
|
||||
match Path::new(name).file_name() {
|
||||
Some(basename) => target_dir.join(basename),
|
||||
// This can be None only for "." or "..". Trying
|
||||
// to create a link with such name will fail with
|
||||
// EEXIST, which agrees with the behavior of GNU
|
||||
// coreutils.
|
||||
None => target_dir.join(name),
|
||||
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) {
|
||||
if target_dir.is_file() {
|
||||
if let Err(e) = fs::remove_file(target_dir) {
|
||||
show_error!("Could not update {}: {}", target_dir.display(), e)
|
||||
};
|
||||
}
|
||||
if target_dir.is_dir() {
|
||||
// Not sure why but on Windows, the symlink can be
|
||||
// considered as a dir
|
||||
// See test_ln::test_symlink_no_deref_dir
|
||||
if let Err(e) = fs::remove_dir(target_dir) {
|
||||
show_error!("Could not update {}: {}", target_dir.display(), e)
|
||||
};
|
||||
}
|
||||
}
|
||||
None => {
|
||||
show_error!(
|
||||
"cannot stat '{}': No such file or directory",
|
||||
srcpath.display()
|
||||
);
|
||||
all_successful = false;
|
||||
continue;
|
||||
target_dir.to_path_buf()
|
||||
} else {
|
||||
match srcpath.as_os_str().to_str() {
|
||||
Some(name) => {
|
||||
match Path::new(name).file_name() {
|
||||
Some(basename) => target_dir.join(basename),
|
||||
// This can be None only for "." or "..". Trying
|
||||
// to create a link with such name will fail with
|
||||
// EEXIST, which agrees with the behavior of GNU
|
||||
// coreutils.
|
||||
None => target_dir.join(name),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
show_error!(
|
||||
"cannot stat '{}': No such file or directory",
|
||||
srcpath.display()
|
||||
);
|
||||
all_successful = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if let Err(e) = link(srcpath, &targetpath, settings) {
|
||||
show_error!(
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue