1
0
mirror of https://github.com/uutils/coreutils synced 2024-07-08 19:56:10 +00:00

Merge branch 'master' into id_selinux_context

This commit is contained in:
Jan Scheer 2021-07-05 11:51:12 +02:00
commit e53f4db33a
260 changed files with 7579 additions and 4977 deletions

View File

@ -7,6 +7,8 @@ name: CICD
# spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs
# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend runtest tempfile testsuite uutils
# ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45
env:
PROJECT_NAME: coreutils
PROJECT_DESC: "Core universal (cross-platform) utilities"
@ -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,13 +62,13 @@ 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() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
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='' ;
@ -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,13 +106,13 @@ 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() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
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='' ;
@ -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,49 +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" ; }
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
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 libselinux1 libselinux1-dev ;
make build
- name: "`make test`"
shell: bash
run: |
make test
build:
name: Build
@ -235,8 +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-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , 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 }
@ -249,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 ;;
@ -267,7 +279,7 @@ jobs:
shell: bash
run: |
## VARs setup
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
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)
@ -353,7 +365,7 @@ jobs:
- 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'
@ -373,7 +385,7 @@ jobs:
shell: bash
run: |
## Dependent VARs setup
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
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}
@ -390,26 +402,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:
@ -436,7 +448,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)
@ -477,6 +489,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 }}
@ -489,11 +532,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
@ -504,7 +547,7 @@ jobs:
shell: bash
run: |
## VARs setup
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
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
@ -539,7 +582,7 @@ jobs:
shell: bash
run: |
## Dependent VARs setup
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
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;)"
@ -587,7 +630,7 @@ jobs:
id: coverage
shell: bash
run: |
# generate coverage data
## Generate coverage data
COVERAGE_REPORT_DIR="target/debug"
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
# GRCOV_IGNORE_OPTION='--ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?)

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

@ -0,0 +1,135 @@
name: FixPR
# Trigger automated fixes for PRs being merged (with associated commits)
# ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45
env:
BRANCH_TARGET: master
on:
# * only trigger on pull request closed to specific branches
# ref: https://github.community/t/trigger-workflow-only-on-pull-request-merge/17359/9
pull_request:
branches:
- master # == env.BRANCH_TARGET ## unfortunately, env context variables are only available in jobs/steps (see <https://github.community/t/how-to-use-env-context/16975/2>)
types: [ closed ]
jobs:
code_deps:
# Refresh dependencies (ie, 'Cargo.lock') and show updated dependency tree
if: github.event.pull_request.merged == true ## only for PR merges
name: Update/dependencies
runs-on: ${{ matrix.job.os }}
strategy:
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v2
- name: Initialize job variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# surface MSRV from CICD workflow
RUST_MIN_SRV=$(grep -P "^\s+RUST_MIN_SRV:" .github/workflows/CICD.yml | grep -Po "(?<=\x22)\d+[.]\d+(?:[.]\d+)?(?=\x22)" )
outputs RUST_MIN_SRV
- name: Install `rust` toolchain (v${{ steps.vars.outputs.RUST_MIN_SRV }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ steps.vars.outputs.RUST_MIN_SRV }}
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: Install `cargo-tree` # for dependency information
uses: actions-rs/install@v0.1
with:
crate: cargo-tree
version: latest
use-tool-cache: true
env:
RUSTUP_TOOLCHAIN: stable
- name: Ensure updated 'Cargo.lock'
shell: bash
run: |
# Ensure updated 'Cargo.lock'
# * 'Cargo.lock' is required to be in a format that `cargo` of MinSRV can interpret (eg, v1-format for MinSRV < v1.38)
cargo fetch --locked --quiet || cargo +${{ steps.vars.outputs.RUST_MIN_SRV }} update
- name: Info
shell: bash
run: |
# Info
## environment
echo "## environment"
echo "CI='${CI}'"
## tooling info display
echo "## tooling"
which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
rustup -V 2>/dev/null
rustup show active-toolchain
cargo -V
rustc -V
cargo-tree tree -V
## dependencies
echo "## dependency list"
cargo fetch --locked --quiet
## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors
RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
uses: EndBug/add-and-commit@v7
with:
branch: ${{ env.BRANCH_TARGET }}
default_author: github_actions
message: "maint ~ refresh 'Cargo.lock'"
add: Cargo.lock
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
code_format:
# Recheck/refresh code formatting
if: github.event.pull_request.merged == true ## only for PR merges
name: Update/format
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v2
- name: Initialize job variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
outputs CARGO_FEATURES_OPTION
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt
- name: "`cargo fmt`"
shell: bash
run: |
cargo fmt
- name: "`cargo fmt` tests"
shell: bash
run: |
# `cargo fmt` of tests
find tests -name "*.rs" -print0 | xargs -0 cargo fmt --
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
uses: EndBug/add-and-commit@v7
with:
branch: ${{ env.BRANCH_TARGET }}
default_author: github_actions
message: "maint ~ rustfmt (`cargo fmt`)"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,4 +1,6 @@
name: GNU
name: GnuTests
# spell-checker:ignore (names) gnulib ; (utils) autopoint gperf pyinotify texinfo ; (vars) XPASS
on: [push, pull_request]
@ -7,7 +9,6 @@ jobs:
name: Run GNU tests
runs-on: ubuntu-latest
steps:
# Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code uutil
uses: actions/checkout@v2
with:
@ -18,7 +19,7 @@ jobs:
repository: 'coreutils/coreutils'
path: 'gnu'
ref: v8.32
- name: Checkout GNU corelib
- name: Checkout GNU coreutils library (gnulib)
uses: actions/checkout@v2
with:
repository: 'coreutils/gnulib'
@ -32,16 +33,18 @@ 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: |
@ -53,9 +56,10 @@ jobs:
bash uutils/util/run-gnu-test.sh tests/id/setgid.sh
bash uutils/util/run-gnu-test.sh tests/id/zero.sh
bash uutils/util/run-gnu-test.sh tests/id/gnu-zero-uids.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
@ -65,7 +69,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" \
@ -79,12 +89,10 @@ jobs:
else
echo "::error ::Failed to get summary of test results"
fi
- uses: actions/upload-artifact@v2
with:
name: test-report
path: gnu/tests/**/*.log
- uses: actions/upload-artifact@v2
with:
name: gnu-result

View File

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

View File

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

View File

@ -58,6 +58,10 @@ Haitao Li
Inokentiy Babushkin
Inokentiy
Babushkin
Jan Scheer * jhscheer
Jan
Scheer
jhscheer
Jeremiah Peschka
Jeremiah
Peschka

View File

@ -48,17 +48,19 @@ xattr
# * rust/rustc
RUSTDOCFLAGS
RUSTFLAGS
clippy
rustc
rustfmt
rustup
#
bitor # BitOr trait function
bitxor # BitXor trait function
clippy
concat
fract
powi
println
repr
rfind
rustc
rustfmt
struct
structs
substr

25
Cargo.lock generated
View File

