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

Merge branch 'master' into id_zero_2351

This commit is contained in:
Jan Scheer 2021-06-13 11:14:56 +02:00
commit 052202ca19
136 changed files with 2205 additions and 1916 deletions

View file

@ -5,7 +5,7 @@ name: CICD
# spell-checker:ignore (jargon) SHAs deps softprops toolchain # spell-checker:ignore (jargon) SHAs deps softprops toolchain
# spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy # spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy
# spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs # spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs
# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend tempfile testsuite uutils # spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend runtest tempfile testsuite uutils
env: env:
PROJECT_NAME: coreutils PROJECT_NAME: coreutils
@ -32,12 +32,12 @@ jobs:
shell: bash shell: bash
run: | run: |
## VARs setup ## VARs setup
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# target-specific options # target-specific options
# * CARGO_FEATURES_OPTION # * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ; CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} outputs CARGO_FEATURES_OPTION
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -93,12 +93,12 @@ jobs:
shell: bash shell: bash
run: | run: |
## VARs setup ## VARs setup
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# target-specific options # target-specific options
# * CARGO_FEATURES_OPTION # * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ; CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} outputs CARGO_FEATURES_OPTION
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -197,7 +197,11 @@ jobs:
run: | run: |
bindir=$(pwd)/target/debug bindir=$(pwd)/target/debug
cd tmp/busybox-*/testsuite 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" ; } ## 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: makefile_build:
name: Test the build target of the Makefile name: Test the build target of the Makefile
@ -261,22 +265,20 @@ jobs:
shell: bash shell: bash
run: | run: |
## VARs setup ## VARs setup
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# toolchain # toolchain
TOOLCHAIN="stable" ## default to "stable" toolchain TOOLCHAIN="stable" ## default to "stable" toolchain
# * specify alternate/non-default TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: GH:rust-lang/rust#47048, GH:rust-lang/rust#53454, GH:rust-lang/cargo#6754) # * specify alternate/non-default TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: GH:rust-lang/rust#47048, GH:rust-lang/rust#53454, GH:rust-lang/cargo#6754)
case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac; case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac;
# * use requested TOOLCHAIN if specified # * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
echo set-output name=TOOLCHAIN::${TOOLCHAIN:-<empty>/false} outputs TOOLCHAIN
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
# staging directory # staging directory
STAGING='_staging' STAGING='_staging'
echo set-output name=STAGING::${STAGING} outputs STAGING
echo ::set-output name=STAGING::${STAGING}
# determine EXE suffix # determine EXE suffix
EXE_suffix="" ; case '${{ matrix.job.target }}' in *-pc-windows-*) EXE_suffix=".exe" ;; esac; EXE_suffix="" ; case '${{ matrix.job.target }}' in *-pc-windows-*) EXE_suffix=".exe" ;; esac;
echo set-output name=EXE_suffix::${EXE_suffix} outputs EXE_suffix
echo ::set-output name=EXE_suffix::${EXE_suffix}
# parse commit reference info # parse commit reference info
echo GITHUB_REF=${GITHUB_REF} echo GITHUB_REF=${GITHUB_REF}
echo GITHUB_SHA=${GITHUB_SHA} echo GITHUB_SHA=${GITHUB_SHA}
@ -284,14 +286,7 @@ jobs:
unset REF_BRANCH ; case "${GITHUB_REF}" in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac; unset REF_BRANCH ; case "${GITHUB_REF}" in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac;
unset REF_TAG ; case "${GITHUB_REF}" in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac; unset REF_TAG ; case "${GITHUB_REF}" in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac;
REF_SHAS=${GITHUB_SHA:0:8} REF_SHAS=${GITHUB_SHA:0:8}
echo set-output name=REF_NAME::${REF_NAME} outputs REF_NAME REF_BRANCH REF_TAG REF_SHAS
echo set-output name=REF_BRANCH::${REF_BRANCH}
echo set-output name=REF_TAG::${REF_TAG}
echo set-output name=REF_SHAS::${REF_SHAS}
echo ::set-output name=REF_NAME::${REF_NAME}
echo ::set-output name=REF_BRANCH::${REF_BRANCH}
echo ::set-output name=REF_TAG::${REF_TAG}
echo ::set-output name=REF_SHAS::${REF_SHAS}
# parse target # parse target
unset TARGET_ARCH unset TARGET_ARCH
case '${{ matrix.job.target }}' in case '${{ matrix.job.target }}' in
@ -301,68 +296,50 @@ jobs:
i686-*) TARGET_ARCH=i686 ;; i686-*) TARGET_ARCH=i686 ;;
x86_64-*) TARGET_ARCH=x86_64 ;; x86_64-*) TARGET_ARCH=x86_64 ;;
esac; esac;
echo set-output name=TARGET_ARCH::${TARGET_ARCH}
echo ::set-output name=TARGET_ARCH::${TARGET_ARCH}
unset TARGET_OS ; case '${{ matrix.job.target }}' in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac; unset TARGET_OS ; case '${{ matrix.job.target }}' in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac;
echo set-output name=TARGET_OS::${TARGET_OS} outputs TARGET_ARCH TARGET_OS
echo ::set-output name=TARGET_OS::${TARGET_OS}
# package name # package name
PKG_suffix=".tar.gz" ; case '${{ matrix.job.target }}' in *-pc-windows-*) PKG_suffix=".zip" ;; esac; PKG_suffix=".tar.gz" ; case '${{ matrix.job.target }}' in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }} PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }}
PKG_NAME=${PKG_BASENAME}${PKG_suffix} PKG_NAME=${PKG_BASENAME}${PKG_suffix}
echo set-output name=PKG_suffix::${PKG_suffix} outputs PKG_suffix PKG_BASENAME PKG_NAME
echo set-output name=PKG_BASENAME::${PKG_BASENAME}
echo set-output name=PKG_NAME::${PKG_NAME}
echo ::set-output name=PKG_suffix::${PKG_suffix}
echo ::set-output name=PKG_BASENAME::${PKG_BASENAME}
echo ::set-output name=PKG_NAME::${PKG_NAME}
# deployable tag? (ie, leading "vM" or "M"; M == version number) # deployable tag? (ie, leading "vM" or "M"; M == version number)
unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi
echo set-output name=DEPLOY::${DEPLOY:-<empty>/false} outputs DEPLOY
echo ::set-output name=DEPLOY::${DEPLOY}
# DPKG architecture? # DPKG architecture?
unset DPKG_ARCH unset DPKG_ARCH
case ${{ matrix.job.target }} in case ${{ matrix.job.target }} in
x86_64-*-linux-*) DPKG_ARCH=amd64 ;; x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
*-linux-*) DPKG_ARCH=${TARGET_ARCH} ;; *-linux-*) DPKG_ARCH=${TARGET_ARCH} ;;
esac esac
echo set-output name=DPKG_ARCH::${DPKG_ARCH} outputs DPKG_ARCH
echo ::set-output name=DPKG_ARCH::${DPKG_ARCH}
# DPKG version? # DPKG version?
unset DPKG_VERSION ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DPKG_VERSION=${REF_TAG/#[vV]/} ; fi unset DPKG_VERSION ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DPKG_VERSION=${REF_TAG/#[vV]/} ; fi
echo set-output name=DPKG_VERSION::${DPKG_VERSION} outputs DPKG_VERSION
echo ::set-output name=DPKG_VERSION::${DPKG_VERSION}
# DPKG base name/conflicts? # DPKG base name/conflicts?
DPKG_BASENAME=${PROJECT_NAME} DPKG_BASENAME=${PROJECT_NAME}
DPKG_CONFLICTS=${PROJECT_NAME}-musl DPKG_CONFLICTS=${PROJECT_NAME}-musl
case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac; case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac;
echo set-output name=DPKG_BASENAME::${DPKG_BASENAME} outputs DPKG_BASENAME DPKG_CONFLICTS
echo set-output name=DPKG_CONFLICTS::${DPKG_CONFLICTS}
echo ::set-output name=DPKG_BASENAME::${DPKG_BASENAME}
echo ::set-output name=DPKG_CONFLICTS::${DPKG_CONFLICTS}
# DPKG name # DPKG name
unset DPKG_NAME; unset DPKG_NAME;
if [[ -n $DPKG_ARCH && -n $DPKG_VERSION ]]; then DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" ; fi if [[ -n $DPKG_ARCH && -n $DPKG_VERSION ]]; then DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" ; fi
echo set-output name=DPKG_NAME::${DPKG_NAME} outputs DPKG_NAME
echo ::set-output name=DPKG_NAME::${DPKG_NAME}
# target-specific options # target-specific options
# * CARGO_FEATURES_OPTION # * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ; CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} outputs CARGO_FEATURES_OPTION
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
# * CARGO_USE_CROSS (truthy) # * CARGO_USE_CROSS (truthy)
CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac; CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac;
echo set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS:-<empty>/false} outputs CARGO_USE_CROSS
echo ::set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS}
# ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml") # ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml")
if [ -n "${CARGO_USE_CROSS}" ] && [ ! -e "Cross.toml" ] ; then if [ -n "${CARGO_USE_CROSS}" ] && [ ! -e "Cross.toml" ] ; then
printf "[build.env]\npassthrough = [\"CI\"]\n" > Cross.toml printf "[build.env]\npassthrough = [\"CI\"]\n" > Cross.toml
fi fi
# * test only library and/or binaries for arm-type targets # * test only library and/or binaries for arm-type targets
unset CARGO_TEST_OPTIONS ; case '${{ matrix.job.target }}' in aarch64-* | arm-*) CARGO_TEST_OPTIONS="--bins" ;; esac; unset CARGO_TEST_OPTIONS ; case '${{ matrix.job.target }}' in aarch64-* | arm-*) CARGO_TEST_OPTIONS="--bins" ;; esac;
echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} outputs CARGO_TEST_OPTIONS
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
# * executable for `strip`? # * executable for `strip`?
STRIP="strip" STRIP="strip"
case ${{ matrix.job.target }} in case ${{ matrix.job.target }} in
@ -370,8 +347,7 @@ jobs:
arm-*-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; arm-*-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;;
*-pc-windows-msvc) STRIP="" ;; *-pc-windows-msvc) STRIP="" ;;
esac; esac;
echo set-output name=STRIP::${STRIP:-<empty>/false} outputs STRIP
echo ::set-output name=STRIP::${STRIP}
- name: Create all needed build/work directories - name: Create all needed build/work directories
shell: bash shell: bash
run: | run: |
@ -395,11 +371,12 @@ jobs:
shell: bash shell: bash
run: | run: |
## Dependent VARs setup ## Dependent VARs setup
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# * determine sub-crate utility list # * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
echo UTILITY_LIST=${UTILITY_LIST}
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
echo set-output name=UTILITY_LIST::${UTILITY_LIST} outputs CARGO_UTILITY_LIST_OPTIONS
echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS}
- name: Install `cargo-tree` # for dependency information - name: Install `cargo-tree` # for dependency information
uses: actions-rs/install@v0.1 uses: actions-rs/install@v0.1
with: with:
@ -524,34 +501,31 @@ jobs:
id: vars id: vars
shell: bash shell: bash
run: | run: |
## VARs setup
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# toolchain # toolchain
TOOLCHAIN="nightly-${{ env.RUST_COV_SRV }}" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support TOOLCHAIN="nightly-${{ env.RUST_COV_SRV }}" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support
# * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files # * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files
case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac; case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
# * use requested TOOLCHAIN if specified # * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
echo set-output name=TOOLCHAIN::${TOOLCHAIN} outputs TOOLCHAIN
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
# staging directory # staging directory
STAGING='_staging' STAGING='_staging'
echo set-output name=STAGING::${STAGING} outputs STAGING
echo ::set-output name=STAGING::${STAGING}
## # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>) ## # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>)
## # note: CODECOV_TOKEN / HAS_CODECOV_TOKEN is not needed for public repositories when using AppVeyor, Azure Pipelines, CircleCI, GitHub Actions, Travis (see <https://docs.codecov.io/docs/about-the-codecov-bash-uploader#section-upload-token>) ## # note: CODECOV_TOKEN / HAS_CODECOV_TOKEN is not needed for public repositories when using AppVeyor, Azure Pipelines, CircleCI, GitHub Actions, Travis (see <https://docs.codecov.io/docs/about-the-codecov-bash-uploader#section-upload-token>)
## unset HAS_CODECOV_TOKEN ## unset HAS_CODECOV_TOKEN
## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi ## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi
## echo set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN} ## outputs HAS_CODECOV_TOKEN
## echo ::set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN}
# target-specific options # target-specific options
# * CARGO_FEATURES_OPTION # * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} outputs CARGO_FEATURES_OPTION
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
# * CODECOV_FLAGS # * CODECOV_FLAGS
CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' ) CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' )
echo set-output name=CODECOV_FLAGS::${CODECOV_FLAGS} outputs CODECOV_FLAGS
echo ::set-output name=CODECOV_FLAGS::${CODECOV_FLAGS}
- name: rust toolchain ~ install - name: rust toolchain ~ install
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -563,12 +537,11 @@ jobs:
shell: bash shell: bash
run: | run: |
## Dependent VARs setup ## Dependent VARs setup
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# * determine sub-crate utility list # * determine sub-crate utility list
# UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
UTILITY_LIST="id" # TODO: remove after debugging
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
echo set-output name=UTILITY_LIST::${UTILITY_LIST} outputs CARGO_UTILITY_LIST_OPTIONS
echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS}
- name: Test uucore - name: Test uucore
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
@ -607,7 +580,7 @@ jobs:
with: with:
crate: grcov crate: grcov
version: latest version: latest
use-tool-cache: true use-tool-cache: false
- name: Generate coverage data (via `grcov`) - name: Generate coverage data (via `grcov`)
id: coverage id: coverage
shell: bash shell: bash

View file

@ -97,6 +97,9 @@ Michael Debertol
Michael Gehring Michael Gehring
Michael Michael
Gehring Gehring
Mitchell Mebane
Mitchell
Mebane
Morten Olsen Lysgaard Morten Olsen Lysgaard
Morten Morten
Olsen Olsen

View file

@ -7,6 +7,7 @@ advapi
advapi32-sys advapi32-sys
aho-corasick aho-corasick
backtrace backtrace
blake2b_simd
bstr bstr
byteorder byteorder
chacha chacha
@ -47,6 +48,7 @@ xattr
# * rust/rustc # * rust/rustc
RUSTDOCFLAGS RUSTDOCFLAGS
RUSTFLAGS RUSTFLAGS
bitor # BitOr trait function
bitxor # BitXor trait function bitxor # BitXor trait function
clippy clippy
concat concat

18
Cargo.lock generated
View file