@ -220,6 +220,7 @@ dependencies = [
"atty",
"bitflags",
"strsim",
"term_size",
"textwrap",
"unicode-width",
"vec_map",
@ -261,6 +262,7 @@ version = "0.0.6"
dependencies = [
"atty",
"chrono",
"clap",
"conv",
"filetime",
"glob 0.3.0",
@ -1566,21 +1568,6 @@ dependencies = [
"walkdir",
]
[[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"
@ -2107,6 +2094,7 @@ dependencies = [
name = "uu_expr"
version = "0.0.6"
dependencies = [
"clap",
"libc",
"num-bigint",
"num-traits",
@ -2134,6 +2122,7 @@ dependencies = [
name = "uu_false"
version = "0.0.6"
dependencies = [
"clap",
"uucore",
"uucore_procs",
]
@ -2199,6 +2188,7 @@ dependencies = [
name = "uu_hostid"
version = "0.0.6"
dependencies = [
"clap",
"libc",
"uucore",
"uucore_procs",
@ -2476,6 +2466,7 @@ name = "uu_pr"
version = "0.0.6"
dependencies = [
"chrono",
"clap",
"getopts",
"itertools 0.10.0",
"quick-error 2.0.1",
@ -2498,6 +2489,7 @@ dependencies = [
name = "uu_printf"
version = "0.0.6"
dependencies = [
"clap",
"itertools 0.8.2",
"uucore",
"uucore_procs",
@ -2631,7 +2623,6 @@ dependencies = [
"ouroboros",
"rand 0.7.3",
"rayon",
"semver",
"tempfile",
"unicode-width",
"uucore",
@ -2734,6 +2725,7 @@ dependencies = [
name = "uu_test"
version = "0.0.6"
dependencies = [
"clap",
"libc",
"redox_syscall 0.1.57",
"uucore",
@ -2777,6 +2769,7 @@ dependencies = [
name = "uu_true"
version = "0.0.6"
dependencies = [
"clap",
"uucore",
"uucore_procs",
]

View File

@ -63,6 +63,7 @@ feat_common_core = [
"more",
"mv",
"nl",
"numfmt",
"od",
"paste",
"pr",
@ -160,7 +161,6 @@ feat_require_unix = [
"mkfifo",
"mknod",
"nice",
"numfmt",
"nohup",
"pathchk",
"stat",
@ -225,6 +225,7 @@ test = [ "uu_test" ]
[workspace]
[dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
lazy_static = { version="1.3" }
textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review
uucore = { version=">=0.0.8", package="uucore", path="src/uucore" }

View File

@ -314,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)
@ -321,6 +326,9 @@ ifeq (${MULTICALL}, y)
endif
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz)
rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS))
rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(PROG_PREFIX),$(PROGS))
rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(PROG_PREFIX),$(PROGS))
rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(PROG_PREFIX),$(addsuffix .fish,$(PROGS)))
rm -f $(addprefix $(INSTALLDIR_MAN)/$(PROG_PREFIX),$(addsuffix .1.gz,$(PROGS)))
.PHONY: all build build-coreutils build-pkgs build-docs test distclean clean busytest install uninstall

View File

@ -134,6 +134,9 @@ $ cargo install --path .
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
This does not install files necessary for shell completion. For shell completion to work,
use `GNU Make` or see `Manually install shell completions`.
### GNU Make
To install all available utilities:
@ -179,6 +182,10 @@ Set install parent directory (default value is /usr/local):
$ make PREFIX=/my/path install
```
Installing with `make` installs shell completions for all installed utilities
for `bash`, `fish` and `zsh`. Completions for `elvish` and `powershell` can also
be generated; See `Manually install shell completions`.
### NixOS
The [standard package set](https://nixos.org/nixpkgs/manual/) of [NixOS](https://nixos.org/)
@ -188,6 +195,23 @@ provides this package out of the box since 18.03:
$ nix-env -iA nixos.uutils-coreutils
```
### Manually install shell completions
The `coreutils` binary can generate completions for the `bash`, `elvish`, `fish`, `powershell`
and `zsh` shells. It prints the result to stdout.
The syntax is:
```bash
cargo run completion <utility> <shell>
```
So, to install completions for `ls` on `bash` to `/usr/local/share/bash-completion/completions/ls`,
run:
```bash
cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
```
## Un-installation Instructions
Un-installation differs depending on how you have installed uutils. If you used

View File

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

View File

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

View File

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

View File

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

View File

@ -6,24 +6,32 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
#[macro_use]
extern crate uucore;
use platform_info::*;
use clap::{crate_version, App};
use uucore::error::{FromIo, UResult};
static ABOUT: &str = "Display machine architecture";
static SUMMARY: &str = "Determine architecture name for current machine.";
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uu_app().get_matches_from(args);
let uts = PlatformInfo::new().map_err_context(|| "cannot get system name".to_string())?;
println!("{}", uts.machine().trim());
Ok(())
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.after_help(SUMMARY)
.get_matches_from(args);
let uts = return_if_err!(1, PlatformInfo::new());
println!("{}", uts.machine().trim());
0
}

View File

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

View File

@ -10,6 +10,7 @@ extern crate uucore;
use std::io::{stdin, Read};
use clap::App;
use uucore::encoding::Format;
pub mod base_common;
@ -56,3 +57,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0
}
pub fn uu_app() -> App<'static, 'static> {
base_common::base_app(executable!(), VERSION, ABOUT)
}

View File

@ -39,7 +39,7 @@ impl Config {
Some(mut values) => {
let name = values.next().unwrap();
if values.len() != 0 {
return Err(format!("extra operand {}", name));
return Err(format!("extra operand '{}'", name));
}
if name == "-" {
@ -58,7 +58,7 @@ impl Config {
.value_of(options::WRAP)
.map(|num| {
num.parse::<usize>()
.map_err(|e| format!("Invalid wrap size: {}: {}", num, e))
.map_err(|e| format!("Invalid wrap size: '{}': {}", num, e))
})
.transpose()?;
@ -78,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)
@ -106,11 +113,7 @@ pub fn parse_base_cmd_args(
)
// "multiple" arguments are used to check whether there is more than one
// file passed in.
.arg(Arg::with_name(options::FILE).index(1).multiple(true));
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
Config::from(app.get_matches_from(arg_list))
.arg(Arg::with_name(options::FILE).index(1).multiple(true))
}
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> {

View File

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

View File

@ -10,6 +10,7 @@
extern crate uucore;
use uu_base32::base_common;
pub use uu_base32::uu_app;
use uucore::encoding::Format;

View File

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

View File

@ -40,31 +40,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
//
// Argument parsing
//
let matches = App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
.usage(&usage[..])
.arg(
Arg::with_name(options::MULTIPLE)
.short("a")
.long(options::MULTIPLE)
.help("support multiple arguments and treat each as a NAME"),
)
.arg(Arg::with_name(options::NAME).multiple(true).hidden(true))
.arg(
Arg::with_name(options::SUFFIX)
.short("s")
.long(options::SUFFIX)
.value_name("SUFFIX")
.help("remove a trailing SUFFIX; implies -a"),
)
.arg(
Arg::with_name(options::ZERO)
.short("z")
.long(options::ZERO)
.help("end each output line with NUL, not newline"),
)
.get_matches_from(args);
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
// too few arguments
if !matches.is_present(options::NAME) {
@ -116,6 +92,32 @@ 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::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 path = fullname.trim_end_matches(is_separator);

View File

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

View File

@ -10,6 +10,9 @@
// spell-checker:ignore (ToDO) nonprint nonblank nonprinting
// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
#[cfg(unix)]
extern crate unix_socket;
#[macro_use]
@ -169,7 +172,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)
@ -229,61 +290,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>(

View File

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

View File

@ -73,84 +73,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let mut app = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.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"),
);
let mut app = uu_app().usage(&usage[..]);
// we change the positional args based on whether
// --reference was used.
@ -274,6 +197,86 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
executor.exec()
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(VERSION)
.about(ABOUT)
.arg(
Arg::with_name(options::verbosity::CHANGES)
.short("c")
.long(options::verbosity::CHANGES)
.help("like verbose but report only when a change is made"),
)
.arg(
Arg::with_name(options::verbosity::SILENT)
.short("f")
.long(options::verbosity::SILENT),
)
.arg(
Arg::with_name(options::verbosity::QUIET)
.long(options::verbosity::QUIET)
.help("suppress most error messages"),
)
.arg(
Arg::with_name(options::verbosity::VERBOSE)
.short("v")
.long(options::verbosity::VERBOSE)
.help("output a diagnostic for every file processed"),
)
.arg(
Arg::with_name(options::dereference::DEREFERENCE)
.long(options::dereference::DEREFERENCE),
)
.arg(
Arg::with_name(options::dereference::NO_DEREFERENCE)
.short("h")
.long(options::dereference::NO_DEREFERENCE)
.help(
"affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)",
),
)
.arg(
Arg::with_name(options::preserve_root::PRESERVE)
.long(options::preserve_root::PRESERVE)
.help("fail to operate recursively on '/'"),
)
.arg(
Arg::with_name(options::preserve_root::NO_PRESERVE)
.long(options::preserve_root::NO_PRESERVE)
.help("do not treat '/' specially (the default)"),
)
.arg(
Arg::with_name(options::REFERENCE)
.long(options::REFERENCE)
.value_name("RFILE")
.help("use RFILE's group rather than specifying GROUP values")
.takes_value(true)
.multiple(false),
)
.arg(
Arg::with_name(options::RECURSIVE)
.short("R")
.long(options::RECURSIVE)
.help("operate on files and directories recursively"),
)
.arg(
Arg::with_name(options::traverse::TRAVERSE)
.short(options::traverse::TRAVERSE)
.help("if a command line argument is a symbolic link to a directory, traverse it"),
)
.arg(
Arg::with_name(options::traverse::NO_TRAVERSE)
.short(options::traverse::NO_TRAVERSE)
.help("do not traverse any symbolic links (default)")
.overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]),
)
.arg(
Arg::with_name(options::traverse::EVERY)
.short(options::traverse::EVERY)
.help("traverse every symbolic link to a directory encountered"),
)
}
struct Chgrper {
dest_gid: gid_t,
bit_flag: u8,

View File

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

View File

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

View File

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

View File

@ -73,101 +73,7 @@ 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::verbosity::CHANGES)
.short("c")
.long(options::verbosity::CHANGES)
.help("like verbose but report only when a change is made"),
)
.arg(Arg::with_name(options::dereference::DEREFERENCE).long(options::dereference::DEREFERENCE).help(
"affect the referent of each symbolic link (this is the default), rather than the symbolic link itself",
))
.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::FROM)
.long(options::FROM)
.help(
"change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute",
)
.value_name("CURRENT_OWNER:CURRENT_GROUP"),
)
.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::verbosity::QUIET)
.long(options::verbosity::QUIET)
.help("suppress most error messages"),
)
.arg(
Arg::with_name(options::RECURSIVE)
.short("R")
.long(options::RECURSIVE)
.help("operate on files and directories recursively"),
)
.arg(
Arg::with_name(options::REFERENCE)
.long(options::REFERENCE)
.help("use RFILE's owner and group rather than specifying OWNER:GROUP values")
.value_name("RFILE")
.min_values(1),
)
.arg(Arg::with_name(options::verbosity::SILENT).short("f").long(options::verbosity::SILENT))
.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")
.overrides_with_all(&[options::traverse::EVERY, options::traverse::NO_TRAVERSE]),
)
.arg(
Arg::with_name(options::traverse::EVERY)
.short(options::traverse::EVERY)
.help("traverse every symbolic link to a directory encountered")
.overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::NO_TRAVERSE]),
)
.arg(
Arg::with_name(options::traverse::NO_TRAVERSE)
.short(options::traverse::NO_TRAVERSE)
.help("do not traverse any symbolic links (default)")
.overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]),
)
.arg(
Arg::with_name(options::verbosity::VERBOSE)
.long(options::verbosity::VERBOSE)
.help("output a diagnostic for every file processed"),
)
.arg(
Arg::with_name(ARG_OWNER)
.multiple(false)
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name(ARG_FILES)
.multiple(true)
.takes_value(true)
.required(true)
.min_values(1),
)
.get_matches_from(args);
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
/* First arg is the owner/group */
let owner = matches.value_of(ARG_OWNER).unwrap();
@ -273,6 +179,102 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
executor.exec()
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_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::dereference::DEREFERENCE).long(options::dereference::DEREFERENCE).help(
"affect the referent of each symbolic link (this is the default), rather than the symbolic link itself",
))
.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::FROM)
.long(options::FROM)
.help(
"change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute",
)
.value_name("CURRENT_OWNER:CURRENT_GROUP"),
)
.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::verbosity::QUIET)
.long(options::verbosity::QUIET)
.help("suppress most error messages"),
)
.arg(
Arg::with_name(options::RECURSIVE)
.short("R")
.long(options::RECURSIVE)
.help("operate on files and directories recursively"),
)
.arg(
Arg::with_name(options::REFERENCE)
.long(options::REFERENCE)
.help("use RFILE's owner and group rather than specifying OWNER:GROUP values")
.value_name("RFILE")
.min_values(1),
)
.arg(Arg::with_name(options::verbosity::SILENT).short("f").long(options::verbosity::SILENT))
.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")
.overrides_with_all(&[options::traverse::EVERY, options::traverse::NO_TRAVERSE]),
)
.arg(
Arg::with_name(options::traverse::EVERY)
.short(options::traverse::EVERY)
.help("traverse every symbolic link to a directory encountered")
.overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::NO_TRAVERSE]),
)
.arg(
Arg::with_name(options::traverse::NO_TRAVERSE)
.short(options::traverse::NO_TRAVERSE)
.help("do not traverse any symbolic links (default)")
.overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]),
)
.arg(
Arg::with_name(options::verbosity::VERBOSE)
.long(options::verbosity::VERBOSE)
.help("output a diagnostic for every file processed"),
)
.arg(
Arg::with_name(ARG_OWNER)
.multiple(false)
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name(ARG_FILES)
.multiple(true)
.takes_value(true)
.required(true)
.min_values(1),
)
}
fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
let args = spec.split_terminator(':').collect::<Vec<_>>();
let usr_only = args.len() == 1 && !args[0].is_empty();
@ -281,7 +283,7 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
let uid = if usr_only || usr_grp {
Some(
Passwd::locate(args[0])
.map_err(|_| format!("invalid user: {}", spec))?
.map_err(|_| format!("invalid user: '{}'", spec))?
.uid(),
)
} else {
@ -290,7 +292,7 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
let gid = if grp_only || usr_grp {
Some(
Group::locate(args[1])
.map_err(|_| format!("invalid group: {}", spec))?
.map_err(|_| format!("invalid group: '{}'", spec))?
.gid(),
)
} else {

View File

@ -36,54 +36,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(SYNTAX)
.arg(
Arg::with_name(options::NEWROOT)
.hidden(true)
.required(true)
.index(1),
)
.arg(
Arg::with_name(options::USER)
.short("u")
.long(options::USER)
.help("User (ID or name) to switch before running the program")
.value_name("USER"),
)
.arg(
Arg::with_name(options::GROUP)
.short("g")
.long(options::GROUP)
.help("Group (ID or name) to switch to")
.value_name("GROUP"),
)
.arg(
Arg::with_name(options::GROUPS)
.short("G")
.long(options::GROUPS)
.help("Comma-separated list of groups to switch to")
.value_name("GROUP1,GROUP2..."),
)
.arg(
Arg::with_name(options::USERSPEC)
.long(options::USERSPEC)
.help(
"Colon-separated user and group to switch to. \
Same as -u USER -g GROUP. \
Userspec has higher preference than -u and/or -g",
)
.value_name("USER:GROUP"),
)
.arg(
Arg::with_name(options::COMMAND)
.hidden(true)
.multiple(true)
.index(2),
)
.get_matches_from(args);
let matches = uu_app().get_matches_from(args);
let default_shell: &'static str = "/bin/sh";
let default_option: &'static str = "-i";
@ -138,6 +91,56 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
}
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)
.index(1),
)
.arg(
Arg::with_name(options::USER)
.short("u")
.long(options::USER)
.help("User (ID or name) to switch before running the program")
.value_name("USER"),
)
.arg(
Arg::with_name(options::GROUP)
.short("g")
.long(options::GROUP)
.help("Group (ID or name) to switch to")
.value_name("GROUP"),
)
.arg(
Arg::with_name(options::GROUPS)
.short("G")
.long(options::GROUPS)
.help("Comma-separated list of groups to switch to")
.value_name("GROUP1,GROUP2..."),
)
.arg(
Arg::with_name(options::USERSPEC)
.long(options::USERSPEC)
.help(
"Colon-separated user and group to switch to. \
Same as -u USER -g GROUP. \
Userspec has higher preference than -u and/or -g",
)
.value_name("USER:GROUP"),
)
.arg(
Arg::with_name(options::COMMAND)
.hidden(true)
.multiple(true)
.index(2),
)
}
fn set_context(root: &Path, options: &clap::ArgMatches) {
let userspec_str = options.value_of(options::USERSPEC);
let user_str = options.value_of(options::USER).unwrap_or_default();

View File

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

View File

@ -180,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(),
@ -217,3 +211,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
exit_code
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME)
.version(crate_version!())
.about(SUMMARY)
.usage(SYNTAX)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
}

View File

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

View File

@ -137,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)
@ -167,12 +177,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
)
.arg(Arg::with_name(options::FILE_1).required(true))
.arg(Arg::with_name(options::FILE_2).required(true))
.get_matches_from(args);
let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap();
let mut f2 = open_file(matches.value_of(options::FILE_2).unwrap()).unwrap();
comm(&mut f1, &mut f2, &matches);
0
}

View File

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

View File

@ -290,13 +290,10 @@ 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(options::TARGET_DIRECTORY)
.short("t")
.conflicts_with(options::NO_TARGET_DIRECTORY)
@ -378,7 +375,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg(Arg::with_name(options::UPDATE)
.short("u")
.long(options::UPDATE)
.help("copy only when the SOURCE file is newer than the destination file\
.help("copy only when the SOURCE file is newer than the destination file \
or when the destination file is missing"))
.arg(Arg::with_name(options::REFLINK)
.long(options::REFLINK)
@ -401,7 +398,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.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(options::PRESERVE_DEFAULT_ATTRIBUTES)
.short("-p")
@ -464,6 +461,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.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));
@ -667,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,
@ -1218,28 +1233,39 @@ 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.dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
copy_link(source, 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))?;
}
@ -1254,11 +1280,16 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> {
Some(name) => dest.join(name).into(),
None => crash!(
EXIT_ERR,
"cannot stat {}: No such file or directory",
"cannot stat '{}': No such file or directory",
source.display()
),
}
} else {
// we always need to remove the file to be able to create a symlink,
// even if it is writeable.
if dest.exists() {
fs::remove_file(dest)?;
}
dest.into()
};
symlink_file(&link, &dest, &*context_for(&link, &dest))

View File

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

View File

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

View File

@ -1,3 +1,6 @@
// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
use std::io;
use thiserror::Error;

View File

@ -15,7 +15,7 @@ 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"

View File

@ -396,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);
@ -627,3 +546,87 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
}
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME)
.version(crate_version!())
.usage(SYNTAX)
.about(SUMMARY)
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::BYTES)
.short("b")
.long(options::BYTES)
.takes_value(true)
.help("filter byte columns from the input source")
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(1),
)
.arg(
Arg::with_name(options::CHARACTERS)
.short("c")
.long(options::CHARACTERS)
.help("alias for character mode")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(2),
)
.arg(
Arg::with_name(options::DELIMITER)
.short("d")
.long(options::DELIMITER)
.help("specify the delimiter character that separates fields in the input source. Defaults to Tab.")
.takes_value(true)
.value_name("DELIM")
.display_order(3),
)
.arg(
Arg::with_name(options::FIELDS)
.short("f")
.long(options::FIELDS)
.help("filter field columns from the input source")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(4),
)
.arg(
Arg::with_name(options::COMPLEMENT)
.long(options::COMPLEMENT)
.help("invert the filter - instead of displaying only the filtered columns, display all but those columns")
.takes_value(false)
.display_order(5),
)
.arg(
Arg::with_name(options::ONLY_DELIMITED)
.short("s")
.long(options::ONLY_DELIMITED)
.help("in field mode, only print lines which contain the delimiter")
.takes_value(false)
.display_order(6),
)
.arg(
Arg::with_name(options::ZERO_TERMINATED)
.short("z")
.long(options::ZERO_TERMINATED)
.help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)")
.takes_value(false)
.display_order(8),
)
.arg(
Arg::with_name(options::OUTPUT_DELIMITER)
.long(options::OUTPUT_DELIMITER)
.help("in field mode, replace the delimiter in output lines with this option's argument")
.takes_value(true)
.value_name("NEW_DELIM")
.display_order(7),
)
.arg(
Arg::with_name(options::FILE)
.hidden(true)
.multiple(true)
)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ edition = "2018"
path = "src/dircolors.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" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View File

@ -73,36 +73,7 @@ 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::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))
.get_matches_from(&args);
let matches = uu_app().usage(&usage[..]).get_matches_from(&args);
let files = matches
.values_of(options::FILE)
@ -123,7 +94,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
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)",
files[0]
);
@ -155,7 +126,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
result = parse(INTERNAL_DB.lines(), out_format, "")
} else {
if files.len() > 1 {
show_usage_error!("extra operand {}", files[1]);
show_usage_error!("extra operand '{}'", files[1]);
return 1;
}
match File::open(files[0]) {
@ -181,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;
@ -192,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()

View File

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

View File

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

View File

@ -15,7 +15,7 @@ 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" }

View File

@ -10,6 +10,7 @@ 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;
@ -62,6 +63,8 @@ mod options {
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";
}
@ -87,6 +90,8 @@ struct Options {
total: bool,
separate_dirs: bool,
one_file_system: bool,
dereference: bool,
inodes: bool,
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
@ -100,6 +105,7 @@ struct Stat {
is_dir: bool,
size: u64,
blocks: u64,
inodes: u64,
inode: Option<FileInfo>,
created: Option<u64>,
accessed: u64,
@ -107,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 {
@ -121,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,
@ -138,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()),
@ -251,6 +263,18 @@ fn read_block_size(s: Option<&str>) -> usize {
}
}
fn choose_size(matches: &ArgMatches, stat: &Stat) -> u64 {
if matches.is_present(options::INODES) {
stat.inodes
} else if matches.is_present(options::APPARENT_SIZE) || matches.is_present(options::BYTES) {
stat.size
} else {
// The st_blocks field indicates the number of blocks allocated to the file, 512-byte units.
// See: http://linux.die.net/man/2/stat
stat.blocks * 512
}
}
// this takes `my_stat` to avoid having to stat files multiple times.
// XXX: this should use the impl Trait return type when it is stabilized
fn du(
@ -268,7 +292,7 @@ fn du(
Err(e) => {
safe_writeln!(
stderr(),
"{}: cannot read directory {}: {}",
"{}: cannot read directory '{}': {}",
options.program_name,
my_stat.path.display(),
e
@ -279,8 +303,14 @@ 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)) =
@ -293,14 +323,9 @@ fn du(
}
futures.push(du(this_stat, options, depth + 1, inodes));
} else {
if let Some(inode) = this_stat.inode {
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);
}
@ -308,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),
@ -327,10 +345,11 @@ 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
@ -388,10 +407,196 @@ 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 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(s), _) if summarize => {
show_error!("summarizing conflicts with --max-depth={}", s);
return 1;
}
(Some(s), None) => {
show_error!("invalid maximum depth '{}'", s);
return 1;
}
(Some(_), Some(_)) | (None, _) => { /* valid */ }
}
let options = Options {
all: matches.is_present(options::ALL),
program_name: NAME.to_owned(),
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!["."],
};
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
} else {
1024
};
let convert_size_fn = {
if matches.is_present(options::HUMAN_READABLE) || matches.is_present(options::SI) {
convert_size_human
} else if matches.is_present(options::BYTES) {
convert_size_b
} else if matches.is_present(options::BLOCK_SIZE_1K) {
convert_size_k
} else if matches.is_present(options::BLOCK_SIZE_1M) {
convert_size_m
} else {
convert_size_other
}
};
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 {
"full-iso" => "%Y-%m-%d %H:%M:%S.%f %z",
"long-iso" => "%Y-%m-%d %H:%M",
"iso" => "%Y-%m-%d",
_ => {
show_error!(
"invalid argument '{}' for 'time style'
Valid arguments are:
- 'full-iso'
- 'long-iso'
- 'iso'
Try '{} --help' for more information.",
s,
NAME
);
return 1;
}
},
None => "%Y-%m-%d %H:%M",
};
let line_separator = if matches.is_present(options::NULL) {
"\0"
} else {
"\n"
};
let mut grand_total = 0;
for path_string in files {
let path = PathBuf::from(&path_string);
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 = 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 = {
match matches.value_of(options::TIME) {
Some(s) => match s {
"ctime" | "status" => stat.modified,
"access" | "atime" | "use" => stat.accessed,
"birth" | "creation" => {
if let Some(time) = stat.created {
time
} else {
show_error!(
"Invalid argument '{}' for --time.
'birth' and 'creation' arguments are not supported on this platform.",
s
);
return 1;
}
}
// below should never happen as clap already restricts the values.
_ => unreachable!("Invalid field for --time"),
},
None => stat.modified,
}
};
DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs))
};
if !summarize || index == len - 1 {
let time_str = tm.format(time_format_str).to_string();
print!(
"{}\t{}\t{}{}",
convert_size(size),
time_str,
stat.path.display(),
line_separator
);
}
} else if !summarize || index == len - 1 {
print!(
"{}\t{}{}",
convert_size(size),
stat.path.display(),
line_separator
);
}
if options.total && index == (len - 1) {
// The last element will be the total size of the the path under
// path_string. We add it to the grand total.
grand_total += size;
}
}
}
Err(_) => {
show_error!("{}: {}", path_string, "No such file or directory");
}
}
}
if options.total {
print!("{}\ttotal", convert_size(grand_total));
print!("{}", line_separator);
}
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
.usage(&usage[..])
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::ALL)
@ -449,8 +654,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("print sizes in human readable format (e.g., 1K 234M 2G)")
)
.arg(
Arg::with_name("inodes")
.long("inodes")
Arg::with_name(options::INODES)
.long(options::INODES)
.help(
"list inode usage information instead of block usage like --block-size=1K"
)
@ -466,12 +671,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.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(options::DEREFERENCE)
.short("L")
.long(options::DEREFERENCE)
.help("dereference all symbolic links")
)
// .arg(
// Arg::with_name("no-dereference")
// .short("P")
@ -563,184 +768,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.hidden(true)
.multiple(true)
)
.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(s), _) if summarize => {
show_error!("summarizing conflicts with --max-depth={}", s);
return 1;
}
(Some(s), None) => {
show_error!("invalid maximum depth '{}'", s);
return 1;
}
(Some(_), Some(_)) | (None, _) => { /* valid */ }
}
let options = Options {
all: matches.is_present(options::ALL),
program_name: NAME.to_owned(),
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),
};
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
}
};
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
} else {
1024
};
let convert_size_fn = {
if matches.is_present(options::HUMAN_READABLE) || matches.is_present(options::SI) {
convert_size_human
} else if matches.is_present(options::BYTES) {
convert_size_b
} else if matches.is_present(options::BLOCK_SIZE_1K) {
convert_size_k
} else if matches.is_present(options::BLOCK_SIZE_1M) {
convert_size_m
} else {
convert_size_other
}
};
let convert_size = |size| convert_size_fn(size, multiplier, block_size);
let time_format_str = match matches.value_of("time-style") {
Some(s) => match s {
"full-iso" => "%Y-%m-%d %H:%M:%S.%f %z",
"long-iso" => "%Y-%m-%d %H:%M",
"iso" => "%Y-%m-%d",
_ => {
show_error!(
"invalid argument '{}' for 'time style'
Valid arguments are:
- 'full-iso'
- 'long-iso'
- 'iso'
Try '{} --help' for more information.",
s,
NAME
);
return 1;
}
},
None => "%Y-%m-%d %H:%M",
};
let line_separator = if matches.is_present(options::NULL) {
"\0"
} else {
"\n"
};
let mut grand_total = 0;
for path_string in files {
let path = PathBuf::from(&path_string);
match Stat::new(path) {
Ok(stat) => {
let mut inodes: HashSet<FileInfo> = HashSet::new();
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
};
if threshold.map_or(false, |threshold| threshold.should_exclude(size)) {
continue;
}
if matches.is_present(options::TIME) {
let tm = {
let secs = {
match matches.value_of(options::TIME) {
Some(s) => match s {
"ctime" | "status" => stat.modified,
"access" | "atime" | "use" => stat.accessed,
"birth" | "creation" => {
if let Some(time) = stat.created {
time
} else {
show_error!(
"Invalid argument {} for --time.
birth and creation arguments are not supported on this platform.",
s
);
return 1;
}
}
// below should never happen as clap already restricts the values.
_ => unreachable!("Invalid field for --time"),
},
None => stat.modified,
}
};
DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs))
};
if !summarize || index == len - 1 {
let time_str = tm.format(time_format_str).to_string();
print!(
"{}\t{}\t{}{}",
convert_size(size),
time_str,
stat.path.display(),
line_separator
);
}
} else if !summarize || index == len - 1 {
print!(
"{}\t{}{}",
convert_size(size),
stat.path.display(),
line_separator
);
}
if options.total && index == (len - 1) {
// The last element will be the total size of the the path under
// path_string. We add it to the grand total.
grand_total += size;
}
}
}
Err(_) => {
show_error!("{}: {}", path_string, "No such file or directory");
}
}
}
if options.total {
print!("{}\ttotal", convert_size(grand_total));
print!("{}", line_separator);
}
0
}
#[derive(Clone, Copy)]