@ -6,16 +6,6 @@ version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "advapi32-sys"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a"
dependencies = [
"winapi 0.2.8",
"winapi-build",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.18"
@ -1772,6 +1762,7 @@ dependencies = [
name = "uu_cat" name = "uu_cat"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"atty",
"clap", "clap",
"nix 0.20.0", "nix 0.20.0",
"thiserror", "thiserror",
@ -1784,6 +1775,7 @@ dependencies = [
name = "uu_chgrp" name = "uu_chgrp"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"walkdir", "walkdir",
@ -1872,6 +1864,7 @@ dependencies = [
name = "uu_cut" name = "uu_cut"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"atty",
"bstr", "bstr",
"clap", "clap",
"memchr 2.4.0", "memchr 2.4.0",
@ -2262,6 +2255,7 @@ dependencies = [
name = "uu_nohup" name = "uu_nohup"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"atty",
"clap", "clap",
"libc", "libc",
"uucore", "uucore",
@ -2420,6 +2414,7 @@ dependencies = [
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"walkdir", "walkdir",
"winapi 0.3.9",
] ]
[[package]] [[package]]
@ -2601,7 +2596,6 @@ name = "uu_timeout"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap", "clap",
"getopts",
"libc", "libc",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
@ -2659,6 +2653,7 @@ dependencies = [
name = "uu_tty" name = "uu_tty"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"atty",
"clap", "clap",
"libc", "libc",
"uucore", "uucore",
@ -2750,7 +2745,6 @@ dependencies = [
name = "uu_whoami" name = "uu_whoami"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"advapi32-sys",
"clap", "clap",
"uucore", "uucore",
"uucore_procs", "uucore_procs",

View file

@ -351,7 +351,7 @@ time = "0.1"
unindent = "0.1" unindent = "0.1"
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries", "process"] } uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries", "process"] }
walkdir = "2.2" walkdir = "2.2"
atty = "0.2.14" atty = "0.2"
[target.'cfg(unix)'.dev-dependencies] [target.'cfg(unix)'.dev-dependencies]
rlimit = "0.4.0" rlimit = "0.4.0"

View file

@ -268,11 +268,11 @@ test:
${CARGO} test ${CARGOFLAGS} --features "$(TESTS) $(TEST_SPEC_FEATURE)" --no-default-features $(TEST_NO_FAIL_FAST) ${CARGO} test ${CARGOFLAGS} --features "$(TESTS) $(TEST_SPEC_FEATURE)" --no-default-features $(TEST_NO_FAIL_FAST)
busybox-src: busybox-src:
if [ ! -e $(BUSYBOX_SRC) ]; then \ if [ ! -e "$(BUSYBOX_SRC)" ] ; then \
mkdir -p $(BUSYBOX_ROOT); \ mkdir -p "$(BUSYBOX_ROOT)" ; \
wget https://busybox.net/downloads/busybox-$(BUSYBOX_VER).tar.bz2 -P $(BUSYBOX_ROOT); \ wget "https://busybox.net/downloads/busybox-$(BUSYBOX_VER).tar.bz2" -P "$(BUSYBOX_ROOT)" ; \
tar -C $(BUSYBOX_ROOT) -xf $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER).tar.bz2; \ tar -C "$(BUSYBOX_ROOT)" -xf "$(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER).tar.bz2" ; \
fi; \ fi ;
# This is a busybox-specific config file their test suite wants to parse. # This is a busybox-specific config file their test suite wants to parse.
$(BUILDDIR)/.config: $(BASEDIR)/.busybox-config $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
@ -280,10 +280,12 @@ $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
# Test under the busybox test suite # Test under the busybox test suite
$(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config $(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config
cp $(BUILDDIR)/coreutils $(BUILDDIR)/busybox; \ cp "$(BUILDDIR)/coreutils" "$(BUILDDIR)/busybox"
chmod +x $@; chmod +x $@
prepare-busytest: $(BUILDDIR)/busybox prepare-busytest: $(BUILDDIR)/busybox
# disable inapplicable tests
-( cd "$(BUSYBOX_SRC)/testsuite" ; if [ -e "busybox.tests" ] ; then mv busybox.tests busybox.tests- ; fi ; )
ifeq ($(EXES),) ifeq ($(EXES),)
busytest: busytest:

View file

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

View file

@ -54,15 +54,13 @@ impl Config {
None => None, None => None,
}; };
let cols = match options.value_of(options::WRAP) { let cols = options
Some(num) => match num.parse::<usize>() { .value_of(options::WRAP)
Ok(n) => Some(n), .map(|num| {
Err(e) => { num.parse::<usize>()
return Err(format!("Invalid wrap size: {}: {}", num, e)); .map_err(|e| format!("Invalid wrap size: {}: {}", num, e))
} })
}, .transpose()?;
None => None,
};
Ok(Config { Ok(Config {
decode: options.is_present(options::DECODE), decode: options.is_present(options::DECODE),

View file

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

View file

@ -110,7 +110,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let line_ending = if opt_zero { "\0" } else { "\n" }; let line_ending = if opt_zero { "\0" } else { "\n" };
for path in paths { for path in paths {
print!("{}{}", basename(&path, &suffix), line_ending); print!("{}{}", basename(path, suffix), line_ending);
} }
0 0
@ -118,14 +118,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
fn basename(fullname: &str, suffix: &str) -> String { fn basename(fullname: &str, suffix: &str) -> String {
// Remove all platform-specific path separators from the end // Remove all platform-specific path separators from the end
let mut path: String = fullname let path = fullname.trim_end_matches(is_separator);
.chars()
.rev()
.skip_while(|&ch| is_separator(ch))
.collect();
// Undo reverse
path = path.chars().rev().collect();
// Convert to path buffer and get last path component // Convert to path buffer and get last path component
let pb = PathBuf::from(path); let pb = PathBuf::from(path);

View file

@ -17,6 +17,7 @@ path = "src/cat.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
thiserror = "1.0" thiserror = "1.0"
atty = "0.2"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -20,7 +20,6 @@ use clap::{crate_version, App, Arg};
use std::fs::{metadata, File}; use std::fs::{metadata, File};
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use thiserror::Error; use thiserror::Error;
use uucore::fs::is_stdin_interactive;
/// Linux splice support /// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
@ -295,7 +294,7 @@ fn cat_handle<R: Read>(
if options.can_write_fast() { if options.can_write_fast() {
write_fast(handle) write_fast(handle)
} else { } else {
write_lines(handle, &options, state) write_lines(handle, options, state)
} }
} }
@ -306,9 +305,9 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: stdin.as_raw_fd(), file_descriptor: stdin.as_raw_fd(),
reader: stdin, reader: stdin,
is_interactive: is_stdin_interactive(), is_interactive: atty::is(atty::Stream::Stdin),
}; };
return cat_handle(&mut handle, &options, state); return cat_handle(&mut handle, options, state);
} }
match get_input_type(path)? { match get_input_type(path)? {
InputType::Directory => Err(CatError::IsDirectory), InputType::Directory => Err(CatError::IsDirectory),
@ -322,7 +321,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
reader: socket, reader: socket,
is_interactive: false, is_interactive: false,
}; };
cat_handle(&mut handle, &options, state) cat_handle(&mut handle, options, state)
} }
_ => { _ => {
let file = File::open(path)?; let file = File::open(path)?;
@ -332,7 +331,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
reader: file, reader: file,
is_interactive: false, is_interactive: false,
}; };
cat_handle(&mut handle, &options, state) cat_handle(&mut handle, options, state)
} }
} }
} }
@ -345,7 +344,7 @@ fn cat_files(files: Vec<String>, options: &OutputOptions) -> Result<(), u32> {
}; };
for path in &files { for path in &files {
if let Err(err) = cat_path(path, &options, &mut state) { if let Err(err) = cat_path(path, options, &mut state) {
show_error!("{}: {}", path, err); show_error!("{}: {}", path, err);
error_count += 1; error_count += 1;
} }

View file

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

View file

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

View file

@ -127,31 +127,32 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let verbose = matches.is_present(options::VERBOSE); let verbose = matches.is_present(options::VERBOSE);
let preserve_root = matches.is_present(options::PRESERVE_ROOT); let preserve_root = matches.is_present(options::PRESERVE_ROOT);
let recursive = matches.is_present(options::RECURSIVE); let recursive = matches.is_present(options::RECURSIVE);
let fmode = let fmode = matches
matches .value_of(options::REFERENCE)
.value_of(options::REFERENCE) .and_then(|fref| match fs::metadata(fref) {
.and_then(|ref fref| match fs::metadata(fref) { Ok(meta) => Some(meta.mode()),
Ok(meta) => Some(meta.mode()), Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err),
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 modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
let mut cmode = if mode_had_minus_prefix { let cmode = if mode_had_minus_prefix {
// clap parsing is finished, now put prefix back // clap parsing is finished, now put prefix back
Some(format!("-{}", modes)) format!("-{}", modes)
} else { } else {
Some(modes.to_string()) modes.to_string()
}; };
let mut files: Vec<String> = matches let mut files: Vec<String> = matches
.values_of(options::FILE) .values_of(options::FILE)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default(); .unwrap_or_default();
if fmode.is_some() { let cmode = if fmode.is_some() {
// "--reference" and MODE are mutually exclusive // "--reference" and MODE are mutually exclusive
// if "--reference" was used MODE needs to be interpreted as another FILE // if "--reference" was used MODE needs to be interpreted as another FILE
// it wasn't possible to implement this behavior directly with clap // it wasn't possible to implement this behavior directly with clap
files.push(cmode.unwrap()); files.push(cmode);
cmode = None; None
} } else {
Some(cmode)
};
let chmoder = Chmoder { let chmoder = Chmoder {
changes, changes,
@ -230,11 +231,11 @@ impl Chmoder {
return Err(1); return Err(1);
} }
if !self.recursive { if !self.recursive {
r = self.chmod_file(&file).and(r); r = self.chmod_file(file).and(r);
} else { } else {
for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) { for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) {
let file = entry.path(); let file = entry.path();
r = self.chmod_file(&file).and(r); r = self.chmod_file(file).and(r);
} }
} }
} }

View file

@ -220,7 +220,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}; };
let filter = if let Some(spec) = matches.value_of(options::FROM) { let filter = if let Some(spec) = matches.value_of(options::FROM) {
match parse_spec(&spec) { match parse_spec(spec) {
Ok((Some(uid), None)) => IfFrom::User(uid), Ok((Some(uid), None)) => IfFrom::User(uid),
Ok((None, Some(gid))) => IfFrom::Group(gid), Ok((None, Some(gid))) => IfFrom::Group(gid),
Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid), Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid),
@ -248,7 +248,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
} else { } else {
match parse_spec(&owner) { match parse_spec(owner) {
Ok((u, g)) => { Ok((u, g)) => {
dest_uid = u; dest_uid = u;
dest_gid = g; dest_gid = g;
@ -278,37 +278,25 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
let usr_only = args.len() == 1 && !args[0].is_empty(); let usr_only = args.len() == 1 && !args[0].is_empty();
let grp_only = args.len() == 2 && args[0].is_empty(); let grp_only = args.len() == 2 && args[0].is_empty();
let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty(); let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty();
let uid = if usr_only || usr_grp {
if usr_only { Some(
Ok(( Passwd::locate(args[0])
Some(match Passwd::locate(args[0]) { .map_err(|_| format!("invalid user: {}", spec))?
Ok(v) => v.uid(), .uid(),
_ => return Err(format!("invalid user: {}", spec)), )
}),
None,
))
} else if grp_only {
Ok((
None,
Some(match Group::locate(args[1]) {
Ok(v) => v.gid(),
_ => return Err(format!("invalid group: {}", spec)),
}),
))
} else if usr_grp {
Ok((
Some(match Passwd::locate(args[0]) {
Ok(v) => v.uid(),
_ => return Err(format!("invalid user: {}", spec)),
}),
Some(match Group::locate(args[1]) {
Ok(v) => v.gid(),
_ => return Err(format!("invalid group: {}", spec)),
}),
))
} else { } else {
Ok((None, None)) None
} };
let gid = if grp_only || usr_grp {
Some(
Group::locate(args[1])
.map_err(|_| format!("invalid group: {}", spec))?
.gid(),
)
} else {
None
};
Ok((uid, gid))
} }
enum IfFrom { enum IfFrom {
@ -497,3 +485,17 @@ impl Chowner {
} }
} }
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_spec() {
assert_eq!(parse_spec(":"), Ok((None, None)));
assert!(parse_spec("::")
.err()
.unwrap()
.starts_with("invalid group: "));
}
}

View file

@ -28,6 +28,7 @@ mod options {
pub const GROUP: &str = "group"; pub const GROUP: &str = "group";
pub const GROUPS: &str = "groups"; pub const GROUPS: &str = "groups";
pub const USERSPEC: &str = "userspec"; pub const USERSPEC: &str = "userspec";
pub const COMMAND: &str = "command";
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
@ -39,7 +40,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.usage(SYNTAX) .usage(SYNTAX)
.arg(Arg::with_name(options::NEWROOT).hidden(true).required(true)) .arg(
Arg::with_name(options::NEWROOT)
.hidden(true)
.required(true)
.index(1),
)
.arg( .arg(
Arg::with_name(options::USER) Arg::with_name(options::USER)
.short("u") .short("u")
@ -71,6 +77,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
) )
.value_name("USER:GROUP"), .value_name("USER:GROUP"),
) )
.arg(
Arg::with_name(options::COMMAND)
.hidden(true)
.multiple(true)
.index(2),
)
.get_matches_from(args); .get_matches_from(args);
let default_shell: &'static str = "/bin/sh"; let default_shell: &'static str = "/bin/sh";
@ -94,7 +106,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
); );
} }
let command: Vec<&str> = match matches.args.len() { let commands = match matches.values_of(options::COMMAND) {
Some(v) => v.collect(),
None => vec![],
};
// TODO: refactor the args and command matching
// See: https://github.com/uutils/coreutils/pull/2365#discussion_r647849967
let command: Vec<&str> = match commands.len() {
1 => { 1 => {
let shell: &str = match user_shell { let shell: &str = match user_shell {
Err(_) => default_shell, Err(_) => default_shell,
@ -102,17 +121,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}; };
vec![shell, default_option] vec![shell, default_option]
} }
_ => { _ => commands,
let mut vector: Vec<&str> = Vec::new();
for (&k, v) in matches.args.iter() {
vector.push(k);
vector.push(&v.vals[0].to_str().unwrap());
}
vector
}
}; };
set_context(&newroot, &matches); set_context(newroot, &matches);
let pstatus = Command::new(command[0]) let pstatus = Command::new(command[0])
.args(&command[1..]) .args(&command[1..])
@ -132,7 +144,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) {
let group_str = options.value_of(options::GROUP).unwrap_or_default(); let group_str = options.value_of(options::GROUP).unwrap_or_default();
let groups_str = options.value_of(options::GROUPS).unwrap_or_default(); let groups_str = options.value_of(options::GROUPS).unwrap_or_default();
let userspec = match userspec_str { let userspec = match userspec_str {
Some(ref u) => { Some(u) => {
let s: Vec<&str> = u.split(':').collect(); let s: Vec<&str> = u.split(':').collect();
if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) { if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) {
crash!(1, "invalid userspec: `{}`", u) crash!(1, "invalid userspec: `{}`", u)

View file

@ -160,18 +160,14 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
let mut bytes = init_byte_array(); let mut bytes = init_byte_array();
loop { loop {
match rd.read(&mut bytes) { let num_bytes = rd.read(&mut bytes)?;
Ok(num_bytes) => { if num_bytes == 0 {
if num_bytes == 0 { return Ok((crc_final(crc, size), size));
return Ok((crc_final(crc, size), size));
}
for &b in bytes[..num_bytes].iter() {
crc = crc_update(crc, b);
}
size += num_bytes;
}
Err(err) => return Err(err),
} }
for &b in bytes[..num_bytes].iter() {
crc = crc_update(crc, b);
}
size += num_bytes;
} }
} }

View file

@ -50,9 +50,8 @@ fn mkdelim(col: usize, opts: &ArgMatches) -> String {
} }
fn ensure_nl(line: &mut String) { fn ensure_nl(line: &mut String) {
match line.chars().last() { if !line.ends_with('\n') {
Some('\n') => (), line.push('\n');
_ => line.push('\n'),
} }
} }

View file

@ -709,27 +709,26 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
return Err(format!("extra operand {:?}", paths[2]).into()); return Err(format!("extra operand {:?}", paths[2]).into());
} }
let (mut sources, target) = match options.target_dir { let target = match options.target_dir {
Some(ref target) => { Some(ref target) => {
// All path args are sources, and the target dir was // All path args are sources, and the target dir was
// specified separately // specified separately
(paths, PathBuf::from(target)) PathBuf::from(target)
} }
None => { None => {
// If there was no explicit target-dir, then use the last // If there was no explicit target-dir, then use the last
// path_arg // path_arg
let target = paths.pop().unwrap(); paths.pop().unwrap()
(paths, target)
} }
}; };
if options.strip_trailing_slashes { if options.strip_trailing_slashes {
for source in sources.iter_mut() { for source in paths.iter_mut() {
*source = source.components().as_path().to_owned() *source = source.components().as_path().to_owned()
} }
} }
Ok((sources, target)) Ok((paths, target))
} }
fn preserve_hardlinks( fn preserve_hardlinks(
@ -1088,7 +1087,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
} }
#[cfg(not(windows))] #[cfg(not(windows))]
#[allow(clippy::unnecessary_unwrap)] // needed for windows version #[allow(clippy::unnecessary_wraps)] // needed for windows version
fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
match std::os::unix::fs::symlink(source, dest).context(context) { match std::os::unix::fs::symlink(source, dest).context(context) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -1271,15 +1270,15 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
ReflinkMode::Always => unsafe { ReflinkMode::Always => unsafe {
let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32); let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32);
if result != 0 { if result != 0 {
return Err(format!( Err(format!(
"failed to clone {:?} from {:?}: {}", "failed to clone {:?} from {:?}: {}",
source, source,
dest, dest,
std::io::Error::last_os_error() std::io::Error::last_os_error()
) )
.into()); .into())
} else { } else {
return Ok(()); Ok(())
} }
}, },
ReflinkMode::Auto => unsafe { ReflinkMode::Auto => unsafe {
@ -1287,11 +1286,10 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
if result != 0 { if result != 0 {
fs::copy(source, dest).context(&*context_for(source, dest))?; fs::copy(source, dest).context(&*context_for(source, dest))?;
} }
Ok(())
}, },
ReflinkMode::Never => unreachable!(), ReflinkMode::Never => unreachable!(),
} }
Ok(())
} }
/// Copies `source` to `dest` using copy-on-write if possible. /// Copies `source` to `dest` using copy-on-write if possible.

View file

@ -92,7 +92,7 @@ where
T: BufRead, T: BufRead,
{ {
let mut input_iter = InputSplitter::new(input.lines().enumerate()); let mut input_iter = InputSplitter::new(input.lines().enumerate());
let mut split_writer = SplitWriter::new(&options); let mut split_writer = SplitWriter::new(options);
let ret = do_csplit(&mut split_writer, patterns, &mut input_iter); let ret = do_csplit(&mut split_writer, patterns, &mut input_iter);
// consume the rest // consume the rest

View file

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

View file

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

View file

@ -20,6 +20,7 @@ uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
memchr = "2" memchr = "2"
bstr = "0.2" bstr = "0.2"
atty = "0.2"
[[bin]] [[bin]]
name = "cut" name = "cut"

View file

@ -17,7 +17,6 @@ use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
use std::path::Path; use std::path::Path;
use self::searcher::Searcher; use self::searcher::Searcher;
use uucore::fs::is_stdout_interactive;
use uucore::ranges::Range; use uucore::ranges::Range;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
@ -127,7 +126,7 @@ enum Mode {
} }
fn stdout_writer() -> Box<dyn Write> { fn stdout_writer() -> Box<dyn Write> {
if is_stdout_interactive() { if atty::is(atty::Stream::Stdout) {
Box::new(stdout()) Box::new(stdout())
} else { } else {
Box::new(BufWriter::new(stdout())) as Box<dyn Write> Box::new(BufWriter::new(stdout())) as Box<dyn Write>

View file

@ -19,6 +19,8 @@ clap = "2.33"
chrono = "0.4" chrono = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version="0.3", features=[] } winapi = { version="0.3", features=[] }
[[bin]] [[bin]]

View file

@ -1,9 +1,9 @@
// This file is part of the uutils coreutils package. // * This file is part of the uutils coreutils package.
// // *
// (c) Derek Chiang <derekchiang93@gmail.com> // * (c) Derek Chiang <derekchiang93@gmail.com>
// // *
// For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // * file that was distributed with this source code.
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
@ -12,6 +12,7 @@ use chrono::prelude::DateTime;
use chrono::Local; use chrono::Local;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom;
use std::env; use std::env;
use std::fs; use std::fs;
#[cfg(not(windows))] #[cfg(not(windows))]
@ -24,8 +25,11 @@ use std::os::unix::fs::MetadataExt;
use std::os::windows::fs::MetadataExt; use std::os::windows::fs::MetadataExt;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::io::AsRawHandle; use std::os::windows::io::AsRawHandle;
#[cfg(windows)]
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, UNIX_EPOCH}; use std::time::{Duration, UNIX_EPOCH};
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
#[cfg(windows)] #[cfg(windows)]
use winapi::shared::minwindef::{DWORD, LPVOID}; use winapi::shared::minwindef::{DWORD, LPVOID};
@ -42,7 +46,7 @@ mod options {
pub const NULL: &str = "0"; pub const NULL: &str = "0";
pub const ALL: &str = "all"; pub const ALL: &str = "all";
pub const APPARENT_SIZE: &str = "apparent-size"; pub const APPARENT_SIZE: &str = "apparent-size";
pub const BLOCK_SIZE: &str = "B"; pub const BLOCK_SIZE: &str = "block-size";
pub const BYTES: &str = "b"; pub const BYTES: &str = "b";
pub const TOTAL: &str = "c"; pub const TOTAL: &str = "c";
pub const MAX_DEPTH: &str = "d"; pub const MAX_DEPTH: &str = "d";
@ -55,6 +59,7 @@ mod options {
pub const SI: &str = "si"; pub const SI: &str = "si";
pub const TIME: &str = "time"; pub const TIME: &str = "time";
pub const TIME_STYLE: &str = "time-style"; pub const TIME_STYLE: &str = "time-style";
pub const ONE_FILE_SYSTEM: &str = "one-file-system";
pub const FILE: &str = "FILE"; pub const FILE: &str = "FILE";
} }
@ -79,6 +84,7 @@ struct Options {
max_depth: Option<usize>, max_depth: Option<usize>,
total: bool, total: bool,
separate_dirs: bool, separate_dirs: bool,
one_file_system: bool,
} }
#[derive(PartialEq, Eq, Hash, Clone, Copy)] #[derive(PartialEq, Eq, Hash, Clone, Copy)]
@ -159,7 +165,7 @@ fn birth_u64(meta: &Metadata) -> Option<u64> {
} }
#[cfg(windows)] #[cfg(windows)]
fn get_size_on_disk(path: &PathBuf) -> u64 { fn get_size_on_disk(path: &Path) -> u64 {
let mut size_on_disk = 0; let mut size_on_disk = 0;
// bind file so it stays in scope until end of function // bind file so it stays in scope until end of function
@ -191,7 +197,7 @@ fn get_size_on_disk(path: &PathBuf) -> u64 {
} }
#[cfg(windows)] #[cfg(windows)]
fn get_file_info(path: &PathBuf) -> Option<FileInfo> { fn get_file_info(path: &Path) -> Option<FileInfo> {
let mut result = None; let mut result = None;
let file = match fs::File::open(path) { let file = match fs::File::open(path) {
@ -223,64 +229,22 @@ fn get_file_info(path: &PathBuf) -> Option<FileInfo> {
result result
} }
fn unit_string_to_number(s: &str) -> Option<u64> { fn read_block_size(s: Option<&str>) -> usize {
let mut offset = 0; if let Some(s) = s {
let mut s_chars = s.chars().rev(); parse_size(s)
.unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::BLOCK_SIZE)))
let (mut ch, multiple) = match s_chars.next() { } else {
Some('B') | Some('b') => ('B', 1000u64), for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
Some(ch) => (ch, 1024u64), if let Ok(env_size) = env::var(env_var) {
None => return None, if let Ok(v) = parse_size(&env_size) {
}; return v;
if ch == 'B' {
ch = s_chars.next()?;
offset += 1;
}
ch = ch.to_ascii_uppercase();
let unit = UNITS
.iter()
.rev()
.find(|&&(unit_ch, _)| unit_ch == ch)
.map(|&(_, val)| {
// we found a match, so increment offset
offset += 1;
val
})
.or_else(|| if multiple == 1024 { Some(0) } else { None })?;
let number = s[..s.len() - offset].parse::<u64>().ok()?;
Some(number * multiple.pow(unit))
}
fn translate_to_pure_number(s: &Option<&str>) -> Option<u64> {
match *s {
Some(ref s) => unit_string_to_number(s),
None => None,
}
}
fn read_block_size(s: Option<&str>) -> u64 {
match translate_to_pure_number(&s) {
Some(v) => v,
None => {
if let Some(value) = s {
show_error!("invalid --block-size argument '{}'", value);
};
for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
let env_size = env::var(env_var).ok();
if let Some(quantity) = translate_to_pure_number(&env_size.as_deref()) {
return quantity;
} }
} }
}
if env::var("POSIXLY_CORRECT").is_ok() { if env::var("POSIXLY_CORRECT").is_ok() {
512 512
} else { } else {
1024 1024
}
} }
} }
} }
@ -316,10 +280,18 @@ fn du(
Ok(entry) => match Stat::new(entry.path()) { Ok(entry) => match Stat::new(entry.path()) {
Ok(this_stat) => { Ok(this_stat) => {
if this_stat.is_dir { if this_stat.is_dir {
if options.one_file_system {
if let (Some(this_inode), Some(my_inode)) =
(this_stat.inode, my_stat.inode)
{
if this_inode.dev_id != my_inode.dev_id {
continue;
}
}
}
futures.push(du(this_stat, options, depth + 1, inodes)); futures.push(du(this_stat, options, depth + 1, inodes));
} else { } else {
if this_stat.inode.is_some() { if let Some(inode) = this_stat.inode {
let inode = this_stat.inode.unwrap();
if inodes.contains(&inode) { if inodes.contains(&inode) {
continue; continue;
} }
@ -358,7 +330,9 @@ fn du(
my_stat.size += stat.size; my_stat.size += stat.size;
my_stat.blocks += stat.blocks; my_stat.blocks += stat.blocks;
} }
options.max_depth == None || depth < options.max_depth.unwrap() options
.max_depth
.map_or(true, |max_depth| depth < max_depth)
})); }));
stats.push(my_stat); stats.push(my_stat);
Box::new(stats.into_iter()) Box::new(stats.into_iter())
@ -431,12 +405,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
although the apparent size is usually smaller, it may be larger due to holes \ although the apparent size is usually smaller, it may be larger due to holes \
in ('sparse') files, internal fragmentation, indirect blocks, and the like" in ('sparse') files, internal fragmentation, indirect blocks, and the like"
) )
.alias("app") // The GNU testsuite uses this alias .alias("app") // The GNU test suite uses this alias
) )
.arg( .arg(
Arg::with_name(options::BLOCK_SIZE) Arg::with_name(options::BLOCK_SIZE)
.short("B") .short("B")
.long("block-size") .long(options::BLOCK_SIZE)
.value_name("SIZE") .value_name("SIZE")
.help( .help(
"scale sizes by SIZE before printing them. \ "scale sizes by SIZE before printing them. \
@ -530,12 +504,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(options::SI) .long(options::SI)
.help("like -h, but use powers of 1000 not 1024") .help("like -h, but use powers of 1000 not 1024")
) )
// .arg( .arg(
// Arg::with_name("one-file-system") Arg::with_name(options::ONE_FILE_SYSTEM)
// .short("x") .short("x")
// .long("one-file-system") .long(options::ONE_FILE_SYSTEM)
// .help("skip directories on different file systems") .help("skip directories on different file systems")
// ) )
// .arg( // .arg(
// Arg::with_name("") // Arg::with_name("")
// .short("x") // .short("x")
@ -583,12 +557,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let max_depth_str = matches.value_of(options::MAX_DEPTH); let max_depth_str = matches.value_of(options::MAX_DEPTH);
let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::<usize>().ok()); let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::<usize>().ok());
match (max_depth_str, max_depth) { match (max_depth_str, max_depth) {
(Some(ref s), _) if summarize => { (Some(s), _) if summarize => {
show_error!("summarizing conflicts with --max-depth={}", *s); show_error!("summarizing conflicts with --max-depth={}", s);
return 1; return 1;
} }
(Some(ref s), None) => { (Some(s), None) => {
show_error!("invalid maximum depth '{}'", *s); show_error!("invalid maximum depth '{}'", s);
return 1; return 1;
} }
(Some(_), Some(_)) | (None, _) => { /* valid */ } (Some(_), Some(_)) | (None, _) => { /* valid */ }
@ -600,6 +574,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
max_depth, max_depth,
total: matches.is_present(options::TOTAL), total: matches.is_present(options::TOTAL),
separate_dirs: matches.is_present(options::SEPARATE_DIRS), separate_dirs: matches.is_present(options::SEPARATE_DIRS),
one_file_system: matches.is_present(options::ONE_FILE_SYSTEM),
}; };
let files = match matches.value_of(options::FILE) { let files = match matches.value_of(options::FILE) {
@ -609,7 +584,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
}; };
let block_size = read_block_size(matches.value_of(options::BLOCK_SIZE)); let block_size = u64::try_from(read_block_size(matches.value_of(options::BLOCK_SIZE))).unwrap();
let multiplier: u64 = if matches.is_present(options::SI) { let multiplier: u64 = if matches.is_present(options::SI) {
1000 1000
@ -745,31 +720,27 @@ Try '{} --help' for more information.",
0 0
} }
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
// NOTE:
// GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection
// GNU's du does distinguish between "invalid (suffix in) argument"
match error {
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
}
}
#[cfg(test)] #[cfg(test)]
mod test_du { mod test_du {
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; use super::*;
#[test]
fn test_translate_to_pure_number() {
let test_data = [
(Some("10".to_string()), Some(10)),
(Some("10K".to_string()), Some(10 * 1024)),
(Some("5M".to_string()), Some(5 * 1024 * 1024)),
(Some("900KB".to_string()), Some(900 * 1000)),
(Some("BAD_STRING".to_string()), None),
];
for it in test_data.iter() {
assert_eq!(translate_to_pure_number(&it.0.as_deref()), it.1);
}
}
#[test] #[test]
fn test_read_block_size() { fn test_read_block_size() {
let test_data = [ let test_data = [
(Some("10".to_string()), 10), (Some("1024".to_string()), 1024),
(Some("K".to_string()), 1024),
(None, 1024), (None, 1024),
(Some("BAD_STRING".to_string()), 1024),
]; ];
for it in test_data.iter() { for it in test_data.iter() {
assert_eq!(read_block_size(it.0.as_deref()), it.1); assert_eq!(read_block_size(it.0.as_deref()), it.1);

View file

@ -181,7 +181,7 @@ fn execute(no_newline: bool, escaped: bool, free: Vec<String>) -> io::Result<()>
write!(output, " ")?; write!(output, " ")?;
} }
if escaped { if escaped {
let should_stop = print_escaped(&input, &mut output)?; let should_stop = print_escaped(input, &mut output)?;
if should_stop { if should_stop {
break; break;
} }

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

@ -82,13 +82,10 @@ fn load_config_file(opts: &mut Options) -> Result<(), i32> {
Ini::load_from_file(file) Ini::load_from_file(file)
}; };
let conf = match conf { let conf = conf.map_err(|error| {
Ok(config) => config, eprintln!("env: error: \"{}\": {}", file, error);
Err(error) => { 1
eprintln!("env: error: \"{}\": {}", file, error); })?;
return Err(1);
}
};
for (_, prop) in &conf { for (_, prop) in &conf {
// ignore all INI section lines (treat them as comments) // ignore all INI section lines (treat them as comments)
@ -245,7 +242,7 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> {
} }
// set specified env vars // set specified env vars
for &(ref name, ref val) in &opts.sets { for &(name, val) in &opts.sets {
// FIXME: set_var() panics if name is an empty string // FIXME: set_var() panics if name is an empty string
env::set_var(name, val); env::set_var(name, val);
} }
@ -256,13 +253,10 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> {
// FIXME: this should just use execvp() (no fork()) on Unix-like systems // FIXME: this should just use execvp() (no fork()) on Unix-like systems
match Command::new(&*prog).args(args).status() { match Command::new(&*prog).args(args).status() {
Ok(exit) => { Ok(exit) if !exit.success() => return Err(exit.code().unwrap()),
if !exit.success() {
return Err(exit.code().unwrap());
}
}
Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127), Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127),
Err(_) => return Err(126), Err(_) => return Err(126),
Ok(_) => (),
} }
} else { } else {
// no program provided, so just dump all env vars to stdout // no program provided, so just dump all env vars to stdout

View file

@ -15,7 +15,6 @@ extern crate uucore;
use clap::{crate_version, App, Arg, ArgMatches}; use clap::{crate_version, App, Arg, ArgMatches};
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
use std::iter::repeat;
use std::str::from_utf8; use std::str::from_utf8;
use unicode_width::UnicodeWidthChar; use unicode_width::UnicodeWidthChar;
@ -90,7 +89,7 @@ impl Options {
}) })
.max() .max()
.unwrap(); // length of tabstops is guaranteed >= 1 .unwrap(); // length of tabstops is guaranteed >= 1
let tspaces = repeat(' ').take(nspaces).collect(); let tspaces = " ".repeat(nspaces);
let files: Vec<String> = match matches.values_of(options::FILES) { let files: Vec<String> = match matches.values_of(options::FILES) {
Some(s) => s.map(|v| v.to_string()).collect(), Some(s) => s.map(|v| v.to_string()).collect(),
@ -236,7 +235,7 @@ fn expand(options: Options) {
// now dump out either spaces if we're expanding, or a literal tab if we're not // now dump out either spaces if we're expanding, or a literal tab if we're not
if init || !options.iflag { if init || !options.iflag {
safe_unwrap!(output.write_all(&options.tspaces[..nts].as_bytes())); safe_unwrap!(output.write_all(options.tspaces[..nts].as_bytes()));
} else { } else {
safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); safe_unwrap!(output.write_all(&buf[byte..byte + nbytes]));
} }

View file

@ -37,7 +37,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
fn process_expr(token_strings: &[String]) -> Result<String, String> { fn process_expr(token_strings: &[String]) -> Result<String, String> {
let maybe_tokens = tokens::strings_to_tokens(&token_strings); let maybe_tokens = tokens::strings_to_tokens(token_strings);
let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens); let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens);
evaluate_ast(maybe_ast) evaluate_ast(maybe_ast)
} }
@ -56,11 +56,7 @@ fn print_expr_error(expr_error: &str) -> ! {
} }
fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> { fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
if maybe_ast.is_err() { maybe_ast.and_then(|ast| ast.evaluate())
Err(maybe_ast.err().unwrap())
} else {
maybe_ast.ok().unwrap().evaluate()
}
} }
fn maybe_handle_help_or_version(args: &[String]) -> bool { fn maybe_handle_help_or_version(args: &[String]) -> bool {

View file

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

View file

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

View file

@ -98,7 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
fn handle_obsolete(args: &[String]) -> (Vec<String>, Option<String>) { fn handle_obsolete(args: &[String]) -> (Vec<String>, Option<String>) {
for (i, arg) in args.iter().enumerate() { for (i, arg) in args.iter().enumerate() {
let slice = &arg; let slice = &arg;
if slice.starts_with('-') && slice.len() > 1 && slice.chars().nth(1).unwrap().is_digit(10) { if slice.starts_with('-') && slice.chars().nth(1).map_or(false, |c| c.is_digit(10)) {
let mut v = args.to_vec(); let mut v = args.to_vec();
v.remove(i); v.remove(i);
return (v, Some(slice[1..].to_owned())); return (v, Some(slice[1..].to_owned()));
@ -109,7 +109,7 @@ fn handle_obsolete(args: &[String]) -> (Vec<String>, Option<String>) {
fn fold(filenames: Vec<String>, bytes: bool, spaces: bool, width: usize) { fn fold(filenames: Vec<String>, bytes: bool, spaces: bool, width: usize) {
for filename in &filenames { for filename in &filenames {
let filename: &str = &filename; let filename: &str = filename;
let mut stdin_buf; let mut stdin_buf;
let mut file_buf; let mut file_buf;
let buffer = BufReader::new(if filename == "-" { let buffer = BufReader::new(if filename == "-" {

View file

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

View file

@ -59,7 +59,7 @@ impl Digest for blake2b_simd::State {
fn result(&mut self, out: &mut [u8]) { fn result(&mut self, out: &mut [u8]) {
let hash_result = &self.finalize(); let hash_result = &self.finalize();
out.copy_from_slice(&hash_result.as_bytes()); out.copy_from_slice(hash_result.as_bytes());
} }
fn reset(&mut self) { fn reset(&mut self) {

View file

@ -90,7 +90,7 @@ fn detect_algo<'a>(
512, 512,
), ),
"sha3sum" => match matches.value_of("bits") { "sha3sum" => match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(224) => ( Ok(224) => (
"SHA3-224", "SHA3-224",
Box::new(Sha3_224::new()) as Box<dyn Digest>, Box::new(Sha3_224::new()) as Box<dyn Digest>,
@ -140,7 +140,7 @@ fn detect_algo<'a>(
512, 512,
), ),
"shake128sum" => match matches.value_of("bits") { "shake128sum" => match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(bits) => ( Ok(bits) => (
"SHAKE128", "SHAKE128",
Box::new(Shake128::new()) as Box<dyn Digest>, Box::new(Shake128::new()) as Box<dyn Digest>,
@ -151,7 +151,7 @@ fn detect_algo<'a>(
None => crash!(1, "--bits required for SHAKE-128"), None => crash!(1, "--bits required for SHAKE-128"),
}, },
"shake256sum" => match matches.value_of("bits") { "shake256sum" => match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(bits) => ( Ok(bits) => (
"SHAKE256", "SHAKE256",
Box::new(Shake256::new()) as Box<dyn Digest>, Box::new(Shake256::new()) as Box<dyn Digest>,
@ -194,7 +194,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("sha3") { if matches.is_present("sha3") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(224) => set_or_crash( Ok(224) => set_or_crash(
"SHA3-224", "SHA3-224",
Box::new(Sha3_224::new()) as Box<dyn Digest>, Box::new(Sha3_224::new()) as Box<dyn Digest>,
@ -238,7 +238,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("shake128") { if matches.is_present("shake128") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits), Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits),
Err(err) => crash!(1, "{}", err), Err(err) => crash!(1, "{}", err),
}, },
@ -247,7 +247,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("shake256") { if matches.is_present("shake256") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match (&bits_str).parse::<usize>() { Some(bits_str) => match (bits_str).parse::<usize>() {
Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits), Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits),
Err(err) => crash!(1, "{}", err), Err(err) => crash!(1, "{}", err),
}, },
@ -255,10 +255,8 @@ fn detect_algo<'a>(
} }
} }
} }
if alg.is_none() { let alg = alg.unwrap_or_else(|| crash!(1, "You must specify hash algorithm!"));
crash!(1, "You must specify hash algorithm!") (name, alg, output_bits)
};
(name, alg.unwrap(), output_bits)
} }
} }
} }

View file

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

View file

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

View file

@ -299,29 +299,17 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
let considering_dir: bool = MainFunction::Directory == main_function; let considering_dir: bool = MainFunction::Directory == main_function;
let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) { let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) {
match matches.value_of(OPT_MODE) { let x = matches.value_of(OPT_MODE).ok_or(1)?;
Some(x) => match mode::parse(x, considering_dir) { Some(mode::parse(x, considering_dir).map_err(|err| {
Ok(y) => Some(y), show_error!("Invalid mode string: {}", err);
Err(err) => { 1
show_error!("Invalid mode string: {}", err); })?)
return Err(1);
}
},
None => {
return Err(1);
}
}
} else { } else {
None None
}; };
let backup_suffix = if matches.is_present(OPT_SUFFIX) { let backup_suffix = if matches.is_present(OPT_SUFFIX) {
match matches.value_of(OPT_SUFFIX) { matches.value_of(OPT_SUFFIX).ok_or(1)?
Some(x) => x,
None => {
return Err(1);
}
}
} else { } else {
"~" "~"
}; };
@ -379,7 +367,7 @@ fn directory(paths: Vec<String>, b: Behavior) -> i32 {
} }
} }
if mode::chmod(&path, b.mode()).is_err() { if mode::chmod(path, b.mode()).is_err() {
all_successful = false; all_successful = false;
continue; continue;
} }
@ -422,7 +410,7 @@ fn standard(paths: Vec<String>, b: Behavior) -> i32 {
return 1; return 1;
} }
if mode::chmod(&parent, b.mode()).is_err() { if mode::chmod(parent, b.mode()).is_err() {
show_error!("failed to chmod {}", parent.display()); show_error!("failed to chmod {}", parent.display());
return 1; return 1;
} }
@ -501,7 +489,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3
/// _target_ must be a non-directory /// _target_ must be a non-directory
/// ///
fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 { fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 {
if copy(file, &target, b).is_err() { if copy(file, target, b).is_err() {
1 1
} else { } else {
0 0
@ -563,7 +551,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
} }
} }
if mode::chmod(&to, b.mode()).is_err() { if mode::chmod(to, b.mode()).is_err() {
return Err(()); return Err(());
} }

View file

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

View file

@ -111,7 +111,7 @@ fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) {
while i < args.len() { while i < args.len() {
// this is safe because slice is valid when it is referenced // this is safe because slice is valid when it is referenced
let slice = &args[i].clone(); let slice = &args[i].clone();
if slice.starts_with('-') && slice.len() > 1 && slice.chars().nth(1).unwrap().is_digit(10) { if slice.starts_with('-') && slice.chars().nth(1).map_or(false, |c| c.is_digit(10)) {
let val = &slice[1..]; let val = &slice[1..];
match val.parse() { match val.parse() {
Ok(num) => { Ok(num) => {

View file

@ -27,7 +27,6 @@ use uucore::fs::{canonicalize, CanonicalizeMode};
pub struct Settings { pub struct Settings {
overwrite: OverwriteMode, overwrite: OverwriteMode,
backup: BackupMode, backup: BackupMode,
force: bool,
suffix: String, suffix: String,
symbolic: bool, symbolic: bool,
relative: bool, relative: bool,
@ -54,7 +53,7 @@ pub enum BackupMode {
fn get_usage() -> String { fn get_usage() -> String {
format!( format!(
"{0} [OPTION]... [-T] TARGET LINK_executable!() (1st form) "{0} [OPTION]... [-T] TARGET LINK_NAME (1st form)
{0} [OPTION]... TARGET (2nd form) {0} [OPTION]... TARGET (2nd form)
{0} [OPTION]... TARGET... DIRECTORY (3rd form) {0} [OPTION]... TARGET... DIRECTORY (3rd form)
{0} [OPTION]... -t DIRECTORY TARGET... (4th form)", {0} [OPTION]... -t DIRECTORY TARGET... (4th form)",
@ -64,7 +63,7 @@ fn get_usage() -> String {
fn get_long_usage() -> String { fn get_long_usage() -> String {
String::from( String::from(
" In the 1st form, create a link to TARGET with the name LINK_executable!(). " In the 1st form, create a link to TARGET with the name LINK_NAME.
In the 2nd form, create a link to TARGET in the current directory. In the 2nd form, create a link to TARGET in the current directory.
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY. In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.
Create hard links by default, symbolic links with --symbolic. Create hard links by default, symbolic links with --symbolic.
@ -78,17 +77,19 @@ fn get_long_usage() -> String {
static ABOUT: &str = "change file owner and group"; static ABOUT: &str = "change file owner and group";
static OPT_B: &str = "b"; mod options {
static OPT_BACKUP: &str = "backup"; pub const B: &str = "b";
static OPT_FORCE: &str = "force"; pub const BACKUP: &str = "backup";
static OPT_INTERACTIVE: &str = "interactive"; pub const FORCE: &str = "force";
static OPT_NO_DEREFERENCE: &str = "no-dereference"; pub const INTERACTIVE: &str = "interactive";
static OPT_SYMBOLIC: &str = "symbolic"; pub const NO_DEREFERENCE: &str = "no-dereference";
static OPT_SUFFIX: &str = "suffix"; pub const SYMBOLIC: &str = "symbolic";
static OPT_TARGET_DIRECTORY: &str = "target-directory"; pub const SUFFIX: &str = "suffix";
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; pub const TARGET_DIRECTORY: &str = "target-directory";
static OPT_RELATIVE: &str = "relative"; pub const NO_TARGET_DIRECTORY: &str = "no-target-directory";
static OPT_VERBOSE: &str = "verbose"; pub const RELATIVE: &str = "relative";
pub const VERBOSE: &str = "verbose";
}
static ARG_FILES: &str = "files"; static ARG_FILES: &str = "files";
@ -101,49 +102,44 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.about(ABOUT) .about(ABOUT)
.usage(&usage[..]) .usage(&usage[..])
.after_help(&long_usage[..]) .after_help(&long_usage[..])
.arg(Arg::with_name(OPT_B).short(OPT_B).help( .arg(Arg::with_name(options::B).short(options::B).help(
"make a backup of each file that would otherwise be overwritten or \ "make a backup of each file that would otherwise be overwritten or \
removed", removed",
)) ))
.arg( .arg(
Arg::with_name(OPT_BACKUP) Arg::with_name(options::BACKUP)
.long(OPT_BACKUP) .long(options::BACKUP)
.help( .help(
"make a backup of each file that would otherwise be overwritten \ "make a backup of each file that would otherwise be overwritten \
or removed", or removed",
) )
.takes_value(true) .takes_value(true)
.possible_value("simple") .possible_values(&[
.possible_value("never") "simple", "never", "numbered", "t", "existing", "nil", "none", "off",
.possible_value("numbered") ])
.possible_value("t")
.possible_value("existing")
.possible_value("nil")
.possible_value("none")
.possible_value("off")
.value_name("METHOD"), .value_name("METHOD"),
) )
// TODO: opts.arg( // TODO: opts.arg(
// Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \ // Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \
// to make hard links to directories"); // to make hard links to directories");
.arg( .arg(
Arg::with_name(OPT_FORCE) Arg::with_name(options::FORCE)
.short("f") .short("f")
.long(OPT_FORCE) .long(options::FORCE)
.help("remove existing destination files"), .help("remove existing destination files"),
) )
.arg( .arg(
Arg::with_name(OPT_INTERACTIVE) Arg::with_name(options::INTERACTIVE)
.short("i") .short("i")
.long(OPT_INTERACTIVE) .long(options::INTERACTIVE)
.help("prompt whether to remove existing destination files"), .help("prompt whether to remove existing destination files"),
) )
.arg( .arg(
Arg::with_name(OPT_NO_DEREFERENCE) Arg::with_name(options::NO_DEREFERENCE)
.short("n") .short("n")
.long(OPT_NO_DEREFERENCE) .long(options::NO_DEREFERENCE)
.help( .help(
"treat LINK_executable!() as a normal file if it is a \ "treat LINK_NAME as a normal file if it is a \
symbolic link to a directory", symbolic link to a directory",
), ),
) )
@ -153,43 +149,46 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// TODO: opts.arg( // TODO: opts.arg(
// Arg::with_name(("P", "physical", "make hard links directly to symbolic links"); // Arg::with_name(("P", "physical", "make hard links directly to symbolic links");
.arg( .arg(
Arg::with_name(OPT_SYMBOLIC) Arg::with_name(options::SYMBOLIC)
.short("s") .short("s")
.long("symbolic") .long("symbolic")
.help("make symbolic links instead of hard links"), .help("make symbolic links instead of hard links")
// override added for https://github.com/uutils/coreutils/issues/2359
.overrides_with(options::SYMBOLIC),
) )
.arg( .arg(
Arg::with_name(OPT_SUFFIX) Arg::with_name(options::SUFFIX)
.short("S") .short("S")
.long(OPT_SUFFIX) .long(options::SUFFIX)
.help("override the usual backup suffix") .help("override the usual backup suffix")
.value_name("SUFFIX") .value_name("SUFFIX")
.takes_value(true), .takes_value(true),
) )
.arg( .arg(
Arg::with_name(OPT_TARGET_DIRECTORY) Arg::with_name(options::TARGET_DIRECTORY)
.short("t") .short("t")
.long(OPT_TARGET_DIRECTORY) .long(options::TARGET_DIRECTORY)
.help("specify the DIRECTORY in which to create the links") .help("specify the DIRECTORY in which to create the links")
.value_name("DIRECTORY") .value_name("DIRECTORY")
.conflicts_with(OPT_NO_TARGET_DIRECTORY), .conflicts_with(options::NO_TARGET_DIRECTORY),
) )
.arg( .arg(
Arg::with_name(OPT_NO_TARGET_DIRECTORY) Arg::with_name(options::NO_TARGET_DIRECTORY)
.short("T") .short("T")
.long(OPT_NO_TARGET_DIRECTORY) .long(options::NO_TARGET_DIRECTORY)
.help("treat LINK_executable!() as a normal file always"), .help("treat LINK_NAME as a normal file always"),
) )
.arg( .arg(
Arg::with_name(OPT_RELATIVE) Arg::with_name(options::RELATIVE)
.short("r") .short("r")
.long(OPT_RELATIVE) .long(options::RELATIVE)
.help("create symbolic links relative to link location"), .help("create symbolic links relative to link location")
.requires(options::SYMBOLIC),
) )
.arg( .arg(
Arg::with_name(OPT_VERBOSE) Arg::with_name(options::VERBOSE)
.short("v") .short("v")
.long(OPT_VERBOSE) .long(options::VERBOSE)
.help("print name of each linked file"), .help("print name of each linked file"),
) )
.arg( .arg(
@ -209,18 +208,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.map(PathBuf::from) .map(PathBuf::from)
.collect(); .collect();
let overwrite_mode = if matches.is_present(OPT_FORCE) { let overwrite_mode = if matches.is_present(options::FORCE) {
OverwriteMode::Force OverwriteMode::Force
} else if matches.is_present(OPT_INTERACTIVE) { } else if matches.is_present(options::INTERACTIVE) {
OverwriteMode::Interactive OverwriteMode::Interactive
} else { } else {
OverwriteMode::NoClobber OverwriteMode::NoClobber
}; };
let backup_mode = if matches.is_present(OPT_B) { let backup_mode = if matches.is_present(options::B) {
BackupMode::ExistingBackup BackupMode::ExistingBackup
} else if matches.is_present(OPT_BACKUP) { } else if matches.is_present(options::BACKUP) {
match matches.value_of(OPT_BACKUP) { match matches.value_of(options::BACKUP) {
None => BackupMode::ExistingBackup, None => BackupMode::ExistingBackup,
Some(mode) => match mode { Some(mode) => match mode {
"simple" | "never" => BackupMode::SimpleBackup, "simple" | "never" => BackupMode::SimpleBackup,
@ -234,8 +233,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
BackupMode::NoBackup BackupMode::NoBackup
}; };
let backup_suffix = if matches.is_present(OPT_SUFFIX) { let backup_suffix = if matches.is_present(options::SUFFIX) {
matches.value_of(OPT_SUFFIX).unwrap() matches.value_of(options::SUFFIX).unwrap()
} else { } else {
"~" "~"
}; };
@ -243,14 +242,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let settings = Settings { let settings = Settings {
overwrite: overwrite_mode, overwrite: overwrite_mode,
backup: backup_mode, backup: backup_mode,
force: matches.is_present(OPT_FORCE),
suffix: backup_suffix.to_string(), suffix: backup_suffix.to_string(),
symbolic: matches.is_present(OPT_SYMBOLIC), symbolic: matches.is_present(options::SYMBOLIC),
relative: matches.is_present(OPT_RELATIVE), relative: matches.is_present(options::RELATIVE),
target_dir: matches.value_of(OPT_TARGET_DIRECTORY).map(String::from), target_dir: matches
no_target_dir: matches.is_present(OPT_NO_TARGET_DIRECTORY), .value_of(options::TARGET_DIRECTORY)
no_dereference: matches.is_present(OPT_NO_DEREFERENCE), .map(String::from),
verbose: matches.is_present(OPT_VERBOSE), no_target_dir: matches.is_present(options::NO_TARGET_DIRECTORY),
no_dereference: matches.is_present(options::NO_DEREFERENCE),
verbose: matches.is_present(options::VERBOSE),
}; };
exec(&paths[..], &settings) exec(&paths[..], &settings)
@ -260,17 +260,17 @@ fn exec(files: &[PathBuf], settings: &Settings) -> i32 {
// Handle cases where we create links in a directory first. // Handle cases where we create links in a directory first.
if let Some(ref name) = settings.target_dir { if let Some(ref name) = settings.target_dir {
// 4th form: a directory is specified by -t. // 4th form: a directory is specified by -t.
return link_files_in_dir(files, &PathBuf::from(name), &settings); return link_files_in_dir(files, &PathBuf::from(name), settings);
} }
if !settings.no_target_dir { if !settings.no_target_dir {
if files.len() == 1 { if files.len() == 1 {
// 2nd form: the target directory is the current directory. // 2nd form: the target directory is the current directory.
return link_files_in_dir(files, &PathBuf::from("."), &settings); return link_files_in_dir(files, &PathBuf::from("."), settings);
} }
let last_file = &PathBuf::from(files.last().unwrap()); let last_file = &PathBuf::from(files.last().unwrap());
if files.len() > 2 || last_file.is_dir() { if files.len() > 2 || last_file.is_dir() {
// 3rd form: create links in the last argument. // 3rd form: create links in the last argument.
return link_files_in_dir(&files[0..files.len() - 1], last_file, &settings); return link_files_in_dir(&files[0..files.len() - 1], last_file, settings);
} }
} }
@ -310,47 +310,48 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
let mut all_successful = true; let mut all_successful = true;
for srcpath in files.iter() { for srcpath in files.iter() {
let targetpath = if settings.no_dereference && settings.force { let targetpath =
// In that case, we don't want to do link resolution if settings.no_dereference && matches!(settings.overwrite, OverwriteMode::Force) {
// We need to clean the target // In that case, we don't want to do link resolution
if is_symlink(target_dir) { // We need to clean the target
if target_dir.is_file() { if is_symlink(target_dir) {
if let Err(e) = fs::remove_file(target_dir) { if target_dir.is_file() {
show_error!("Could not update {}: {}", target_dir.display(), e) if let Err(e) = fs::remove_file(target_dir) {
}; show_error!("Could not update {}: {}", target_dir.display(), e)
} };
if target_dir.is_dir() { }
// Not sure why but on Windows, the symlink can be if target_dir.is_dir() {
// considered as a dir // Not sure why but on Windows, the symlink can be
// See test_ln::test_symlink_no_deref_dir // considered as a dir
if let Err(e) = fs::remove_dir(target_dir) { // See test_ln::test_symlink_no_deref_dir
show_error!("Could not update {}: {}", target_dir.display(), e) if let Err(e) = fs::remove_dir(target_dir) {
}; show_error!("Could not update {}: {}", target_dir.display(), e)
} };
}
target_dir.to_path_buf()
} else {
match srcpath.as_os_str().to_str() {
Some(name) => {
match Path::new(name).file_name() {
Some(basename) => target_dir.join(basename),
// This can be None only for "." or "..". Trying
// to create a link with such name will fail with
// EEXIST, which agrees with the behavior of GNU
// coreutils.
None => target_dir.join(name),
} }
} }
None => { target_dir.to_path_buf()
show_error!( } else {
"cannot stat '{}': No such file or directory", match srcpath.as_os_str().to_str() {
srcpath.display() Some(name) => {
); match Path::new(name).file_name() {
all_successful = false; Some(basename) => target_dir.join(basename),
continue; // This can be None only for "." or "..". Trying
// to create a link with such name will fail with
// EEXIST, which agrees with the behavior of GNU
// coreutils.
None => target_dir.join(name),
}
}
None => {
show_error!(
"cannot stat '{}': No such file or directory",
srcpath.display()
);
all_successful = false;
continue;
}
} }
} };
};
if let Err(e) = link(srcpath, &targetpath, settings) { if let Err(e) = link(srcpath, &targetpath, settings) {
show_error!( show_error!(
@ -371,7 +372,8 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> { fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> {
let src_abs = canonicalize(src, CanonicalizeMode::Normal)?; let src_abs = canonicalize(src, CanonicalizeMode::Normal)?;
let dst_abs = canonicalize(dst, CanonicalizeMode::Normal)?; let mut dst_abs = canonicalize(dst.parent().unwrap(), CanonicalizeMode::Normal)?;
dst_abs.push(dst.components().last().unwrap());
let suffix_pos = src_abs let suffix_pos = src_abs
.components() .components()
.zip(dst_abs.components()) .zip(dst_abs.components())
@ -392,7 +394,7 @@ fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> {
fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> { fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> {
let mut backup_path = None; let mut backup_path = None;
let source: Cow<'_, Path> = if settings.relative { let source: Cow<'_, Path> = if settings.relative {
relative_path(&src, dst)? relative_path(src, dst)?
} else { } else {
src.into() src.into()
}; };
@ -421,10 +423,6 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> {
} }
} }
if settings.no_dereference && settings.force && dst.exists() {
fs::remove_file(dst)?;
}
if settings.symbolic { if settings.symbolic {
symlink(&source, dst)?; symlink(&source, dst)?;
} else { } else {

View file

@ -1243,7 +1243,7 @@ fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
Sort::Time => entries.sort_by_key(|k| { Sort::Time => entries.sort_by_key(|k| {
Reverse( Reverse(
k.md() k.md()
.and_then(|md| get_system_time(&md, config)) .and_then(|md| get_system_time(md, config))
.unwrap_or(UNIX_EPOCH), .unwrap_or(UNIX_EPOCH),
) )
}), }),
@ -1323,7 +1323,7 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter<Stdout>)
.filter(|p| p.file_type().map(|ft| ft.is_dir()).unwrap_or(false)) .filter(|p| p.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
{ {
let _ = writeln!(out, "\n{}:", e.p_buf.display()); let _ = writeln!(out, "\n{}:", e.p_buf.display());
enter_directory(&e, config, out); enter_directory(e, config, out);
} }
} }
} }
@ -1339,8 +1339,8 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) { fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) {
if let Some(md) = entry.md() { if let Some(md) = entry.md() {
( (
display_symlink_count(&md).len(), display_symlink_count(md).len(),
display_size_or_rdev(&md, config).len(), display_size_or_rdev(md, config).len(),
) )
} else { } else {
(0, 0) (0, 0)
@ -1371,7 +1371,7 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
display_item_long(item, max_links, max_width, config, out); display_item_long(item, max_links, max_width, config, out);
} }
} else { } else {
let names = items.iter().filter_map(|i| display_file_name(&i, config)); let names = items.iter().filter_map(|i| display_file_name(i, config));
match (&config.format, config.width) { match (&config.format, config.width) {
(Format::Columns, Some(width)) => { (Format::Columns, Some(width)) => {
@ -1482,40 +1482,40 @@ fn display_item_long(
#[cfg(unix)] #[cfg(unix)]
{ {
if config.inode { if config.inode {
let _ = write!(out, "{} ", get_inode(&md)); let _ = write!(out, "{} ", get_inode(md));
} }
} }
let _ = write!( let _ = write!(
out, out,
"{} {}", "{} {}",
display_permissions(&md, true), display_permissions(md, true),
pad_left(display_symlink_count(&md), max_links), pad_left(display_symlink_count(md), max_links),
); );
if config.long.owner { if config.long.owner {
let _ = write!(out, " {}", display_uname(&md, config)); let _ = write!(out, " {}", display_uname(md, config));
} }
if config.long.group { if config.long.group {
let _ = write!(out, " {}", display_group(&md, config)); let _ = write!(out, " {}", display_group(md, config));
} }
// Author is only different from owner on GNU/Hurd, so we reuse // Author is only different from owner on GNU/Hurd, so we reuse
// the owner, since GNU/Hurd is not currently supported by Rust. // the owner, since GNU/Hurd is not currently supported by Rust.
if config.long.author { if config.long.author {
let _ = write!(out, " {}", display_uname(&md, config)); let _ = write!(out, " {}", display_uname(md, config));
} }
let _ = writeln!( let _ = writeln!(
out, out,
" {} {} {}", " {} {} {}",
pad_left(display_size_or_rdev(md, config), max_size), pad_left(display_size_or_rdev(md, config), max_size),
display_date(&md, config), display_date(md, config),
// unwrap is fine because it fails when metadata is not available // unwrap is fine because it fails when metadata is not available
// but we already know that it is because it's checked at the // but we already know that it is because it's checked at the
// start of the function. // start of the function.
display_file_name(&item, config).unwrap().contents, display_file_name(item, config).unwrap().contents,
); );
} }
@ -1741,7 +1741,7 @@ fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
let mut width = name.width(); let mut width = name.width();
if let Some(ls_colors) = &config.color { if let Some(ls_colors) = &config.color {
name = color_name(&ls_colors, &path.p_buf, name, path.md()?); name = color_name(ls_colors, &path.p_buf, name, path.md()?);
} }
if config.indicator_style != IndicatorStyle::None { if config.indicator_style != IndicatorStyle::None {
@ -1786,7 +1786,7 @@ fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
} }
fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String { fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String {
match ls_colors.style_for_path_with_metadata(path, Some(&md)) { match ls_colors.style_for_path_with_metadata(path, Some(md)) {
Some(style) => style.to_ansi_term_style().paint(name).to_string(), Some(style) => style.to_ansi_term_style().paint(name).to_string(),
None => name, None => name,
} }

View file

@ -77,7 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let mode_match = matches.value_of(OPT_MODE); let mode_match = matches.value_of(OPT_MODE);
let mode: u16 = match mode_match { let mode: u16 = match mode_match {
Some(m) => { Some(m) => {
let res: Option<u16> = u16::from_str_radix(&m, 8).ok(); let res: Option<u16> = u16::from_str_radix(m, 8).ok();
match res { match res {
Some(r) => r, Some(r) => r,
_ => crash!(1, "no mode given"), _ => crash!(1, "no mode given"),

View file

@ -59,7 +59,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
let mode = match matches.value_of(options::MODE) { let mode = match matches.value_of(options::MODE) {
Some(m) => match usize::from_str_radix(&m, 8) { Some(m) => match usize::from_str_radix(m, 8) {
Ok(m) => m, Ok(m) => m,
Err(e) => { Err(e) => {
show_error!("invalid mode: {}", e); show_error!("invalid mode: {}", e);

View file

@ -165,9 +165,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}; };
if dry_run { if dry_run {
dry_exec(tmpdir, prefix, rand, &suffix) dry_exec(tmpdir, prefix, rand, suffix)
} else { } else {
exec(tmpdir, prefix, rand, &suffix, make_dir, suppress_file_err) exec(tmpdir, prefix, rand, suffix, make_dir, suppress_file_err)
} }
} }

View file

@ -19,7 +19,7 @@ clap = "2.33"
uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" } uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" }
uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" } uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
crossterm = ">=0.19" crossterm = ">=0.19"
atty = "0.2.14" atty = "0.2"
unicode-width = "0.1.7" unicode-width = "0.1.7"
unicode-segmentation = "1.7.1" unicode-segmentation = "1.7.1"

View file

@ -11,7 +11,6 @@
extern crate uucore; extern crate uucore;
use std::{ use std::{
convert::TryInto,
fs::File, fs::File,
io::{stdin, stdout, BufReader, Read, Stdout, Write}, io::{stdin, stdout, BufReader, Read, Stdout, Write},
path::Path, path::Path,
@ -32,6 +31,8 @@ use crossterm::{
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
const BELL: &str = "\x07";
pub mod options { pub mod options {
pub const SILENT: &str = "silent"; pub const SILENT: &str = "silent";
pub const LOGICAL: &str = "logical"; pub const LOGICAL: &str = "logical";
@ -53,14 +54,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = App::new(executable!()) let matches = App::new(executable!())
.about("A file perusal filter for CRT viewing.") .about("A file perusal filter for CRT viewing.")
.version(crate_version!()) .version(crate_version!())
// The commented arguments below are unimplemented:
/*
.arg( .arg(
Arg::with_name(options::SILENT) Arg::with_name(options::SILENT)
.short("d") .short("d")
.long(options::SILENT) .long(options::SILENT)
.help("Display help instead of ringing bell"), .help("Display help instead of ringing bell"),
) )
// The commented arguments below are unimplemented:
/*
.arg( .arg(
Arg::with_name(options::LOGICAL) Arg::with_name(options::LOGICAL)
.short("f") .short("f")
@ -140,6 +141,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.get_matches_from(args); .get_matches_from(args);
let mut buff = String::new(); let mut buff = String::new();
let silent = matches.is_present(options::SILENT);
if let Some(files) = matches.values_of(options::FILES) { if let Some(files) = matches.values_of(options::FILES) {
let mut stdout = setup_term(); let mut stdout = setup_term();
let length = files.len(); let length = files.len();
@ -162,14 +164,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
let mut reader = BufReader::new(File::open(file).unwrap()); let mut reader = BufReader::new(File::open(file).unwrap());
reader.read_to_string(&mut buff).unwrap(); reader.read_to_string(&mut buff).unwrap();
more(&buff, &mut stdout, next_file.copied()); more(&buff, &mut stdout, next_file.copied(), silent);
buff.clear(); buff.clear();
} }
reset_term(&mut stdout); reset_term(&mut stdout);
} else if atty::isnt(atty::Stream::Stdin) { } else if atty::isnt(atty::Stream::Stdin) {
stdin().read_to_string(&mut buff).unwrap(); stdin().read_to_string(&mut buff).unwrap();
let mut stdout = setup_term(); let mut stdout = setup_term();
more(&buff, &mut stdout, None); more(&buff, &mut stdout, None, silent);
reset_term(&mut stdout); reset_term(&mut stdout);
} else { } else {
show_usage_error!("bad usage"); show_usage_error!("bad usage");
@ -204,38 +206,18 @@ fn reset_term(stdout: &mut std::io::Stdout) {
#[inline(always)] #[inline(always)]
fn reset_term(_: &mut usize) {} fn reset_term(_: &mut usize) {}
fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) { fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
let (cols, rows) = terminal::size().unwrap(); let (cols, rows) = terminal::size().unwrap();
let lines = break_buff(buff, usize::from(cols)); let lines = break_buff(buff, usize::from(cols));
let line_count: u16 = lines.len().try_into().unwrap();
let mut upper_mark = 0; let mut pager = Pager::new(rows as usize, lines, next_file, silent);
let mut lines_left = line_count.saturating_sub(upper_mark + rows); pager.draw(stdout, false);
if pager.should_close() {
draw( return;
&mut upper_mark,
rows,
&mut stdout,
lines.clone(),
line_count,
next_file,
);
let is_last = next_file.is_none();
// Specifies whether we have reached the end of the file and should
// return on the next key press. However, we immediately return when
// this is the last file.
let mut to_be_done = false;
if lines_left == 0 && is_last {
if is_last {
return;
} else {
to_be_done = true;
}
} }
loop { loop {
let mut wrong_key = false;
if event::poll(Duration::from_millis(10)).unwrap() { if event::poll(Duration::from_millis(10)).unwrap() {
match event::read().unwrap() { match event::read().unwrap() {
Event::Key(KeyEvent { Event::Key(KeyEvent {
@ -257,59 +239,127 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) {
code: KeyCode::Char(' '), code: KeyCode::Char(' '),
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
}) => { }) => {
upper_mark = upper_mark.saturating_add(rows.saturating_sub(1)); pager.page_down();
} }
Event::Key(KeyEvent { Event::Key(KeyEvent {
code: KeyCode::Up, code: KeyCode::Up,
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
}) => { }) => {
upper_mark = upper_mark.saturating_sub(rows.saturating_sub(1)); pager.page_up();
}
_ => {
wrong_key = true;
} }
_ => continue,
} }
lines_left = line_count.saturating_sub(upper_mark + rows);
draw(
&mut upper_mark,
rows,
&mut stdout,
lines.clone(),
line_count,
next_file,
);
if lines_left == 0 { pager.draw(stdout, wrong_key);
if to_be_done || is_last { if pager.should_close() {
return; return;
}
to_be_done = true;
} }
} }
} }
} }
fn draw( struct Pager<'a> {
upper_mark: &mut u16, // The current line at the top of the screen
rows: u16, upper_mark: usize,
mut stdout: &mut std::io::Stdout, // The number of rows that fit on the screen
content_rows: usize,
lines: Vec<String>, lines: Vec<String>,
lc: u16, next_file: Option<&'a str>,
next_file: Option<&str>, line_count: usize,
) { close_on_down: bool,
execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap(); silent: bool,
let (up_mark, lower_mark) = calc_range(*upper_mark, rows, lc); }
// Reduce the row by 1 for the prompt
let displayed_lines = lines
.iter()
.skip(up_mark.into())
.take(usize::from(rows.saturating_sub(1)));
for line in displayed_lines { impl<'a> Pager<'a> {
stdout fn new(rows: usize, lines: Vec<String>, next_file: Option<&'a str>, silent: bool) -> Self {
.write_all(format!("\r{}\n", line).as_bytes()) let line_count = lines.len();
.unwrap(); Self {
upper_mark: 0,
content_rows: rows - 1,
lines,
next_file,
line_count,
close_on_down: false,
silent,
}
}
fn should_close(&mut self) -> bool {
if self.upper_mark + self.content_rows >= self.line_count {
if self.close_on_down {
return true;
}
if self.next_file.is_none() {
return true;
} else {
self.close_on_down = true;
}
} else {
self.close_on_down = false;
}
false
}
fn page_down(&mut self) {
self.upper_mark += self.content_rows;
}
fn page_up(&mut self) {
self.upper_mark = self.upper_mark.saturating_sub(self.content_rows);
}
fn draw(&self, stdout: &mut std::io::Stdout, wrong_key: bool) {
let lower_mark = self.line_count.min(self.upper_mark + self.content_rows);
self.draw_lines(stdout);
self.draw_prompt(stdout, lower_mark, wrong_key);
stdout.flush().unwrap();
}
fn draw_lines(&self, stdout: &mut std::io::Stdout) {
execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
let displayed_lines = self
.lines
.iter()
.skip(self.upper_mark)
.take(self.content_rows);
for line in displayed_lines {
stdout
.write_all(format!("\r{}\n", line).as_bytes())
.unwrap();
}
}
fn draw_prompt(&self, stdout: &mut Stdout, lower_mark: usize, wrong_key: bool) {
let status_inner = if lower_mark == self.line_count {
format!("Next file: {}", self.next_file.unwrap_or_default())
} else {
format!(
"{}%",
(lower_mark as f64 / self.line_count as f64 * 100.0).round() as u16
)
};
let status = format!("--More--({})", status_inner);
let banner = match (self.silent, wrong_key) {
(true, true) => "[Press 'h' for instructions. (unimplemented)]".to_string(),
(true, false) => format!("{}[Press space to continue, 'q' to quit.]", status),
(false, true) => format!("{}{}", status, BELL),
(false, false) => status,
};
write!(
stdout,
"\r{}{}{}",
Attribute::Reverse,
banner,
Attribute::Reset
)
.unwrap();
} }
make_prompt_and_flush(&mut stdout, lower_mark, lc, next_file);
*upper_mark = up_mark;
} }
// Break the lines on the cols of the terminal // Break the lines on the cols of the terminal
@ -350,52 +400,11 @@ fn break_line(line: &str, cols: usize) -> Vec<String> {
lines lines
} }
// Calculate upper_mark based on certain parameters
fn calc_range(mut upper_mark: u16, rows: u16, line_count: u16) -> (u16, u16) {
let mut lower_mark = upper_mark.saturating_add(rows);
if lower_mark >= line_count {
upper_mark = line_count.saturating_sub(rows).saturating_add(1);
lower_mark = line_count;
} else {
lower_mark = lower_mark.saturating_sub(1)
}
(upper_mark, lower_mark)
}
// Make a prompt similar to original more
fn make_prompt_and_flush(stdout: &mut Stdout, lower_mark: u16, lc: u16, next_file: Option<&str>) {
let status = if lower_mark == lc {
format!("Next file: {}", next_file.unwrap_or_default())
} else {
format!(
"{}%",
(lower_mark as f64 / lc as f64 * 100.0).round() as u16
)
};
write!(
stdout,
"\r{}--More--({}){}",
Attribute::Reverse,
status,
Attribute::Reset
)
.unwrap();
stdout.flush().unwrap();
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{break_line, calc_range}; use super::break_line;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
// It is good to test the above functions
#[test]
fn test_calc_range() {
assert_eq!((0, 24), calc_range(0, 25, 100));
assert_eq!((50, 74), calc_range(50, 25, 100));
assert_eq!((76, 100), calc_range(85, 25, 100));
}
#[test] #[test]
fn test_break_lines_long() { fn test_break_lines_long() {
let mut test_string = String::with_capacity(100); let mut test_string = String::with_capacity(100);

View file

@ -389,7 +389,7 @@ fn rename_with_fallback(from: &Path, to: &Path) -> io::Result<()> {
let file_type = metadata.file_type(); let file_type = metadata.file_type();
if file_type.is_symlink() { if file_type.is_symlink() {
rename_symlink_fallback(&from, &to)?; rename_symlink_fallback(from, to)?;
} else if file_type.is_dir() { } else if file_type.is_dir() {
// We remove the destination directory if it exists to match the // We remove the destination directory if it exists to match the
// behavior of `fs::rename`. As far as I can tell, `fs_extra`'s // behavior of `fs::rename`. As far as I can tell, `fs_extra`'s

View file

@ -247,7 +247,7 @@ fn nl<T: Read>(reader: &mut BufReader<T>, settings: &Settings) {
let mut line_filter: fn(&str, &regex::Regex) -> bool = pass_regex; let mut line_filter: fn(&str, &regex::Regex) -> bool = pass_regex;
for mut l in reader.lines().map(|r| r.unwrap()) { for mut l in reader.lines().map(|r| r.unwrap()) {
// Sanitize the string. We want to print the newline ourselves. // Sanitize the string. We want to print the newline ourselves.
if !l.is_empty() && l.chars().rev().next().unwrap() == '\n' { if l.ends_with('\n') {
l.pop(); l.pop();
} }
// Next we iterate through the individual chars to see if this // Next we iterate through the individual chars to see if this

View file

@ -17,6 +17,7 @@ path = "src/nohup.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
libc = "0.2.42" libc = "0.2.42"
atty = "0.2"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -19,7 +19,6 @@ use std::fs::{File, OpenOptions};
use std::io::Error; use std::io::Error;
use std::os::unix::prelude::*; use std::os::unix::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static ABOUT: &str = "Run COMMAND ignoring hangup signals."; static ABOUT: &str = "Run COMMAND ignoring hangup signals.";
@ -84,7 +83,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
fn replace_fds() { fn replace_fds() {
if is_stdin_interactive() { if atty::is(atty::Stream::Stdin) {
let new_stdin = match File::open(Path::new("/dev/null")) { let new_stdin = match File::open(Path::new("/dev/null")) {
Ok(t) => t, Ok(t) => t,
Err(e) => crash!(2, "Cannot replace STDIN: {}", e), Err(e) => crash!(2, "Cannot replace STDIN: {}", e),
@ -94,7 +93,7 @@ fn replace_fds() {
} }
} }
if is_stdout_interactive() { if atty::is(atty::Stream::Stdout) {
let new_stdout = find_stdout(); let new_stdout = find_stdout();
let fd = new_stdout.as_raw_fd(); let fd = new_stdout.as_raw_fd();
@ -103,7 +102,7 @@ fn replace_fds() {
} }
} }
if is_stderr_interactive() && unsafe { dup2(1, 2) } != 2 { if atty::is(atty::Stream::Stderr) && unsafe { dup2(1, 2) } != 2 {
crash!(2, "Cannot replace STDERR: {}", Error::last_os_error()) crash!(2, "Cannot replace STDERR: {}", Error::last_os_error())
} }
} }

View file

@ -238,7 +238,7 @@ fn format_and_print_delimited(s: &str, options: &NumfmtOptions) -> Result<()> {
} }
if field_selected { if field_selected {
print!("{}", format_string(&field.trim_start(), options, None)?); print!("{}", format_string(field.trim_start(), options, None)?);
} else { } else {
// print unselected field without conversion // print unselected field without conversion
print!("{}", field); print!("{}", field);
@ -271,7 +271,7 @@ fn format_and_print_whitespace(s: &str, options: &NumfmtOptions) -> Result<()> {
None None
}; };
print!("{}", format_string(&field, options, implicit_padding)?); print!("{}", format_string(field, options, implicit_padding)?);
} else { } else {
// print unselected field without conversion // print unselected field without conversion
print!("{}{}", prefix, field); print!("{}{}", prefix, field);

View file

@ -2,6 +2,7 @@
use std::fmt; use std::fmt;
#[allow(clippy::enum_variant_names)]
#[derive(Copy)] #[derive(Copy)]
pub enum FormatWriter { pub enum FormatWriter {
IntWriter(fn(u64) -> String), IntWriter(fn(u64) -> String),

View file

@ -115,7 +115,7 @@ impl<'a> MemoryDecoder<'a> {
/// Creates a clone of the internal buffer. The clone only contain the valid data. /// Creates a clone of the internal buffer. The clone only contain the valid data.
pub fn clone_buffer(&self, other: &mut Vec<u8>) { pub fn clone_buffer(&self, other: &mut Vec<u8>) {
other.clone_from(&self.data); other.clone_from(self.data);
other.resize(self.used_normal_length, 0); other.resize(self.used_normal_length, 0);
} }

View file

@ -43,6 +43,7 @@ use crate::partialreader::*;
use crate::peekreader::*; use crate::peekreader::*;
use crate::prn_char::format_ascii_dump; use crate::prn_char::format_ascii_dump;
use clap::{self, crate_version, AppSettings, Arg, ArgMatches}; use clap::{self, crate_version, AppSettings, Arg, ArgMatches};
use uucore::parse_size::ParseSizeError;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes
@ -128,42 +129,34 @@ impl OdOptions {
} }
}; };
let mut skip_bytes = match matches.value_of(options::SKIP_BYTES) { let mut skip_bytes = matches.value_of(options::SKIP_BYTES).map_or(0, |s| {
None => 0, parse_number_of_bytes(s).unwrap_or_else(|e| {
Some(s) => match parse_number_of_bytes(&s) { crash!(1, "{}", format_error_message(e, s, options::SKIP_BYTES))
Ok(i) => i, })
Err(_) => { });
return Err(format!("Invalid argument --skip-bytes={}", s));
}
},
};
let mut label: Option<usize> = None; let mut label: Option<usize> = None;
let input_strings = match parse_inputs(&matches) { let parsed_input = parse_inputs(&matches).map_err(|e| format!("Invalid inputs: {}", e))?;
Ok(CommandLineInputs::FileNames(v)) => v, let input_strings = match parsed_input {
Ok(CommandLineInputs::FileAndOffset((f, s, l))) => { CommandLineInputs::FileNames(v) => v,
CommandLineInputs::FileAndOffset((f, s, l)) => {
skip_bytes = s; skip_bytes = s;
label = l; label = l;
vec![f] vec![f]
} }
Err(e) => {
return Err(format!("Invalid inputs: {}", e));
}
}; };
let formats = match parse_format_flags(&args) { let formats = parse_format_flags(&args)?;
Ok(f) => f,
Err(e) => { let mut line_bytes = matches.value_of(options::WIDTH).map_or(16, |s| {
return Err(e); if matches.occurrences_of(options::WIDTH) == 0 {
} return 16;
}; };
parse_number_of_bytes(s)
.unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::WIDTH)))
});
let mut line_bytes = match matches.value_of(options::WIDTH) {
None => 16,
Some(_) if matches.occurrences_of(options::WIDTH) == 0 => 16,
Some(s) => s.parse::<usize>().unwrap_or(0),
};
let min_bytes = formats.iter().fold(1, |max, next| { let min_bytes = formats.iter().fold(1, |max, next| {
cmp::max(max, next.formatter_item_info.byte_size) cmp::max(max, next.formatter_item_info.byte_size)
}); });
@ -174,15 +167,11 @@ impl OdOptions {
let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES); let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES);
let read_bytes = match matches.value_of(options::READ_BYTES) { let read_bytes = matches.value_of(options::READ_BYTES).map(|s| {
None => None, parse_number_of_bytes(s).unwrap_or_else(|e| {
Some(s) => match parse_number_of_bytes(&s) { crash!(1, "{}", format_error_message(e, s, options::READ_BYTES))
Ok(i) => Some(i), })
Err(_) => { });
return Err(format!("Invalid argument --read-bytes={}", s));
}
},
};
let radix = match matches.value_of(options::ADDRESS_RADIX) { let radix = match matches.value_of(options::ADDRESS_RADIX) {
None => Radix::Octal, None => Radix::Octal,
@ -263,7 +252,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("S") .short("S")
.long(options::STRINGS) .long(options::STRINGS)
.help( .help(
"output strings of at least BYTES graphic chars. 3 is assumed when \ "NotImplemented: output strings of at least BYTES graphic chars. 3 is assumed when \
BYTES is not specified.", BYTES is not specified.",
) )
.default_value("3") .default_value("3")
@ -453,8 +442,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let od_options = match OdOptions::new(clap_matches, args) { let od_options = match OdOptions::new(clap_matches, args) {
Err(s) => { Err(s) => {
show_usage_error!("{}", s); crash!(1, "{}", s);
return 1;
} }
Ok(o) => o, Ok(o) => o,
}; };
@ -537,7 +525,7 @@ where
print_bytes( print_bytes(
&input_offset.format_byte_offset(), &input_offset.format_byte_offset(),
&memory_decoder, &memory_decoder,
&output_info, output_info,
); );
} }
@ -636,3 +624,13 @@ fn open_input_peek_reader(
let pr = PartialReader::new(mf, skip_bytes, read_bytes); let pr = PartialReader::new(mf, skip_bytes, read_bytes);
PeekReader::new(pr) PeekReader::new(pr)
} }
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
// NOTE:
// GNU's od echos affected flag, -N or --read-bytes (-j or --skip-bytes, etc.), depending user's selection
// GNU's od does distinguish between "invalid (suffix in) argument"
match error {
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
}
}

View file

@ -68,7 +68,7 @@ impl OutputInfo {
let print_width_line = print_width_block * (line_bytes / byte_size_block); let print_width_line = print_width_block * (line_bytes / byte_size_block);
let spaced_formatters = let spaced_formatters =
OutputInfo::create_spaced_formatter_info(&formats, byte_size_block, print_width_block); OutputInfo::create_spaced_formatter_info(formats, byte_size_block, print_width_block);
OutputInfo { OutputInfo {
byte_size_line: line_bytes, byte_size_line: line_bytes,

View file

@ -108,10 +108,8 @@ pub fn parse_format_flags(args: &[String]) -> Result<Vec<ParsedFormatterItemInfo
for arg in arg_iter { for arg in arg_iter {
if expect_type_string { if expect_type_string {
match parse_type_string(arg) { let v = parse_type_string(arg)?;
Ok(v) => formats.extend(v.into_iter()), formats.extend(v.into_iter());
Err(e) => return Err(e),
}
expect_type_string = false; expect_type_string = false;
} else if arg.starts_with("--") { } else if arg.starts_with("--") {
if arg.len() == 2 { if arg.len() == 2 {
@ -119,10 +117,8 @@ pub fn parse_format_flags(args: &[String]) -> Result<Vec<ParsedFormatterItemInfo
} }
if arg.starts_with("--format=") { if arg.starts_with("--format=") {
let params: String = arg.chars().skip_while(|c| *c != '=').skip(1).collect(); let params: String = arg.chars().skip_while(|c| *c != '=').skip(1).collect();
match parse_type_string(&params) { let v = parse_type_string(&params)?;
Ok(v) => formats.extend(v.into_iter()), formats.extend(v.into_iter());
Err(e) => return Err(e),
}
} }
if arg == "--format" { if arg == "--format" {
expect_type_string = true; expect_type_string = true;
@ -145,10 +141,8 @@ pub fn parse_format_flags(args: &[String]) -> Result<Vec<ParsedFormatterItemInfo
} }
} }
if !format_spec.is_empty() { if !format_spec.is_empty() {
match parse_type_string(&format_spec) { let v = parse_type_string(&format_spec)?;
Ok(v) => formats.extend(v.into_iter()), formats.extend(v.into_iter());
Err(e) => return Err(e),
}
expect_type_string = false; expect_type_string = false;
} }
} }
@ -275,17 +269,13 @@ fn parse_type_string(params: &str) -> Result<Vec<ParsedFormatterItemInfo>, Strin
let mut chars = params.chars(); let mut chars = params.chars();
let mut ch = chars.next(); let mut ch = chars.next();
while ch.is_some() { while let Some(type_char) = ch {
let type_char = ch.unwrap(); let type_char = format_type(type_char).ok_or_else(|| {
let type_char = match format_type(type_char) { format!(
Some(t) => t, "unexpected char '{}' in format specification '{}'",
None => { type_char, params
return Err(format!( )
"unexpected char '{}' in format specification '{}'", })?;
type_char, params
));
}
};
let type_cat = format_type_category(type_char); let type_cat = format_type_category(type_char);
@ -301,30 +291,25 @@ fn parse_type_string(params: &str) -> Result<Vec<ParsedFormatterItemInfo>, Strin
ch = chars.next(); ch = chars.next();
} }
if !decimal_size.is_empty() { if !decimal_size.is_empty() {
byte_size = match decimal_size.parse() { byte_size = decimal_size.parse().map_err(|_| {
Err(_) => { format!(
return Err(format!( "invalid number '{}' in format specification '{}'",
"invalid number '{}' in format specification '{}'", decimal_size, params
decimal_size, params )
)) })?;
}
Ok(n) => n,
}
} }
} }
if is_format_dump_char(ch, &mut show_ascii_dump) { if is_format_dump_char(ch, &mut show_ascii_dump) {
ch = chars.next(); ch = chars.next();
} }
match od_format_type(type_char, byte_size) { let ft = od_format_type(type_char, byte_size).ok_or_else(|| {
Some(ft) => formats.push(ParsedFormatterItemInfo::new(ft, show_ascii_dump)), format!(
None => { "invalid size '{}' in format specification '{}'",
return Err(format!( byte_size, params
"invalid size '{}' in format specification '{}'", )
byte_size, params })?;
)) formats.push(ParsedFormatterItemInfo::new(ft, show_ascii_dump));
}
}
} }
Ok(formats) Ok(formats)
@ -335,16 +320,13 @@ pub fn parse_format_flags_str(
args_str: &Vec<&'static str>, args_str: &Vec<&'static str>,
) -> Result<Vec<FormatterItemInfo>, String> { ) -> Result<Vec<FormatterItemInfo>, String> {
let args: Vec<String> = args_str.iter().map(|s| s.to_string()).collect(); let args: Vec<String> = args_str.iter().map(|s| s.to_string()).collect();
match parse_format_flags(&args) { parse_format_flags(&args).map(|v| {
Err(e) => Err(e), // tests using this function assume add_ascii_dump is not set
Ok(v) => { v.into_iter()
// tests using this function assume add_ascii_dump is not set .inspect(|f| assert!(!f.add_ascii_dump))
Ok(v.into_iter() .map(|f| f.formatter_item_info)
.inspect(|f| assert!(!f.add_ascii_dump)) .collect()
.map(|f| f.formatter_item_info) })
.collect())
}
}
} }
#[test] #[test]

View file

@ -55,7 +55,7 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs,
// if any of the options -A, -j, -N, -t, -v or -w are present there is no offset // if any of the options -A, -j, -N, -t, -v or -w are present there is no offset
if !matches.opts_present(&["A", "j", "N", "t", "v", "w"]) { if !matches.opts_present(&["A", "j", "N", "t", "v", "w"]) {
// test if the last input can be parsed as an offset. // test if the last input can be parsed as an offset.
let offset = parse_offset_operand(&input_strings[input_strings.len() - 1]); let offset = parse_offset_operand(input_strings[input_strings.len() - 1]);
if let Ok(n) = offset { if let Ok(n) = offset {
// if there is just 1 input (stdin), an offset must start with '+' // if there is just 1 input (stdin), an offset must start with '+'
if input_strings.len() == 1 && input_strings[0].starts_with('+') { if input_strings.len() == 1 && input_strings[0].starts_with('+') {
@ -88,7 +88,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineI
match input_strings.len() { match input_strings.len() {
0 => Ok(CommandLineInputs::FileNames(vec!["-".to_string()])), 0 => Ok(CommandLineInputs::FileNames(vec!["-".to_string()])),
1 => { 1 => {
let offset0 = parse_offset_operand(&input_strings[0]); let offset0 = parse_offset_operand(input_strings[0]);
Ok(match offset0 { Ok(match offset0 {
Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)), Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)),
_ => CommandLineInputs::FileNames( _ => CommandLineInputs::FileNames(
@ -97,8 +97,8 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineI
}) })
} }
2 => { 2 => {
let offset0 = parse_offset_operand(&input_strings[0]); let offset0 = parse_offset_operand(input_strings[0]);
let offset1 = parse_offset_operand(&input_strings[1]); let offset1 = parse_offset_operand(input_strings[1]);
match (offset0, offset1) { match (offset0, offset1) {
(Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
"-".to_string(), "-".to_string(),
@ -114,8 +114,8 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineI
} }
} }
3 => { 3 => {
let offset = parse_offset_operand(&input_strings[1]); let offset = parse_offset_operand(input_strings[1]);
let label = parse_offset_operand(&input_strings[2]); let label = parse_offset_operand(input_strings[2]);
match (offset, label) { match (offset, label) {
(Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].to_string(), input_strings[0].to_string(),

View file

@ -1,14 +1,17 @@
pub fn parse_number_of_bytes(s: &str) -> Result<usize, &'static str> { use uucore::parse_size::{parse_size, ParseSizeError};
pub fn parse_number_of_bytes(s: &str) -> Result<usize, ParseSizeError> {
let mut start = 0; let mut start = 0;
let mut len = s.len(); let mut len = s.len();
let mut radix = 10; let mut radix = 16;
let mut multiply = 1; let mut multiply = 1;
if s.starts_with("0x") || s.starts_with("0X") { if s.starts_with("0x") || s.starts_with("0X") {
start = 2; start = 2;
radix = 16;
} else if s.starts_with('0') { } else if s.starts_with('0') {
radix = 8; radix = 8;
} else {
return parse_size(&s[start..]);
} }
let mut ends_with = s.chars().rev(); let mut ends_with = s.chars().rev();
@ -56,78 +59,33 @@ pub fn parse_number_of_bytes(s: &str) -> Result<usize, &'static str> {
Some('P') => 1000 * 1000 * 1000 * 1000 * 1000, Some('P') => 1000 * 1000 * 1000 * 1000 * 1000,
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
Some('E') => 1000 * 1000 * 1000 * 1000 * 1000 * 1000, Some('E') => 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
_ => return Err("parse failed"), _ => return Err(ParseSizeError::ParseFailure(s.to_string())),
} }
} }
_ => {} _ => {}
} }
match usize::from_str_radix(&s[start..len], radix) { let factor = match usize::from_str_radix(&s[start..len], radix) {
Ok(i) => Ok(i * multiply), Ok(f) => f,
Err(_) => Err("parse failed"), Err(e) => return Err(ParseSizeError::ParseFailure(e.to_string())),
} };
} factor
.checked_mul(multiply)
#[allow(dead_code)] .ok_or_else(|| ParseSizeError::SizeTooBig(s.to_string()))
fn parse_number_of_bytes_str(s: &str) -> Result<usize, &'static str> {
parse_number_of_bytes(&String::from(s))
} }
#[test] #[test]
fn test_parse_number_of_bytes() { fn test_parse_number_of_bytes() {
// normal decimal numbers
assert_eq!(0, parse_number_of_bytes_str("0").unwrap());
assert_eq!(5, parse_number_of_bytes_str("5").unwrap());
assert_eq!(999, parse_number_of_bytes_str("999").unwrap());
assert_eq!(2 * 512, parse_number_of_bytes_str("2b").unwrap());
assert_eq!(2 * 1024, parse_number_of_bytes_str("2k").unwrap());
assert_eq!(4 * 1024, parse_number_of_bytes_str("4K").unwrap());
assert_eq!(2 * 1048576, parse_number_of_bytes_str("2m").unwrap());
assert_eq!(4 * 1048576, parse_number_of_bytes_str("4M").unwrap());
assert_eq!(1073741824, parse_number_of_bytes_str("1G").unwrap());
assert_eq!(2000, parse_number_of_bytes_str("2kB").unwrap());
assert_eq!(4000, parse_number_of_bytes_str("4KB").unwrap());
assert_eq!(2000000, parse_number_of_bytes_str("2mB").unwrap());
assert_eq!(4000000, parse_number_of_bytes_str("4MB").unwrap());
assert_eq!(2000000000, parse_number_of_bytes_str("2GB").unwrap());
// octal input // octal input
assert_eq!(8, parse_number_of_bytes_str("010").unwrap()); assert_eq!(8, parse_number_of_bytes("010").unwrap());
assert_eq!(8 * 512, parse_number_of_bytes_str("010b").unwrap()); assert_eq!(8 * 512, parse_number_of_bytes("010b").unwrap());
assert_eq!(8 * 1024, parse_number_of_bytes_str("010k").unwrap()); assert_eq!(8 * 1024, parse_number_of_bytes("010k").unwrap());
assert_eq!(8 * 1048576, parse_number_of_bytes_str("010m").unwrap()); assert_eq!(8 * 1_048_576, parse_number_of_bytes("010m").unwrap());
// hex input // hex input
assert_eq!(15, parse_number_of_bytes_str("0xf").unwrap()); assert_eq!(15, parse_number_of_bytes("0xf").unwrap());
assert_eq!(15, parse_number_of_bytes_str("0XF").unwrap()); assert_eq!(15, parse_number_of_bytes("0XF").unwrap());
assert_eq!(27, parse_number_of_bytes_str("0x1b").unwrap()); assert_eq!(27, parse_number_of_bytes("0x1b").unwrap());
assert_eq!(16 * 1024, parse_number_of_bytes_str("0x10k").unwrap()); assert_eq!(16 * 1024, parse_number_of_bytes("0x10k").unwrap());
assert_eq!(16 * 1048576, parse_number_of_bytes_str("0x10m").unwrap()); assert_eq!(16 * 1_048_576, parse_number_of_bytes("0x10m").unwrap());
// invalid input
parse_number_of_bytes_str("").unwrap_err();
parse_number_of_bytes_str("-1").unwrap_err();
parse_number_of_bytes_str("1e2").unwrap_err();
parse_number_of_bytes_str("xyz").unwrap_err();
parse_number_of_bytes_str("b").unwrap_err();
parse_number_of_bytes_str("1Y").unwrap_err();
parse_number_of_bytes_str("").unwrap_err();
}
#[test]
#[cfg(target_pointer_width = "64")]
fn test_parse_number_of_bytes_64bits() {
assert_eq!(1099511627776, parse_number_of_bytes_str("1T").unwrap());
assert_eq!(1125899906842624, parse_number_of_bytes_str("1P").unwrap());
assert_eq!(
1152921504606846976,
parse_number_of_bytes_str("1E").unwrap()
);
assert_eq!(2000000000000, parse_number_of_bytes_str("2TB").unwrap());
assert_eq!(2000000000000000, parse_number_of_bytes_str("2PB").unwrap());
assert_eq!(
2000000000000000000,
parse_number_of_bytes_str("2EB").unwrap()
);
} }

View file

@ -36,16 +36,15 @@ impl<R: Read> Read for PartialReader<R> {
while self.skip > 0 { while self.skip > 0 {
let skip_count = cmp::min(self.skip, MAX_SKIP_BUFFER); let skip_count = cmp::min(self.skip, MAX_SKIP_BUFFER);
match self.inner.read(&mut bytes[..skip_count]) { match self.inner.read(&mut bytes[..skip_count])? {
Ok(0) => { 0 => {
// this is an error as we still have more to skip // this is an error as we still have more to skip
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::UnexpectedEof, io::ErrorKind::UnexpectedEof,
"tried to skip past end of input", "tried to skip past end of input",
)); ));
} }
Ok(n) => self.skip -= n, n => self.skip -= n,
Err(e) => return Err(e),
} }
} }
} }

View file

@ -118,10 +118,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// check a path, given as a slice of it's components and an operating mode // check a path, given as a slice of it's components and an operating mode
fn check_path(mode: &Mode, path: &[String]) -> bool { fn check_path(mode: &Mode, path: &[String]) -> bool {
match *mode { match *mode {
Mode::Basic => check_basic(&path), Mode::Basic => check_basic(path),
Mode::Extra => check_default(&path) && check_extra(&path), Mode::Extra => check_default(path) && check_extra(path),
Mode::Both => check_basic(&path) && check_extra(&path), Mode::Both => check_basic(path) && check_extra(path),
_ => check_default(&path), _ => check_default(path),
} }
} }
@ -156,7 +156,7 @@ fn check_basic(path: &[String]) -> bool {
); );
return false; return false;
} }
if !check_portable_chars(&p) { if !check_portable_chars(p) {
return false; return false;
} }
} }
@ -168,7 +168,7 @@ fn check_basic(path: &[String]) -> bool {
fn check_extra(path: &[String]) -> bool { fn check_extra(path: &[String]) -> bool {
// components: leading hyphens // components: leading hyphens
for p in path { for p in path {
if !no_leading_hyphen(&p) { if !no_leading_hyphen(p) {
writeln!( writeln!(
&mut std::io::stderr(), &mut std::io::stderr(),
"leading hyphen in file name component '{}'", "leading hyphen in file name component '{}'",
@ -241,13 +241,14 @@ fn no_leading_hyphen(path_segment: &str) -> bool {
// check whether a path segment contains only valid (read: portable) characters // check whether a path segment contains only valid (read: portable) characters
fn check_portable_chars(path_segment: &str) -> bool { fn check_portable_chars(path_segment: &str) -> bool {
let valid_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-".to_string(); const VALID_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-";
for ch in path_segment.chars() { for (i, ch) in path_segment.as_bytes().iter().enumerate() {
if !valid_str.contains(ch) { if !VALID_CHARS.contains(ch) {
let invalid = path_segment[i..].chars().next().unwrap();
writeln!( writeln!(
&mut std::io::stderr(), &mut std::io::stderr(),
"nonportable character '{}' in file name component '{}'", "nonportable character '{}' in file name component '{}'",
ch, invalid,
path_segment path_segment
); );
return false; return false;

View file

@ -283,7 +283,7 @@ impl Pinky {
} }
} }
print!(" {}", time_string(&ut)); print!(" {}", time_string(ut));
let mut s = ut.host(); let mut s = ut.host();
if self.include_where && !s.is_empty() { if self.include_where && !s.is_empty() {

View file

@ -401,18 +401,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
for file_group in file_groups { for file_group in file_groups {
let result_options = build_options(&matches, &file_group, args.join(" ")); let result_options = build_options(&matches, &file_group, args.join(" "));
let options = match result_options {
Ok(options) => options,
Err(err) => {
print_error(&matches, err);
return 1;
}
};
if result_options.is_err() { let cmd_result = if let Ok(group) = file_group.iter().exactly_one() {
print_error(&matches, result_options.err().unwrap()); pr(group, &options)
return 1;
}
let options = &result_options.unwrap();
let cmd_result = if file_group.len() == 1 {
pr(&file_group.get(0).unwrap(), options)
} else { } else {
mpr(&file_group, options) mpr(&file_group, &options)
}; };
let status = match cmd_result { let status = match cmd_result {
@ -442,11 +442,12 @@ fn recreate_arguments(args: &[String]) -> Vec<String> {
let mut arguments = args.to_owned(); let mut arguments = args.to_owned();
let num_option = args.iter().find_position(|x| n_regex.is_match(x.trim())); let num_option = args.iter().find_position(|x| n_regex.is_match(x.trim()));
if let Some((pos, _value)) = num_option { if let Some((pos, _value)) = num_option {
let num_val_opt = args.get(pos + 1); if let Some(num_val_opt) = args.get(pos + 1) {
if num_val_opt.is_some() && !num_regex.is_match(num_val_opt.unwrap()) { if !num_regex.is_match(num_val_opt) {
let could_be_file = arguments.remove(pos + 1); let could_be_file = arguments.remove(pos + 1);
arguments.insert(pos + 1, format!("{}", NumberingMode::default().width)); arguments.insert(pos + 1, format!("{}", NumberingMode::default().width));
arguments.insert(pos + 2, could_be_file); arguments.insert(pos + 2, could_be_file);
}
} }
} }
@ -666,12 +667,13 @@ fn build_options(
None => end_page_in_plus_option, None => end_page_in_plus_option,
}; };
if end_page.is_some() && start_page > end_page.unwrap() { if let Some(end_page) = end_page {
return Err(PrError::EncounteredErrors(format!( if start_page > end_page {
"invalid --pages argument '{}:{}'", return Err(PrError::EncounteredErrors(format!(
start_page, "invalid --pages argument '{}:{}'",
end_page.unwrap() start_page, end_page
))); )));
}
} }
let default_lines_per_page = if form_feed_used { let default_lines_per_page = if form_feed_used {
@ -947,7 +949,7 @@ fn read_stream_and_create_pages(
let current_page = x + 1; let current_page = x + 1;
current_page >= start_page current_page >= start_page
&& (last_page.is_none() || current_page <= last_page.unwrap()) && last_page.map_or(true, |last_page| current_page <= last_page)
}), }),
) )
} }
@ -996,8 +998,8 @@ fn mpr(paths: &[String], options: &OutputOptions) -> Result<i32, PrError> {
for (_key, file_line_group) in file_line_groups.into_iter() { for (_key, file_line_group) in file_line_groups.into_iter() {
for file_line in file_line_group { for file_line in file_line_group {
if file_line.line_content.is_err() { if let Err(e) = file_line.line_content {
return Err(file_line.line_content.unwrap_err().into()); return Err(e.into());
} }
let new_page_number = file_line.page_number; let new_page_number = file_line.page_number;
if page_counter != new_page_number { if page_counter != new_page_number {
@ -1030,8 +1032,7 @@ fn print_page(lines: &[FileLine], options: &OutputOptions, page: usize) -> Resul
let lines_written = write_columns(lines, options, out)?; let lines_written = write_columns(lines, options, out)?;
for index in 0..trailer_content.len() { for (index, x) in trailer_content.iter().enumerate() {
let x = trailer_content.get(index).unwrap();
out.write_all(x.as_bytes())?; out.write_all(x.as_bytes())?;
if index + 1 != trailer_content.len() { if index + 1 != trailer_content.len() {
out.write_all(line_separator)?; out.write_all(line_separator)?;
@ -1074,8 +1075,7 @@ fn write_columns(
let mut offset = 0; let mut offset = 0;
for col in 0..columns { for col in 0..columns {
let mut inserted = 0; let mut inserted = 0;
for i in offset..lines.len() { for line in &lines[offset..] {
let line = lines.get(i).unwrap();
if line.file_id != col { if line.file_id != col {
break; break;
} }
@ -1114,7 +1114,7 @@ fn write_columns(
for (i, cell) in row.iter().enumerate() { for (i, cell) in row.iter().enumerate() {
if cell.is_none() && options.merge_files_print.is_some() { if cell.is_none() && options.merge_files_print.is_some() {
out.write_all( out.write_all(
get_line_for_printing(&options, &blank_line, columns, i, &line_width, indexes) get_line_for_printing(options, &blank_line, columns, i, &line_width, indexes)
.as_bytes(), .as_bytes(),
)?; )?;
} else if cell.is_none() { } else if cell.is_none() {
@ -1124,7 +1124,7 @@ fn write_columns(
let file_line = cell.unwrap(); let file_line = cell.unwrap();
out.write_all( out.write_all(
get_line_for_printing(&options, file_line, columns, i, &line_width, indexes) get_line_for_printing(options, file_line, columns, i, &line_width, indexes)
.as_bytes(), .as_bytes(),
)?; )?;
lines_printed += 1; lines_printed += 1;
@ -1149,7 +1149,7 @@ fn get_line_for_printing(
indexes: usize, indexes: usize,
) -> String { ) -> String {
let blank_line = String::new(); let blank_line = String::new();
let formatted_line_number = get_formatted_line_number(&options, file_line.line_number, index); let formatted_line_number = get_formatted_line_number(options, file_line.line_number, index);
let mut complete_line = format!( let mut complete_line = format!(
"{}{}", "{}{}",

View file

@ -26,7 +26,7 @@ impl Formatter for CninetyNineHexFloatf {
) -> Option<FormatPrimitive> { ) -> Option<FormatPrimitive> {
let second_field = field.second_field.unwrap_or(6) + 1; let second_field = field.second_field.unwrap_or(6) + 1;
let analysis = FloatAnalysis::analyze( let analysis = FloatAnalysis::analyze(
&str_in, str_in,
initial_prefix, initial_prefix,
Some(second_field as usize), Some(second_field as usize),
None, None,

View file

@ -55,18 +55,9 @@ impl Formatter for Decf {
); );
// strip trailing zeroes // strip trailing zeroes
if let Some(ref post_dec) = f_sci.post_decimal { if let Some(ref post_dec) = f_sci.post_decimal {
let mut i = post_dec.len(); let trimmed = post_dec.trim_end_matches('0');
{ if trimmed.len() != post_dec.len() {
let mut it = post_dec.chars(); f_sci.post_decimal = Some(trimmed.to_owned());
while let Some(c) = it.next_back() {
if c != '0' {
break;
}
i -= 1;
}
}
if i != post_dec.len() {
f_sci.post_decimal = Some(String::from(&post_dec[0..i]));
} }
} }
let f_fl = get_primitive_dec( let f_fl = get_primitive_dec(

View file

@ -247,8 +247,12 @@ pub fn get_primitive_dec(
first_segment.len() as isize - 1, first_segment.len() as isize - 1,
) )
} else { } else {
match first_segment.chars().next() { match first_segment
Some('0') => { .chars()
.next()
.expect("float_common: no chars in first segment.")
{
'0' => {
let it = second_segment.chars().enumerate(); let it = second_segment.chars().enumerate();
let mut m: isize = 0; let mut m: isize = 0;
let mut pre = String::from("0"); let mut pre = String::from("0");
@ -266,10 +270,7 @@ pub fn get_primitive_dec(
} }
(pre, post, m) (pre, post, m)
} }
Some(_) => (first_segment, second_segment, 0), _ => (first_segment, second_segment, 0),
None => {
panic!("float_common: no chars in first segment.");
}
} }
} }
} else { } else {
@ -298,11 +299,11 @@ pub fn get_primitive_dec(
pub fn primitive_to_str_common(prim: &FormatPrimitive, field: &FormatField) -> String { pub fn primitive_to_str_common(prim: &FormatPrimitive, field: &FormatField) -> String {
let mut final_str = String::new(); let mut final_str = String::new();
if let Some(ref prefix) = prim.prefix { if let Some(ref prefix) = prim.prefix {
final_str.push_str(&prefix); final_str.push_str(prefix);
} }
match prim.pre_decimal { match prim.pre_decimal {
Some(ref pre_decimal) => { Some(ref pre_decimal) => {
final_str.push_str(&pre_decimal); final_str.push_str(pre_decimal);
} }
None => { None => {
panic!( panic!(

View file

@ -21,7 +21,7 @@ impl Formatter for Floatf {
) -> Option<FormatPrimitive> { ) -> Option<FormatPrimitive> {
let second_field = field.second_field.unwrap_or(6) + 1; let second_field = field.second_field.unwrap_or(6) + 1;
let analysis = FloatAnalysis::analyze( let analysis = FloatAnalysis::analyze(
&str_in, str_in,
initial_prefix, initial_prefix,
None, None,
Some(second_field as usize), Some(second_field as usize),

View file

@ -252,7 +252,7 @@ impl Formatter for Intf {
fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String {
let mut final_str: String = String::new(); let mut final_str: String = String::new();
if let Some(ref prefix) = prim.prefix { if let Some(ref prefix) = prim.prefix {
final_str.push_str(&prefix); final_str.push_str(prefix);
} }
// integral second fields is zero-padded minimum-width // integral second fields is zero-padded minimum-width
// which gets handled before general minimum-width // which gets handled before general minimum-width
@ -266,7 +266,7 @@ impl Formatter for Intf {
i -= 1; i -= 1;
} }
} }
final_str.push_str(&pre_decimal); final_str.push_str(pre_decimal);
} }
None => { None => {
panic!( panic!(

View file

@ -235,7 +235,7 @@ pub fn num_format(field: &FormatField, in_str_opt: Option<&String>) -> Option<St
let as_str = format!("{}", provided_num); let as_str = format!("{}", provided_num);
let initial_prefix = get_initial_prefix( let initial_prefix = get_initial_prefix(
&as_str, &as_str,
&field.field_type field.field_type
); );
tmp=formatter.get_primitive(field, &initial_prefix, &as_str) tmp=formatter.get_primitive(field, &initial_prefix, &as_str)
.expect("err during default provided num"); .expect("err during default provided num");
@ -258,7 +258,7 @@ pub fn num_format(field: &FormatField, in_str_opt: Option<&String>) -> Option<St
// any formatter (int or float) // any formatter (int or float)
let initial_prefix = get_initial_prefix( let initial_prefix = get_initial_prefix(
in_str, in_str,
&field.field_type field.field_type
); );
// then get the FormatPrimitive from the Formatter // then get the FormatPrimitive from the Formatter
formatter.get_primitive(field, &initial_prefix, in_str) formatter.get_primitive(field, &initial_prefix, in_str)

View file

@ -275,7 +275,7 @@ impl SubParser {
} }
None => { None => {
text_so_far.push('%'); text_so_far.push('%');
err_conv(&text_so_far); err_conv(text_so_far);
false false
} }
} }

View file

@ -213,7 +213,7 @@ fn read_input(input_files: &[String], config: &Config) -> FileMap {
files.push("-"); files.push("-");
} else if config.gnu_ext { } else if config.gnu_ext {
for file in input_files { for file in input_files {
files.push(&file); files.push(file);
} }
} else { } else {
files.push(&input_files[0]); files.push(&input_files[0]);
@ -503,7 +503,7 @@ fn format_tex_line(
let keyword = &line[word_ref.position..word_ref.position_end]; let keyword = &line[word_ref.position..word_ref.position_end];
let after_chars_trim_idx = (word_ref.position_end, chars_line.len()); let after_chars_trim_idx = (word_ref.position_end, chars_line.len());
let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1]; let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1];
let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config); let (tail, before, after, head) = get_output_chunks(all_before, keyword, all_after, config);
output.push_str(&format!( output.push_str(&format!(
"{5}{0}{6}{5}{1}{6}{5}{2}{6}{5}{3}{6}{5}{4}{6}", "{5}{0}{6}{5}{1}{6}{5}{2}{6}{5}{3}{6}{5}{4}{6}",
format_tex_field(&tail), format_tex_field(&tail),
@ -515,7 +515,7 @@ fn format_tex_line(
"}" "}"
)); ));
if config.auto_ref || config.input_ref { if config.auto_ref || config.input_ref {
output.push_str(&format!("{}{}{}", "{", format_tex_field(&reference), "}")); output.push_str(&format!("{}{}{}", "{", format_tex_field(reference), "}"));
} }
output output
} }
@ -546,7 +546,7 @@ fn format_roff_line(
let keyword = &line[word_ref.position..word_ref.position_end]; let keyword = &line[word_ref.position..word_ref.position_end];
let after_chars_trim_idx = (word_ref.position_end, chars_line.len()); let after_chars_trim_idx = (word_ref.position_end, chars_line.len());
let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1]; let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1];
let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config); let (tail, before, after, head) = get_output_chunks(all_before, keyword, all_after, config);
output.push_str(&format!( output.push_str(&format!(
" \"{}\" \"{}\" \"{}{}\" \"{}\"", " \"{}\" \"{}\" \"{}{}\" \"{}\"",
format_roff_field(&tail), format_roff_field(&tail),
@ -556,7 +556,7 @@ fn format_roff_line(
format_roff_field(&head) format_roff_field(&head)
)); ));
if config.auto_ref || config.input_ref { if config.auto_ref || config.input_ref {
output.push_str(&format!(" \"{}\"", format_roff_field(&reference))); output.push_str(&format!(" \"{}\"", format_roff_field(reference)));
} }
output output
} }

View file

@ -18,10 +18,12 @@ path = "src/rm.rs"
clap = "2.33" clap = "2.33"
walkdir = "2.2" walkdir = "2.2"
remove_dir_all = "0.5.1" remove_dir_all = "0.5.1"
winapi = { version="0.3", features=[] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]] [[bin]]
name = "rm" name = "rm"
path = "src/main.rs" path = "src/main.rs"

View file

@ -5,7 +5,7 @@
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
// spell-checker:ignore (ToDO) bitor ulong // spell-checker:ignore (path) eacces
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
@ -430,9 +430,7 @@ use std::os::windows::prelude::MetadataExt;
#[cfg(windows)] #[cfg(windows)]
fn is_symlink_dir(metadata: &fs::Metadata) -> bool { fn is_symlink_dir(metadata: &fs::Metadata) -> bool {
use std::os::raw::c_ulong; use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
pub type DWORD = c_ulong;
pub const FILE_ATTRIBUTE_DIRECTORY: DWORD = 0x10;
metadata.file_type().is_symlink() metadata.file_type().is_symlink()
&& ((metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY) != 0) && ((metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY) != 0)

View file

@ -88,7 +88,7 @@ fn remove(dirs: Vec<String>, ignore: bool, parents: bool, verbose: bool) -> Resu
for dir in &dirs { for dir in &dirs {
let path = Path::new(&dir[..]); let path = Path::new(&dir[..]);
r = remove_dir(&path, ignore, verbose).and(r); r = remove_dir(path, ignore, verbose).and(r);
if parents { if parents {
let mut p = path; let mut p = path;
while let Some(new_p) = p.parent() { while let Some(new_p) = p.parent() {
@ -109,17 +109,14 @@ fn remove(dirs: Vec<String>, ignore: bool, parents: bool, verbose: bool) -> Resu
} }
fn remove_dir(path: &Path, ignore: bool, verbose: bool) -> Result<(), i32> { fn remove_dir(path: &Path, ignore: bool, verbose: bool) -> Result<(), i32> {
let mut read_dir = match fs::read_dir(path) { let mut read_dir = fs::read_dir(path).map_err(|e| {
Ok(m) => m, if e.raw_os_error() == Some(ENOTDIR) {
Err(e) if e.raw_os_error() == Some(ENOTDIR) => {
show_error!("failed to remove '{}': Not a directory", path.display()); show_error!("failed to remove '{}': Not a directory", path.display());
return Err(1); } else {
}
Err(e) => {
show_error!("reading directory '{}': {}", path.display(), e); show_error!("reading directory '{}': {}", path.display(), e);
return Err(1);
} }
}; 1
})?;
let mut r = Ok(()); let mut r = Ok(());

View file

@ -24,7 +24,7 @@ extern crate uucore;
static NAME: &str = "shred"; static NAME: &str = "shred";
const BLOCK_SIZE: usize = 512; const BLOCK_SIZE: usize = 512;
const NAME_CHARSET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_."; const NAME_CHARSET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.";
// Patterns as shown in the GNU coreutils shred implementation // Patterns as shown in the GNU coreutils shred implementation
const PATTERNS: [&[u8]; 22] = [ const PATTERNS: [&[u8]; 22] = [
@ -89,7 +89,7 @@ impl Iterator for FilenameGenerator {
// Make the return value, then increment // Make the return value, then increment
let mut ret = String::new(); let mut ret = String::new();
for i in name_charset_indices.iter() { for i in name_charset_indices.iter() {
let c: char = NAME_CHARSET.chars().nth(*i).unwrap(); let c = char::from(NAME_CHARSET[*i]);
ret.push(c); ret.push(c);
} }
@ -163,16 +163,14 @@ impl<'a> BytesGenerator<'a> {
return None; return None;
} }
let this_block_size = { let this_block_size = if !self.exact {
if !self.exact { self.block_size
} else {
let bytes_left = self.total_bytes - self.bytes_generated.get();
if bytes_left >= self.block_size as u64 {
self.block_size self.block_size
} else { } else {
let bytes_left = self.total_bytes - self.bytes_generated.get(); (bytes_left % self.block_size as u64) as usize
if bytes_left >= self.block_size as u64 {
self.block_size
} else {
(bytes_left % self.block_size as u64) as usize
}
} }
}; };
@ -184,12 +182,10 @@ impl<'a> BytesGenerator<'a> {
rng.fill(bytes); rng.fill(bytes);
} }
PassType::Pattern(pattern) => { PassType::Pattern(pattern) => {
let skip = { let skip = if self.bytes_generated.get() == 0 {
if self.bytes_generated.get() == 0 { 0
0 } else {
} else { (pattern.len() as u64 % self.bytes_generated.get()) as usize
(pattern.len() as u64 % self.bytes_generated.get()) as usize
}
}; };
// Copy the pattern in chunks rather than simply one byte at a time // Copy the pattern in chunks rather than simply one byte at a time
@ -381,7 +377,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
for path_str in matches.values_of(options::FILE).unwrap() { for path_str in matches.values_of(options::FILE).unwrap() {
wipe_file( wipe_file(
&path_str, iterations, remove, size, exact, zero, verbose, force, path_str, iterations, remove, size, exact, zero, verbose, force,
); );
} }
@ -659,7 +655,7 @@ fn do_remove(path: &Path, orig_filename: &str, verbose: bool) -> Result<(), io::
println!("{}: {}: removing", NAME, orig_filename); println!("{}: {}: removing", NAME, orig_filename);
} }
let renamed_path: Option<PathBuf> = wipe_name(&path, verbose); let renamed_path: Option<PathBuf> = wipe_name(path, verbose);
if let Some(rp) = renamed_path { if let Some(rp) = renamed_path {
fs::remove_file(rp)?; fs::remove_file(rp)?;
} }

View file

@ -285,14 +285,12 @@ fn parse_range(input_range: &str) -> Result<(usize, usize), String> {
if split.len() != 2 { if split.len() != 2 {
Err(format!("invalid input range: '{}'", input_range)) Err(format!("invalid input range: '{}'", input_range))
} else { } else {
let begin = match split[0].parse::<usize>() { let begin = split[0]
Ok(m) => m, .parse::<usize>()
Err(_) => return Err(format!("invalid input range: '{}'", split[0])), .map_err(|_| format!("invalid input range: '{}'", split[0]))?;
}; let end = split[1]
let end = match split[1].parse::<usize>() { .parse::<usize>()
Ok(m) => m, .map_err(|_| format!("invalid input range: '{}'", split[1]))?;
Err(_) => return Err(format!("invalid input range: '{}'", split[1])),
};
Ok((begin, end + 1)) Ok((begin, end + 1))
} }
} }

View file

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

View file

@ -49,7 +49,7 @@ pub fn check(path: &str, settings: &GlobalSettings) -> i32 {
let prev_last = prev_chunk.borrow_lines().last().unwrap(); let prev_last = prev_chunk.borrow_lines().last().unwrap();
let new_first = chunk.borrow_lines().first().unwrap(); let new_first = chunk.borrow_lines().first().unwrap();
if compare_by(prev_last, new_first, &settings) == Ordering::Greater { if compare_by(prev_last, new_first, settings) == Ordering::Greater {
if !settings.check_silent { if !settings.check_silent {
println!("sort: {}:{}: disorder: {}", path, line_idx, new_first.line); println!("sort: {}:{}: disorder: {}", path, line_idx, new_first.line);
} }
@ -60,7 +60,7 @@ pub fn check(path: &str, settings: &GlobalSettings) -> i32 {
for (a, b) in chunk.borrow_lines().iter().tuple_windows() { for (a, b) in chunk.borrow_lines().iter().tuple_windows() {
line_idx += 1; line_idx += 1;
if compare_by(a, b, &settings) == Ordering::Greater { if compare_by(a, b, settings) == Ordering::Greater {
if !settings.check_silent { if !settings.check_silent {
println!("sort: {}:{}: disorder: {}", path, line_idx, b.line); println!("sort: {}:{}: disorder: {}", path, line_idx, b.line);
} }

View file

@ -90,7 +90,7 @@ pub fn read(
if buffer.len() < carry_over.len() { if buffer.len() < carry_over.len() {
buffer.resize(carry_over.len() + 10 * 1024, 0); buffer.resize(carry_over.len() + 10 * 1024, 0);
} }
buffer[..carry_over.len()].copy_from_slice(&carry_over); buffer[..carry_over.len()].copy_from_slice(carry_over);
let (read, should_continue) = read_to_buffer( let (read, should_continue) = read_to_buffer(
file, file,
next_files, next_files,
@ -110,7 +110,7 @@ pub fn read(
std::mem::transmute::<Vec<Line<'static>>, Vec<Line<'_>>>(lines) std::mem::transmute::<Vec<Line<'static>>, Vec<Line<'_>>>(lines)
}; };
let read = crash_if_err!(1, std::str::from_utf8(&buf[..read])); let read = crash_if_err!(1, std::str::from_utf8(&buf[..read]));
parse_lines(read, &mut lines, separator, &settings); parse_lines(read, &mut lines, separator, settings);
lines lines
}); });
sender.send(payload).unwrap(); sender.send(payload).unwrap();
@ -194,7 +194,7 @@ fn read_to_buffer(
continue; continue;
} }
} }
let mut sep_iter = memchr_iter(separator, &buffer).rev(); let mut sep_iter = memchr_iter(separator, buffer).rev();
let last_line_end = sep_iter.next(); let last_line_end = sep_iter.next();
if sep_iter.next().is_some() { if sep_iter.next().is_some() {
// We read enough lines. // We read enough lines.

View file

@ -38,7 +38,7 @@ pub fn custom_str_cmp(
) -> Ordering { ) -> Ordering {
if !(ignore_case || ignore_non_dictionary || ignore_non_printing) { if !(ignore_case || ignore_non_dictionary || ignore_non_printing) {
// There are no custom settings. Fall back to the default strcmp, which is faster. // There are no custom settings. Fall back to the default strcmp, which is faster.
return a.cmp(&b); return a.cmp(b);
} }
let mut a_chars = a let mut a_chars = a
.chars() .chars()

View file

@ -43,6 +43,7 @@ use std::ops::Range;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static NAME: &str = "sort"; static NAME: &str = "sort";
@ -162,32 +163,29 @@ pub struct GlobalSettings {
} }
impl GlobalSettings { impl GlobalSettings {
/// Interpret this `&str` as a number with an optional trailing si unit. /// Parse a SIZE string into a number of bytes.
/// /// A size string comprises an integer and an optional unit.
/// If there is no trailing si unit, the implicit unit is K. /// The unit may be k, K, m, M, g, G, t, T, P, E, Z, Y (powers of 1024), or b which is 1.
/// The suffix B causes the number to be interpreted as a byte count. /// Default is K.
fn parse_byte_count(input: &str) -> usize { fn parse_byte_count(input: &str) -> Result<usize, ParseSizeError> {
const SI_UNITS: &[char] = &['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; // GNU sort (8.32) valid: 1b, k, K, m, M, g, G, t, T, P, E, Z, Y
// GNU sort (8.32) invalid: b, B, 1B, p, e, z, y
const ALLOW_LIST: &[char] = &[
'b', 'k', 'K', 'm', 'M', 'g', 'G', 't', 'T', 'P', 'E', 'Z', 'Y',
];
let mut size_string = input.trim().to_string();
let input = input.trim(); if size_string.ends_with(|c: char| ALLOW_LIST.contains(&c) || c.is_digit(10)) {
// b 1, K 1024 (default)
let (num_str, si_unit) = if size_string.ends_with(|c: char| c.is_digit(10)) {
if input.ends_with(|c: char| SI_UNITS.contains(&c.to_ascii_uppercase())) { size_string.push('K');
let mut chars = input.chars(); } else if size_string.ends_with('b') {
let si_suffix = chars.next_back().unwrap().to_ascii_uppercase(); size_string.pop();
let si_unit = SI_UNITS.iter().position(|&c| c == si_suffix).unwrap(); }
let num_str = chars.as_str(); parse_size(&size_string)
(num_str, si_unit) } else {
} else { Err(ParseSizeError::ParseFailure("invalid suffix".to_string()))
(input, 1) }
};
let num_usize: usize = num_str
.trim()
.parse()
.unwrap_or_else(|e| crash!(1, "failed to parse buffer size `{}`: {}", num_str, e));
num_usize.saturating_mul(1000usize.saturating_pow(si_unit as u32))
} }
fn out_writer(&self) -> BufWriter<Box<dyn Write>> { fn out_writer(&self) -> BufWriter<Box<dyn Write>> {
@ -400,9 +398,9 @@ impl<'a> Line<'a> {
let line = self.line.replace('\t', ">"); let line = self.line.replace('\t', ">");
writeln!(writer, "{}", line)?; writeln!(writer, "{}", line)?;
let fields = tokenize(&self.line, settings.separator); let fields = tokenize(self.line, settings.separator);
for selector in settings.selectors.iter() { for selector in settings.selectors.iter() {
let mut selection = selector.get_range(&self.line, Some(&fields)); let mut selection = selector.get_range(self.line, Some(&fields));
match selector.settings.mode { match selector.settings.mode {
SortMode::Numeric | SortMode::HumanNumeric => { SortMode::Numeric | SortMode::HumanNumeric => {
// find out which range is used for numeric comparisons // find out which range is used for numeric comparisons
@ -756,7 +754,7 @@ impl FieldSelector {
/// Get the selection that corresponds to this selector for the line. /// Get the selection that corresponds to this selector for the line.
/// If needs_fields returned false, tokens may be None. /// If needs_fields returned false, tokens may be None.
fn get_selection<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Selection<'a> { fn get_selection<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Selection<'a> {
let mut range = &line[self.get_range(&line, tokens)]; let mut range = &line[self.get_range(line, tokens)];
let num_cache = if self.settings.mode == SortMode::Numeric let num_cache = if self.settings.mode == SortMode::Numeric
|| self.settings.mode == SortMode::HumanNumeric || self.settings.mode == SortMode::HumanNumeric
{ {
@ -846,7 +844,7 @@ impl FieldSelector {
match resolve_index(line, tokens, &self.from) { match resolve_index(line, tokens, &self.from) {
Resolution::StartOfChar(from) => { Resolution::StartOfChar(from) => {
let to = self.to.as_ref().map(|to| resolve_index(line, tokens, &to)); let to = self.to.as_ref().map(|to| resolve_index(line, tokens, to));
let mut range = match to { let mut range = match to {
Some(Resolution::StartOfChar(mut to)) => { Some(Resolution::StartOfChar(mut to)) => {
@ -1176,8 +1174,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
settings.buffer_size = matches settings.buffer_size = matches
.value_of(OPT_BUF_SIZE) .value_of(OPT_BUF_SIZE)
.map(GlobalSettings::parse_byte_count) .map_or(DEFAULT_BUF_SIZE, |s| {
.unwrap_or(DEFAULT_BUF_SIZE); GlobalSettings::parse_byte_count(s)
.unwrap_or_else(|e| crash!(2, "{}", format_error_message(e, s, OPT_BUF_SIZE)))
});
settings.tmp_dir = matches settings.tmp_dir = matches
.value_of(OPT_TMP_DIR) .value_of(OPT_TMP_DIR)
@ -1257,11 +1257,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
fn output_sorted_lines<'a>(iter: impl Iterator<Item = &'a Line<'a>>, settings: &GlobalSettings) { fn output_sorted_lines<'a>(iter: impl Iterator<Item = &'a Line<'a>>, settings: &GlobalSettings) {
if settings.unique { if settings.unique {
print_sorted( print_sorted(
iter.dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), iter.dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal),
&settings, settings,
); );
} else { } else {
print_sorted(iter, &settings); print_sorted(iter, settings);
} }
} }
@ -1277,16 +1277,16 @@ fn exec(files: &[String], settings: &GlobalSettings) -> i32 {
} else { } else {
let mut lines = files.iter().map(open); let mut lines = files.iter().map(open);
ext_sort(&mut lines, &settings); ext_sort(&mut lines, settings);
} }
0 0
} }
fn sort_by<'a>(unsorted: &mut Vec<Line<'a>>, settings: &GlobalSettings) { fn sort_by<'a>(unsorted: &mut Vec<Line<'a>>, settings: &GlobalSettings) {
if settings.stable || settings.unique { if settings.stable || settings.unique {
unsorted.par_sort_by(|a, b| compare_by(a, b, &settings)) unsorted.par_sort_by(|a, b| compare_by(a, b, settings))
} else { } else {
unsorted.par_sort_unstable_by(|a, b| compare_by(a, b, &settings)) unsorted.par_sort_unstable_by(|a, b| compare_by(a, b, settings))
} }
} }
@ -1587,6 +1587,16 @@ fn open(path: impl AsRef<OsStr>) -> Box<dyn Read + Send> {
} }
} }
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
// NOTE:
// GNU's sort echos affected flag, -S or --buffer-size, depending user's selection
// GNU's sort does distinguish between "invalid (suffix in) argument"
match error {
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -1676,4 +1686,48 @@ mod tests {
// How big is a selection? Constant cost all lines pay when we need selections. // How big is a selection? Constant cost all lines pay when we need selections.
assert_eq!(std::mem::size_of::<Selection>(), 24); assert_eq!(std::mem::size_of::<Selection>(), 24);
} }
#[test]
fn test_parse_byte_count() {
let valid_input = [
("0", 0),
("50K", 50 * 1024),
("50k", 50 * 1024),
("1M", 1024 * 1024),
("100M", 100 * 1024 * 1024),
#[cfg(not(target_pointer_width = "32"))]
("1000G", 1000 * 1024 * 1024 * 1024),
#[cfg(not(target_pointer_width = "32"))]
("10T", 10 * 1024 * 1024 * 1024 * 1024),
("1b", 1),
("1024b", 1024),
("1024Mb", 1024 * 1024 * 1024), // NOTE: This might not be how GNU `sort` behaves for 'Mb'
("1", 1024), // K is default
("50", 50 * 1024),
("K", 1024),
("k", 1024),
("m", 1024 * 1024),
#[cfg(not(target_pointer_width = "32"))]
("E", 1024 * 1024 * 1024 * 1024 * 1024 * 1024),
];
for (input, expected_output) in &valid_input {
assert_eq!(
GlobalSettings::parse_byte_count(input),
Ok(*expected_output)
);
}
// SizeTooBig
let invalid_input = ["500E", "1Y"];
for input in &invalid_input {
#[cfg(not(target_pointer_width = "128"))]
assert!(GlobalSettings::parse_byte_count(input).is_err());
}
// ParseFailure
let invalid_input = ["nonsense", "1B", "B", "b", "p", "e", "z", "y"];
for input in &invalid_input {
assert!(GlobalSettings::parse_byte_count(input).is_err());
}
}
} }

View file

@ -66,7 +66,7 @@ impl FilterWriter {
/// * `filepath` - Path of the output file (forwarded to command as $FILE) /// * `filepath` - Path of the output file (forwarded to command as $FILE)
fn new(command: &str, filepath: &str) -> FilterWriter { fn new(command: &str, filepath: &str) -> FilterWriter {
// set $FILE, save previous value (if there was one) // set $FILE, save previous value (if there was one)
let _with_env_var_set = WithEnvVarSet::new("FILE", &filepath); let _with_env_var_set = WithEnvVarSet::new("FILE", filepath);
let shell_process = let shell_process =
Command::new(env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_owned())) Command::new(env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_owned()))
@ -117,7 +117,7 @@ pub fn instantiate_current_writer(
) as Box<dyn Write>), ) as Box<dyn Write>),
Some(ref filter_command) => BufWriter::new(Box::new( Some(ref filter_command) => BufWriter::new(Box::new(
// spawn a shell command and write to it // spawn a shell command and write to it
FilterWriter::new(&filter_command, &filename), FilterWriter::new(filter_command, filename),
) as Box<dyn Write>), ) as Box<dyn Write>),
} }
} }

View file

@ -13,11 +13,13 @@ extern crate uucore;
mod platform; mod platform;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::convert::TryFrom;
use std::env; use std::env;
use std::fs::File; use std::fs::File;
use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write}; use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write};
use std::path::Path; use std::path::Path;
use std::{char, fs::remove_file}; use std::{char, fs::remove_file};
use uucore::parse_size::parse_size;
static NAME: &str = "split"; static NAME: &str = "split";
@ -231,10 +233,9 @@ struct LineSplitter {
impl LineSplitter { impl LineSplitter {
fn new(settings: &Settings) -> LineSplitter { fn new(settings: &Settings) -> LineSplitter {
LineSplitter { LineSplitter {
lines_per_split: settings lines_per_split: settings.strategy_param.parse().unwrap_or_else(|_| {
.strategy_param crash!(1, "invalid number of lines: {}", settings.strategy_param)
.parse() }),
.unwrap_or_else(|e| crash!(1, "invalid number of lines: {}", e)),
} }
} }
} }
@ -276,40 +277,14 @@ struct ByteSplitter {
impl ByteSplitter { impl ByteSplitter {
fn new(settings: &Settings) -> ByteSplitter { fn new(settings: &Settings) -> ByteSplitter {
// These multipliers are the same as supported by GNU coreutils. let size_string = &settings.strategy_param;
let modifiers: Vec<(&str, u128)> = vec![ let size_num = match parse_size(size_string) {
("K", 1024u128), Ok(n) => n,
("M", 1024 * 1024), Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()),
("G", 1024 * 1024 * 1024), };
("T", 1024 * 1024 * 1024 * 1024),
("P", 1024 * 1024 * 1024 * 1024 * 1024),
("E", 1024 * 1024 * 1024 * 1024 * 1024 * 1024),
("Z", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024),
("Y", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024),
("KB", 1000),
("MB", 1000 * 1000),
("GB", 1000 * 1000 * 1000),
("TB", 1000 * 1000 * 1000 * 1000),
("PB", 1000 * 1000 * 1000 * 1000 * 1000),
("EB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000),
("ZB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000),
("YB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000),
];
// This sequential find is acceptable since none of the modifiers are
// suffixes of any other modifiers, a la Huffman codes.
let (suffix, multiplier) = modifiers
.iter()
.find(|(suffix, _)| settings.strategy_param.ends_with(suffix))
.unwrap_or(&("", 1));
// Try to parse the actual numeral.
let n = &settings.strategy_param[0..(settings.strategy_param.len() - suffix.len())]
.parse::<u128>()
.unwrap_or_else(|e| crash!(1, "invalid number of bytes: {}", e));
ByteSplitter { ByteSplitter {
bytes_per_split: n * multiplier, bytes_per_split: u128::try_from(size_num).unwrap(),
} }
} }
} }

View file

@ -477,7 +477,7 @@ impl Stater {
Stater::generate_tokens(&Stater::default_format(show_fs, terse, false), use_printf) Stater::generate_tokens(&Stater::default_format(show_fs, terse, false), use_printf)
.unwrap() .unwrap()
} else { } else {
Stater::generate_tokens(&format_str, use_printf)? Stater::generate_tokens(format_str, use_printf)?
}; };
let default_dev_tokens = let default_dev_tokens =
Stater::generate_tokens(&Stater::default_format(show_fs, terse, true), use_printf) Stater::generate_tokens(&Stater::default_format(show_fs, terse, true), use_printf)

View file

@ -19,6 +19,7 @@ use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use tempfile::tempdir; use tempfile::tempdir;
use tempfile::TempDir; use tempfile::TempDir;
use uucore::parse_size::parse_size;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static ABOUT: &str = static ABOUT: &str =
@ -55,7 +56,7 @@ const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf
enum BufferType { enum BufferType {
Default, Default,
Line, Line,
Size(u64), Size(usize),
} }
struct ProgramOptions { struct ProgramOptions {
@ -69,9 +70,9 @@ impl<'a> TryFrom<&ArgMatches<'a>> for ProgramOptions {
fn try_from(matches: &ArgMatches) -> Result<Self, Self::Error> { fn try_from(matches: &ArgMatches) -> Result<Self, Self::Error> {
Ok(ProgramOptions { Ok(ProgramOptions {
stdin: check_option(&matches, options::INPUT)?, stdin: check_option(matches, options::INPUT)?,
stdout: check_option(&matches, options::OUTPUT)?, stdout: check_option(matches, options::OUTPUT)?,
stderr: check_option(&matches, options::ERROR)?, stderr: check_option(matches, options::ERROR)?,
}) })
} }
} }
@ -104,41 +105,6 @@ fn preload_strings() -> (&'static str, &'static str) {
crash!(1, "Command not supported for this operating system!") crash!(1, "Command not supported for this operating system!")
} }
fn parse_size(size: &str) -> Option<u64> {
let ext = size.trim_start_matches(|c: char| c.is_digit(10));
let num = size.trim_end_matches(char::is_alphabetic);
let mut recovered = num.to_owned();
recovered.push_str(ext);
if recovered != size {
return None;
}
let buf_size: u64 = match num.parse().ok() {
Some(m) => m,
None => return None,
};
let (power, base): (u32, u64) = match ext {
"" => (0, 0),
"KB" => (1, 1024),
"K" => (1, 1000),
"MB" => (2, 1024),
"M" => (2, 1000),
"GB" => (3, 1024),
"G" => (3, 1000),
"TB" => (4, 1024),
"T" => (4, 1000),
"PB" => (5, 1024),
"P" => (5, 1000),
"EB" => (6, 1024),
"E" => (6, 1000),
"ZB" => (7, 1024),
"Z" => (7, 1000),
"YB" => (8, 1024),
"Y" => (8, 1000),
_ => return None,
};
Some(buf_size * base.pow(power))
}
fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramOptionsError> { fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramOptionsError> {
match matches.value_of(name) { match matches.value_of(name) {
Some(value) => match value { Some(value) => match value {
@ -151,13 +117,10 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramO
Ok(BufferType::Line) Ok(BufferType::Line)
} }
} }
x => { x => parse_size(x).map_or_else(
let size = match parse_size(x) { |e| crash!(125, "invalid mode {}", e),
Some(m) => m, |m| Ok(BufferType::Size(m)),
None => return Err(ProgramOptionsError(format!("invalid mode {}", x))), ),
};
Ok(BufferType::Size(size))
}
}, },
None => Ok(BufferType::Default), None => Ok(BufferType::Default),
} }

View file

@ -5,7 +5,6 @@
// * // *
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
// *
// spell-checker:ignore (ToDO) seekable seek'd tail'ing ringbuffer ringbuf // spell-checker:ignore (ToDO) seekable seek'd tail'ing ringbuffer ringbuf
@ -21,19 +20,18 @@ use chunks::ReverseChunks;
use clap::{App, Arg}; use clap::{App, Arg};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::error::Error;
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write}; use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::path::Path; use std::path::Path;
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::ringbuffer::RingBuffer; use uucore::ringbuffer::RingBuffer;
pub mod options { pub mod options {
pub mod verbosity { pub mod verbosity {
pub static QUIET: &str = "quiet"; pub static QUIET: &str = "quiet";
pub static SILENT: &str = "silent";
pub static VERBOSE: &str = "verbose"; pub static VERBOSE: &str = "verbose";
} }
pub static BYTES: &str = "bytes"; pub static BYTES: &str = "bytes";
@ -42,13 +40,12 @@ pub mod options {
pub static PID: &str = "pid"; pub static PID: &str = "pid";
pub static SLEEP_INT: &str = "sleep-interval"; pub static SLEEP_INT: &str = "sleep-interval";
pub static ZERO_TERM: &str = "zero-terminated"; pub static ZERO_TERM: &str = "zero-terminated";
pub static ARG_FILES: &str = "files";
} }
static ARG_FILES: &str = "files";
enum FilterMode { enum FilterMode {
Bytes(u64), Bytes(usize),
Lines(u64, u8), // (number of lines, delimiter) Lines(usize, u8), // (number of lines, delimiter)
} }
struct Settings { struct Settings {
@ -78,12 +75,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let app = App::new(executable!()) let app = App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about("output the last part of files") .about("output the last part of files")
// TODO: add usage
.arg( .arg(
Arg::with_name(options::BYTES) Arg::with_name(options::BYTES)
.short("c") .short("c")
.long(options::BYTES) .long(options::BYTES)
.takes_value(true) .takes_value(true)
.allow_hyphen_values(true) .allow_hyphen_values(true)
.overrides_with_all(&[options::BYTES, options::LINES])
.help("Number of bytes to print"), .help("Number of bytes to print"),
) )
.arg( .arg(
@ -98,6 +97,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(options::LINES) .long(options::LINES)
.takes_value(true) .takes_value(true)
.allow_hyphen_values(true) .allow_hyphen_values(true)
.overrides_with_all(&[options::BYTES, options::LINES])
.help("Number of lines to print"), .help("Number of lines to print"),
) )
.arg( .arg(
@ -110,13 +110,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(options::verbosity::QUIET) Arg::with_name(options::verbosity::QUIET)
.short("q") .short("q")
.long(options::verbosity::QUIET) .long(options::verbosity::QUIET)
.visible_alias("silent")
.overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE])
.help("never output headers giving file names"), .help("never output headers giving file names"),
) )
.arg(
Arg::with_name(options::verbosity::SILENT)
.long(options::verbosity::SILENT)
.help("synonym of --quiet"),
)
.arg( .arg(
Arg::with_name(options::SLEEP_INT) Arg::with_name(options::SLEEP_INT)
.short("s") .short("s")
@ -128,6 +125,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(options::verbosity::VERBOSE) Arg::with_name(options::verbosity::VERBOSE)
.short("v") .short("v")
.long(options::verbosity::VERBOSE) .long(options::verbosity::VERBOSE)
.overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE])
.help("always output headers giving file names"), .help("always output headers giving file names"),
) )
.arg( .arg(
@ -137,7 +135,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("Line delimiter is NUL, not newline"), .help("Line delimiter is NUL, not newline"),
) )
.arg( .arg(
Arg::with_name(ARG_FILES) Arg::with_name(options::ARG_FILES)
.multiple(true) .multiple(true)
.takes_value(true) .takes_value(true)
.min_values(1), .min_values(1),
@ -171,38 +169,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
match matches.value_of(options::LINES) { let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) {
Some(n) => { match parse_num(arg) {
let mut slice: &str = n; Ok((n, beginning)) => (FilterMode::Bytes(n), beginning),
if slice.chars().next().unwrap_or('_') == '+' { Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()),
settings.beginning = true;
slice = &slice[1..];
}
match parse_size(slice) {
Ok(m) => settings.mode = FilterMode::Lines(m, b'\n'),
Err(e) => {
show_error!("{}", e.to_string());
return 1;
}
}
} }
None => { } else if let Some(arg) = matches.value_of(options::LINES) {
if let Some(n) = matches.value_of(options::BYTES) { match parse_num(arg) {
let mut slice: &str = n; Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning),
if slice.chars().next().unwrap_or('_') == '+' { Err(e) => crash!(1, "invalid number of lines: {}", e.to_string()),
settings.beginning = true;
slice = &slice[1..];
}
match parse_size(slice) {
Ok(m) => settings.mode = FilterMode::Bytes(m),
Err(e) => {
show_error!("{}", e.to_string());
return 1;
}
}
}
} }
} else {
(FilterMode::Lines(10, b'\n'), false)
}; };
settings.mode = mode_and_beginning.0;
settings.beginning = mode_and_beginning.1;
if matches.is_present(options::ZERO_TERM) { if matches.is_present(options::ZERO_TERM) {
if let FilterMode::Lines(count, _) = settings.mode { if let FilterMode::Lines(count, _) = settings.mode {
@ -211,11 +192,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
let verbose = matches.is_present(options::verbosity::VERBOSE); let verbose = matches.is_present(options::verbosity::VERBOSE);
let quiet = matches.is_present(options::verbosity::QUIET) let quiet = matches.is_present(options::verbosity::QUIET);
|| matches.is_present(options::verbosity::SILENT);
let files: Vec<String> = matches let files: Vec<String> = matches
.values_of(ARG_FILES) .values_of(options::ARG_FILES)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default(); .unwrap_or_default();
@ -264,98 +244,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0 0
} }
#[derive(Debug, PartialEq, Eq)]
pub enum ParseSizeErr {
ParseFailure(String),
SizeTooBig(String),
}
impl Error for ParseSizeErr {
fn description(&self) -> &str {
match *self {
ParseSizeErr::ParseFailure(ref s) => &*s,
ParseSizeErr::SizeTooBig(ref s) => &*s,
}
}
}
impl fmt::Display for ParseSizeErr {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let s = match self {
ParseSizeErr::ParseFailure(s) => s,
ParseSizeErr::SizeTooBig(s) => s,
};
write!(f, "{}", s)
}
}
impl ParseSizeErr {
fn parse_failure(s: &str) -> ParseSizeErr {
ParseSizeErr::ParseFailure(format!("invalid size: '{}'", s))
}
fn size_too_big(s: &str) -> ParseSizeErr {
ParseSizeErr::SizeTooBig(format!(
"invalid size: '{}': Value too large to be stored in data type",
s
))
}
}
pub type ParseSizeResult = Result<u64, ParseSizeErr>;
pub fn parse_size(mut size_slice: &str) -> Result<u64, ParseSizeErr> {
let mut base = if size_slice.chars().last().unwrap_or('_') == 'B' {
size_slice = &size_slice[..size_slice.len() - 1];
1000u64
} else {
1024u64
};
let exponent = if !size_slice.is_empty() {
let mut has_suffix = true;
let exp = match size_slice.chars().last().unwrap_or('_') {
'K' | 'k' => 1u64,
'M' => 2u64,
'G' => 3u64,
'T' => 4u64,
'P' => 5u64,
'E' => 6u64,
'Z' | 'Y' => {
return Err(ParseSizeErr::size_too_big(size_slice));
}
'b' => {
base = 512u64;
1u64
}
_ => {
has_suffix = false;
0u64
}
};
if has_suffix {
size_slice = &size_slice[..size_slice.len() - 1];
}
exp
} else {
0u64
};
let mut multiplier = 1u64;
for _ in 0u64..exponent {
multiplier *= base;
}
if base == 1000u64 && exponent == 0u64 {
// sole B is not a valid suffix
Err(ParseSizeErr::parse_failure(size_slice))
} else {
let value: Option<i64> = size_slice.parse().ok();
value
.map(|v| Ok((multiplier as i64 * v.abs()) as u64))
.unwrap_or_else(|| Err(ParseSizeErr::parse_failure(size_slice)))
}
}
fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings: &Settings) { fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings: &Settings) {
assert!(settings.follow); assert!(settings.follow);
let mut last = readers.len() - 1; let mut last = readers.len() - 1;
@ -469,7 +357,7 @@ fn bounded_tail(file: &mut File, settings: &Settings) {
/// If any element of `iter` is an [`Err`], then this function panics. /// If any element of `iter` is an [`Err`], then this function panics.
fn unbounded_tail_collect<T, E>( fn unbounded_tail_collect<T, E>(
iter: impl Iterator<Item = Result<T, E>>, iter: impl Iterator<Item = Result<T, E>>,
count: u64, count: usize,
beginning: bool, beginning: bool,
) -> VecDeque<T> ) -> VecDeque<T>
where where
@ -514,3 +402,22 @@ fn print_byte<T: Write>(stdout: &mut T, ch: u8) {
crash!(1, "{}", err); crash!(1, "{}", err);
} }
} }
fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> {
let mut size_string = src.trim();
let mut starting_with = false;
if let Some(c) = size_string.chars().next() {
if c == '+' || c == '-' {
// tail: '-' is not documented (8.32 man pages)
size_string = &size_string[1..];
if c == '+' {
starting_with = true;
}
}
} else {
return Err(ParseSizeError::ParseFailure(src.to_string()));
}
parse_size(size_string).map(|n| (n, starting_with))
}

View file

@ -16,9 +16,8 @@ path = "src/timeout.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
getopts = "0.2.18"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["process", "signals"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -55,7 +55,7 @@ impl Config {
fn from(options: clap::ArgMatches) -> Config { fn from(options: clap::ArgMatches) -> Config {
let signal = match options.value_of(options::SIGNAL) { let signal = match options.value_of(options::SIGNAL) {
Some(signal_) => { Some(signal_) => {
let signal_result = signal_by_name_or_value(&signal_); let signal_result = signal_by_name_or_value(signal_);
match signal_result { match signal_result {
None => { None => {
unreachable!("invalid signal '{}'", signal_); unreachable!("invalid signal '{}'", signal_);
@ -67,7 +67,7 @@ impl Config {
}; };
let kill_after: Duration = match options.value_of(options::KILL_AFTER) { let kill_after: Duration = match options.value_of(options::KILL_AFTER) {
Some(time) => uucore::parse_time::from_str(&time).unwrap(), Some(time) => uucore::parse_time::from_str(time).unwrap(),
None => Duration::new(0, 0), None => Duration::new(0, 0),
}; };

View file

@ -22,14 +22,15 @@ use std::ops::RangeInclusive;
/// character; octal escape sequences consume 1 to 3 octal digits. /// character; octal escape sequences consume 1 to 3 octal digits.
#[inline] #[inline]
fn parse_sequence(s: &str) -> (char, usize) { fn parse_sequence(s: &str) -> (char, usize) {
let c = s.chars().next().expect("invalid escape: empty string"); let mut s = s.chars();
let c = s.next().expect("invalid escape: empty string");
if ('0'..='7').contains(&c) { if ('0'..='7').contains(&c) {
let mut v = c.to_digit(8).unwrap(); let mut v = c.to_digit(8).unwrap();
let mut consumed = 1; let mut consumed = 1;
let bits_per_digit = 3; let bits_per_digit = 3;
for c in s.chars().skip(1).take(2) { for c in s.take(2) {
match c.to_digit(8) { match c.to_digit(8) {
Some(c) => { Some(c) => {
v = (v << bits_per_digit) | c; v = (v << bits_per_digit) | c;

View file

@ -11,19 +11,21 @@
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::convert::TryFrom;
use std::fs::{metadata, OpenOptions}; use std::fs::{metadata, OpenOptions};
use std::io::ErrorKind; use std::io::ErrorKind;
use std::path::Path; use std::path::Path;
use uucore::parse_size::{parse_size, ParseSizeError};
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
enum TruncateMode { enum TruncateMode {
Absolute(u64), Absolute(usize),
Extend(u64), Extend(usize),
Reduce(u64), Reduce(usize),
AtMost(u64), AtMost(usize),
AtLeast(u64), AtLeast(usize),
RoundDown(u64), RoundDown(usize),
RoundUp(u64), RoundUp(usize),
} }
impl TruncateMode { impl TruncateMode {
@ -38,7 +40,7 @@ impl TruncateMode {
/// let fsize = 10; /// let fsize = 10;
/// assert_eq!(mode.to_size(fsize), 15); /// assert_eq!(mode.to_size(fsize), 15);
/// ``` /// ```
fn to_size(&self, fsize: u64) -> u64 { fn to_size(&self, fsize: usize) -> usize {
match self { match self {
TruncateMode::Absolute(size) => *size, TruncateMode::Absolute(size) => *size,
TruncateMode::Extend(size) => fsize + size, TruncateMode::Extend(size) => fsize + size,
@ -58,10 +60,9 @@ pub mod options {
pub static NO_CREATE: &str = "no-create"; pub static NO_CREATE: &str = "no-create";
pub static REFERENCE: &str = "reference"; pub static REFERENCE: &str = "reference";
pub static SIZE: &str = "size"; pub static SIZE: &str = "size";
pub static ARG_FILES: &str = "files";
} }
static ARG_FILES: &str = "files";
fn get_usage() -> String { fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!()) format!("{0} [OPTION]... [FILE]...", executable!())
} }
@ -113,21 +114,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(options::REFERENCE) Arg::with_name(options::REFERENCE)
.short("r") .short("r")
.long(options::REFERENCE) .long(options::REFERENCE)
.required_unless(options::SIZE)
.help("base the size of each file on the size of RFILE") .help("base the size of each file on the size of RFILE")
.value_name("RFILE") .value_name("RFILE")
) )
.arg( .arg(
Arg::with_name(options::SIZE) Arg::with_name(options::SIZE)
.short("s") .short("s")
.long("size") .long(options::SIZE)
.required_unless(options::REFERENCE)
.help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified") .help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified")
.value_name("SIZE") .value_name("SIZE")
) )
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1)) .arg(Arg::with_name(options::ARG_FILES)
.value_name("FILE")
.multiple(true)
.takes_value(true)
.required(true)
.min_values(1))
.get_matches_from(args); .get_matches_from(args);
let files: Vec<String> = matches let files: Vec<String> = matches
.values_of(ARG_FILES) .values_of(options::ARG_FILES)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default(); .unwrap_or_default();
@ -149,8 +157,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
crash!( crash!(
1, 1,
"cannot stat '{}': No such file or directory", "cannot stat '{}': No such file or directory",
reference.unwrap() reference.unwrap_or_else(|| "".to_string())
); ); // TODO: fix '--no-create' see test_reference and test_truncate_bytes_size
} }
_ => crash!(1, "{}", e.to_string()), _ => crash!(1, "{}", e.to_string()),
} }
@ -172,10 +180,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
/// ///
/// If the file could not be opened, or there was a problem setting the /// If the file could not be opened, or there was a problem setting the
/// size of the file. /// size of the file.
fn file_truncate(filename: &str, create: bool, size: u64) -> std::io::Result<()> { fn file_truncate(filename: &str, create: bool, size: usize) -> std::io::Result<()> {
let path = Path::new(filename); let path = Path::new(filename);
let f = OpenOptions::new().write(true).create(create).open(path)?; let f = OpenOptions::new().write(true).create(create).open(path)?;
f.set_len(size) f.set_len(u64::try_from(size).unwrap())
} }
/// Truncate files to a size relative to a given file. /// Truncate files to a size relative to a given file.
@ -206,9 +214,9 @@ fn truncate_reference_and_size(
} }
_ => m, _ => m,
}, },
Err(_) => crash!(1, "Invalid number: {}", size_string), Err(e) => crash!(1, "Invalid number: {}", e.to_string()),
}; };
let fsize = metadata(rfilename)?.len(); let fsize = usize::try_from(metadata(rfilename)?.len()).unwrap();
let tsize = mode.to_size(fsize); let tsize = mode.to_size(fsize);
for filename in &filenames { for filename in &filenames {
file_truncate(filename, create, tsize)?; file_truncate(filename, create, tsize)?;
@ -232,7 +240,7 @@ fn truncate_reference_file_only(
filenames: Vec<String>, filenames: Vec<String>,
create: bool, create: bool,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let tsize = metadata(rfilename)?.len(); let tsize = usize::try_from(metadata(rfilename)?.len()).unwrap();
for filename in &filenames { for filename in &filenames {
file_truncate(filename, create, tsize)?; file_truncate(filename, create, tsize)?;
} }
@ -261,10 +269,10 @@ fn truncate_size_only(
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let mode = match parse_mode_and_size(size_string) { let mode = match parse_mode_and_size(size_string) {
Ok(m) => m, Ok(m) => m,
Err(_) => crash!(1, "Invalid number: {}", size_string), Err(e) => crash!(1, "Invalid number: {}", e.to_string()),
}; };
for filename in &filenames { for filename in &filenames {
let fsize = metadata(filename).map(|m| m.len()).unwrap_or(0); let fsize = usize::try_from(metadata(filename)?.len()).unwrap();
let tsize = mode.to_size(fsize); let tsize = mode.to_size(fsize);
file_truncate(filename, create, tsize)?; file_truncate(filename, create, tsize)?;
} }
@ -290,7 +298,7 @@ fn truncate(
} }
(Some(rfilename), None) => truncate_reference_file_only(&rfilename, filenames, create), (Some(rfilename), None) => truncate_reference_file_only(&rfilename, filenames, create),
(None, Some(size_string)) => truncate_size_only(&size_string, filenames, create), (None, Some(size_string)) => truncate_size_only(&size_string, filenames, create),
(None, None) => crash!(1, "you must specify either --reference or --size"), (None, None) => crash!(1, "you must specify either --reference or --size"), // this case cannot happen anymore because it's handled by clap
} }
} }
@ -317,117 +325,35 @@ fn is_modifier(c: char) -> bool {
/// ```rust,ignore /// ```rust,ignore
/// assert_eq!(parse_mode_and_size("+123"), (TruncateMode::Extend, 123)); /// assert_eq!(parse_mode_and_size("+123"), (TruncateMode::Extend, 123));
/// ``` /// ```
fn parse_mode_and_size(size_string: &str) -> Result<TruncateMode, ()> { fn parse_mode_and_size(size_string: &str) -> Result<TruncateMode, ParseSizeError> {
// Trim any whitespace. // Trim any whitespace.
let size_string = size_string.trim(); let mut size_string = size_string.trim();
// Get the modifier character from the size string, if any. For // Get the modifier character from the size string, if any. For
// example, if the argument is "+123", then the modifier is '+'. // example, if the argument is "+123", then the modifier is '+'.
let c = size_string.chars().next().unwrap(); if let Some(c) = size_string.chars().next() {
let size_string = if is_modifier(c) { if is_modifier(c) {
&size_string[1..] size_string = &size_string[1..];
}
parse_size(size_string).map(match c {
'+' => TruncateMode::Extend,
'-' => TruncateMode::Reduce,
'<' => TruncateMode::AtMost,
'>' => TruncateMode::AtLeast,
'/' => TruncateMode::RoundDown,
'%' => TruncateMode::RoundUp,
_ => TruncateMode::Absolute,
})
} else { } else {
size_string Err(ParseSizeError::ParseFailure(size_string.to_string()))
}; }
parse_size(size_string).map(match c {
'+' => TruncateMode::Extend,
'-' => TruncateMode::Reduce,
'<' => TruncateMode::AtMost,
'>' => TruncateMode::AtLeast,
'/' => TruncateMode::RoundDown,
'%' => TruncateMode::RoundUp,
_ => TruncateMode::Absolute,
})
}
/// Parse a size string into a number of bytes.
///
/// A size string comprises an integer and an optional unit. The unit
/// may be K, M, G, T, P, E, Z, or Y (powers of 1024) or KB, MB,
/// etc. (powers of 1000).
///
/// # Errors
///
/// This function returns an error if the string does not begin with a
/// numeral, or if the unit is not one of the supported units described
/// in the preceding section.
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(parse_size("123").unwrap(), 123);
/// assert_eq!(parse_size("123K").unwrap(), 123 * 1024);
/// assert_eq!(parse_size("123KB").unwrap(), 123 * 1000);
/// ```
fn parse_size(size: &str) -> Result<u64, ()> {
// Get the numeric part of the size argument. For example, if the
// argument is "123K", then the numeric part is "123".
let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect();
let number: u64 = match numeric_string.parse() {
Ok(n) => n,
Err(_) => return Err(()),
};
// Get the alphabetic units part of the size argument and compute
// the factor it represents. For example, if the argument is "123K",
// then the unit part is "K" and the factor is 1024. This may be the
// empty string, in which case, the factor is 1.
let n = numeric_string.len();
let (base, exponent): (u64, u32) = match &size[n..] {
"" => (1, 0),
"K" | "k" => (1024, 1),
"M" | "m" => (1024, 2),
"G" | "g" => (1024, 3),
"T" | "t" => (1024, 4),
"P" | "p" => (1024, 5),
"E" | "e" => (1024, 6),
"Z" | "z" => (1024, 7),
"Y" | "y" => (1024, 8),
"KB" | "kB" => (1000, 1),
"MB" | "mB" => (1000, 2),
"GB" | "gB" => (1000, 3),
"TB" | "tB" => (1000, 4),
"PB" | "pB" => (1000, 5),
"EB" | "eB" => (1000, 6),
"ZB" | "zB" => (1000, 7),
"YB" | "yB" => (1000, 8),
_ => return Err(()),
};
let factor = base.pow(exponent);
Ok(number * factor)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::parse_mode_and_size; use crate::parse_mode_and_size;
use crate::parse_size;
use crate::TruncateMode; use crate::TruncateMode;
#[test]
fn test_parse_size_zero() {
assert_eq!(parse_size("0").unwrap(), 0);
assert_eq!(parse_size("0K").unwrap(), 0);
assert_eq!(parse_size("0KB").unwrap(), 0);
}
#[test]
fn test_parse_size_without_factor() {
assert_eq!(parse_size("123").unwrap(), 123);
}
#[test]
fn test_parse_size_kilobytes() {
assert_eq!(parse_size("123K").unwrap(), 123 * 1024);
assert_eq!(parse_size("123KB").unwrap(), 123 * 1000);
}
#[test]
fn test_parse_size_megabytes() {
assert_eq!(parse_size("123").unwrap(), 123);
assert_eq!(parse_size("123M").unwrap(), 123 * 1024 * 1024);
assert_eq!(parse_size("123MB").unwrap(), 123 * 1000 * 1000);
}
#[test] #[test]
fn test_parse_mode_and_size() { fn test_parse_mode_and_size() {
assert_eq!(parse_mode_and_size("10"), Ok(TruncateMode::Absolute(10))); assert_eq!(parse_mode_and_size("10"), Ok(TruncateMode::Absolute(10)));

View file

@ -17,6 +17,7 @@ path = "src/tty.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
libc = "0.2.42" libc = "0.2.42"
atty = "0.2"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -14,7 +14,6 @@ extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::ffi::CStr; use std::ffi::CStr;
use uucore::fs::is_stdin_interactive;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static ABOUT: &str = "Print the file name of the terminal connected to standard input."; static ABOUT: &str = "Print the file name of the terminal connected to standard input.";
@ -67,7 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
if is_stdin_interactive() { if atty::is(atty::Stream::Stdin) {
libc::EXIT_SUCCESS libc::EXIT_SUCCESS
} else { } else {
libc::EXIT_FAILURE libc::EXIT_FAILURE

View file

@ -374,8 +374,8 @@ fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
let num_inputs = inputs.len(); let num_inputs = inputs.len();
for input in &inputs { for input in &inputs {
let word_count = word_count_from_input(&input, settings).unwrap_or_else(|err| { let word_count = word_count_from_input(input, settings).unwrap_or_else(|err| {
show_error(&input, err); show_error(input, err);
error_count += 1; error_count += 1;
WordCount::default() WordCount::default()
}); });

View file

@ -179,124 +179,58 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// Ignored for 'who am i'. // Ignored for 'who am i'.
let short_list = matches.is_present(options::COUNT); let short_list = matches.is_present(options::COUNT);
// If true, display only name, line, and time fields. let all = matches.is_present(options::ALL);
let mut short_output = false;
// If true, display the hours:minutes since each user has touched
// the keyboard, or "." if within the last minute, or "old" if
// not within the last day.
let mut include_idle = false;
// If true, display a line at the top describing each field. // If true, display a line at the top describing each field.
let include_heading = matches.is_present(options::HEADING); let include_heading = matches.is_present(options::HEADING);
// If true, display a '+' for each user if mesg y, a '-' if mesg n, // If true, display a '+' for each user if mesg y, a '-' if mesg n,
// or a '?' if their tty cannot be statted. // or a '?' if their tty cannot be statted.
let include_mesg = matches.is_present(options::ALL) let include_mesg = all || matches.is_present(options::MESG) || matches.is_present("w");
|| matches.is_present(options::MESG)
|| matches.is_present("w");
// If true, display process termination & exit status.
let mut include_exit = false;
// If true, display the last boot time. // If true, display the last boot time.
let mut need_boottime = false; let need_boottime = all || matches.is_present(options::BOOT);
// If true, display dead processes. // If true, display dead processes.
let mut need_deadprocs = false; let need_deadprocs = all || matches.is_present(options::DEAD);
// If true, display processes waiting for user login. // If true, display processes waiting for user login.
let mut need_login = false; let need_login = all || matches.is_present(options::LOGIN);
// If true, display processes started by init. // If true, display processes started by init.
let mut need_initspawn = false; let need_initspawn = all || matches.is_present(options::PROCESS);
// If true, display the last clock change. // If true, display the last clock change.
let mut need_clockchange = false; let need_clockchange = all || matches.is_present(options::TIME);
// If true, display the current runlevel. // If true, display the current runlevel.
let mut need_runlevel = false; let need_runlevel = all || matches.is_present(options::RUNLEVEL);
let use_defaults = !(all
|| need_boottime
|| need_deadprocs
|| need_login
|| need_initspawn
|| need_runlevel
|| need_clockchange
|| matches.is_present(options::USERS));
// If true, display user processes. // If true, display user processes.
let mut need_users = false; let need_users = all || matches.is_present(options::USERS) || use_defaults;
// If true, display the hours:minutes since each user has touched
// the keyboard, or "." if within the last minute, or "old" if
// not within the last day.
let include_idle = need_deadprocs || need_login || need_runlevel || need_users;
// If true, display process termination & exit status.
let include_exit = need_deadprocs;
// If true, display only name, line, and time fields.
let short_output = !include_exit && use_defaults;
// If true, display info only for the controlling tty. // If true, display info only for the controlling tty.
let mut my_line_only = false; let my_line_only = matches.is_present(options::ONLY_HOSTNAME_USER) || files.len() == 2;
let mut assumptions = true;
#[allow(clippy::useless_let_if_seq)]
{
if matches.is_present(options::ALL) {
need_boottime = true;
need_deadprocs = true;
need_login = true;
need_initspawn = true;
need_runlevel = true;
need_clockchange = true;
need_users = true;
include_idle = true;
include_exit = true;
assumptions = false;
}
if matches.is_present(options::BOOT) {
need_boottime = true;
assumptions = false;
}
if matches.is_present(options::DEAD) {
need_deadprocs = true;
include_idle = true;
include_exit = true;
assumptions = false;
}
if matches.is_present(options::LOGIN) {
need_login = true;
include_idle = true;
assumptions = false;
}
if matches.is_present(options::ONLY_HOSTNAME_USER) || files.len() == 2 {
my_line_only = true;
}
if matches.is_present(options::PROCESS) {
need_initspawn = true;
assumptions = false;
}
if matches.is_present(options::RUNLEVEL) {
need_runlevel = true;
include_idle = true;
assumptions = false;
}
if matches.is_present(options::SHORT) {
short_output = true;
}
if matches.is_present(options::TIME) {
need_clockchange = true;
assumptions = false;
}
if matches.is_present(options::USERS) {
need_users = true;
include_idle = true;
assumptions = false;
}
if assumptions {
need_users = true;
short_output = true;
}
if include_exit {
short_output = false;
}
}
let mut who = Who { let mut who = Who {
do_lookup, do_lookup,

View file

@ -20,7 +20,6 @@ uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
advapi32-sys = "0.2.0"
winapi = { version = "0.3", features = ["lmcons"] } winapi = { version = "0.3", features = ["lmcons"] }
[[bin]] [[bin]]

View file

@ -11,7 +11,7 @@ extern crate winapi;
use self::winapi::shared::lmcons; use self::winapi::shared::lmcons;
use self::winapi::shared::minwindef; use self::winapi::shared::minwindef;
use self::winapi::um::winnt; use self::winapi::um::{winbase, winnt};
use std::io::{Error, Result}; use std::io::{Error, Result};
use std::mem; use std::mem;
use uucore::wide::FromWide; use uucore::wide::FromWide;
@ -20,7 +20,7 @@ pub unsafe fn get_username() -> Result<String> {
#[allow(deprecated)] #[allow(deprecated)]
let mut buffer: [winnt::WCHAR; lmcons::UNLEN as usize + 1] = mem::uninitialized(); let mut buffer: [winnt::WCHAR; lmcons::UNLEN as usize + 1] = mem::uninitialized();
let mut len = buffer.len() as minwindef::DWORD; let mut len = buffer.len() as minwindef::DWORD;
if advapi32::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 { if winbase::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 {
return Err(Error::last_os_error()); return Err(Error::last_os_error());
} }
let username = String::from_wide(&buffer[..len as usize - 1]); let username = String::from_wide(&buffer[..len as usize - 1]);

View file

@ -44,7 +44,6 @@ entries = ["libc"]
fs = ["libc"] fs = ["libc"]
fsext = ["libc", "time"] fsext = ["libc", "time"]
mode = ["libc"] mode = ["libc"]
parse_time = []
perms = ["libc"] perms = ["libc"]
process = ["libc"] process = ["libc"]
ringbuffer = [] ringbuffer = []

View file

@ -6,8 +6,6 @@ pub mod encoding;
pub mod fs; pub mod fs;
#[cfg(feature = "fsext")] #[cfg(feature = "fsext")]
pub mod fsext; pub mod fsext;
#[cfg(feature = "parse_time")]
pub mod parse_time;
#[cfg(feature = "ringbuffer")] #[cfg(feature = "ringbuffer")]
pub mod ringbuffer; pub mod ringbuffer;
#[cfg(feature = "zero-copy")] #[cfg(feature = "zero-copy")]

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