View File

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

View File

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

View File

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

View File

@ -114,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!())
@ -158,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");

View File

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

View File

@ -108,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)
@ -138,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>> {

View File

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

View File

@ -8,13 +8,20 @@
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use uucore::InvalidEncodingHandling;
mod syntax_tree;
mod tokens;
static NAME: &str = "expr";
static VERSION: &str = env!("CARGO_PKG_VERSION");
const VERSION: &str = "version";
const HELP: &str = "help";
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.arg(Arg::with_name(VERSION).long(VERSION))
.arg(Arg::with_name(HELP).long(HELP))
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args
@ -133,5 +140,5 @@ Environment variables:
}
fn print_version() {
println!("{} {}", NAME, VERSION);
println!("{} {}", executable!(), crate_version!());
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,7 +36,35 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.accept_any();
let (args, obs_width) = handle_obsolete(&args[..]);
let matches = App::new(executable!())
let matches = uu_app().get_matches_from(args);
let bytes = matches.is_present(options::BYTES);
let spaces = matches.is_present(options::SPACES);
let poss_width = match matches.value_of(options::WIDTH) {
Some(v) => Some(v.to_owned()),
None => obs_width,
};
let width = match poss_width {
Some(inp_width) => match inp_width.parse::<usize>() {
Ok(width) => width,
Err(e) => crash!(1, "illegal width value (\"{}\"): {}", inp_width, e),
},
None => 80,
};
let files = match matches.values_of(options::FILE) {
Some(v) => v.map(|v| v.to_owned()).collect(),
None => vec!["-".to_owned()],
};
fold(files, bytes, spaces, width);
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME)
.version(crate_version!())
.usage(SYNTAX)
@ -68,31 +96,6 @@ 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>) {

View File

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

View File

@ -5,6 +5,13 @@
//
// 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
@ -14,50 +21,77 @@ use uucore::entries::{get_groups_gnu, gid2grp, Locate, Passwd};
use clap::{crate_version, App, Arg};
static ABOUT: &str = "display current group names";
static OPT_USER: &str = "user";
mod options {
pub const USERS: &str = "USERNAME";
}
static ABOUT: &str = "Print group memberships for each USERNAME or, \
if no USERNAME is specified, for\nthe current process \
(which may differ if the groups database has changed).";
fn get_usage() -> String {
format!("{0} [USERNAME]", executable!())
format!("{0} [OPTION]... [USERNAME]...", executable!())
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let matches = App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(Arg::with_name(OPT_USER))
.get_matches_from(args);
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
match matches.value_of(OPT_USER) {
None => {
let users: Vec<String> = matches
.values_of(options::USERS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let mut exit_code = 0;
if users.is_empty() {
println!(
"{}",
get_groups_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_gnu(None)
.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),
)
}

View File

@ -16,7 +16,7 @@ path = "src/hashsum.rs"
[dependencies]
digest = "0.6.2"
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
hex = "0.2.0"
libc = "0.2.42"
md5 = "0.3.5"

View File

@ -285,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
@ -445,6 +333,124 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 {
}
}
pub fn uu_app_common() -> App<'static, 'static> {
#[cfg(windows)]
const BINARY_HELP: &str = "read in binary mode (default)";
#[cfg(not(windows))]
const BINARY_HELP: &str = "read in binary mode";
#[cfg(windows)]
const TEXT_HELP: &str = "read in text mode";
#[cfg(not(windows))]
const TEXT_HELP: &str = "read in text mode (default)";
App::new(executable!())
.version(crate_version!())
.about("Compute and check message digests.")
.arg(
Arg::with_name("binary")
.short("b")
.long("binary")
.help(BINARY_HELP),
)
.arg(
Arg::with_name("check")
.short("c")
.long("check")
.help("read hashsums from the FILEs and check them"),
)
.arg(
Arg::with_name("tag")
.long("tag")
.help("create a BSD-style checksum"),
)
.arg(
Arg::with_name("text")
.short("t")
.long("text")
.help(TEXT_HELP)
.conflicts_with("binary"),
)
.arg(
Arg::with_name("quiet")
.short("q")
.long("quiet")
.help("don't print OK for each successfully verified file"),
)
.arg(
Arg::with_name("status")
.short("s")
.long("status")
.help("don't output anything, status code shows success"),
)
.arg(
Arg::with_name("strict")
.long("strict")
.help("exit non-zero for improperly formatted checksum lines"),
)
.arg(
Arg::with_name("warn")
.short("w")
.long("warn")
.help("warn about improperly formatted checksum lines"),
)
// Needed for variable-length output sums (e.g. SHAKE)
.arg(
Arg::with_name("bits")
.long("bits")
.help("set the size of the output (only for SHAKE)")
.takes_value(true)
.value_name("BITS")
// XXX: should we actually use validators? they're not particularly efficient
.validator(is_valid_bit_num),
)
.arg(
Arg::with_name("FILE")
.index(1)
.multiple(true)
.value_name("FILE"),
)
}
pub fn uu_app_custom() -> App<'static, 'static> {
let mut app = uu_app_common();
let algorithms = &[
("md5", "work with MD5"),
("sha1", "work with SHA1"),
("sha224", "work with SHA224"),
("sha256", "work with SHA256"),
("sha384", "work with SHA384"),
("sha512", "work with SHA512"),
("sha3", "work with SHA3"),
("sha3-224", "work with SHA3-224"),
("sha3-256", "work with SHA3-256"),
("sha3-384", "work with SHA3-384"),
("sha3-512", "work with SHA3-512"),
(
"shake128",
"work with SHAKE128 using BITS for the output size",
),
(
"shake256",
"work with SHAKE256 using BITS for the output size",
),
("b2sum", "work with BLAKE2"),
];
for (name, desc) in algorithms {
app = app.arg(Arg::with_name(name).long(name).help(desc));
}
app
}
// hashsum is handled differently in build.rs, therefore this is not the same
// as in other utilities.
fn uu_app(binary_name: &str) -> App<'static, 'static> {
if !is_custom_binary(binary_name) {
uu_app_custom()
} else {
uu_app_common()
}
}
#[allow(clippy::cognitive_complexity)]
fn hashsum<'a, I>(mut options: Options, files: I) -> Result<(), i32>
where

View File

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

View File

@ -40,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)
@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,11 +13,11 @@
// 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) testsuite for `id`.
// 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 Testsuite (8.32)
// * 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
@ -26,7 +26,7 @@
// * 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 gsflag zflag cflag testsuite
// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag cflag
#![allow(non_camel_case_types)]
#![allow(dead_code)]
@ -119,109 +119,9 @@ 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[..])
.after_help(&after_help[..])
.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)
.conflicts_with(options::OPT_EFFECTIVE_USER)
.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_CONTEXT,
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)
.conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER])
.help("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),
)
.get_matches_from(args);
let users: Vec<String> = matches
@ -255,7 +155,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
crash!(1, "cannot print only names or real IDs in default format");
}
if state.zflag && default_format && !state.cflag {
// NOTE: GNU testsuite "id/zero.sh" needs this stderr output:
// NOTE: GNU test suite "id/zero.sh" needs this stderr output:
crash!(1, "option --zero not permitted in default format");
}
if state.user_specified && state.cflag {
@ -302,7 +202,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
match Passwd::locate(users[i].as_str()) {
Ok(p) => Some(p),
Err(_) => {
show_error!("{}: no such user", users[i]);
show_error!("'{}': no such user", users[i]);
exit_code = 1;
if i + 1 >= users.len() {
break;
@ -418,6 +318,110 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
exit_code
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(options::OPT_AUDIT)
.short("A")
.conflicts_with_all(&[
options::OPT_GROUP,
options::OPT_EFFECTIVE_USER,
options::OPT_HUMAN_READABLE,
options::OPT_PASSWORD,
options::OPT_GROUPS,
options::OPT_ZERO,
])
.help(
"Display the process audit user ID and other process audit properties,\n\
which requires privilege (not available on Linux).",
),
)
.arg(
Arg::with_name(options::OPT_EFFECTIVE_USER)
.short("u")
.long(options::OPT_EFFECTIVE_USER)
.conflicts_with(options::OPT_GROUP)
.help("Display only the effective user ID as a number."),
)
.arg(
Arg::with_name(options::OPT_GROUP)
.short("g")
.long(options::OPT_GROUP)
.conflicts_with(options::OPT_EFFECTIVE_USER)
.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_CONTEXT,
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)
.conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER])
.help("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>) {
if let Some(p) = possible_pw {
print!("uid\t{}\ngroups\t", p.name());

View File

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

View File

@ -15,6 +15,7 @@ extern crate uucore;
use clap::{crate_version, App, Arg, ArgMatches};
use file_diff::diff;
use filetime::{set_file_times, FileTime};
use uucore::backup_control::{self, BackupMode};
use uucore::entries::{grp2gid, usr2uid};
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
@ -33,6 +34,7 @@ const DEFAULT_STRIP_PROGRAM: &str = "strip";
pub struct Behavior {
main_function: MainFunction,
specified_mode: Option<u32>,
backup_mode: BackupMode,
suffix: String,
owner: String,
group: String,
@ -42,6 +44,7 @@ pub struct Behavior {
strip: bool,
strip_program: String,
create_leading: bool,
target_dir: Option<String>,
}
#[derive(Clone, Eq, PartialEq)]
@ -67,7 +70,7 @@ static ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing
static OPT_COMPARE: &str = "compare";
static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_2: &str = "backup2";
static OPT_BACKUP_NO_ARG: &str = "backup2";
static OPT_DIRECTORY: &str = "directory";
static OPT_IGNORED: &str = "ignored";
static OPT_CREATE_LEADING: &str = "create-leading";
@ -97,21 +100,49 @@ fn get_usage() -> String {
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let matches = App::new(executable!())
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
let paths: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
if let Err(s) = check_unimplemented(&matches) {
show_error!("Unimplemented feature: {}", s);
return 2;
}
let behavior = match behavior(&matches) {
Ok(x) => x,
Err(ret) => {
return ret;
}
};
match behavior.main_function {
MainFunction::Directory => directory(paths, behavior),
MainFunction::Standard => standard(paths, behavior),
}
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(OPT_BACKUP)
.long(OPT_BACKUP)
.help("(unimplemented) make a backup of each existing destination file")
.help("make a backup of each existing destination file")
.takes_value(true)
.require_equals(true)
.min_values(0)
.value_name("CONTROL")
)
.arg(
// TODO implement flag
Arg::with_name(OPT_BACKUP_2)
Arg::with_name(OPT_BACKUP_NO_ARG)
.short("b")
.help("(unimplemented) like --backup but does not accept an argument")
.help("like --backup but does not accept an argument")
)
.arg(
Arg::with_name(OPT_IGNORED)
@ -184,7 +215,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(OPT_SUFFIX)
.short("S")
.long(OPT_SUFFIX)
.help("(unimplemented) override the usual backup suffix")
.help("override the usual backup suffix")
.value_name("SUFFIX")
.takes_value(true)
.min_values(1)
@ -194,7 +225,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(OPT_TARGET_DIRECTORY)
.short("t")
.long(OPT_TARGET_DIRECTORY)
.help("(unimplemented) move all SOURCE arguments into DIRECTORY")
.help("move all SOURCE arguments into DIRECTORY")
.value_name("DIRECTORY")
)
.arg(
@ -227,29 +258,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.value_name("CONTEXT")
)
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1))
.get_matches_from(args);
let paths: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
if let Err(s) = check_unimplemented(&matches) {
show_error!("Unimplemented feature: {}", s);
return 2;
}
let behavior = match behavior(&matches) {
Ok(x) => x,
Err(ret) => {
return ret;
}
};
match behavior.main_function {
MainFunction::Directory => directory(paths, behavior),
MainFunction::Standard => standard(paths, behavior),
}
}
/// Check for unimplemented command line arguments.
@ -262,15 +270,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
///
///
fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
if matches.is_present(OPT_BACKUP) {
Err("--backup")
} else if matches.is_present(OPT_BACKUP_2) {
Err("-b")
} else if matches.is_present(OPT_SUFFIX) {
Err("--suffix, -S")
} else if matches.is_present(OPT_TARGET_DIRECTORY) {
Err("--target-directory, -t")
} else if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
Err("--no-target-directory, -T")
} else if matches.is_present(OPT_PRESERVE_CONTEXT) {
Err("--preserve-context, -P")
@ -308,16 +308,16 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
None
};
let backup_suffix = if matches.is_present(OPT_SUFFIX) {
matches.value_of(OPT_SUFFIX).ok_or(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),
@ -330,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,
})
}
@ -392,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 {
@ -417,8 +419,8 @@ fn standard(paths: Vec<String>, b: Behavior) -> i32 {
}
}
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",
@ -512,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
@ -619,7 +643,11 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
}
if b.verbose {
show_error!("'{}' -> '{}'", from.display(), to.display());
print!("'{}' -> '{}'", from.display(), to.display());
match backup_path {
Some(path) => println!(" (backup: '{}')", path.display()),
None => println!(),
}
}
Ok(())

View File

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

View File

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

View File

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

View File

@ -43,38 +43,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let (args, obs_signal) = handle_obsolete(args);
let usage = format!("{} [OPTIONS]... PID...", executable!());
let matches = App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(options::LIST)
.short("l")
.long(options::LIST)
.help("Lists signals")
.conflicts_with(options::TABLE)
.conflicts_with(options::TABLE_OLD),
)
.arg(
Arg::with_name(options::TABLE)
.short("t")
.long(options::TABLE)
.help("Lists table of signals"),
)
.arg(Arg::with_name(options::TABLE_OLD).short("L").hidden(true))
.arg(
Arg::with_name(options::SIGNAL)
.short("s")
.long(options::SIGNAL)
.help("Sends given signal")
.takes_value(true),
)
.arg(
Arg::with_name(options::PIDS_OR_SIGNALS)
.hidden(true)
.multiple(true),
)
.get_matches_from(args);
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) {
Mode::Table
@ -106,6 +75,39 @@ 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() {

View File

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

View File

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

View File

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

View File

@ -97,11 +97,71 @@ 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[..])
.get_matches_from(args);
/* the list of files */
let paths: Vec<PathBuf> = matches
.values_of(ARG_FILES)
.unwrap()
.map(PathBuf::from)
.collect();
let overwrite_mode = if matches.is_present(options::FORCE) {
OverwriteMode::Force
} else if matches.is_present(options::INTERACTIVE) {
OverwriteMode::Interactive
} else {
OverwriteMode::NoClobber
};
let backup_mode = if matches.is_present(options::B) {
BackupMode::ExistingBackup
} else if matches.is_present(options::BACKUP) {
match matches.value_of(options::BACKUP) {
None => BackupMode::ExistingBackup,
Some(mode) => match mode {
"simple" | "never" => BackupMode::SimpleBackup,
"numbered" | "t" => BackupMode::NumberedBackup,
"existing" | "nil" => BackupMode::ExistingBackup,
"none" | "off" => BackupMode::NoBackup,
_ => panic!(), // cannot happen as it is managed by clap
},
}
} else {
BackupMode::NoBackup
};
let backup_suffix = if matches.is_present(options::SUFFIX) {
matches.value_of(options::SUFFIX).unwrap()
} else {
"~"
};
let settings = Settings {
overwrite: overwrite_mode,
backup: backup_mode,
suffix: backup_suffix.to_string(),
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",
@ -198,62 +258,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.required(true)
.min_values(1),
)
.get_matches_from(args);
/* the list of files */
let paths: Vec<PathBuf> = matches
.values_of(ARG_FILES)
.unwrap()
.map(PathBuf::from)
.collect();
let overwrite_mode = if matches.is_present(options::FORCE) {
OverwriteMode::Force
} else if matches.is_present(options::INTERACTIVE) {
OverwriteMode::Interactive
} else {
OverwriteMode::NoClobber
};
let backup_mode = if matches.is_present(options::B) {
BackupMode::ExistingBackup
} else if matches.is_present(options::BACKUP) {
match matches.value_of(options::BACKUP) {
None => BackupMode::ExistingBackup,
Some(mode) => match mode {
"simple" | "never" => BackupMode::SimpleBackup,
"numbered" | "t" => BackupMode::NumberedBackup,
"existing" | "nil" => BackupMode::ExistingBackup,
"none" | "off" => BackupMode::NoBackup,
_ => panic!(), // cannot happen as it is managed by clap
},
}
} else {
BackupMode::NoBackup
};
let backup_suffix = if matches.is_present(options::SUFFIX) {
matches.value_of(options::SUFFIX).unwrap()
} else {
"~"
};
let settings = Settings {
overwrite: overwrite_mode,
backup: backup_mode,
suffix: backup_suffix.to_string(),
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)
}
fn exec(files: &[PathBuf], settings: &Settings) -> i32 {
@ -382,12 +386,15 @@ 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())
}

View File

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

View File

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

View File

@ -17,7 +17,7 @@ path = "src/ls.rs"
[dependencies]
locale = "0.2.2"
chrono = "0.4.19"
clap = "2.33"
clap = { version = "2.33", features = ["wrap_help"] }
unicode-width = "0.1.8"
number_prefix = "0.4"
term_grid = "0.1.5"

View File

@ -7,6 +7,9 @@
// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf
// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
#[macro_use]
extern crate uucore;
#[cfg(unix)]
@ -14,7 +17,6 @@ extern crate uucore;
extern crate lazy_static;
mod quoting_style;
mod version_cmp;
use clap::{crate_version, App, Arg};
use globset::{self, Glob, GlobSet, GlobSetBuilder};
@ -26,10 +28,11 @@ use quoting_style::{escape_name, QuotingStyle};
use std::os::windows::fs::MetadataExt;
use std::{
cmp::Reverse,
error::Error,
fmt::Display,
fs::{self, DirEntry, FileType, Metadata},
io::{stdout, BufWriter, Stdout, Write},
path::{Path, PathBuf},
process::exit,
time::{SystemTime, UNIX_EPOCH},
};
#[cfg(unix)]
@ -38,26 +41,20 @@ use std::{
os::unix::fs::{FileTypeExt, MetadataExt},
time::Duration,
};
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use uucore::error::{set_exit_code, FromIo, UCustomError, UResult};
use unicode_width::UnicodeWidthStr;
#[cfg(unix)]
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
static ABOUT: &str = "
By default, ls will list the files and contents of any directories on
the command line, expect that it will ignore files and directories
whose names start with '.'
";
static AFTER_HELP: &str = "The TIME_STYLE argument can be full-iso, long-iso, iso.
Also the TIME_STYLE environment variable sets the default style to use.";
use uucore::{fs::display_permissions, version_cmp::version_cmp};
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!())
}
pub mod options {
pub mod format {
pub static ONE_LINE: &str = "1";
pub static LONG: &str = "long";
@ -68,10 +65,12 @@ pub mod options {
pub static LONG_NO_GROUP: &str = "o";
pub static LONG_NUMERIC_UID_GID: &str = "numeric-uid-gid";
}
pub mod files {
pub static ALL: &str = "all";
pub static ALMOST_ALL: &str = "almost-all";
}
pub mod sort {
pub static SIZE: &str = "S";
pub static TIME: &str = "t";
@ -79,30 +78,36 @@ pub mod options {
pub static VERSION: &str = "v";
pub static EXTENSION: &str = "X";
}
pub mod time {
pub static ACCESS: &str = "u";
pub static CHANGE: &str = "c";
}
pub mod size {
pub static HUMAN_READABLE: &str = "human-readable";
pub static SI: &str = "si";
}
pub mod quoting {
pub static ESCAPE: &str = "escape";
pub static LITERAL: &str = "literal";
pub static C: &str = "quote-name";
}
pub static QUOTING_STYLE: &str = "quoting-style";
pub mod indicator_style {
pub static SLASH: &str = "p";
pub static FILE_TYPE: &str = "file-type";
pub static CLASSIFY: &str = "classify";
}
pub mod dereference {
pub static ALL: &str = "dereference";
pub static ARGS: &str = "dereference-command-line";
pub static DIR_ARGS: &str = "dereference-command-line-symlink-to-dir";
}
pub static QUOTING_STYLE: &str = "quoting-style";
pub static HIDE_CONTROL_CHARS: &str = "hide-control-chars";
pub static SHOW_CONTROL_CHARS: &str = "show-control-chars";
pub static WIDTH: &str = "width";
@ -125,6 +130,32 @@ pub mod options {
pub static IGNORE: &str = "ignore";
}
#[derive(Debug)]
enum LsError {
InvalidLineWidth(String),
NoMetadata(PathBuf),
}
impl UCustomError for LsError {
fn code(&self) -> i32 {
match self {
LsError::InvalidLineWidth(_) => 2,
LsError::NoMetadata(_) => 1,
}
}
}
impl Error for LsError {}
impl Display for LsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LsError::InvalidLineWidth(s) => write!(f, "invalid line width: '{}'", s),
LsError::NoMetadata(p) => write!(f, "could not open file: '{}'", p.display()),
}
}
}
#[derive(PartialEq, Eq)]
enum Format {
Columns,
@ -218,7 +249,7 @@ struct LongFormat {
impl Config {
#[allow(clippy::cognitive_complexity)]
fn from(options: clap::ArgMatches) -> Config {
fn from(options: clap::ArgMatches) -> UResult<Config> {
let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) {
(
match format_ {
@ -369,15 +400,13 @@ impl Config {
}
};
let width = options
.value_of(options::WIDTH)
.map(|x| {
x.parse::<u16>().unwrap_or_else(|_e| {
show_error!("invalid line width: {}", x);
exit(2);
})
})
.or_else(|| termsize::get().map(|s| s.cols));
let width = match options.value_of(options::WIDTH) {
Some(x) => match x.parse::<u16>() {
Ok(u) => Some(u),
Err(_) => return Err(LsError::InvalidLineWidth(x.into()).into()),
},
None => termsize::get().map(|s| s.cols),
};
#[allow(clippy::needless_bool)]
let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) {
@ -528,7 +557,7 @@ impl Config {
Dereference::DirArgs
};
Config {
Ok(Config {
format,
files,
sort,
@ -547,29 +576,54 @@ impl Config {
quoting_style,
indicator_style,
time_style,
}
})
}
}
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let usage = get_usage();
let app = App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
let app = uu_app().usage(&usage[..]);
let matches = app.get_matches_from(args);
let locs = matches
.values_of(options::PATHS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_else(|| vec![String::from(".")]);
list(locs, Config::from(matches)?)
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(
"By default, ls will list the files and contents of any directories on \
the command line, expect that it will ignore files and directories \
whose names start with '.'.",
)
// Format arguments
.arg(
Arg::with_name(options::FORMAT)
.long(options::FORMAT)
.help("Set the display format.")
.takes_value(true)
.possible_values(&["long", "verbose", "single-column", "columns", "vertical", "across", "horizontal", "commas"])
.possible_values(&[
"long",
"verbose",
"single-column",
"columns",
"vertical",
"across",
"horizontal",
"commas",
])
.hide_possible_values(true)
.require_equals(true)
.overrides_with_all(&[
@ -639,41 +693,51 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(options::format::ONE_LINE)
.short(options::format::ONE_LINE)
.help("List one file per line.")
.multiple(true)
.multiple(true),
)
.arg(
Arg::with_name(options::format::LONG_NO_GROUP)
.short(options::format::LONG_NO_GROUP)
.help("Long format without group information. Identical to --format=long with --no-group.")
.multiple(true)
.help(
"Long format without group information. \
Identical to --format=long with --no-group.",
)
.multiple(true),
)
.arg(
Arg::with_name(options::format::LONG_NO_OWNER)
.short(options::format::LONG_NO_OWNER)
.help("Long format without owner information.")
.multiple(true)
.multiple(true),
)
.arg(
Arg::with_name(options::format::LONG_NUMERIC_UID_GID)
.short("n")
.long(options::format::LONG_NUMERIC_UID_GID)
.help("-l with numeric UIDs and GIDs.")
.multiple(true)
.multiple(true),
)
// Quoting style
.arg(
Arg::with_name(options::QUOTING_STYLE)
.long(options::QUOTING_STYLE)
.takes_value(true)
.help("Set quoting style.")
.possible_values(&["literal", "shell", "shell-always", "shell-escape", "shell-escape-always", "c", "escape"])
.possible_values(&[
"literal",
"shell",
"shell-always",
"shell-escape",
"shell-escape-always",
"c",
"escape",
])
.overrides_with_all(&[
options::QUOTING_STYLE,
options::quoting::LITERAL,
options::quoting::ESCAPE,
options::quoting::C,
])
]),
)
.arg(
Arg::with_name(options::quoting::LITERAL)
@ -685,7 +749,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::quoting::LITERAL,
options::quoting::ESCAPE,
options::quoting::C,
])
]),
)
.arg(
Arg::with_name(options::quoting::ESCAPE)
@ -697,7 +761,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::quoting::LITERAL,
options::quoting::ESCAPE,
options::quoting::C,
])
]),
)
.arg(
Arg::with_name(options::quoting::C)
@ -709,76 +773,63 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::quoting::LITERAL,
options::quoting::ESCAPE,
options::quoting::C,
])
]),
)
// Control characters
.arg(
Arg::with_name(options::HIDE_CONTROL_CHARS)
.short("q")
.long(options::HIDE_CONTROL_CHARS)
.help("Replace control characters with '?' if they are not escaped.")
.overrides_with_all(&[
options::HIDE_CONTROL_CHARS,
options::SHOW_CONTROL_CHARS,
])
.overrides_with_all(&[options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS]),
)
.arg(
Arg::with_name(options::SHOW_CONTROL_CHARS)
.long(options::SHOW_CONTROL_CHARS)
.help("Show control characters 'as is' if they are not escaped.")
.overrides_with_all(&[
options::HIDE_CONTROL_CHARS,
options::SHOW_CONTROL_CHARS,
])
.overrides_with_all(&[options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS]),
)
// Time arguments
.arg(
Arg::with_name(options::TIME)
.long(options::TIME)
.help("Show time in <field>:\n\
.help(
"Show time in <field>:\n\
\taccess time (-u): atime, access, use;\n\
\tchange time (-t): ctime, status.\n\
\tbirth time: birth, creation;")
\tbirth time: birth, creation;",
)
.value_name("field")
.takes_value(true)
.possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"])
.possible_values(&[
"atime", "access", "use", "ctime", "status", "birth", "creation",
])
.hide_possible_values(true)
.require_equals(true)
.overrides_with_all(&[
options::TIME,
options::time::ACCESS,
options::time::CHANGE,
])
.overrides_with_all(&[options::TIME, options::time::ACCESS, options::time::CHANGE]),
)
.arg(
Arg::with_name(options::time::CHANGE)
.short(options::time::CHANGE)
.help("If the long listing format (e.g., -l, -o) is being used, print the status \
change time (the ctime in the inode) instead of the modification time. When \
.help(
"If the long listing format (e.g., -l, -o) is being used, print the status \
change time (the 'ctime' in the inode) instead of the modification time. When \
explicitly sorting by time (--sort=time or -t) or when not using a long listing \
format, sort according to the status change time.")
.overrides_with_all(&[
options::TIME,
options::time::ACCESS,
options::time::CHANGE,
])
format, sort according to the status change time.",
)
.overrides_with_all(&[options::TIME, options::time::ACCESS, options::time::CHANGE]),
)
.arg(
Arg::with_name(options::time::ACCESS)
.short(options::time::ACCESS)
.help("If the long listing format (e.g., -l, -o) is being used, print the status \
.help(
"If the long listing format (e.g., -l, -o) is being used, print the status \
access time instead of the modification time. When explicitly sorting by time \
(--sort=time or -t) or when not using a long listing format, sort according to the \
access time.")
.overrides_with_all(&[
options::TIME,
options::time::ACCESS,
options::time::CHANGE,
])
access time.",
)
.overrides_with_all(&[options::TIME, options::time::ACCESS, options::time::CHANGE]),
)
// Hide and ignore
.arg(
Arg::with_name(options::HIDE)
@ -786,7 +837,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(true)
.multiple(true)
.value_name("PATTERN")
.help("do not list implied entries matching shell PATTERN (overridden by -a or -A)")
.help(
"do not list implied entries matching shell PATTERN (overridden by -a or -A)",
),
)
.arg(
Arg::with_name(options::IGNORE)
@ -795,7 +848,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(true)
.multiple(true)
.value_name("PATTERN")
.help("do not list implied entries matching shell PATTERN")
.help("do not list implied entries matching shell PATTERN"),
)
.arg(
Arg::with_name(options::IGNORE_BACKUPS)
@ -803,7 +856,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(options::IGNORE_BACKUPS)
.help("Ignore entries which end with ~."),
)
// Sort arguments
.arg(
Arg::with_name(options::SORT)
@ -820,7 +872,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::sort::NONE,
options::sort::VERSION,
options::sort::EXTENSION,
])
]),
)
.arg(
Arg::with_name(options::sort::SIZE)
@ -833,7 +885,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::sort::NONE,
options::sort::VERSION,
options::sort::EXTENSION,
])
]),
)
.arg(
Arg::with_name(options::sort::TIME)
@ -846,7 +898,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::sort::NONE,
options::sort::VERSION,
options::sort::EXTENSION,
])
]),
)
.arg(
Arg::with_name(options::sort::VERSION)
@ -859,7 +911,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::sort::NONE,
options::sort::VERSION,
options::sort::EXTENSION,
])
]),
)
.arg(
Arg::with_name(options::sort::EXTENSION)
@ -872,14 +924,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::sort::NONE,
options::sort::VERSION,
options::sort::EXTENSION,
])
]),
)
.arg(
Arg::with_name(options::sort::NONE)
.short(options::sort::NONE)
.help("Do not sort; list the files in whatever order they are stored in the \
directory. This is especially useful when listing very large directories, \
since not doing any sorting can be noticeably faster.")
.help(
"Do not sort; list the files in whatever order they are stored in the \
directory. This is especially useful when listing very large directories, \
since not doing any sorting can be noticeably faster.",
)
.overrides_with_all(&[
options::SORT,
options::sort::SIZE,
@ -887,9 +941,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::sort::NONE,
options::sort::VERSION,
options::sort::EXTENSION,
])
]),
)
// Dereferencing
.arg(
Arg::with_name(options::dereference::ALL)
@ -903,48 +956,43 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::dereference::ALL,
options::dereference::DIR_ARGS,
options::dereference::ARGS,
])
]),
)
.arg(
Arg::with_name(options::dereference::DIR_ARGS)
.long(options::dereference::DIR_ARGS)
.help(
"Do not dereference symlinks except when they link to directories and are \
given as command line arguments.",
given as command line arguments.",
)
.overrides_with_all(&[
options::dereference::ALL,
options::dereference::DIR_ARGS,
options::dereference::ARGS,
])
]),
)
.arg(
Arg::with_name(options::dereference::ARGS)
.short("H")
.long(options::dereference::ARGS)
.help(
"Do not dereference symlinks except when given as command line arguments.",
)
.help("Do not dereference symlinks except when given as command line arguments.")
.overrides_with_all(&[
options::dereference::ALL,
options::dereference::DIR_ARGS,
options::dereference::ARGS,
])
]),
)
// Long format options
.arg(
Arg::with_name(options::NO_GROUP)
.long(options::NO_GROUP)
.short("-G")
.help("Do not show group in long format.")
)
.arg(
Arg::with_name(options::AUTHOR)
.long(options::AUTHOR)
.help("Show author in long format. On the supported platforms, the author \
always matches the file owner.")
.help("Do not show group in long format."),
)
.arg(Arg::with_name(options::AUTHOR).long(options::AUTHOR).help(
"Show author in long format. \
On the supported platforms, the author always matches the file owner.",
))
// Other Flags
.arg(
Arg::with_name(options::files::ALL)
@ -957,9 +1005,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("A")
.long(options::files::ALMOST_ALL)
.help(
"In a directory, do not ignore all file names that start with '.', only ignore \
'.' and '..'.",
),
"In a directory, do not ignore all file names that start with '.', \
only ignore '.' and '..'.",
),
)
.arg(
Arg::with_name(options::DIRECTORY)
@ -982,7 +1030,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg(
Arg::with_name(options::size::SI)
.long(options::size::SI)
.help("Print human readable file sizes using powers of 1000 instead of 1024.")
.help("Print human readable file sizes using powers of 1000 instead of 1024."),
)
.arg(
Arg::with_name(options::INODE)
@ -994,9 +1042,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(options::REVERSE)
.short("r")
.long(options::REVERSE)
.help("Reverse whatever the sorting method is--e.g., list files in reverse \
.help(
"Reverse whatever the sorting method is e.g., list files in reverse \
alphabetical order, youngest first, smallest first, or whatever.",
))
),
)
.arg(
Arg::with_name(options::RECURSIVE)
.short("R")
@ -1009,7 +1059,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("w")
.help("Assume that the terminal is COLS columns wide.")
.value_name("COLS")
.takes_value(true)
.takes_value(true),
)
.arg(
Arg::with_name(options::COLOR)
@ -1022,8 +1072,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg(
Arg::with_name(options::INDICATOR_STYLE)
.long(options::INDICATOR_STYLE)
.help(" append indicator with style WORD to entry names: none (default), slash\
(-p), file-type (--file-type), classify (-F)")
.help(
"Append indicator with style WORD to entry names: \
none (default), slash (-p), file-type (--file-type), classify (-F)",
)
.takes_value(true)
.possible_values(&["none", "slash", "file-type", "classify"])
.overrides_with_all(&[
@ -1031,21 +1083,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::indicator_style::SLASH,
options::indicator_style::CLASSIFY,
options::INDICATOR_STYLE,
]))
.arg(
]),
)
.arg(
Arg::with_name(options::indicator_style::CLASSIFY)
.short("F")
.long(options::indicator_style::CLASSIFY)
.help("Append a character to each file name indicating the file type. Also, for \
regular files that are executable, append '*'. The file type indicators are \
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
'>' for doors, and nothing for regular files.")
.help(
"Append a character to each file name indicating the file type. Also, for \
regular files that are executable, append '*'. The file type indicators are \
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
'>' for doors, and nothing for regular files.",
)
.overrides_with_all(&[
options::indicator_style::FILE_TYPE,
options::indicator_style::SLASH,
options::indicator_style::CLASSIFY,
options::INDICATOR_STYLE,
])
]),
)
.arg(
Arg::with_name(options::indicator_style::FILE_TYPE)
@ -1056,18 +1111,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::indicator_style::SLASH,
options::indicator_style::CLASSIFY,
options::INDICATOR_STYLE,
]))
]),
)
.arg(
Arg::with_name(options::indicator_style::SLASH)
.short(options::indicator_style::SLASH)
.help("Append / indicator to directories."
)
.help("Append / indicator to directories.")
.overrides_with_all(&[
options::indicator_style::FILE_TYPE,
options::indicator_style::SLASH,
options::indicator_style::CLASSIFY,
options::INDICATOR_STYLE,
]))
]),
)
.arg(
//This still needs support for posix-*, +FORMAT
Arg::with_name(options::TIME_STYLE)
@ -1075,36 +1131,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("time/date format with -l; see TIME_STYLE below")
.value_name("TIME_STYLE")
.env("TIME_STYLE")
.possible_values(&[
"full-iso",
"long-iso",
"iso",
"locale",
])
.overrides_with_all(&[
options::TIME_STYLE
])
.possible_values(&["full-iso", "long-iso", "iso", "locale"])
.overrides_with_all(&[options::TIME_STYLE]),
)
.arg(
Arg::with_name(options::FULL_TIME)
.long(options::FULL_TIME)
.overrides_with(options::FULL_TIME)
.help("like -l --time-style=full-iso")
.long(options::FULL_TIME)
.overrides_with(options::FULL_TIME)
.help("like -l --time-style=full-iso"),
)
// Positional arguments
.arg(
Arg::with_name(options::PATHS)
.multiple(true)
.takes_value(true),
)
.after_help(
"The TIME_STYLE argument can be full-iso, long-iso, iso. \
Also the TIME_STYLE environment variable sets the default style to use.",
)
// Positional arguments
.arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true))
.after_help(AFTER_HELP);
let matches = app.get_matches_from(args);
let locs = matches
.values_of(options::PATHS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_else(|| vec![String::from(".")]);
list(locs, Config::from(matches))
}
/// Represents a Path along with it's associated data
@ -1187,31 +1232,27 @@ impl PathData {
}
}
fn list(locs: Vec<String>, config: Config) -> i32 {
fn list(locs: Vec<String>, config: Config) -> UResult<()> {
let mut files = Vec::<PathData>::new();
let mut dirs = Vec::<PathData>::new();
let mut has_failed = false;
let mut out = BufWriter::new(stdout());
for loc in &locs {
let p = PathBuf::from(&loc);
if !p.exists() {
show_error!("'{}': {}", &loc, "No such file or directory");
/*
We found an error, the return code of ls should not be 0
And no need to continue the execution
*/
has_failed = true;
let path_data = PathData::new(p, None, None, &config, true);
if path_data.md().is_none() {
show!(std::io::ErrorKind::NotFound
.map_err_context(|| format!("cannot access '{}'", path_data.p_buf.display())));
// We found an error, no need to continue the execution
continue;
}
let path_data = PathData::new(p, None, None, &config, true);
let show_dir_contents = match path_data.file_type() {
Some(ft) => !config.directory && ft.is_dir(),
None => {
has_failed = true;
set_exit_code(1);
false
}
};
@ -1232,11 +1273,8 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
}
enter_directory(&dir, &config, &mut out);
}
if has_failed {
1
} else {
0
}
Ok(())
}
fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
@ -1253,7 +1291,8 @@ fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
}
// The default sort in GNU ls is case insensitive
Sort::Name => entries.sort_by(|a, b| a.display_name.cmp(&b.display_name)),
Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(&a.p_buf, &b.p_buf)),
Sort::Version => entries
.sort_by(|a, b| version_cmp(&a.p_buf.to_string_lossy(), &b.p_buf.to_string_lossy())),
Sort::Extension => entries.sort_by(|a, b| {
a.p_buf
.extension()
@ -1270,7 +1309,8 @@ fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
#[cfg(windows)]
fn is_hidden(file_path: &DirEntry) -> bool {
let metadata = fs::metadata(file_path.path()).unwrap();
let path = file_path.path();
let metadata = fs::metadata(&path).unwrap_or_else(|_| fs::symlink_metadata(&path).unwrap());
let attr = metadata.file_attributes();
(attr & 0x2) > 0
}
@ -1331,7 +1371,7 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter<Stdout>)
fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
if dereference {
entry.metadata().or_else(|_| entry.symlink_metadata())
entry.metadata()
} else {
entry.symlink_metadata()
}
@ -1463,8 +1503,6 @@ fn display_grid(
}
}
use uucore::fs::display_permissions;
fn display_item_long(
item: &PathData,
max_links: usize,
@ -1474,7 +1512,7 @@ fn display_item_long(
) {
let md = match item.md() {
None => {
show_error!("could not show file: {}", &item.p_buf.display());
show!(LsError::NoMetadata(item.p_buf.clone()));
return;
}
Some(md) => md,
@ -1733,7 +1771,11 @@ fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
#[cfg(unix)]
{
if config.format != Format::Long && config.inode {
name = get_inode(path.md()?) + " " + &name;
name = path
.md()
.map_or_else(|| "?".to_string(), |md| get_inode(md))
+ " "
+ &name;
}
}

View File

@ -1,306 +0,0 @@
use std::cmp::Ordering;
use std::path::Path;
/// Compare paths in a way that matches the GNU version sort, meaning that
/// numbers get sorted in a natural way.
pub(crate) fn version_cmp(a: &Path, b: &Path) -> Ordering {
let a_string = a.to_string_lossy();
let b_string = b.to_string_lossy();
let mut a = a_string.chars().peekable();
let mut b = b_string.chars().peekable();
// The order determined from the number of leading zeroes.
// This is used if the filenames are equivalent up to leading zeroes.
let mut leading_zeroes = Ordering::Equal;
loop {
match (a.next(), b.next()) {
// If the characters are both numerical. We collect the rest of the number
// and parse them to u64's and compare them.
(Some(a_char @ '0'..='9'), Some(b_char @ '0'..='9')) => {
let mut a_leading_zeroes = 0;
if a_char == '0' {
a_leading_zeroes = 1;
while let Some('0') = a.peek() {
a_leading_zeroes += 1;
a.next();
}
}
let mut b_leading_zeroes = 0;
if b_char == '0' {
b_leading_zeroes = 1;
while let Some('0') = b.peek() {
b_leading_zeroes += 1;
b.next();
}
}
// The first different number of leading zeros determines the order
// so if it's already been determined by a previous number, we leave
// it as that ordering.
// It's b.cmp(&a), because the *largest* number of leading zeros
// should go first
if leading_zeroes == Ordering::Equal {
leading_zeroes = b_leading_zeroes.cmp(&a_leading_zeroes);
}
let mut a_str = String::new();
let mut b_str = String::new();
if a_char != '0' {
a_str.push(a_char);
}
if b_char != '0' {
b_str.push(b_char);
}
// Unwrapping here is fine because we only call next if peek returns
// Some(_), so next should also return Some(_).
while let Some('0'..='9') = a.peek() {
a_str.push(a.next().unwrap());
}
while let Some('0'..='9') = b.peek() {
b_str.push(b.next().unwrap());
}
// Since the leading zeroes are stripped, the length can be
// used to compare the numbers.
match a_str.len().cmp(&b_str.len()) {
Ordering::Equal => {}
x => return x,
}
// At this point, leading zeroes are stripped and the lengths
// are equal, meaning that the strings can be compared using
// the standard compare function.
match a_str.cmp(&b_str) {
Ordering::Equal => {}
x => return x,
}
}
// If there are two characters we just compare the characters
(Some(a_char), Some(b_char)) => match a_char.cmp(&b_char) {
Ordering::Equal => {}
x => return x,
},
// Otherwise, we compare the options (because None < Some(_))
(a_opt, b_opt) => match a_opt.cmp(&b_opt) {
// If they are completely equal except for leading zeroes, we use the leading zeroes.
Ordering::Equal => return leading_zeroes,
x => return x,
},
}
}
}
#[cfg(test)]
mod tests {
use crate::version_cmp::version_cmp;
use std::cmp::Ordering;
use std::path::PathBuf;
#[test]
fn test_version_cmp() {
// Identical strings
assert_eq!(
version_cmp(&PathBuf::from("hello"), &PathBuf::from("hello")),
Ordering::Equal
);
assert_eq!(
version_cmp(&PathBuf::from("file12"), &PathBuf::from("file12")),
Ordering::Equal
);
assert_eq!(
version_cmp(
&PathBuf::from("file12-suffix"),
&PathBuf::from("file12-suffix")
),
Ordering::Equal
);
assert_eq!(
version_cmp(
&PathBuf::from("file12-suffix24"),
&PathBuf::from("file12-suffix24")
),
Ordering::Equal
);
// Shortened names
assert_eq!(
version_cmp(&PathBuf::from("world"), &PathBuf::from("wo")),
Ordering::Greater,
);
assert_eq!(
version_cmp(&PathBuf::from("hello10wo"), &PathBuf::from("hello10world")),
Ordering::Less,
);
// Simple names
assert_eq!(
version_cmp(&PathBuf::from("world"), &PathBuf::from("hello")),
Ordering::Greater,
);
assert_eq!(
version_cmp(&PathBuf::from("hello"), &PathBuf::from("world")),
Ordering::Less
);
assert_eq!(
version_cmp(&PathBuf::from("apple"), &PathBuf::from("ant")),
Ordering::Greater
);
assert_eq!(
version_cmp(&PathBuf::from("ant"), &PathBuf::from("apple")),
Ordering::Less
);
// Uppercase letters
assert_eq!(
version_cmp(&PathBuf::from("Beef"), &PathBuf::from("apple")),
Ordering::Less,
"Uppercase letters are sorted before all lowercase letters"
);
assert_eq!(
version_cmp(&PathBuf::from("Apple"), &PathBuf::from("apple")),
Ordering::Less
);
assert_eq!(
version_cmp(&PathBuf::from("apple"), &PathBuf::from("aPple")),
Ordering::Greater
);
// Numbers
assert_eq!(
version_cmp(&PathBuf::from("100"), &PathBuf::from("20")),
Ordering::Greater,
"Greater numbers are greater even if they start with a smaller digit",
);
assert_eq!(
version_cmp(&PathBuf::from("20"), &PathBuf::from("20")),
Ordering::Equal,
"Equal numbers are equal"
);
assert_eq!(
version_cmp(&PathBuf::from("15"), &PathBuf::from("200")),
Ordering::Less,
"Small numbers are smaller"
);
// Comparing numbers with other characters
assert_eq!(
version_cmp(&PathBuf::from("1000"), &PathBuf::from("apple")),
Ordering::Less,
"Numbers are sorted before other characters"
);
assert_eq!(
// spell-checker:disable-next-line
version_cmp(&PathBuf::from("file1000"), &PathBuf::from("fileapple")),
Ordering::Less,
"Numbers in the middle of the name are sorted before other characters"
);
// Leading zeroes
assert_eq!(
version_cmp(&PathBuf::from("012"), &PathBuf::from("12")),
Ordering::Less,
"A single leading zero can make a difference"
);
assert_eq!(
version_cmp(&PathBuf::from("000800"), &PathBuf::from("0000800")),
Ordering::Greater,
"Leading number of zeroes is used even if both non-zero number of zeros"
);
// Numbers and other characters combined
assert_eq!(
version_cmp(&PathBuf::from("ab10"), &PathBuf::from("aa11")),
Ordering::Greater
);
assert_eq!(
version_cmp(&PathBuf::from("aa10"), &PathBuf::from("aa11")),
Ordering::Less,
"Numbers after other characters are handled correctly."
);
assert_eq!(
version_cmp(&PathBuf::from("aa2"), &PathBuf::from("aa100")),
Ordering::Less,
"Numbers after alphabetical characters are handled correctly."
);
assert_eq!(
version_cmp(&PathBuf::from("aa10bb"), &PathBuf::from("aa11aa")),
Ordering::Less,
"Number is used even if alphabetical characters after it differ."
);
assert_eq!(
version_cmp(&PathBuf::from("aa10aa0010"), &PathBuf::from("aa11aa1")),
Ordering::Less,
"Second number is ignored if the first number differs."
);
assert_eq!(
version_cmp(&PathBuf::from("aa10aa0010"), &PathBuf::from("aa10aa1")),
Ordering::Greater,
"Second number is used if the rest is equal."
);
assert_eq!(
version_cmp(&PathBuf::from("aa10aa0010"), &PathBuf::from("aa00010aa1")),
Ordering::Greater,
"Second number is used if the rest is equal up to leading zeroes of the first number."
);
assert_eq!(
version_cmp(&PathBuf::from("aa10aa0022"), &PathBuf::from("aa010aa022")),
Ordering::Greater,
"The leading zeroes of the first number has priority."
);
assert_eq!(
version_cmp(&PathBuf::from("aa10aa0022"), &PathBuf::from("aa10aa022")),
Ordering::Less,
"The leading zeroes of other numbers than the first are used."
);
assert_eq!(
version_cmp(&PathBuf::from("file-1.4"), &PathBuf::from("file-1.13")),
Ordering::Less,
"Periods are handled as normal text, not as a decimal point."
);
// Greater than u64::Max
// u64 == 18446744073709551615 so this should be plenty:
// 20000000000000000000000
assert_eq!(
version_cmp(
&PathBuf::from("aa2000000000000000000000bb"),
&PathBuf::from("aa002000000000000000000001bb")
),
Ordering::Less,
"Numbers larger than u64::MAX are handled correctly without crashing"
);
assert_eq!(
version_cmp(
&PathBuf::from("aa2000000000000000000000bb"),
&PathBuf::from("aa002000000000000000000000bb")
),
Ordering::Greater,
"Leading zeroes for numbers larger than u64::MAX are handled correctly without crashing"
);
}
}

View File

@ -15,7 +15,7 @@ edition = "2018"
path = "src/mkdir.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", "mode"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View File

@ -5,151 +5,126 @@
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
#[macro_use]
extern crate uucore;
use clap::OsValues;
use clap::{crate_version, App, Arg};
use std::fs;
use std::path::Path;
use uucore::error::{FromIo, UResult, USimpleError};
static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
static OPT_MODE: &str = "mode";
static OPT_PARENTS: &str = "parents";
static OPT_VERBOSE: &str = "verbose";
static ARG_DIRS: &str = "dirs";
mod options {
pub const MODE: &str = "mode";
pub const PARENTS: &str = "parents";
pub const VERBOSE: &str = "verbose";
pub const DIRS: &str = "dirs";
}
fn get_usage() -> String {
format!("{0} [OPTION]... [USER]", executable!())
}
/**
* Handles option parsing
*/
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = get_usage();
// Linux-specific options, not implemented
// opts.optflag("Z", "context", "set SELinux security context" +
// " of each created directory to CTX"),
let matches = App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(OPT_MODE)
.short("m")
.long(OPT_MODE)
.help("set file mode")
.default_value("755"),
)
.arg(
Arg::with_name(OPT_PARENTS)
.short("p")
.long(OPT_PARENTS)
.alias("parent")
.help("make parent directories as needed"),
)
.arg(
Arg::with_name(OPT_VERBOSE)
.short("v")
.long(OPT_VERBOSE)
.help("print a message for each printed directory"),
)
.arg(
Arg::with_name(ARG_DIRS)
.multiple(true)
.takes_value(true)
.min_values(1),
)
.get_matches_from(args);
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
let dirs: Vec<String> = matches
.values_of(ARG_DIRS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let verbose = matches.is_present(OPT_VERBOSE);
let recursive = matches.is_present(OPT_PARENTS);
let dirs = matches.values_of_os(options::DIRS).unwrap_or_default();
let verbose = matches.is_present(options::VERBOSE);
let recursive = matches.is_present(options::PARENTS);
// Translate a ~str in octal form to u16, default to 755
// Not tested on Windows
let mode_match = matches.value_of(OPT_MODE);
let mode: u16 = match mode_match {
Some(m) => {
let res: Option<u16> = u16::from_str_radix(m, 8).ok();
match res {
Some(r) => r,
_ => crash!(1, "no mode given"),
}
}
_ => 0o755_u16,
let mode: u16 = match matches.value_of(options::MODE) {
Some(m) => u16::from_str_radix(m, 8)
.map_err(|_| USimpleError::new(1, format!("invalid mode '{}'", m)))?,
None => 0o755_u16,
};
exec(dirs, recursive, mode, verbose)
}
/**
* Create the list of new directories
*/
fn exec(dirs: Vec<String>, recursive: bool, mode: u16, verbose: bool) -> i32 {
let mut status = 0;
let empty = Path::new("");
for dir in &dirs {
let path = Path::new(dir);
if !recursive {
if let Some(parent) = path.parent() {
if parent != empty && !parent.exists() {
show_error!(
"cannot create directory '{}': No such file or directory",
path.display()
);
status = 1;
continue;
}
}
}
status |= mkdir(path, recursive, mode, verbose);
}
status
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(options::MODE)
.short("m")
.long(options::MODE)
.help("set file mode (not implemented on windows)")
.default_value("755"),
)
.arg(
Arg::with_name(options::PARENTS)
.short("p")
.long(options::PARENTS)
.alias("parent")
.help("make parent directories as needed"),
)
.arg(
Arg::with_name(options::VERBOSE)
.short("v")
.long(options::VERBOSE)
.help("print a message for each printed directory"),
)
.arg(
Arg::with_name(options::DIRS)
.multiple(true)
.takes_value(true)
.min_values(1),
)
}
/**
* Wrapper to catch errors, return 1 if failed
* Create the list of new directories
*/
fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> i32 {
fn exec(dirs: OsValues, recursive: bool, mode: u16, verbose: bool) -> UResult<()> {
for dir in dirs {
let path = Path::new(dir);
show_if_err!(mkdir(path, recursive, mode, verbose));
}
Ok(())
}
fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> UResult<()> {
let create_dir = if recursive {
fs::create_dir_all
} else {
fs::create_dir
};
if let Err(e) = create_dir(path) {
show_error!("{}: {}", path.display(), e.to_string());
return 1;
}
create_dir(path).map_err_context(|| format!("cannot create directory '{}'", path.display()))?;
if verbose {
println!("{}: created directory '{}'", executable!(), path.display());
}
#[cfg(any(unix, target_os = "redox"))]
fn chmod(path: &Path, mode: u16) -> i32 {
use std::fs::{set_permissions, Permissions};
use std::os::unix::fs::PermissionsExt;
let mode = Permissions::from_mode(u32::from(mode));
if let Err(err) = set_permissions(path, mode) {
show_error!("{}: {}", path.display(), err);
return 1;
}
0
}
#[cfg(windows)]
#[allow(unused_variables)]
fn chmod(path: &Path, mode: u16) -> i32 {
// chmod on Windows only sets the readonly flag, which isn't even honored on directories
0
}
chmod(path, mode)
}
#[cfg(any(unix, target_os = "redox"))]
fn chmod(path: &Path, mode: u16) -> UResult<()> {
use std::fs::{set_permissions, Permissions};
use std::os::unix::fs::PermissionsExt;
let mode = Permissions::from_mode(u32::from(mode));
set_permissions(path, mode)
.map_err_context(|| format!("cannot set permissions '{}'", path.display()))
}
#[cfg(windows)]
fn chmod(_path: &Path, _mode: u16) -> UResult<()> {
// chmod on Windows only sets the readonly flag, which isn't even honored on directories
Ok(())
}

View File

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

View File

@ -29,27 +29,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(USAGE)
.about(SUMMARY)
.arg(
Arg::with_name(options::MODE)
.short("m")
.long(options::MODE)
.help("file permissions for the fifo")
.default_value("0666")
.value_name("0666"),
)
.arg(
Arg::with_name(options::SE_LINUX_SECURITY_CONTEXT)
.short(options::SE_LINUX_SECURITY_CONTEXT)
.help("set the SELinux security context to default type")
)
.arg(Arg::with_name(options::CONTEXT).long(options::CONTEXT).value_name("CTX").help("like -Z, or if CTX is specified then set the SELinux\nor SMACK security context to CTX"))
.arg(Arg::with_name(options::FIFO).hidden(true).multiple(true))
.get_matches_from(args);
let matches = uu_app().get_matches_from(args);
if matches.is_present(options::CONTEXT) {
crash!(1, "--context is not implemented");
@ -88,3 +68,34 @@ 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!())
.usage(USAGE)
.about(SUMMARY)
.arg(
Arg::with_name(options::MODE)
.short("m")
.long(options::MODE)
.help("file permissions for the fifo")
.default_value("0666")
.value_name("0666"),
)
.arg(
Arg::with_name(options::SE_LINUX_SECURITY_CONTEXT)
.short(options::SE_LINUX_SECURITY_CONTEXT)
.help("set the SELinux security context to default type"),
)
.arg(
Arg::with_name(options::CONTEXT)
.long(options::CONTEXT)
.value_name("CTX")
.help(
"like -Z, or if CTX is specified then set the SELinux \
or SMACK security context to CTX",
),
)
.arg(Arg::with_name(options::FIFO).hidden(true).multiple(true))
}

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