Merge branch 'master' of https://github.com/uutils/coreutils into uutils-master

This commit is contained in:
Tyler 2021-07-01 14:33:30 -07:00
commit 92281585a7
754 changed files with 108268 additions and 17074 deletions

View file

@ -1,7 +1,14 @@
env:
# Temporary workaround for error `error: sysinfo not supported on
# this platform` seen on FreeBSD platforms, affecting Rustup
#
# References: https://github.com/rust-lang/rustup/issues/2774
RUSTUP_IO_THREADS: 1
task:
name: stable x86_64-unknown-freebsd-12
freebsd_instance:
image: freebsd-12-1-release-amd64
image: freebsd-12-2-release-amd64
setup_script:
- pkg install -y curl gmake
- curl https://sh.rustup.rs -sSf --output rustup.sh
@ -11,4 +18,4 @@ task:
- cargo build
test_script:
- . $HOME/.cargo/env
- cargo test
- cargo test -p uucore -p coreutils

3
.codespell.rc Normal file
View file

@ -0,0 +1,3 @@
[codespell]
ignore-words-list = crate
skip = ./.git/**,./.vscode/cspell.dictionaries/**,./target/**,./tests/fixtures/**

View file

@ -3,22 +3,38 @@
# * top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
# default ~ utf-8, unix-style newlines with a newline ending every file, 4 space indentation
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 100
trim_trailing_whitespace = true
[*.{bat,cmd,[Bb][Aa][Tt],[Cc][Mm][Dd]}]
# DOS/Win requires BAT/CMD files to have CRLF EOLNs
end_of_line = crlf
[[Mm]akefile{,.*}]
# TAB-style indentation
[[Mm]akefile{,.*}, *.{mk,[Mm][Kk]}]
# makefiles ~ TAB-style indentation
indent_style = tab
[*.{yml,[Yy][Mm][Ll]}]
[*.{bat,cmd,[Bb][Aa][Tt],[Cc][Mm][Dd]}]
# BAT/CMD ~ DOS/Win requires BAT/CMD files to have CRLF EOLNs
end_of_line = crlf
[*.go]
# go ~ TAB-style indentation (SPACE-style alignment); ref: <https://blog.golang.org/gofmt>@@<https://archive.is/wip/9B6FC>
indent_style = tab
[*.{cjs,js,json,mjs,ts}]
# js/ts
indent_size = 2
[*.{markdown,md,mkd,[Mm][Dd],[Mm][Kk][Dd],[Mm][Dd][Oo][Ww][Nn],[Mm][Kk][Dd][Oo][Ww][Nn],[Mm][Aa][Rr][Kk][Dd][Oo][Ww][Nn]}]
# markdown
indent_size = 2
indent_style = space
[*.{yaml,yml,[Yy][Mm][Ll],[Yy][Aa][Mm][Ll]}]
# YAML
indent_size = 2
indent_style = space

19
.github/stale.yml vendored Normal file
View file

@ -0,0 +1,19 @@
# spell-checker:ignore (labels) wontfix
# Number of days of inactivity before an issue/PR becomes stale
daysUntilStale: 365
# Number of days of inactivity before a stale issue/PR is closed
daysUntilClose: 365
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- "Good first bug"
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View file

@ -5,18 +5,54 @@ name: CICD
# spell-checker:ignore (jargon) SHAs deps softprops toolchain
# spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy
# spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs
# spell-checker:ignore (misc) aarch alnum armhf coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend tempfile uutils
# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend runtest tempfile testsuite uutils
# ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45
env:
PROJECT_NAME: coreutils
PROJECT_DESC: "Core universal (cross-platform) utilities"
PROJECT_AUTH: "uutils"
RUST_MIN_SRV: "1.33.0" ## v1.33.0 - minimum version for tempfile 3.1.0 and libc needed for aarch64
RUST_MIN_SRV: "1.43.1" ## v1.43.0
RUST_COV_SRV: "2020-08-01" ## (~v1.47.0) supported rust version for code coverage; (date required/used by 'coverage') ## !maint: refactor when code coverage support is included in the stable channel
on: [push, pull_request]
jobs:
code_deps:
name: Style/dependencies
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v2
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
outputs CARGO_FEATURES_OPTION
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: "`cargo update` testing"
shell: bash
run: |
## `cargo update` testing
# * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::'Cargo.lock' file requires update (use \`cargo +${{ env.RUST_MIN_SRV }} update\`)" ; exit 1 ; }
code_format:
name: Style/format
runs-on: ${{ matrix.job.os }}
@ -26,18 +62,18 @@ jobs:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
outputs CARGO_FEATURES_OPTION
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
@ -48,18 +84,19 @@ jobs:
- name: "`fmt` testing"
shell: bash
run: |
# `fmt` testing
## `fmt` testing
# * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::warning file=\1,line=\2::WARNING: \`cargo fmt\`: style violation/p" ; }
S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::error file=\1,line=\2::ERROR: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; exit 1 ; }
- name: "`fmt` testing of tests"
if: success() || failure() # run regardless of prior step success/failure
shell: bash
run: |
# `fmt` testing of tests
## `fmt` testing of tests
# * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
S=$(find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" | sed -E -n "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::warning file=\1,line=\2::WARNING: \`cargo fmt\`: style violation/p" ; }
S=$(find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::error file=\1,line=\2::ERROR: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; exit 1 ; }
code_warnings:
name: Style/warnings
code_lint:
name: Style/lint
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
@ -69,32 +106,51 @@ jobs:
- { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
outputs CARGO_FEATURES_OPTION
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: nightly
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: clippy
- name: "`clippy` testing"
if: success() || failure() # run regardless of prior step success/failure
- name: "`clippy` lint testing"
shell: bash
run: |
# `clippy` testing
## `clippy` lint testing
# * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
S=$(cargo clippy ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::warning file=\2,line=\3,col=\4::WARNING: \`cargo clippy\`: \1/p;" -e '}' ; }
S=$(cargo +nightly clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+${PWD//\//\\/}\/(.*):([0-9]+):([0-9]+).*$/::error file=\2,line=\3,col=\4::ERROR: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; exit 1 ; }
code_spellcheck:
name: Style/spelling
runs-on: ${{ matrix.job.os }}
strategy:
matrix:
job:
- { os: ubuntu-latest }
steps:
- uses: actions/checkout@v2
- name: Install/setup prerequisites
shell: bash
run: |
## Install/setup prerequisites
sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell -g ;
- name: Run `cspell`
shell: bash
run: |
## Run `cspell`
cspell --config .vscode/cSpell.json --no-summary --no-progress "**/*" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::error file=\1,line=\2,col=\3::ERROR: \4 (file:'\1', line:\2)/p"
min_version:
name: MinRustV # Minimum supported rust version
@ -104,7 +160,7 @@ jobs:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
uses: actions-rs/toolchain@v1
with:
@ -119,43 +175,42 @@ jobs:
use-tool-cache: true
env:
RUSTUP_TOOLCHAIN: stable
- name: Confirm compatible 'Cargo.lock'
- name: Confirm MinSRV compatible 'Cargo.lock'
shell: bash
run: |
# Confirm compatible 'Cargo.lock'
## Confirm MinSRV compatible 'Cargo.lock'
# * 'Cargo.lock' is required to be in a format that `cargo` of MinSRV can interpret (eg, v1-format for MinSRV < v1.38)
cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::Incompatible 'Cargo.lock' format; try \`cargo +${{ env.RUST_MIN_SRV }} update\`" ; exit 1 ; }
cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::Incompatible (or out-of-date) 'Cargo.lock' file; update using \`cargo +${{ env.RUST_MIN_SRV }} update\`" ; exit 1 ; }
- name: Info
shell: bash
run: |
# Info
## environment
## Info
# environment
echo "## environment"
echo "CI='${CI}'"
## tooling info display
# tooling info display
echo "## tooling"
which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
rustup -V
rustup -V 2>/dev/null
rustup show active-toolchain
cargo -V
rustc -V
cargo-tree tree -V
## dependencies
# dependencies
echo "## dependency list"
cargo fetch --locked --quiet
## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors
RUSTUP_TOOLCHAIN=stable cargo-tree tree --frozen --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
args: --features "feat_os_unix"
args: --features "feat_os_unix" -p uucore -p coreutils
env:
RUSTFLAGS: '-Awarnings'
busybox_test:
name: Busybox test suite
build_makefile:
name: Build/Makefile
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
@ -163,23 +218,26 @@ jobs:
job:
- { os: ubuntu-latest }
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: "prepare busytest"
- name: Install/setup prerequisites
shell: bash
run: |
make prepare-busytest
- name: "run busybox testsuite"
## Install/setup prerequisites
sudo apt-get -y update ; sudo apt-get -y install python3-sphinx ;
- name: "`make build`"
shell: bash
run: |
bindir=$(pwd)/target/debug
cd tmp/busybox-*/testsuite
S=$(bindir=$bindir ./runtest) && printf "%s\n" "$S" || { printf "%s\n" "$S" | grep "FAIL:" | sed -e "s/FAIL: /::warning ::Test failure:/g" ; }
make build
- name: "`make test`"
shell: bash
run: |
make test
build:
name: Build
@ -191,7 +249,6 @@ jobs:
# { os, target, cargo-options, features, use-cross, toolchain }
- { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , features: feat_os_unix_gnueabihf , use-cross: use-cross }
- { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross }
- { os: ubuntu-16.04 , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross }
# - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only
# - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only
- { os: ubuntu-18.04 , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross }
@ -204,36 +261,37 @@ jobs:
- { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly
- { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows }
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Install/setup prerequisites
shell: bash
run: |
## install/setup prerequisites
## Install/setup prerequisites
case '${{ matrix.job.target }}' in
arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
esac
case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for testing
esac
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# toolchain
TOOLCHAIN="stable" ## default to "stable" toolchain
# * specify alternate/non-default TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: GH:rust-lang/rust#47048, GH:rust-lang/rust#53454, GH:rust-lang/cargo#6754)
case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac;
# * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
echo set-output name=TOOLCHAIN::${TOOLCHAIN:-<empty>/false}
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
outputs TOOLCHAIN
# staging directory
STAGING='_staging'
echo set-output name=STAGING::${STAGING}
echo ::set-output name=STAGING::${STAGING}
outputs STAGING
# determine EXE suffix
EXE_suffix="" ; case '${{ matrix.job.target }}' in *-pc-windows-*) EXE_suffix=".exe" ;; esac;
echo set-output name=EXE_suffix::${EXE_suffix}
echo ::set-output name=EXE_suffix::${EXE_suffix}
outputs EXE_suffix
# parse commit reference info
echo GITHUB_REF=${GITHUB_REF}
echo GITHUB_SHA=${GITHUB_SHA}
@ -241,14 +299,7 @@ jobs:
unset REF_BRANCH ; case "${GITHUB_REF}" in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac;
unset REF_TAG ; case "${GITHUB_REF}" in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac;
REF_SHAS=${GITHUB_SHA:0:8}
echo set-output name=REF_NAME::${REF_NAME}
echo set-output name=REF_BRANCH::${REF_BRANCH}
echo set-output name=REF_TAG::${REF_TAG}
echo set-output name=REF_SHAS::${REF_SHAS}
echo ::set-output name=REF_NAME::${REF_NAME}
echo ::set-output name=REF_BRANCH::${REF_BRANCH}
echo ::set-output name=REF_TAG::${REF_TAG}
echo ::set-output name=REF_SHAS::${REF_SHAS}
outputs REF_NAME REF_BRANCH REF_TAG REF_SHAS
# parse target
unset TARGET_ARCH
case '${{ matrix.job.target }}' in
@ -258,68 +309,50 @@ jobs:
i686-*) TARGET_ARCH=i686 ;;
x86_64-*) TARGET_ARCH=x86_64 ;;
esac;
echo set-output name=TARGET_ARCH::${TARGET_ARCH}
echo ::set-output name=TARGET_ARCH::${TARGET_ARCH}
unset TARGET_OS ; case '${{ matrix.job.target }}' in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac;
echo set-output name=TARGET_OS::${TARGET_OS}
echo ::set-output name=TARGET_OS::${TARGET_OS}
outputs TARGET_ARCH TARGET_OS
# package name
PKG_suffix=".tar.gz" ; case '${{ matrix.job.target }}' in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }}
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
echo set-output name=PKG_suffix::${PKG_suffix}
echo set-output name=PKG_BASENAME::${PKG_BASENAME}
echo set-output name=PKG_NAME::${PKG_NAME}
echo ::set-output name=PKG_suffix::${PKG_suffix}
echo ::set-output name=PKG_BASENAME::${PKG_BASENAME}
echo ::set-output name=PKG_NAME::${PKG_NAME}
outputs PKG_suffix PKG_BASENAME PKG_NAME
# deployable tag? (ie, leading "vM" or "M"; M == version number)
unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi
echo set-output name=DEPLOY::${DEPLOY:-<empty>/false}
echo ::set-output name=DEPLOY::${DEPLOY}
outputs DEPLOY
# DPKG architecture?
unset DPKG_ARCH
case ${{ matrix.job.target }} in
x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
*-linux-*) DPKG_ARCH=${TARGET_ARCH} ;;
esac
echo set-output name=DPKG_ARCH::${DPKG_ARCH}
echo ::set-output name=DPKG_ARCH::${DPKG_ARCH}
outputs DPKG_ARCH
# DPKG version?
unset DPKG_VERSION ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DPKG_VERSION=${REF_TAG/#[vV]/} ; fi
echo set-output name=DPKG_VERSION::${DPKG_VERSION}
echo ::set-output name=DPKG_VERSION::${DPKG_VERSION}
outputs DPKG_VERSION
# DPKG base name/conflicts?
DPKG_BASENAME=${PROJECT_NAME}
DPKG_CONFLICTS=${PROJECT_NAME}-musl
case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac;
echo set-output name=DPKG_BASENAME::${DPKG_BASENAME}
echo set-output name=DPKG_CONFLICTS::${DPKG_CONFLICTS}
echo ::set-output name=DPKG_BASENAME::${DPKG_BASENAME}
echo ::set-output name=DPKG_CONFLICTS::${DPKG_CONFLICTS}
outputs DPKG_BASENAME DPKG_CONFLICTS
# DPKG name
unset DPKG_NAME;
if [[ -n $DPKG_ARCH && -n $DPKG_VERSION ]]; then DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" ; fi
echo set-output name=DPKG_NAME::${DPKG_NAME}
echo ::set-output name=DPKG_NAME::${DPKG_NAME}
outputs DPKG_NAME
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
outputs CARGO_FEATURES_OPTION
# * CARGO_USE_CROSS (truthy)
CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac;
echo set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS:-<empty>/false}
echo ::set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS}
outputs CARGO_USE_CROSS
# ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml")
if [ -n "${CARGO_USE_CROSS}" ] && [ ! -e "Cross.toml" ] ; then
printf "[build.env]\npassthrough = [\"CI\"]\n" > Cross.toml
fi
# * test only library and/or binaries for arm-type targets
unset CARGO_TEST_OPTIONS ; case '${{ matrix.job.target }}' in aarch64-* | arm-*) CARGO_TEST_OPTIONS="--bins" ;; esac;
echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
outputs CARGO_TEST_OPTIONS
# * executable for `strip`?
STRIP="strip"
case ${{ matrix.job.target }} in
@ -327,17 +360,20 @@ jobs:
arm-*-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;;
*-pc-windows-msvc) STRIP="" ;;
esac;
echo set-output name=STRIP::${STRIP:-<empty>/false}
echo ::set-output name=STRIP::${STRIP}
outputs STRIP
- name: Create all needed build/work directories
shell: bash
run: |
## create build/work space
## Create build/work space
mkdir -p '${{ steps.vars.outputs.STAGING }}'
mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}'
mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg'
- name: rust toolchain ~ install
uses: actions-rs/toolchain@v1
env:
# Override auto-detection of RAM for Rustc install.
# https://github.com/rust-lang/rustup/issues/2229#issuecomment-585855925
RUSTUP_UNPACK_RAM: "21474836480"
with:
toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
target: ${{ matrix.job.target }}
@ -348,11 +384,12 @@ jobs:
shell: bash
run: |
## Dependent VARs setup
outputs() { step_id="dep_vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
echo UTILITY_LIST=${UTILITY_LIST}
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
echo set-output name=UTILITY_LIST::${UTILITY_LIST}
echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS}
outputs CARGO_UTILITY_LIST_OPTIONS
- name: Install `cargo-tree` # for dependency information
uses: actions-rs/install@v0.1
with:
@ -364,26 +401,26 @@ jobs:
- name: Info
shell: bash
run: |
# Info
## commit info
## Info
# commit info
echo "## commit"
echo GITHUB_REF=${GITHUB_REF}
echo GITHUB_SHA=${GITHUB_SHA}
## environment
# environment
echo "## environment"
echo "CI='${CI}'"
## tooling info display
# tooling info display
echo "## tooling"
which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
rustup -V
rustup -V 2>/dev/null
rustup show active-toolchain
cargo -V
rustc -V
cargo-tree tree -V
## dependencies
# dependencies
echo "## dependency list"
cargo fetch --locked --quiet
cargo-tree tree --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all --no-dev-dependencies --no-indent | grep -vE "$PWD" | sort --unique
cargo-tree tree --locked --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all --no-dev-dependencies --no-indent | grep -vE "$PWD" | sort --unique
- name: Build
uses: actions-rs/cargo@v1
with:
@ -410,7 +447,7 @@ jobs:
- name: Package
shell: bash
run: |
## package artifact(s)
## Package artifact(s)
# binary
cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
# `strip` binary (if needed)
@ -451,6 +488,37 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
test_busybox:
name: Tests/BusyBox test suite
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest }
steps:
- uses: actions/checkout@v2
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: Install/setup prerequisites
shell: bash
run: |
make prepare-busytest
- name: "Run BusyBox test suite"
shell: bash
run: |
## Run BusyBox test suite
bindir=$(pwd)/target/debug
cd tmp/busybox-*/testsuite
output=$(bindir=$bindir ./runtest 2>&1 || true)
printf "%s\n" "${output}"
n_fails=$(echo "$output" | grep "^FAIL:\s" | wc --lines)
if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi
coverage:
name: Code Coverage
runs-on: ${{ matrix.job.os }}
@ -463,41 +531,45 @@ jobs:
- { os: macos-latest , features: macos }
- { os: windows-latest , features: windows }
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Install/setup prerequisites
shell: bash
run: |
## Install/setup prerequisites
case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for testing
esac
# - name: Reattach HEAD ## may be needed for accurate code coverage info
# run: git checkout ${{ github.head_ref }}
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# toolchain
TOOLCHAIN="nightly-${{ env.RUST_COV_SRV }}" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support
# * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files
case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
# * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
echo set-output name=TOOLCHAIN::${TOOLCHAIN}
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
outputs TOOLCHAIN
# staging directory
STAGING='_staging'
echo set-output name=STAGING::${STAGING}
echo ::set-output name=STAGING::${STAGING}
outputs STAGING
## # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>)
## # note: CODECOV_TOKEN / HAS_CODECOV_TOKEN is not needed for public repositories when using AppVeyor, Azure Pipelines, CircleCI, GitHub Actions, Travis (see <https://docs.codecov.io/docs/about-the-codecov-bash-uploader#section-upload-token>)
## unset HAS_CODECOV_TOKEN
## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi
## echo set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN}
## echo ::set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN}
## outputs HAS_CODECOV_TOKEN
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
outputs CARGO_FEATURES_OPTION
# * CODECOV_FLAGS
CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' )
echo set-output name=CODECOV_FLAGS::${CODECOV_FLAGS}
echo ::set-output name=CODECOV_FLAGS::${CODECOV_FLAGS}
outputs CODECOV_FLAGS
- name: rust toolchain ~ install
uses: actions-rs/toolchain@v1
with:
@ -509,11 +581,22 @@ jobs:
shell: bash
run: |
## Dependent VARs setup
outputs() { step_id="dep_vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
echo set-output name=UTILITY_LIST::${UTILITY_LIST}
echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS}
outputs CARGO_UTILITY_LIST_OPTIONS
- name: Test uucore
uses: actions-rs/cargo@v1
with:
command: test
args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast -p uucore
env:
CARGO_INCREMENTAL: '0'
RUSTC_WRAPPER: ''
RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort'
RUSTDOCFLAGS: '-Cpanic=abort'
# RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }}
- name: Test
uses: actions-rs/cargo@v1
with:
@ -541,12 +624,12 @@ jobs:
with:
crate: grcov
version: latest
use-tool-cache: true
use-tool-cache: false
- name: Generate coverage data (via `grcov`)
id: coverage
shell: bash
run: |
# generate coverage data
## Generate coverage data
COVERAGE_REPORT_DIR="target/debug"
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
# GRCOV_IGNORE_OPTION='--ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?)

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

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

View file

@ -1,101 +0,0 @@
name: GNU
on: [push, pull_request]
jobs:
gnu:
name: Run GNU tests
runs-on: ubuntu-latest
steps:
# Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code uutil
uses: actions/checkout@v2
with:
path: 'uutils'
- name: Chechout GNU coreutils
uses: actions/checkout@v2
with:
repository: 'coreutils/coreutils'
path: 'gnu'
- name: Chechout GNU corelib
uses: actions/checkout@v2
with:
repository: 'coreutils/gnulib'
path: 'gnulib'
fetch-depth: 0 # gnu gets upset if gnulib is a shallow checkout
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt
- name: Build binaries
shell: bash
run: |
sudo apt-get update
sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify expect python3-sphinx
pushd uutils
make PROFILE=release
BUILDDIR="$PWD/target/release/"
popd
GNULIB_SRCDIR="$PWD/gnulib"
pushd gnu/
./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR"
./configure --quiet --disable-gcc-warnings
# Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils
sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile
sed -i 's| tr | /usr/bin/tr |' tests/init.sh
make
# Generate the factor tests, so they can be fixed
for i in $(seq -w 1 36)
do
make tests/factor/t${i}.sh
done
grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||'
sed -i -e 's|^seq |/usr/bin/seq |' tests/factor/t*sh
sed -i -e '/tests\/misc\/cat-self.sh/ D' Makefile # issue #1707
sed -i -e '/tests\/misc\/numfmt.pl/ D' Makefile # issue #1708
sed -i -e '/tests\/chown\/preserve-root.sh/ D' Makefile # issue #1709
sed -i -e '/tests\/cp\/file-perm-race.sh/ D' Makefile # issue #1710
sed -i -e '/tests\/cp\/special-f.sh/ D' Makefile # issue #1710
sed -i -e '/tests\/mv\/mv-special-1.sh/ D' Makefile # issue #1710
sed -i -e '/tests\/dd\/stats.sh/ D' Makefile # issue #1710
sed -i -e '/tests\/cp\/existing-perm-race.sh/ D' Makefile # hangs, cp doesn't implement --copy-contents
# Remove tests that rely on seq with large numbers. See issue #1703
sed -i -e '/tests\/misc\/seq.pl/ D' \
-e '/tests\/misc\/seq-precision.sh/D' \
Makefile
# Remove tests checking for --version & --help
# Not really interesting for us and logs are too big
sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \
-e '/tests\/misc\/help-version.sh/ D' \
-e '/tests\/misc\/help-version-getopt.sh/ D' \
Makefile
sed -i -e '/tests\/tail-2\/pid.sh/ D' Makefile # hangs on github, tail doesn't support -f
sed -i -e'/incompat4/ D' -e"/options '-co' are incompatible/ d" tests/misc/sort.pl # Sort doesn't correctly check for incompatible options, waits for input
test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}"
- name: Run GNU tests
shell: bash
run: |
BUILDDIR="${PWD}/uutils/target/release"
GNULIB_DIR="${PWD}/gnulib"
pushd gnu
unbuffer timeout -sKILL 3600 make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || :
- name: Extract tests info
shell: bash
run: |
TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-)
FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
XPASS=$( grep "# XPASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
ERROR=$( grep "# ERROR:" gnu/tests/test-suite.log|cut -d' ' -f2-)
echo "::warning ::GNU testsuite = $TOTAL / $PASS / $FAIL / $ERROR"
- uses: actions/upload-artifact@v2
with:
name: test-report
path: gnu/tests/**/*.log

92
.github/workflows/GnuTests.yml vendored Normal file
View file

@ -0,0 +1,92 @@
name: GnuTests
# spell-checker:ignore (names) gnulib ; (utils) autopoint gperf pyinotify texinfo ; (vars) XPASS
on: [push, pull_request]
jobs:
gnu:
name: Run GNU tests
runs-on: ubuntu-latest
steps:
- name: Checkout code uutil
uses: actions/checkout@v2
with:
path: 'uutils'
- name: Checkout GNU coreutils
uses: actions/checkout@v2
with:
repository: 'coreutils/coreutils'
path: 'gnu'
ref: v8.32
- name: Checkout GNU coreutils library (gnulib)
uses: actions/checkout@v2
with:
repository: 'coreutils/gnulib'
path: 'gnulib'
ref: 8e99f24c0931a38880c6ee9b8287c7da80b0036b
fetch-depth: 0 # gnu gets upset if gnulib is a shallow checkout
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt
- name: Install dependencies
shell: bash
run: |
## Install dependencies
sudo apt-get update
sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx jq
- name: Build binaries
shell: bash
run: |
## Build binaries
cd uutils
bash util/build-gnu.sh
- name: Run GNU tests
shell: bash
run: |
bash uutils/util/run-gnu-test.sh
- name: Extract testing info
shell: bash
run: |
## Extract testing info
LOG_FILE=gnu/tests/test-suite.log
if test -f "$LOG_FILE"
then
TOTAL=$(sed -n "s/.*# TOTAL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
PASS=$(sed -n "s/.*# PASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
SKIP=$(sed -n "s/.*# SKIP: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
if [[ "$TOTAL" -eq 0 || "$TOTAL" -eq 1 ]]; then
echo "Error in the execution, failing early"
exit 1
fi
output="GNU tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR"
echo "${output}"
if [[ "$FAIL" -gt 0 || "$ERROR" -gt 0 ]]; then echo "::warning ::${output}" ; fi
jq -n \
--arg date "$(date --rfc-email)" \
--arg sha "$GITHUB_SHA" \
--arg total "$TOTAL" \
--arg pass "$PASS" \
--arg skip "$SKIP" \
--arg fail "$FAIL" \
--arg xpass "$XPASS" \
--arg error "$ERROR" \
'{($date): { sha: $sha, total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }}' > gnu-result.json
else
echo "::error ::Failed to get summary of test results"
fi
- uses: actions/upload-artifact@v2
with:
name: test-report
path: gnu/tests/**/*.log
- uses: actions/upload-artifact@v2
with:
name: gnu-result
path: gnu-result.json

3
.gitignore vendored
View file

@ -12,3 +12,6 @@ target/
Cargo.lock
lib*.a
/docs/_build
*.iml
### macOS ###
.DS_Store

17
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,17 @@
repos:
- repo: local
hooks:
- id: rust-linting
name: Rust linting
description: Run cargo fmt on files included in the commit.
entry: cargo +nightly fmt --
pass_filenames: true
types: [file, rust]
language: system
- id: rust-clippy
name: Rust clippy
description: Run cargo clippy on files included in the commit.
entry: cargo +nightly clippy --all-targets --all-features --
pass_filenames: false
types: [file, rust]
language: system

View file

@ -1,72 +0,0 @@
language: rust
rust:
- stable
- beta
os:
- linux
# - osx
env:
# sphinx v1.8.0 is bugged & fails for linux builds; so, force specific `sphinx` version
global: FEATURES='' TEST_INSTALL='' SPHINX_VERSIONED='sphinx==1.7.8'
matrix:
allow_failures:
- rust: beta
- rust: nightly
fast_finish: true
include:
- rust: 1.33.0
env: FEATURES=unix
# - rust: stable
# os: linux
# env: FEATURES=unix TEST_INSTALL=true
# - rust: stable
# os: osx
# env: FEATURES=macos TEST_INSTALL=true
- rust: nightly
os: linux
env: FEATURES=nightly,unix TEST_INSTALL=true
- rust: nightly
os: osx
env: FEATURES=nightly,macos TEST_INSTALL=true
- rust: nightly
os: linux
env: FEATURES=nightly,feat_os_unix_redox CC=x86_64-unknown-redox-gcc CARGO_ARGS='--no-default-features --target=x86_64-unknown-redox' REDOX=1
cache:
directories:
- $HOME/.cargo
sudo: true
before_install:
- if [ $REDOX ]; then ./.travis/redox-toolchain.sh; fi
install:
- if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python-pip && sudo pip install $SPHINX_VERSIONED; fi
- |
if [ $TRAVIS_OS_NAME = osx ]; then
brew update
brew upgrade python
pip3 install $SPHINX_VERSIONED
fi
script:
- cargo build $CARGO_ARGS --features "$FEATURES"
- if [ ! $REDOX ]; then cargo test $CARGO_ARGS --features "$FEATURES" --no-fail-fast; fi
- if [ -n "$TEST_INSTALL" ]; then mkdir installdir_test; DESTDIR=installdir_test make install; [ `ls installdir_test/usr/local/bin | wc -l` -gt 0 ]; fi
addons:
apt:
packages:
- libssl-dev
after_success: |
if [ "$TRAVIS_OS_NAME" = linux -a "$TRAVIS_RUST_VERSION" = stable ]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
cargo tarpaulin --out Xml
bash <(curl -s https://codecov.io/bash)
fi

365
.vscode/cSpell.json vendored
View file

@ -1,352 +1,19 @@
// `cspell` settings
{
"version": "0.1", // Version of the setting file. Always 0.1
"language": "en", // language - current active spelling language
// ignoreWords
"ignoreWords": [
// abbrev/acronyms
"Cygwin",
"FreeBSD",
"Gmail",
"GNUEABI",
"GNUEABIhf",
"Irix",
"MacOS",
"MinGW",
"Minix",
"MS-DOS",
"MSDOS",
"NetBSD",
"Novell",
"OpenBSD",
"POSIX",
"SELinux",
"Solaris",
"Xenix",
"flac",
"lzma",
// cargo
"cdylib",
"rlib",
// crates
"advapi",
"advapi32-sys",
"aho-corasick",
"backtrace",
"byteorder",
"chacha",
"chrono",
"conv",
"corasick",
"filetime",
"formatteriteminfo",
"getopts",
"itertools",
"memchr",
"multifilereader",
"onig",
"peekreader",
"quickcheck",
"rand_chacha",
"smallvec",
"tempfile",
"termion",
"termios",
"termsize",
"termwidth",
"textwrap",
"walkdir",
"winapi",
"xattr",
// jargon
"AST", // abstract syntax tree
"CPU",
"CPUs",
"FIFO",
"FIFOs",
"FQDN", // fully qualified domain name
"GID", // group ID
"GIDs",
"POSIXLY",
"RNG", // random number generator
"RNGs",
"UID", // user ID
"UIDs",
"UUID", // universally unique identifier
"arity",
"bitmask",
"canonicalization",
"canonicalize",
"colorizable",
"colorize",
"consts",
"dedup",
"demangle",
"deque",
"dequeue",
"enqueue",
"executable",
"executables",
"gibibytes",
"hardfloat",
"hardlink",
"hardlinks",
"hashsums",
"kibibytes",
"mebibytes",
"mergeable",
"multibyte",
"nonportable",
"peekable",
"precompiled",
"precompute",
"preload",
"prepend",
"prepended",
"primality",
"pseudoprime",
"pseudoprimes",
"procs",
"readonly",
"seedable",
"semver",
"shortcode",
"shortcodes",
"symlink",
"symlinks",
"syscall",
"toekenize",
"unbuffered",
"unportable",
"whitespace",
// names
"Akira Hayakawa", "Akira", "Hayakawa",
"Alan Andrade", "Alan", "Andrade",
"Alex Lyon", "Alex", "Lyon",
"Alexander Batischev", "Alexander", "Batischev",
"Aleksander Bielawski", "Aleksander", "Bielawski",
"Alexander Fomin", "Alexander", "Fomin",
"Anthony Deschamps", "Anthony", "Deschamps",
"Ben Eills", "Ben", "Eills",
"Ben Hirsch", "Ben", "Hirsch",
"Benoit Benedetti", "Benoit", "Benedetti",
"Boden Garman", "Boden", "Garman",
"Chirag B Jadwani", "Chirag", "Jadwani",
"Derek Chiang", "Derek", "Chiang",
"Dorota Kapturkiewicz", "Dorota", "Kapturkiewicz",
"Evgeniy Klyuchikov", "Evgeniy", "Klyuchikov",
"Fangxu Hu", "Fangxu", "Hu",
"Gil Cottle", "Gil", "Cottle",
"Haitao Li", "Haitao", "Li",
"Inokentiy Babushkin", "Inokentiy", "Babushkin",
"Joao Oliveira", "Joao", "Oliveira",
"Jeremiah Peschka", "Jeremiah", "Peschka",
"Jian Zeng", "Jian", "Zeng",
"Jimmy Lu", "Jimmy", "Lu",
"Jordi Boggiano", "Jordi", "Boggiano",
"Jordy Dickinson", "Jordy", "Dickinson",
"Joseph Crail", "Joseph", "Crail",
"Joshua S Miller", "Joshua", "Miller",
"KokaKiwi",
"Konstantin Pospelov", "Konstantin", "Pospelov",
"Mahkoh",
"Maciej Dziardziel", "Maciej", "Dziardziel",
"Michael Gehring", "Michael", "Gehring",
"Martin Kysel", "Martin", "Kysel",
"Morten Olsen Lysgaard", "Morten", "Olsen", "Lysgaard",
"Nicholas Juszczak", "Nicholas", "Juszczak",
"Nick Platt", "Nick", "Platt",
"Orvar Segerström", "Orvar", "Segerström",
"Peter Atashian", "Peter", "Atashian",
"Rolf Morel", "Rolf", "Morel",
"Roman Gafiyatullin", "Roman", "Gafiyatullin",
"Roy Ivy III", "Roy", "Ivy", "III",
"Sergey 'Shnatsel' Davidoff", "Sergey", "Shnatsel", "Davidoff",
"Sokovikov Evgeniy", "Sokovikov", "Evgeniy",
"Sunrin SHIMURA", "Sunrin", "SHIMURA",
"Smigle00", "Smigle",
"Sylvestre Ledru", "Sylvestre", "Ledru",
"T Jameson Little", "Jameson", "Little",
"Tobias Bohumir Schottdorf", "Tobias", "Bohumir", "Schottdorf",
"Virgile Andreani", "Virgile", "Andreani",
"Vsevolod Velichko", "Vsevolod", "Velichko",
"Wiktor Kuropatwa", "Wiktor", "Kuropatwa",
"Yury Krivopalov", "Yury", "Krivopalov",
"anonymousknight",
"kwantam",
"nicoo",
"rivy",
// rust
"clippy",
"concat",
"fract",
"powi",
"println",
"repr",
"rfind",
"rustc",
"rustfmt",
"struct",
"structs",
"substr",
"splitn",
"trunc",
// shell
"passwd",
"pipefail",
"tcsh",
// tags
"Maint",
// uutils
"chcon",
"chgrp",
"chmod",
"chown",
"chroot",
"cksum",
"csplit",
"dircolors",
"hashsum",
"hostid",
"logname",
"mkdir",
"mkfifo",
"mknod",
"mktemp",
"nohup",
"nproc",
"numfmt",
"pathchk",
"printenv",
"printf",
"readlink",
"realpath",
"relpath",
"rmdir",
"runcon",
"shuf",
"stdbuf",
"stty",
"tsort",
"uname",
"unexpand",
"whoami",
// vars/errno
"errno",
"EOPNOTSUPP",
// vars/fcntl
"F_GETFL",
"GETFL",
"fcntl",
"vmsplice",
// vars/libc
"FILENO",
"HOSTSIZE",
"IDSIZE",
"IFIFO",
"IFREG",
"IRGRP",
"IROTH",
"IRUSR",
"ISGID",
"ISUID",
"ISVTX",
"IWGRP",
"IWOTH",
"IWUSR",
"IXGRP",
"IXOTH",
"IXUSR",
"LINESIZE",
"NAMESIZE",
"USERSIZE",
"addrinfo",
"addrlen",
"canonname",
"chroot",
"freeaddrinfo",
"getaddrinfo",
"getegid",
"geteuid",
"getgid",
"getgrgid",
"getgrnam",
"getgrouplist",
"getgroups",
"getpwnam",
"getpwuid",
"getuid",
"inode",
"isatty",
"lchown",
"setgid",
"setgroups",
"setuid",
"socktype",
"umask",
"waitpid",
// vars/nix
"iovec",
"unistd",
// vars/signals
"SIGPIPE",
// vars/sync
"Condvar",
// vars/stat
"fstat",
"stat",
// vars/time
"Timespec",
"nsec",
"nsecs",
"strftime",
"usec",
"usecs",
// vars/utmpx
"endutxent",
"getutxent",
"getutxid",
"getutxline",
"pututxline",
"setutxent",
"utmp",
"utmpx",
"utmpxname",
// vars/winapi
"errhandlingapi",
"fileapi",
"handleapi",
"lmcons",
"minwindef",
"processthreadsapi",
"synchapi",
"sysinfoapi",
"winbase",
"winerror",
"winnt",
"winsock",
"DWORD",
"LPWSTR",
"WCHAR",
// uucore
"optflag",
"optflagmulti",
"optflagopt",
"optmulti",
"optopt",
// uutils
"coreopts",
"coreutils",
"libc",
"libstdbuf",
"musl",
"ucmd",
"utmpx",
"uucore",
"uucore_procs",
"uumain",
"uutils"
],
// words - list of words to be always considered correct
"words": []
"version": "0.1", // Version of the setting file. Always 0.1
"language": "en", // language - current active spelling language
"dictionaries": ["acronyms+names", "jargon", "people", "shell", "workspace"],
"dictionaryDefinitions": [
{ "name": "acronyms+names", "path": "./cspell.dictionaries/acronyms+names.wordlist.txt" },
{ "name": "jargon", "path": "./cspell.dictionaries/jargon.wordlist.txt" },
{ "name": "people", "path": "./cspell.dictionaries/people.wordlist.txt" },
{ "name": "shell", "path": "./cspell.dictionaries/shell.wordlist.txt" },
{ "name": "workspace", "path": "./cspell.dictionaries/workspace.wordlist.txt" }
],
// ignorePaths - a list of globs to specify which files are to be ignored
"ignorePaths": ["Cargo.lock", "target/**", "tests/**/fixtures/**"],
// ignoreWords - a list of words to be ignored (even if they are in the flagWords)
"ignoreWords": [],
// words - list of words to be always considered correct
"words": []
}

View file

@ -0,0 +1,66 @@
# * abbreviations / acronyms
AIX
ASLR # address space layout randomization
AST # abstract syntax tree
CICD # continuous integration/deployment
CPU
CPUs
DevOps
Ext3
FIFO
FIFOs
FQDN # fully qualified domain name
GID # group ID
GIDs
GNU
GNUEABI
GNUEABIhf
JFS
MSRV # minimum supported rust version
MSVC
NixOS
POSIX
POSIXLY
RISC
RISCV
RNG # random number generator
RNGs
ReiserFS
Solaris
UID # user ID
UIDs
UUID # universally unique identifier
WASI
WASM
XFS
aarch
flac
lzma
# * names
BusyBox
BusyTest
Codacy
Cygwin
Deno
EditorConfig
FreeBSD
Gmail
GNU
Irix
MS-DOS
MSDOS
MacOS
MinGW
Minix
NetBSD
Novell
OpenBSD
POSIX
PowerPC
SELinux
SkyPack
Solaris
SysV
Xenix
Yargs

View file

@ -0,0 +1,111 @@
arity
autogenerate
autogenerated
autogenerates
bitmask
bitwise
bytewise
canonicalization
canonicalize
canonicalizing
colorizable
colorize
coprime
consts
cyclomatic
dedup
deduplication
demangle
denoland
deque
dequeue
dev
devs
discoverability
duplicative
enqueue
errored
executable
executables
exponentiate
eval
falsey
flamegraph
gibibytes
glob
globbing
hardfloat
hardlink
hardlinks
hasher
hashsums
kibi
kibibytes
mebi
mebibytes
mergeable
microbenchmark
microbenchmarks
microbenchmarking
multibyte
multicall
nonportable
nonprinting
peekable
performant
precompiled
precompute
preload
prepend
prepended
primality
pseudoprime
pseudoprimes
quantiles
readonly
reparse
seedable
semver
semiprime
semiprimes
shortcode
shortcodes
subcommand
subexpression
submodule
symlink
symlinks
syscall
syscalls
tokenize
toolchain
truthy
unbuffered
unescape
unintuitive
unprefixed
unportable
unsync
whitespace
wordlist
wordlists
# * abbreviations
consts
deps
dev
maint
proc
procs
# * constants
xffff
# * variables
delim
errno
progname
retval
subdir
val
vals

View file

@ -0,0 +1,178 @@
Akira Hayakawa
Akira
Hayakawa
Alan Andrade
Alan
Andrade
Aleksander Bielawski
Aleksander
Bielawski
Alex Lyon
Alex
Lyon
Alexander Batischev
Alexander
Batischev
Alexander Fomin
Alexander
Fomin
Anthony Deschamps
Anthony
Deschamps
Árni Dagur
Árni
Dagur
Ben Eills
Ben
Eills
Ben Hirsch
Ben
Hirsch
Benoit Benedetti
Benoit
Benedetti
Boden Garman
Boden
Garman
Chirag B Jadwani
Chirag
Jadwani
Derek Chiang
Derek
Chiang
Dorota Kapturkiewicz
Dorota
Kapturkiewicz
Evgeniy Klyuchikov
Evgeniy
Klyuchikov
Fangxu Hu
Fangxu
Hu
Gil Cottle
Gil
Cottle
Haitao Li
Haitao
Li
Inokentiy Babushkin
Inokentiy
Babushkin
Jan Scheer * jhscheer
Jan
Scheer
jhscheer
Jeremiah Peschka
Jeremiah
Peschka
Jian Zeng
Jian
Zeng
Jimmy Lu
Jimmy
Lu
Joao Oliveira
Joao
Oliveira
Jordi Boggiano
Jordi
Boggiano
Jordy Dickinson
Jordy
Dickinson
Joseph Crail
Joseph
Crail
Joshua S Miller
Joshua
Miller
Konstantin Pospelov
Konstantin
Pospelov
Maciej Dziardziel
Maciej
Dziardziel
Martin Kysel
Martin
Kysel
Michael Debertol
Michael
Debertol
Michael Gehring
Michael
Gehring
Mitchell Mebane
Mitchell
Mebane
Morten Olsen Lysgaard
Morten
Olsen
Lysgaard
Nicholas Juszczak
Nicholas
Juszczak
Nick Platt
Nick
Platt
Orvar Segerström
Orvar
Segerström
Peter Atashian
Peter
Atashian
Robert Swinford
Robert
Swinford
Rolf Morel
Rolf
Morel
Roman Gafiyatullin
Roman
Gafiyatullin
Roy Ivy III * rivy
Roy
Ivy
III
rivy
Sergey "Shnatsel" Davidoff
Sergey Shnatsel Davidoff
Sergey
Shnatsel
Davidoff
Sokovikov Evgeniy
Sokovikov
Evgeniy
Sunrin SHIMURA
Sunrin
SHIMURA
Sylvestre Ledru
Sylvestre
Ledru
T Jameson Little
Jameson
Little
Tobias Bohumir Schottdorf
Tobias
Bohumir
Schottdorf
Virgile Andreani
Virgile
Andreani
Vsevolod Velichko
Vsevolod
Velichko
Wiktor Kuropatwa
Wiktor
Kuropatwa
Yury Krivopalov
Yury
Krivopalov
KokaKiwi
Mahkoh
Smigle00
Smigle00
Smigle
anonymousknight
kwantam
nicoo

View file

@ -0,0 +1,93 @@
# * Mac
clonefile
# * POSIX
TMPDIR
adduser
csh
globstar
inotify
localtime
mountinfo
mountpoint
mtab
nullglob
passwd
pipefail
popd
ptmx
pushd
setarch
sh
sudo
sudoedit
tcsh
tzselect
urandom
wtmp
zsh
# * Windows
APPDATA
COMSPEC
HKCU
HKLM
HOMEDRIVE
HOMEPATH
LOCALAPPDATA
PATHEXT
PATHEXT
SYSTEMROOT
USERDOMAIN
USERNAME
USERPROFILE
procmon
# * `git`
gitattributes
gitignore
# * `make` (`gmake`)
CURDIR
GNUMAKEFLAGS
GNUMakefile
LIBPATTERNS
MAKECMDGOALS
MAKEFILES
MAKEFLAGS
MAKELEVEL
MAKESHELL
SHELLSTATUS
VPATH
abspath
addprefix
addsuffix
endef
firstword
ifeq
ifneq
lastword
notdir
patsubst
# * `npm`
preversion
# * utilities
cachegrind
chglog
codespell
commitlint
dprint
dtrace
gcov
gmake
grcov
grep
markdownlint
rerast
rollup
sed
wslpath
xargs

View file

@ -0,0 +1,300 @@
# * cargo
cdylib
rlib
# * crates
advapi
advapi32-sys
aho-corasick
backtrace
blake2b_simd
bstr
byteorder
chacha
chrono
conv
corasick
crossterm
filetime
formatteriteminfo
fsext
getopts
getrandom
globset
itertools
lscolors
memchr
multifilereader
onig
ouroboros
peekreader
quickcheck
rand_chacha
ringbuffer
rlimit
smallvec
tempdir
tempfile
termion
termios
termsize
termwidth
textwrap
thiserror
walkdir
winapi
xattr
# * rust/rustc
RUSTDOCFLAGS
RUSTFLAGS
clippy
rustc
rustfmt
rustup
#
bitor # BitOr trait function
bitxor # BitXor trait function
concat
fract
powi
println
repr
rfind
struct
structs
substr
splitn
trunc
# * uutils
chcon
chgrp
chmod
chown
chroot
cksum
csplit
dircolors
hashsum
hostid
logname
mkdir
mkfifo
mknod
mktemp
nohup
nproc
numfmt
pathchk
printenv
printf
readlink
realpath
relpath
rmdir
runcon
shuf
sprintf
stdbuf
stty
tsort
uname
unexpand
whoami
# * vars/errno
errno
EEXIST
ENOENT
ENOSYS
EPERM
EOPNOTSUPP
# * vars/fcntl
F_GETFL
GETFL
fcntl
vmsplice
# * vars/libc
FILENO
HOSTSIZE
IDSIZE
IFBLK
IFCHR
IFDIR
IFIFO
IFLNK
IFMT
IFREG
IFSOCK
IRGRP
IROTH
IRUSR
ISGID
ISUID
ISVTX
IWGRP
IWOTH
IWUSR
IXGRP
IXOTH
IXUSR
LINESIZE
NAMESIZE
RTLD_NEXT
RTLD
SIGINT
SIGKILL
SIGTERM
SYS_fdatasync
SYS_syncfs
USERSIZE
addrinfo
addrlen
blocksize
canonname
chroot
dlsym
fdatasync
freeaddrinfo
getaddrinfo
getegid
geteuid
getgid
getgrgid
getgrnam
getgrouplist
getgroups
getpwnam
getpwuid
getuid
inode
inodes
isatty
lchown
setgid
setgroups
settime
setuid
socktype
statfs
statvfs
strcmp
strerror
syncfs
umask
waitpid
wcslen
# * vars/nix
iovec
unistd
# * vars/signals
SIGPIPE
# * vars/std
CString
pathbuf
# * vars/stat
bavail
bfree
bsize
ffree
frsize
fsid
fstat
fstype
namelen
# unix::fs::MetadataExt
atime # access time
blksize # blocksize for file system I/O
blocks # number of blocks allocated to file
ctime # creation time
dev # ID of device containing the file
gid # group ID of file owner
ino # inode number
mode # permissions
mtime # modification time
nlink # number of hard links to file
rdev # device ID if file is a character/block special file
size # total size of file in bytes
uid # user ID of file owner
nsec # nanosecond measurement scale
# freebsd::MetadataExt
iosize
# * vars/time
Timespec
isdst
nanos
nsec
nsecs
strftime
strptime
subsec
usec
usecs
utcoff
# * vars/utmpx
endutxent
getutxent
getutxid
getutxline
pututxline
setutxent
utmp
utmpx
utmpxname
# * vars/winapi
DWORD
SYSTEMTIME
LPVOID
LPWSTR
ULONG
ULONGLONG
UNLEN
WCHAR
errhandlingapi
fileapi
handleapi
lmcons
minwinbase
minwindef
processthreadsapi
synchapi
sysinfoapi
winbase
winerror
winnt
winsock
# * vars/uucore
optflag
optflagmulti
optflagopt
optmulti
optopt
# * uutils
ccmd
coreopts
coreutils
keepenv
libc
libstdbuf
musl
tmpd
ucmd
ucommand
utmpx
uucore
uucore_procs
uumain
uutil
uutils

View file

@ -1,10 +1,12 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
// spell-checker:ignore (misc) matklad
// see <http://go.microsoft.com/fwlink/?LinkId=827846> for the documentation about the extensions.json format
"recommendations": [
// Rust language support.
"rust-lang.rust",
// Provides support for rust-analyzer: novel LSP server for the Rust programming language.
"matklad.rust-analyzer"
"matklad.rust-analyzer",
// `cspell` spell-checker support
"streetsidesoftware.code-spell-checker"
]
}

128
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
sylvestre@debian.org.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View file

@ -18,10 +18,12 @@ search the issues to make sure no one else is working on it.
## Best practices
1. Follow what GNU is doing in term of options and behavior.
1. If possible, look at the GNU test suite execution in the CI and make the test work if failing.
1. Use clap for argument management.
1. Make sure that the code coverage is covering all of the cases, including errors.
1. The code must be clippy-warning-free and rustfmt-compliant.
1. Don't hesitate to move common functions into uucore if they can be reused by other binaries.
1. Unsafe code should be documented with Safety comments.
## Commit messages
@ -69,10 +71,6 @@ lines for non-utility modules include:
README: add help
```
```
travis: fix build
```
```
uucore: add new modules
```

1228
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
[package]
name = "coreutils"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust"
@ -43,6 +43,7 @@ feat_common_core = [
"df",
"dircolors",
"dirname",
"du",
"echo",
"env",
"expand",
@ -62,8 +63,10 @@ feat_common_core = [
"more",
"mv",
"nl",
"numfmt",
"od",
"paste",
"pr",
"printenv",
"printf",
"ptx",
@ -149,7 +152,6 @@ feat_require_unix = [
"chmod",
"chown",
"chroot",
"du",
"dd",
"groups",
"hostid",
@ -160,7 +162,6 @@ feat_require_unix = [
"mkfifo",
"mknod",
"nice",
"numfmt",
"nohup",
"pathchk",
"stat",
@ -225,135 +226,142 @@ test = [ "uu_test" ]
[workspace]
[dependencies]
clap = "2.33.3"
lazy_static = { version="1.3" }
textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review
uucore = { version=">=0.0.7", package="uucore", path="src/uucore" }
uucore = { version=">=0.0.8", package="uucore", path="src/uucore" }
# * uutils
uu_test = { optional=true, version="0.0.4", package="uu_test", path="src/uu/test" }
uu_test = { optional=true, version="0.0.6", package="uu_test", path="src/uu/test" }
#
arch = { optional=true, version="0.0.4", package="uu_arch", path="src/uu/arch" }
base32 = { optional=true, version="0.0.4", package="uu_base32", path="src/uu/base32" }
base64 = { optional=true, version="0.0.4", package="uu_base64", path="src/uu/base64" }
basename = { optional=true, version="0.0.4", package="uu_basename", path="src/uu/basename" }
cat = { optional=true, version="0.0.4", package="uu_cat", path="src/uu/cat" }
chgrp = { optional=true, version="0.0.4", package="uu_chgrp", path="src/uu/chgrp" }
chmod = { optional=true, version="0.0.4", package="uu_chmod", path="src/uu/chmod" }
chown = { optional=true, version="0.0.4", package="uu_chown", path="src/uu/chown" }
chroot = { optional=true, version="0.0.4", package="uu_chroot", path="src/uu/chroot" }
cksum = { optional=true, version="0.0.4", package="uu_cksum", path="src/uu/cksum" }
comm = { optional=true, version="0.0.4", package="uu_comm", path="src/uu/comm" }
cp = { optional=true, version="0.0.4", package="uu_cp", path="src/uu/cp" }
csplit = { optional=true, version="0.0.4", package="uu_csplit", path="src/uu/csplit" }
cut = { optional=true, version="0.0.4", package="uu_cut", path="src/uu/cut" }
date = { optional=true, version="0.0.4", package="uu_date", path="src/uu/date" }
df = { optional=true, version="0.0.4", package="uu_df", path="src/uu/df" }
dircolors= { optional=true, version="0.0.4", package="uu_dircolors", path="src/uu/dircolors" }
dirname = { optional=true, version="0.0.4", package="uu_dirname", path="src/uu/dirname" }
du = { optional=true, version="0.0.4", package="uu_du", path="src/uu/du" }
arch = { optional=true, version="0.0.6", package="uu_arch", path="src/uu/arch" }
base32 = { optional=true, version="0.0.6", package="uu_base32", path="src/uu/base32" }
base64 = { optional=true, version="0.0.6", package="uu_base64", path="src/uu/base64" }
basename = { optional=true, version="0.0.6", package="uu_basename", path="src/uu/basename" }
cat = { optional=true, version="0.0.6", package="uu_cat", path="src/uu/cat" }
chgrp = { optional=true, version="0.0.6", package="uu_chgrp", path="src/uu/chgrp" }
chmod = { optional=true, version="0.0.6", package="uu_chmod", path="src/uu/chmod" }
chown = { optional=true, version="0.0.6", package="uu_chown", path="src/uu/chown" }
chroot = { optional=true, version="0.0.6", package="uu_chroot", path="src/uu/chroot" }
cksum = { optional=true, version="0.0.6", package="uu_cksum", path="src/uu/cksum" }
comm = { optional=true, version="0.0.6", package="uu_comm", path="src/uu/comm" }
cp = { optional=true, version="0.0.6", package="uu_cp", path="src/uu/cp" }
csplit = { optional=true, version="0.0.6", package="uu_csplit", path="src/uu/csplit" }
cut = { optional=true, version="0.0.6", package="uu_cut", path="src/uu/cut" }
date = { optional=true, version="0.0.6", package="uu_date", path="src/uu/date" }
dd = { optional=true, version="0.0.4", package="uu_dd", path="src/uu/dd" }
echo = { optional=true, version="0.0.4", package="uu_echo", path="src/uu/echo" }
env = { optional=true, version="0.0.4", package="uu_env", path="src/uu/env" }
expand = { optional=true, version="0.0.4", package="uu_expand", path="src/uu/expand" }
expr = { optional=true, version="0.0.4", package="uu_expr", path="src/uu/expr" }
factor = { optional=true, version="0.0.4", package="uu_factor", path="src/uu/factor" }
false = { optional=true, version="0.0.4", package="uu_false", path="src/uu/false" }
fmt = { optional=true, version="0.0.4", package="uu_fmt", path="src/uu/fmt" }
fold = { optional=true, version="0.0.4", package="uu_fold", path="src/uu/fold" }
groups = { optional=true, version="0.0.4", package="uu_groups", path="src/uu/groups" }
hashsum = { optional=true, version="0.0.4", package="uu_hashsum", path="src/uu/hashsum" }
head = { optional=true, version="0.0.4", package="uu_head", path="src/uu/head" }
hostid = { optional=true, version="0.0.4", package="uu_hostid", path="src/uu/hostid" }
hostname = { optional=true, version="0.0.4", package="uu_hostname", path="src/uu/hostname" }
id = { optional=true, version="0.0.4", package="uu_id", path="src/uu/id" }
install = { optional=true, version="0.0.4", package="uu_install", path="src/uu/install" }
join = { optional=true, version="0.0.4", package="uu_join", path="src/uu/join" }
kill = { optional=true, version="0.0.4", package="uu_kill", path="src/uu/kill" }
link = { optional=true, version="0.0.4", package="uu_link", path="src/uu/link" }
ln = { optional=true, version="0.0.4", package="uu_ln", path="src/uu/ln" }
ls = { optional=true, version="0.0.4", package="uu_ls", path="src/uu/ls" }
logname = { optional=true, version="0.0.4", package="uu_logname", path="src/uu/logname" }
mkdir = { optional=true, version="0.0.4", package="uu_mkdir", path="src/uu/mkdir" }
mkfifo = { optional=true, version="0.0.4", package="uu_mkfifo", path="src/uu/mkfifo" }
mknod = { optional=true, version="0.0.4", package="uu_mknod", path="src/uu/mknod" }
mktemp = { optional=true, version="0.0.4", package="uu_mktemp", path="src/uu/mktemp" }
more = { optional=true, version="0.0.4", package="uu_more", path="src/uu/more" }
mv = { optional=true, version="0.0.4", package="uu_mv", path="src/uu/mv" }
nice = { optional=true, version="0.0.4", package="uu_nice", path="src/uu/nice" }
nl = { optional=true, version="0.0.4", package="uu_nl", path="src/uu/nl" }
nohup = { optional=true, version="0.0.4", package="uu_nohup", path="src/uu/nohup" }
nproc = { optional=true, version="0.0.4", package="uu_nproc", path="src/uu/nproc" }
numfmt = { optional=true, version="0.0.4", package="uu_numfmt", path="src/uu/numfmt" }
od = { optional=true, version="0.0.4", package="uu_od", path="src/uu/od" }
paste = { optional=true, version="0.0.4", package="uu_paste", path="src/uu/paste" }
pathchk = { optional=true, version="0.0.4", package="uu_pathchk", path="src/uu/pathchk" }
pinky = { optional=true, version="0.0.4", package="uu_pinky", path="src/uu/pinky" }
printenv = { optional=true, version="0.0.4", package="uu_printenv", path="src/uu/printenv" }
printf = { optional=true, version="0.0.4", package="uu_printf", path="src/uu/printf" }
ptx = { optional=true, version="0.0.4", package="uu_ptx", path="src/uu/ptx" }
pwd = { optional=true, version="0.0.4", package="uu_pwd", path="src/uu/pwd" }
readlink = { optional=true, version="0.0.4", package="uu_readlink", path="src/uu/readlink" }
realpath = { optional=true, version="0.0.4", package="uu_realpath", path="src/uu/realpath" }
relpath = { optional=true, version="0.0.4", package="uu_relpath", path="src/uu/relpath" }
rm = { optional=true, version="0.0.4", package="uu_rm", path="src/uu/rm" }
rmdir = { optional=true, version="0.0.4", package="uu_rmdir", path="src/uu/rmdir" }
seq = { optional=true, version="0.0.4", package="uu_seq", path="src/uu/seq" }
shred = { optional=true, version="0.0.4", package="uu_shred", path="src/uu/shred" }
shuf = { optional=true, version="0.0.4", package="uu_shuf", path="src/uu/shuf" }
sleep = { optional=true, version="0.0.4", package="uu_sleep", path="src/uu/sleep" }
sort = { optional=true, version="0.0.4", package="uu_sort", path="src/uu/sort" }
split = { optional=true, version="0.0.4", package="uu_split", path="src/uu/split" }
stat = { optional=true, version="0.0.4", package="uu_stat", path="src/uu/stat" }
stdbuf = { optional=true, version="0.0.4", package="uu_stdbuf", path="src/uu/stdbuf" }
sum = { optional=true, version="0.0.4", package="uu_sum", path="src/uu/sum" }
sync = { optional=true, version="0.0.4", package="uu_sync", path="src/uu/sync" }
tac = { optional=true, version="0.0.4", package="uu_tac", path="src/uu/tac" }
tail = { optional=true, version="0.0.4", package="uu_tail", path="src/uu/tail" }
tee = { optional=true, version="0.0.4", package="uu_tee", path="src/uu/tee" }
timeout = { optional=true, version="0.0.4", package="uu_timeout", path="src/uu/timeout" }
touch = { optional=true, version="0.0.4", package="uu_touch", path="src/uu/touch" }
tr = { optional=true, version="0.0.4", package="uu_tr", path="src/uu/tr" }
true = { optional=true, version="0.0.4", package="uu_true", path="src/uu/true" }
truncate = { optional=true, version="0.0.4", package="uu_truncate", path="src/uu/truncate" }
tsort = { optional=true, version="0.0.4", package="uu_tsort", path="src/uu/tsort" }
tty = { optional=true, version="0.0.4", package="uu_tty", path="src/uu/tty" }
uname = { optional=true, version="0.0.4", package="uu_uname", path="src/uu/uname" }
unexpand = { optional=true, version="0.0.4", package="uu_unexpand", path="src/uu/unexpand" }
uniq = { optional=true, version="0.0.4", package="uu_uniq", path="src/uu/uniq" }
unlink = { optional=true, version="0.0.4", package="uu_unlink", path="src/uu/unlink" }
uptime = { optional=true, version="0.0.4", package="uu_uptime", path="src/uu/uptime" }
users = { optional=true, version="0.0.4", package="uu_users", path="src/uu/users" }
wc = { optional=true, version="0.0.4", package="uu_wc", path="src/uu/wc" }
who = { optional=true, version="0.0.4", package="uu_who", path="src/uu/who" }
whoami = { optional=true, version="0.0.4", package="uu_whoami", path="src/uu/whoami" }
yes = { optional=true, version="0.0.4", package="uu_yes", path="src/uu/yes" }
df = { optional=true, version="0.0.6", package="uu_df", path="src/uu/df" }
dircolors= { optional=true, version="0.0.6", package="uu_dircolors", path="src/uu/dircolors" }
dirname = { optional=true, version="0.0.6", package="uu_dirname", path="src/uu/dirname" }
du = { optional=true, version="0.0.6", package="uu_du", path="src/uu/du" }
echo = { optional=true, version="0.0.6", package="uu_echo", path="src/uu/echo" }
env = { optional=true, version="0.0.6", package="uu_env", path="src/uu/env" }
expand = { optional=true, version="0.0.6", package="uu_expand", path="src/uu/expand" }
expr = { optional=true, version="0.0.6", package="uu_expr", path="src/uu/expr" }
factor = { optional=true, version="0.0.6", package="uu_factor", path="src/uu/factor" }
false = { optional=true, version="0.0.6", package="uu_false", path="src/uu/false" }
fmt = { optional=true, version="0.0.6", package="uu_fmt", path="src/uu/fmt" }
fold = { optional=true, version="0.0.6", package="uu_fold", path="src/uu/fold" }
groups = { optional=true, version="0.0.6", package="uu_groups", path="src/uu/groups" }
hashsum = { optional=true, version="0.0.6", package="uu_hashsum", path="src/uu/hashsum" }
head = { optional=true, version="0.0.6", package="uu_head", path="src/uu/head" }
hostid = { optional=true, version="0.0.6", package="uu_hostid", path="src/uu/hostid" }
hostname = { optional=true, version="0.0.6", package="uu_hostname", path="src/uu/hostname" }
id = { optional=true, version="0.0.6", package="uu_id", path="src/uu/id" }
install = { optional=true, version="0.0.6", package="uu_install", path="src/uu/install" }
join = { optional=true, version="0.0.6", package="uu_join", path="src/uu/join" }
kill = { optional=true, version="0.0.6", package="uu_kill", path="src/uu/kill" }
link = { optional=true, version="0.0.6", package="uu_link", path="src/uu/link" }
ln = { optional=true, version="0.0.6", package="uu_ln", path="src/uu/ln" }
ls = { optional=true, version="0.0.6", package="uu_ls", path="src/uu/ls" }
logname = { optional=true, version="0.0.6", package="uu_logname", path="src/uu/logname" }
mkdir = { optional=true, version="0.0.6", package="uu_mkdir", path="src/uu/mkdir" }
mkfifo = { optional=true, version="0.0.6", package="uu_mkfifo", path="src/uu/mkfifo" }
mknod = { optional=true, version="0.0.6", package="uu_mknod", path="src/uu/mknod" }
mktemp = { optional=true, version="0.0.6", package="uu_mktemp", path="src/uu/mktemp" }
more = { optional=true, version="0.0.6", package="uu_more", path="src/uu/more" }
mv = { optional=true, version="0.0.6", package="uu_mv", path="src/uu/mv" }
nice = { optional=true, version="0.0.6", package="uu_nice", path="src/uu/nice" }
nl = { optional=true, version="0.0.6", package="uu_nl", path="src/uu/nl" }
nohup = { optional=true, version="0.0.6", package="uu_nohup", path="src/uu/nohup" }
nproc = { optional=true, version="0.0.6", package="uu_nproc", path="src/uu/nproc" }
numfmt = { optional=true, version="0.0.6", package="uu_numfmt", path="src/uu/numfmt" }
od = { optional=true, version="0.0.6", package="uu_od", path="src/uu/od" }
paste = { optional=true, version="0.0.6", package="uu_paste", path="src/uu/paste" }
pathchk = { optional=true, version="0.0.6", package="uu_pathchk", path="src/uu/pathchk" }
pinky = { optional=true, version="0.0.6", package="uu_pinky", path="src/uu/pinky" }
pr = { optional=true, version="0.0.6", package="uu_pr", path="src/uu/pr" }
printenv = { optional=true, version="0.0.6", package="uu_printenv", path="src/uu/printenv" }
printf = { optional=true, version="0.0.6", package="uu_printf", path="src/uu/printf" }
ptx = { optional=true, version="0.0.6", package="uu_ptx", path="src/uu/ptx" }
pwd = { optional=true, version="0.0.6", package="uu_pwd", path="src/uu/pwd" }
readlink = { optional=true, version="0.0.6", package="uu_readlink", path="src/uu/readlink" }
realpath = { optional=true, version="0.0.6", package="uu_realpath", path="src/uu/realpath" }
relpath = { optional=true, version="0.0.6", package="uu_relpath", path="src/uu/relpath" }
rm = { optional=true, version="0.0.6", package="uu_rm", path="src/uu/rm" }
rmdir = { optional=true, version="0.0.6", package="uu_rmdir", path="src/uu/rmdir" }
seq = { optional=true, version="0.0.6", package="uu_seq", path="src/uu/seq" }
shred = { optional=true, version="0.0.6", package="uu_shred", path="src/uu/shred" }
shuf = { optional=true, version="0.0.6", package="uu_shuf", path="src/uu/shuf" }
sleep = { optional=true, version="0.0.6", package="uu_sleep", path="src/uu/sleep" }
sort = { optional=true, version="0.0.6", package="uu_sort", path="src/uu/sort" }
split = { optional=true, version="0.0.6", package="uu_split", path="src/uu/split" }
stat = { optional=true, version="0.0.6", package="uu_stat", path="src/uu/stat" }
stdbuf = { optional=true, version="0.0.6", package="uu_stdbuf", path="src/uu/stdbuf" }
sum = { optional=true, version="0.0.6", package="uu_sum", path="src/uu/sum" }
sync = { optional=true, version="0.0.6", package="uu_sync", path="src/uu/sync" }
tac = { optional=true, version="0.0.6", package="uu_tac", path="src/uu/tac" }
tail = { optional=true, version="0.0.6", package="uu_tail", path="src/uu/tail" }
tee = { optional=true, version="0.0.6", package="uu_tee", path="src/uu/tee" }
timeout = { optional=true, version="0.0.6", package="uu_timeout", path="src/uu/timeout" }
touch = { optional=true, version="0.0.6", package="uu_touch", path="src/uu/touch" }
tr = { optional=true, version="0.0.6", package="uu_tr", path="src/uu/tr" }
true = { optional=true, version="0.0.6", package="uu_true", path="src/uu/true" }
truncate = { optional=true, version="0.0.6", package="uu_truncate", path="src/uu/truncate" }
tsort = { optional=true, version="0.0.6", package="uu_tsort", path="src/uu/tsort" }
tty = { optional=true, version="0.0.6", package="uu_tty", path="src/uu/tty" }
uname = { optional=true, version="0.0.6", package="uu_uname", path="src/uu/uname" }
unexpand = { optional=true, version="0.0.6", package="uu_unexpand", path="src/uu/unexpand" }
uniq = { optional=true, version="0.0.6", package="uu_uniq", path="src/uu/uniq" }
unlink = { optional=true, version="0.0.6", package="uu_unlink", path="src/uu/unlink" }
uptime = { optional=true, version="0.0.6", package="uu_uptime", path="src/uu/uptime" }
users = { optional=true, version="0.0.6", package="uu_users", path="src/uu/users" }
wc = { optional=true, version="0.0.6", package="uu_wc", path="src/uu/wc" }
who = { optional=true, version="0.0.6", package="uu_who", path="src/uu/who" }
whoami = { optional=true, version="0.0.6", package="uu_whoami", path="src/uu/whoami" }
yes = { optional=true, version="0.0.6", package="uu_yes", path="src/uu/yes" }
# this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)"
# factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" }
#
# * pinned transitive dependencies
pin_cc = { version="1.0.61, < 1.0.62", package="cc" } ## cc v1.0.62 has compiler errors for MinRustV v1.32.0, requires 1.34 (for `std::str::split_ascii_whitespace()`)
pin_rustc-demangle = { version="0.1.16, < 0.1.17", package="rustc-demangle" } ## rust-demangle v0.1.17 has compiler errors for MinRustV v1.32.0, expects 1.33
pin_same-file = { version="1.0.4, < 1.0.6", package="same-file" } ## same-file v1.0.6 has compiler errors for MinRustV v1.32.0, expects 1.34
pin_winapi-util = { version="0.1.2, < 0.1.3", package="winapi-util" } ## winapi-util v0.1.3 has compiler errors for MinRustV v1.32.0, expects 1.34
pin_byteorder = { version="1.3.4, < 1.4.0", package="byteorder" } ## byteorder v1.4 has compiler errors for MinRustV v1.32.0, requires 1.3 (for `use of unstable library feature 'try_from' (see issue #33417)`)
pin_thread_local = { version="1.1.0, < 1.1.1", package="thread_local" } ## thread_local v1.1.2 has compiler errors for MinRustV v1.32.0, requires 1.36 (for `use of unstable library feature 'maybe_uninit'`)
# Not needed for now. Keep as examples:
#pin_cc = { version="1.0.61, < 1.0.62", package="cc" } ## cc v1.0.62 has compiler errors for MinRustV v1.32.0, requires 1.34 (for `std::str::split_ascii_whitespace()`)
[dev-dependencies]
chrono = "0.4.11"
conv = "0.3"
filetime = "0.2"
glob = "0.3.0"
libc = "0.2"
nix = "0.20.0"
pretty_assertions = "0.7.2"
rand = "0.7"
regex = "1.0"
sha1 = { version="0.6", features=["std"] }
## tempfile 3.2 depends on recent version of rand which depends on getrandom v0.2 which has compiler errors for MinRustV v1.32.0
## min dep for tempfile = Rustc 1.40
tempfile = "= 3.1.0"
tempfile = "3.2.0"
time = "0.1"
unindent = "0.1"
uucore = { version=">=0.0.7", package="uucore", path="src/uucore", features=["entries"] }
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries", "process"] }
walkdir = "2.2"
atty = "0.2"
[target.'cfg(unix)'.dev-dependencies]
rlimit = "0.4.0"
rust-users = { version="0.10", package="users" }
unix_socket = "0.5.0"
[[bin]]
name = "coreutils"
path = "src/bin/coreutils.rs"

44
DEVELOPER_INSTRUCTIONS.md Normal file
View file

@ -0,0 +1,44 @@
Code Coverage Report Generation
---------------------------------
<!-- spell-checker:ignore (flags) Ccodegen Coverflow Cpanic Zinstrument Zpanic -->
Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov).
### Using Nightly Rust
To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-cc) coverage report
```bash
$ export CARGO_INCREMENTAL=0
$ export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
$ export RUSTDOCFLAGS="-Cpanic=abort"
$ cargo build <options...> # e.g., --features feat_os_unix
$ cargo test <options...> # e.g., --features feat_os_unix test_pathchk
$ grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
$ # open target/debug/coverage/index.html in browser
```
if changes are not reflected in the report then run `cargo clean` and run the above commands.
### Using Stable Rust
If you are using stable version of Rust that doesn't enable code coverage instrumentation by default
then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above.
pre-commit hooks
----------------
A configuration for `pre-commit` is provided in the repository. It allows automatically checking every git commit you make to ensure it compiles, and passes `clippy` and `rustfmt` without warnings.
To use the provided hook:
1. [Install `pre-commit`](https://pre-commit.com/#install)
2. Run `pre-commit install` while in the repository directory
Your git commits will then automatically be checked. If a check fails, an error message will explain why, and your commit will be canceled. You can then make the suggested changes, and run `git commit ...` again.
### Using Clippy
The `msrv` key in the clippy configuration file `clippy.toml` is used to disable lints pertaining to newer features by specifying the minimum supported Rust version (MSRV). However, this key is only supported on `nightly`. To invoke clippy without errors, use `cargo +nightly clippy`. In order to also check tests and non-default crate features, use `cargo +nightly clippy --all-targets --all-features`.

View file

@ -1,3 +1,5 @@
# spell-checker:ignore (misc) testsuite runtest (targets) busytest distclean manpages pkgs ; (vars/env) BINDIR BUILDDIR CARGOFLAGS DESTDIR DOCSDIR INSTALLDIR INSTALLEES MANDIR MULTICALL
# Config options
PROFILE ?= debug
MULTICALL ?= n
@ -30,7 +32,7 @@ ifneq ($(.SHELLSTATUS),0)
override INSTALLDIR_MAN=$(DESTDIR)$(PREFIX)$(MANDIR)
endif
#prefix to apply to uutils binary and all tool binaries
#prefix to apply to coreutils binary and all tool binaries
PROG_PREFIX ?=
# This won't support any directory with spaces in its name, but you can just
@ -82,6 +84,7 @@ PROGS := \
nproc \
od \
paste \
pr \
printenv \
printf \
ptx \
@ -188,6 +191,7 @@ TEST_PROGS := \
paste \
pathchk \
pinky \
pr \
printf \
ptx \
pwd \
@ -237,7 +241,7 @@ EXES := \
INSTALLEES := ${EXES}
ifeq (${MULTICALL}, y)
INSTALLEES := ${INSTALLEES} uutils
INSTALLEES := ${INSTALLEES} coreutils
endif
all: build
@ -250,13 +254,13 @@ ifneq (${MULTICALL}, y)
${CARGO} build ${CARGOFLAGS} ${PROFILE_CMD} $(foreach pkg,$(EXES),-p uu_$(pkg))
endif
build-uutils:
build-coreutils:
${CARGO} build ${CARGOFLAGS} --features "${EXES}" ${PROFILE_CMD} --no-default-features
build-manpages:
cd $(DOCSDIR) && $(MAKE) man
build: build-uutils build-pkgs build-manpages
build: build-coreutils build-pkgs build-manpages
$(foreach test,$(filter-out $(SKIP_UTILS),$(PROGS)),$(eval $(call TEST_BUSYBOX,$(test))))
@ -264,22 +268,24 @@ test:
${CARGO} test ${CARGOFLAGS} --features "$(TESTS) $(TEST_SPEC_FEATURE)" --no-default-features $(TEST_NO_FAIL_FAST)
busybox-src:
if [ ! -e $(BUSYBOX_SRC) ]; then \
mkdir -p $(BUSYBOX_ROOT); \
wget https://busybox.net/downloads/busybox-$(BUSYBOX_VER).tar.bz2 -P $(BUSYBOX_ROOT); \
tar -C $(BUSYBOX_ROOT) -xf $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER).tar.bz2; \
fi; \
if [ ! -e "$(BUSYBOX_SRC)" ] ; then \
mkdir -p "$(BUSYBOX_ROOT)" ; \
wget "https://busybox.net/downloads/busybox-$(BUSYBOX_VER).tar.bz2" -P "$(BUSYBOX_ROOT)" ; \
tar -C "$(BUSYBOX_ROOT)" -xf "$(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER).tar.bz2" ; \
fi ;
# This is a busybox-specific config file their test suite wants to parse.
$(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
cp $< $@
# Test under the busybox testsuite
$(BUILDDIR)/busybox: busybox-src build-uutils $(BUILDDIR)/.config
cp $(BUILDDIR)/coreutils $(BUILDDIR)/busybox; \
chmod +x $@;
# Test under the busybox test suite
$(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config
cp "$(BUILDDIR)/coreutils" "$(BUILDDIR)/busybox"
chmod +x $@
prepare-busytest: $(BUILDDIR)/busybox
# disable inapplicable tests
-( cd "$(BUSYBOX_SRC)/testsuite" ; if [ -e "busybox.tests" ] ; then mv busybox.tests busybox.tests- ; fi ; )
ifeq ($(EXES),)
busytest:
@ -298,23 +304,31 @@ install: build
mkdir -p $(INSTALLDIR_BIN)
mkdir -p $(INSTALLDIR_MAN)
ifeq (${MULTICALL}, y)
$(INSTALL) $(BUILDDIR)/uutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)uutils
cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out uutils, $(INSTALLEES)), \
ln -fs $(PROG_PREFIX)uutils $(PROG_PREFIX)$(prog) &&) :
cat $(DOCSDIR)/_build/man/uutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)uutils.1.gz
$(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils
cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out coreutils, $(INSTALLEES)), \
ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)$(prog) &&) :
cat $(DOCSDIR)/_build/man/coreutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)coreutils.1.gz
else
$(foreach prog, $(INSTALLEES), \
$(INSTALL) $(BUILDDIR)/$(prog) $(INSTALLDIR_BIN)/$(PROG_PREFIX)$(prog);)
endif
$(foreach man, $(filter $(INSTALLEES), $(basename $(notdir $(wildcard $(DOCSDIR)/_build/man/*)))), \
cat $(DOCSDIR)/_build/man/$(man).1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)$(man).1.gz &&) :
$(foreach prog, $(INSTALLEES), \
$(BUILDDIR)/coreutils completion $(prog) zsh > $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(PROG_PREFIX)$(prog); \
$(BUILDDIR)/coreutils completion $(prog) bash > $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(PROG_PREFIX)$(prog); \
$(BUILDDIR)/coreutils completion $(prog) fish > $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(PROG_PREFIX)$(prog).fish; \
)
uninstall:
ifeq (${MULTICALL}, y)
rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)uutils)
rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)coreutils)
endif
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)uutils.1.gz)
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz)
rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS))
rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(PROG_PREFIX),$(PROGS))
rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(PROG_PREFIX),$(PROGS))
rm -f $(addprefix $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(PROG_PREFIX),$(addsuffix .fish,$(PROGS)))
rm -f $(addprefix $(INSTALLDIR_MAN)/$(PROG_PREFIX),$(addsuffix .1.gz,$(PROGS)))
.PHONY: all build build-uutils build-pkgs build-docs test distclean clean busytest install uninstall
.PHONY: all build build-coreutils build-pkgs build-docs test distclean clean busytest install uninstall

View file

@ -1,5 +1,5 @@
USEGNU=gmake $*
UseGNU=gmake $*
all:
@$(USEGNU)
@$(UseGNU)
.DEFAULT:
@$(USEGNU)
@$(UseGNU)

264
README.md
View file

@ -1,5 +1,4 @@
uutils coreutils
================
# uutils coreutils
[![Crates.io](https://img.shields.io/crates/v/coreutils.svg)](https://crates.io/crates/coreutils)
[![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ)
@ -7,303 +6,382 @@ uutils coreutils
[![LOC](https://tokei.rs/b1/github/uutils/coreutils?category=code)](https://github.com/Aaronepower/tokei)
[![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils)
[![Build Status](https://api.travis-ci.org/uutils/coreutils.svg?branch=master)](https://travis-ci.org/uutils/coreutils)
[![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master)
[![codecov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils)
[![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils)
-----------------------------------------------
<!-- markdownlint-disable commands-show-output no-duplicate-heading -->
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR RUNTEST UTILNAME -->
uutils is an attempt at writing universal (as in cross-platform) CLI
utilities in [Rust](http://www.rust-lang.org). This repository is intended to
aggregate GNU coreutils rewrites.
Why?
----
## Why?
Many GNU, Linux and other utilities are useful, and obviously
[some](http://gnuwin32.sourceforge.net) [effort](http://unxutils.sourceforge.net)
has been spent in the past to port them to Windows. However, those projects
are either old and abandoned, are hosted on CVS (which makes it more difficult
for new contributors to contribute to them), are written in platform-specific C, or
suffer from other issues.
are written in platform-specific C, a language considered unsafe compared to Rust, and
have other issues.
Rust provides a good, platform-agnostic way of writing systems utilities that are easy
to compile anywhere, and this is as good a way as any to try and learn it.
Requirements
------------
## Requirements
* Rust (`cargo`, `rustc`)
* GNU Make (required to build documentation)
* [Sphinx](http://www.sphinx-doc.org/) (for documentation)
* gzip (for installing documentation)
### Rust Version ###
### Rust Version
uutils follows Rust's release channels and is tested against stable, beta and nightly.
The current oldest supported version of the Rust compiler is `1.33.0`.
The current oldest supported version of the Rust compiler is `1.43.1`.
On both Windows and Redox, only the nightly version is tested currently.
Build Instructions
------------------
## Build Instructions
There are currently two methods to build uutils: GNU Make and Cargo. However,
while there may be two methods, both systems are required to build on Unix
(only Cargo is required on Windows).
There are currently two methods to build the uutils binaries: either Cargo
or GNU Make.
> Building the full package, including all documentation, requires both Cargo
> and Gnu Make on a Unix platform.
For either method, we first need to fetch the repository:
First, for both methods, we need to fetch the repository:
```bash
$ git clone https://github.com/uutils/coreutils
$ cd coreutils
```
### Cargo ###
### Cargo
Building uutils using Cargo is easy because the process is the same as for
every other Rust program:
```bash
# to keep debug information, compile without --release
$ cargo build --release
```
Because the above command attempts to build utilities that only work on
Unix-like platforms at the moment, to build on Windows, you must do the
following:
This command builds the most portable common core set of uutils into a multicall
(BusyBox-type) binary, named 'coreutils', on most Rust-supported platforms.
Additional platform-specific uutils are often available. Building these
expanded sets of uutils for a platform (on that platform) is as simple as
specifying it as a feature:
```bash
# to keep debug information, compile without --release
$ cargo build --release --no-default-features --features windows
$ cargo build --release --features macos
# or ...
$ cargo build --release --features windows
# or ...
$ cargo build --release --features unix
```
If you don't want to build every utility available on your platform into the
multicall binary (the Busybox-esque binary), you can also specify which ones
you want to build manually. For example:
final binary, you can also specify which ones you want to build manually.
For example:
```bash
$ cargo build --features "base32 cat echo rm" --no-default-features
```
If you don't even want to build the multicall binary and would prefer to just
build the utilities as individual binaries, that is possible too. For example:
If you don't want to build the multicall binary and would prefer to build
the utilities as individual binaries, that is also possible. Each utility
is contained in its own package within the main repository, named
"uu_UTILNAME". To build individual utilities, use cargo to build just the
specific packages (using the `--package` [aka `-p`] option). For example:
```bash
$ cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm
```
### GNU Make ###
### GNU Make
Building using `make` is a simple process as well.
To simply build all available utilities:
```bash
$ make
```
To build all but a few of the available utilities:
```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2'
```
To build only a few of the available utilities:
```bash
$ make UTILS='UTILITY_1 UTILITY_2'
```
Installation Instructions
-------------------------
## Installation Instructions
### Cargo ###
### Cargo
Likewise, installing can simply be done using:
```bash
$ cargo install --path .
```
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
### GNU Make ###
This does not install files necessary for shell completion. For shell completion to work,
use `GNU Make` or see `Manually install shell completions`.
### GNU Make
To install all available utilities:
```bash
$ make install
```
To install using `sudo` switch `-E` must be used:
```bash
$ sudo -E make install
```
To install all but a few of the available utilities:
```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' install
```
To install only a few of the available utilities:
```bash
$ make UTILS='UTILITY_1 UTILITY_2' install
```
To install every program with a prefix (e.g. uu-echo uu-cat):
```bash
$ make PROG_PREFIX=PREFIX_GOES_HERE install
```
To install the multicall binary:
```bash
$ make MULTICALL=y install
```
Set install parent directory (default value is /usr/local):
```bash
# DESTDIR is also supported
$ make PREFIX=/my/path install
```
### NixOS ###
Installing with `make` installs shell completions for all installed utilities
for `bash`, `fish` and `zsh`. Completions for `elvish` and `powershell` can also
be generated; See `Manually install shell completions`.
### NixOS
The [standard package set](https://nixos.org/nixpkgs/manual/) of [NixOS](https://nixos.org/)
provides this package out of the box since 18.03:
```
nix-env -iA nixos.uutils-coreutils
```shell
$ nix-env -iA nixos.uutils-coreutils
```
Uninstallation Instructions
---------------------------
### Manually install shell completions
Uninstallation differs depending on how you have installed uutils. If you used
The `coreutils` binary can generate completions for the `bash`, `elvish`, `fish`, `powershell`
and `zsh` shells. It prints the result to stdout.
The syntax is:
```bash
cargo run completion <utility> <shell>
```
So, to install completions for `ls` on `bash` to `/usr/local/share/bash-completion/completions/ls`,
run:
```bash
cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
```
## Un-installation Instructions
Un-installation differs depending on how you have installed uutils. If you used
Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use
Make to uninstall.
### Cargo ###
### Cargo
To uninstall uutils:
```bash
$ cargo uninstall uutils
```
### GNU Make ###
### GNU Make
To uninstall all utilities:
```bash
$ make uninstall
```
To uninstall every program with a set prefix:
```bash
$ make PROG_PREFIX=PREFIX_GOES_HERE uninstall
```
To uninstall the multicall binary:
```bash
$ make MULTICALL=y uninstall
```
To uninstall from a custom parent directory:
```bash
# DESTDIR is also supported
$ make PREFIX=/my/path uninstall
```
Test Instructions
-----------------
## Test Instructions
Testing can be done using either Cargo or `make`.
### Cargo ###
### Cargo
Just like with building, we follow the standard procedure for testing using
Cargo:
```bash
$ cargo test
```
By default, `cargo test` only runs the common programs. To run also platform
specific tests, run:
```bash
$ cargo test --features unix
```
If you would prefer to test a select few utilities:
```bash
$ cargo test --features "chmod mv tail" --no-default-features
```
If you also want to test the core utilities:
```bash
$ cargo test -p uucore -p coreutils
```
To debug:
```bash
$ gdb --args target/debug/coreutils ls
(gdb) b ls.rs:79
(gdb) run
```
### GNU Make ###
### GNU Make
To simply test all available utilities:
```bash
$ make test
```
To test all but a few of the available utilities:
```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' test
```
To test only a few of the available utilities:
```bash
$ make UTILS='UTILITY_1 UTILITY_2' test
```
To include tests for unimplemented behavior:
```bash
$ make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
```
Run Busybox Tests
-----------------
## Run Busybox Tests
This testing functionality is only available on *nix operating systems and
requires `make`.
To run busybox's tests for all utilities for which busybox has tests
To run busybox tests for all utilities for which busybox has tests
```bash
$ make busytest
```
To run busybox's tests for a few of the available utilities
To run busybox tests for a few of the available utilities
```bash
$ make UTILS='UTILITY_1 UTILITY_2' busytest
```
To pass an argument like "-v" to the busybox test runtime
```bash
$ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
```
Contribute
----------
## Comparing with GNU
![Evolution over time](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.png?raw=true)
To run locally:
```bash
$ bash util/build-gnu.sh
$ bash util/run-gnu-test.sh
# To run a single test:
$ bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example
```
Note that it relies on individual utilities (not the multicall binary).
## Contribute
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
Utilities
---------
## Utilities
| Done | Semi-Done | To Do |
|-----------|-----------|--------|
| arch | cp | chcon |
| base32 | expr | dd |
| base64 | install | numfmt |
| basename | ls | pr |
| cat | more | runcon |
| chgrp | od (`--strings` and 128-bit data types missing) | stty |
| chmod | printf | |
| chown | sort | |
| chroot | split | |
| cksum | tail | |
| comm | test | |
| csplit | date | |
| cut | join | |
| dircolors | df | |
| dirname | | |
| du | | |
| echo | | |
| base32 | date | dd |
| base64 | df | runcon |
| basename | expr | stty |
| cat | install | |
| chgrp | join | |
| chmod | ls | |
| chown | more | |
| chroot | numfmt | |
| cksum | od (`--strings` and 128-bit data types missing) | |
| comm | pr | |
| csplit | printf | |
| cut | sort | |
| dircolors | split | |
| dirname | tac | |
| du | tail | |
| echo | test | |
| env | | |
| expand | | |
| factor | | |
@ -320,12 +398,12 @@ Utilities
| link | | |
| ln | | |
| logname | | |
| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | |
| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | |
| mkdir | | |
| mkfifo | | |
| mknod | | |
@ -354,7 +432,6 @@ Utilities
| stdbuf | | |
| sum | | |
| sync | | |
| tac | | |
| tee | | |
| timeout | | |
| touch | | |
@ -374,8 +451,35 @@ Utilities
| whoami | | |
| yes | | |
License
-------
## Targets that compile
This is an auto-generated table showing which binaries compile for each target-triple. Note that this **does not** indicate that they are fully implemented, or that the tests pass.
|######OS######|###ARCH####|arch|base32|base64|basename|cat|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|df|dircolors|dirname|du|echo|env|expand|expr|factor|false|fmt|fold|groups|hashsum|head|hostid|hostname|id|install|join|kill|link|ln|logname|ls|mkdir|mkfifo|mknod|mktemp|more|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|printenv|printf|ptx|pwd|readlink|realpath|relpath|rm|rmdir|seq|shred|shuf|sleep|sort|split|stat|stdbuf|sum|sync|tac|tail|tee|test|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|uptime|users|wc|who|whoami|yes|
|--------------|-----------|----|------|------|--------|---|-----|-----|-----|------|-----|----|--|------|---|----|--|---------|-------|--|----|---|------|----|------|-----|---|----|------|-------|----|------|--------|--|-------|----|----|----|--|-------|--|-----|------|-----|------|----|--|----|--|-----|-----|------|--|-----|-------|-----|--------|------|---|---|--------|--------|-------|--|-----|---|-----|----|-----|----|-----|----|------|---|----|---|----|---|----|-------|-----|--|----|--------|-----|---|-----|--------|----|------|------|-----|--|---|------|---|
|linux-gnu|aarch64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|i686|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|powerpc64|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|riscv64gc| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|linux-gnu|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|windows-msvc|aarch64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y| |y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| | |y|
|windows-gnu|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|
|windows-msvc|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|
|windows-gnu|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|
|windows-msvc|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|
|apple MacOS|aarch64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|apple MacOS|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|freebsd|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|netbsd|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|
|android|aarch64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|
|android|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|
|solaris|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|wasi|wasm32| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|redox|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|fuchsia|aarch64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|fuchsia|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
## License
uutils is licensed under the MIT License - see the `LICENSE` file for details

View file

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

1
clippy.toml Normal file
View file

@ -0,0 +1 @@
msrv = "1.43.1"

View file

@ -1,5 +1,6 @@
# spell-checker:ignore (vars/env) SPHINXOPTS SPHINXBUILD SPHINXPROJ SOURCEDIR BUILDDIR
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =

View file

@ -1,5 +1,5 @@
USEGNU=gmake $*
UseGNU=gmake $*
all:
@$(USEGNU)
@$(UseGNU)
.DEFAULT:
@$(USEGNU)
@$(UseGNU)

21
docs/compiles_table.csv Normal file
View file

@ -0,0 +1,21 @@
target,arch,base32,base64,basename,cat,chgrp,chmod,chown,chroot,cksum,comm,cp,csplit,cut,date,df,dircolors,dirname,du,echo,env,expand,expr,factor,false,fmt,fold,groups,hashsum,head,hostid,hostname,id,install,join,kill,link,ln,logname,ls,mkdir,mkfifo,mknod,mktemp,more,mv,nice,nl,nohup,nproc,numfmt,od,paste,pathchk,pinky,printenv,printf,ptx,pwd,readlink,realpath,relpath,rm,rmdir,seq,shred,shuf,sleep,sort,split,stat,stdbuf,sum,sync,tac,tail,tee,test,timeout,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,wc,who,whoami,yes
aarch64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
i686-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
powerpc64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
riscv64gc-unknown-linux-gnu,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
x86_64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
aarch64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,101,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,101,0
i686-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0
i686-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0
x86_64-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0
x86_64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0
x86_64-apple-darwin,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
x86_64-unknown-freebsd,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
x86_64-unknown-netbsd,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0
aarch64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0
x86_64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0
x86_64-sun-solaris,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
wasm32-wasi,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
x86_64-unknown-redox,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
aarch64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
x86_64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
1 target arch base32 base64 basename cat chgrp chmod chown chroot cksum comm cp csplit cut date df dircolors dirname du echo env expand expr factor false fmt fold groups hashsum head hostid hostname id install join kill link ln logname ls mkdir mkfifo mknod mktemp more mv nice nl nohup nproc numfmt od paste pathchk pinky printenv printf ptx pwd readlink realpath relpath rm rmdir seq shred shuf sleep sort split stat stdbuf sum sync tac tail tee test timeout touch tr true truncate tsort tty uname unexpand uniq unlink uptime users wc who whoami yes
2 aarch64-unknown-linux-gnu 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
3 i686-unknown-linux-gnu 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
4 powerpc64-unknown-linux-gnu 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
5 riscv64gc-unknown-linux-gnu 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
6 x86_64-unknown-linux-gnu 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
7 aarch64-pc-windows-msvc 0 0 0 0 0 101 101 101 101 0 0 0 0 0 0 0 0 0 101 0 0 0 101 0 0 0 0 101 0 0 0 0 101 101 0 101 0 0 0 101 0 101 101 0 0 0 101 0 101 0 0 0 0 101 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 101 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 101 0 101 0 101 101 0
8 i686-pc-windows-gnu 0 0 0 0 0 101 101 101 101 0 0 0 0 0 0 0 0 0 101 0 0 0 101 0 0 0 0 101 0 0 0 0 101 101 0 101 0 0 0 0 0 101 101 0 0 0 101 0 101 0 0 0 0 101 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 101 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 101 0 101 0 101 0 0
9 i686-pc-windows-msvc 0 0 0 0 0 101 101 101 101 0 0 0 0 0 0 0 0 0 101 0 0 0 101 0 0 0 0 101 0 0 0 0 101 101 0 101 0 0 0 0 0 101 101 0 0 0 101 0 101 0 0 0 0 101 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 101 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 101 0 101 0 101 0 0
10 x86_64-pc-windows-gnu 0 0 0 0 0 101 101 101 101 0 0 0 0 0 0 0 0 0 101 0 0 0 101 0 0 0 0 101 0 0 0 0 101 101 0 101 0 0 0 0 0 101 101 0 0 0 101 0 101 0 0 0 0 101 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 101 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 101 0 101 0 101 0 0
11 x86_64-pc-windows-msvc 0 0 0 0 0 101 101 101 101 0 0 0 0 0 0 0 0 0 101 0 0 0 101 0 0 0 0 101 0 0 0 0 101 101 0 101 0 0 0 0 0 101 101 0 0 0 101 0 101 0 0 0 0 101 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 101 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 101 0 101 0 101 0 0
12 x86_64-apple-darwin 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
13 x86_64-unknown-freebsd 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
14 x86_64-unknown-netbsd 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 101 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 101 101 0 101 0 0
15 aarch64-linux-android 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 101 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 101 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 101 101 0 101 0 0
16 x86_64-linux-android 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 101 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 101 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 101 101 0 101 0 0
17 x86_64-sun-solaris 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
18 wasm32-wasi 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
19 x86_64-unknown-redox 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
20 aarch64-fuchsia 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
21 x86_64-fuchsia 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101

237
docs/compiles_table.py Normal file
View file

@ -0,0 +1,237 @@
#!/usr/bin/env python3
import multiprocessing
import subprocess
import argparse
import csv
import sys
from collections import defaultdict
from pathlib import Path
# third party dependencies
from tqdm import tqdm
# spell-checker:ignore (libs) tqdm imap ; (shell/mac) xcrun ; (vars) nargs
BINS_PATH=Path("../src/uu")
CACHE_PATH=Path("compiles_table.csv")
TARGETS = [
# Linux - GNU
"aarch64-unknown-linux-gnu",
"i686-unknown-linux-gnu",
"powerpc64-unknown-linux-gnu",
"riscv64gc-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# Windows
"aarch64-pc-windows-msvc",
"i686-pc-windows-gnu",
"i686-pc-windows-msvc",
"x86_64-pc-windows-gnu",
"x86_64-pc-windows-msvc",
# Apple
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"aarch64-apple-ios",
"x86_64-apple-ios",
# BSDs
"x86_64-unknown-freebsd",
"x86_64-unknown-netbsd",
# Android
"aarch64-linux-android",
"x86_64-linux-android",
# Solaris
"x86_64-sun-solaris",
# WASM
"wasm32-wasi",
# Redox
"x86_64-unknown-redox",
# Fuchsia
"aarch64-fuchsia",
"x86_64-fuchsia",
]
class Target(str):
def __new__(cls, content):
obj = super().__new__(cls, content)
obj.arch, obj.platform, obj.os = Target.parse(content)
return obj
@staticmethod
def parse(s):
elem = s.split("-")
if len(elem) == 2:
arch, platform, os = elem[0], "n/a", elem[1]
else:
arch, platform, os = elem[0], elem[1], "-".join(elem[2:])
if os == "ios":
os = "apple IOS"
if os == "darwin":
os = "apple MacOS"
return (arch, platform, os)
@staticmethod
def get_heading():
return ["OS", "ARCH"]
def get_row_heading(self):
return [self.os, self.arch]
def requires_nightly(self):
return "redox" in self
# Perform the 'it-compiles' check
def check(self, binary):
if self.requires_nightly():
args = [ "cargo", "+nightly", "check",
"-p", f"uu_{binary}", "--bin", binary,
f"--target={self}"]
else:
args = ["cargo", "check",
"-p", f"uu_{binary}", "--bin", binary,
f"--target={self}"]
res = subprocess.run(args, capture_output=True)
return res.returncode
# Validate that the dependencies for running this target are met
def is_installed(self):
# check IOS sdk is installed, raise exception otherwise
if "ios" in self:
res = subprocess.run(["which", "xcrun"], capture_output=True)
if len(res.stdout) == 0:
raise Exception("Error: IOS sdk does not seem to be installed. Please do that manually")
if not self.requires_nightly():
# check std toolchains are installed
toolchains = subprocess.run(["rustup", "target", "list"], capture_output=True)
toolchains = toolchains.stdout.decode('utf-8').split("\n")
if "installed" not in next(filter(lambda x: self in x, toolchains)):
raise Exception(f"Error: the {t} target is not installed. Please do that manually")
else:
# check nightly toolchains are installed
toolchains = subprocess.run(["rustup", "+nightly", "target", "list"], capture_output=True)
toolchains = toolchains.stdout.decode('utf-8').split("\n")
if "installed" not in next(filter(lambda x: self in x, toolchains)):
raise Exception(f"Error: the {t} nightly target is not installed. Please do that manually")
return True
def install_targets():
cmd = ["rustup", "target", "add"] + TARGETS
print(" ".join(cmd))
ret = subprocess.run(cmd)
assert(ret.returncode == 0)
def get_all_bins():
bins = map(lambda x: x.name, BINS_PATH.iterdir())
return sorted(list(bins))
def get_targets(selection):
if "all" in selection:
return list(map(Target, TARGETS))
else:
# preserve the same order as in TARGETS
return list(map(Target, filter(lambda x: x in selection, TARGETS)))
def test_helper(tup):
bin, target = tup
retcode = target.check(bin)
return (target, bin, retcode)
def test_all_targets(targets, bins):
pool = multiprocessing.Pool()
inputs = [(b, t) for b in bins for t in targets]
outputs = list(tqdm(pool.imap(test_helper, inputs), total=len(inputs)))
table = defaultdict(dict)
for (t, b, r) in outputs:
table[t][b] = r
return table
def save_csv(file, table):
targets = get_targets(table.keys()) # preserve order in CSV
bins = list(list(table.values())[0].keys())
with open(file, "w") as csvfile:
header = ["target"] + bins
writer = csv.DictWriter(csvfile, fieldnames=header)
writer.writeheader()
for t in targets:
d = {"target" : t}
d.update(table[t])
writer.writerow(d)
def load_csv(file):
table = {}
cols = []
rows = []
with open(file, "r") as csvfile:
reader = csv.DictReader(csvfile)
cols = list(filter(lambda x: x!="target", reader.fieldnames))
for row in reader:
t = Target(row["target"])
rows += [t]
del row["target"]
table[t] = dict([k, int(v)] for k, v in row.items())
return (table, rows, cols)
def merge_tables(old, new):
from copy import deepcopy
tmp = deepcopy(old)
tmp.update(deepcopy(new))
return tmp
def render_md(fd, table, headings: str, row_headings: Target):
def print_row(lst, lens=[]):
lens = lens + [0] * (len(lst) - len(lens))
for e, l in zip(lst, lens):
fmt = '|{}' if l == 0 else '|{:>%s}' % len(header[0])
fd.write(fmt.format(e))
fd.write("|\n")
def cell_render(target, bin):
return "y" if table[target][bin] == 0 else " "
# add some 'hard' padding to specific columns
lens = [
max(map(lambda x: len(x.os), row_headings)) + 2,
max(map(lambda x: len(x.arch), row_headings)) + 2
]
header = Target.get_heading()
header[0] = ("{:#^%d}" % lens[0]).format(header[0])
header[1] = ("{:#^%d}" % lens[1]).format(header[1])
header += headings
print_row(header)
lines = list(map(lambda x: '-'*len(x), header))
print_row(lines)
for t in row_headings:
row = list(map(lambda b: cell_render(t, b), headings))
row = t.get_row_heading() + row
print_row(row)
if __name__ == "__main__":
# create the top-level parser
parser = argparse.ArgumentParser(prog='compiles_table.py')
subparsers = parser.add_subparsers(help='sub-command to execute', required=True, dest="cmd")
# create the parser for the "check" command
parser_a = subparsers.add_parser('check', help='run cargo check on specified targets and update csv cache')
parser_a.add_argument("targets", metavar="TARGET", type=str, nargs='+', choices=["all"]+TARGETS,
help="target-triple to check, as shown by 'rustup target list'")
# create the parser for the "render" command
parser_b = subparsers.add_parser('render', help='print a markdown table to stdout')
parser_b.add_argument("--equidistant", action="store_true",
help="NOT IMPLEMENTED: render each column with an equal width (in plaintext)")
args = parser.parse_args()
if args.cmd == "render":
table, targets, bins = load_csv(CACHE_PATH)
render_md(sys.stdout, table, bins, targets)
if args.cmd == "check":
targets = get_targets(args.targets)
bins = get_all_bins()
assert(all(map(Target.is_installed, targets)))
table = test_all_targets(targets, bins)
prev_table, _, _ = load_csv(CACHE_PATH)
new_table = merge_tables(prev_table, table)
save_csv(CACHE_PATH, new_table)

View file

@ -21,6 +21,8 @@
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# spell-checker:ignore (words) howto htbp imgmath toctree todos uutilsdoc
import glob
import os
import re
@ -180,6 +182,6 @@ for name in glob.glob('*.rst'):
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'uutils', 'uutils Documentation',
author, 'uutils', 'A cross-platform reimplementation of GNU coreutils in Rust.',
author, 'uutils', 'A cross-platform implementation of GNU coreutils, written in Rust.',
'Miscellaneous'),
]

View file

@ -3,8 +3,11 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to uutils's documentation!
==================================
..
spell-checker:ignore (directives) genindex maxdepth modindex toctree ; (misc) quickstart
Welcome to uutils' documentation!
=================================
.. toctree::
:maxdepth: 2

View file

@ -1,5 +1,8 @@
@setLocal
@ECHO OFF
rem spell-checker:ignore (vars/env) BUILDDIR SOURCEDIR SPHINXBUILD SPHINXOPTS SPHINXPROJ
pushd %~dp0
REM Command file for Sphinx documentation
@ -14,7 +17,7 @@ set SPHINXPROJ=uutils
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
if ErrorLevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point

View file

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

View file

@ -5,6 +5,8 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use clap::App;
use clap::Shell;
use std::cmp;
use std::collections::hash_map::HashMap;
use std::ffi::OsString;
@ -52,13 +54,13 @@ fn main() {
let binary_as_util = name(&binary);
// binary name equals util name?
if let Some(&uumain) = utils.get(binary_as_util) {
if let Some(&(uumain, _)) = utils.get(binary_as_util) {
process::exit(uumain((vec![binary.into()].into_iter()).chain(args)));
}
// binary name equals prefixed util name?
// * prefix/stem may be any string ending in a non-alphanumeric character
let utilname = if let Some(util) = utils.keys().find(|util| {
let util_name = if let Some(util) = utils.keys().find(|util| {
binary_as_util.ends_with(*util)
&& !(&binary_as_util[..binary_as_util.len() - (*util).len()])
.ends_with(char::is_alphanumeric)
@ -71,11 +73,15 @@ fn main() {
};
// 0th argument equals util name?
if let Some(util_os) = utilname {
if let Some(util_os) = util_name {
let util = util_os.as_os_str().to_string_lossy();
if util == "completion" {
gen_completions(args, utils);
}
match utils.get(&util[..]) {
Some(&uumain) => {
Some(&(uumain, _)) => {
process::exit(uumain((vec![util_os].into_iter()).chain(args)));
}
None => {
@ -85,7 +91,7 @@ fn main() {
let util = util_os.as_os_str().to_string_lossy();
match utils.get(&util[..]) {
Some(&uumain) => {
Some(&(uumain, _)) => {
let code = uumain(
(vec![util_os, OsString::from("--help")].into_iter())
.chain(args),
@ -113,3 +119,43 @@ fn main() {
process::exit(0);
}
}
/// Prints completions for the utility in the first parameter for the shell in the second parameter to stdout
fn gen_completions<T: uucore::Args>(
mut args: impl Iterator<Item = OsString>,
util_map: UtilityMap<T>,
) -> ! {
let utility = args
.next()
.expect("expected utility as the first parameter")
.to_str()
.expect("utility name was not valid utf-8")
.to_owned();
let shell = args
.next()
.expect("expected shell as the second parameter")
.to_str()
.expect("shell name was not valid utf-8")
.to_owned();
let mut app = if utility == "coreutils" {
gen_coreutils_app(util_map)
} else if let Some((_, app)) = util_map.get(utility.as_str()) {
app()
} else {
eprintln!("{} is not a valid utility", utility);
process::exit(1)
};
let shell: Shell = shell.parse().unwrap();
let bin_name = std::env::var("PROG_PREFIX").unwrap_or_default() + &utility;
app.gen_completions_to(bin_name, shell, &mut io::stdout());
io::stdout().flush().unwrap();
process::exit(0);
}
fn gen_coreutils_app<T: uucore::Args>(util_map: UtilityMap<T>) -> App<'static, 'static> {
let mut app = App::new("coreutils");
for (_, (_, sub_app)) in util_map {
app = app.subcommand(sub_app());
}
app
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_arch"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "arch ~ (uutils) display machine architecture"
@ -16,7 +16,8 @@ path = "src/arch.rs"
[dependencies]
platform-info = "0.1"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

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

View file

@ -1 +1 @@
uucore_procs::main!(uu_arch); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_arch);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_base32"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "base32 ~ (uutils) decode/encode input (base32-encoding)"
@ -15,7 +15,8 @@ edition = "2018"
path = "src/base32.rs"
[dependencies]
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features = ["encoding"] }
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -7,13 +7,15 @@
#[macro_use]
extern crate uucore;
use std::io::{stdin, Read};
use clap::App;
use uucore::encoding::Format;
mod base_common;
pub mod base_common;
static SYNTAX: &str = "[OPTION]... [FILE]";
static SUMMARY: &str = "Base32 encode or decode FILE, or standard input, to standard output.";
static LONG_HELP: &str = "
static ABOUT: &str = "
With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base32 alphabet in RFC
@ -22,13 +24,40 @@ static LONG_HELP: &str = "
to attempt to recover from any other non-alphabet bytes in the
encoded stream.
";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1;
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]", executable!())
}
pub fn uumain(args: impl uucore::Args) -> i32 {
base_common::execute(
args.collect_str(),
SYNTAX,
SUMMARY,
LONG_HELP,
Format::Base32,
)
let format = Format::Base32;
let usage = get_usage();
let name = executable!();
let config_result: Result<base_common::Config, String> =
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));
// Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args
let stdin_raw = stdin();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
base_common::handle_input(
&mut input,
format,
config.wrap_cols,
config.ignore_garbage,
config.decode,
name,
);
0
}
pub fn uu_app() -> App<'static, 'static> {
base_common::base_app(executable!(), VERSION, ABOUT)
}

View file

@ -7,73 +7,134 @@
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
use std::fs::File;
use std::io::{stdin, stdout, BufReader, Read, Write};
use std::path::Path;
use std::io::{stdout, Read, Write};
use uucore::encoding::{wrap_print, Data, Format};
use uucore::InvalidEncodingHandling;
pub fn execute(
args: Vec<String>,
syntax: &str,
summary: &str,
long_help: &str,
format: Format,
) -> i32 {
let matches = app!(syntax, summary, long_help)
.optflag("d", "decode", "decode data")
.optflag(
"i",
"ignore-garbage",
"when decoding, ignore non-alphabetic characters",
)
.optopt(
"w",
"wrap",
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
"COLS",
)
.parse(args);
use std::fs::File;
use std::io::{BufReader, Stdin};
use std::path::Path;
let line_wrap = matches.opt_str("wrap").map(|s| match s.parse() {
Ok(n) => n,
Err(e) => {
crash!(1, "invalid wrap size: {}: {}", s, e);
}
});
let ignore_garbage = matches.opt_present("ignore-garbage");
let decode = matches.opt_present("decode");
use clap::{App, Arg};
if matches.free.len() > 1 {
show_usage_error!("extra operand {}", matches.free[0]);
return 1;
}
if matches.free.is_empty() || &matches.free[0][..] == "-" {
let stdin_raw = stdin();
handle_input(
&mut stdin_raw.lock(),
format,
line_wrap,
ignore_garbage,
decode,
);
} else {
let path = Path::new(matches.free[0].as_str());
let file_buf = safe_unwrap!(File::open(&path));
let mut input = BufReader::new(file_buf);
handle_input(&mut input, format, line_wrap, ignore_garbage, decode);
};
0
// Config.
pub struct Config {
pub decode: bool,
pub ignore_garbage: bool,
pub wrap_cols: Option<usize>,
pub to_read: Option<String>,
}
fn handle_input<R: Read>(
pub mod options {
pub static DECODE: &str = "decode";
pub static WRAP: &str = "wrap";
pub static IGNORE_GARBAGE: &str = "ignore-garbage";
pub static FILE: &str = "file";
}
impl Config {
fn from(options: clap::ArgMatches) -> Result<Config, String> {
let file: Option<String> = match options.values_of(options::FILE) {
Some(mut values) => {
let name = values.next().unwrap();
if values.len() != 0 {
return Err(format!("extra operand '{}'", name));
}
if name == "-" {
None
} else {
if !Path::exists(Path::new(name)) {
return Err(format!("{}: No such file or directory", name));
}
Some(name.to_owned())
}
}
None => None,
};
let cols = options
.value_of(options::WRAP)
.map(|num| {
num.parse::<usize>()
.map_err(|e| format!("Invalid wrap size: '{}': {}", num, e))
})
.transpose()?;
Ok(Config {
decode: options.is_present(options::DECODE),
ignore_garbage: options.is_present(options::IGNORE_GARBAGE),
wrap_cols: cols,
to_read: file,
})
}
}
pub fn parse_base_cmd_args(
args: impl uucore::Args,
name: &str,
version: &str,
about: &str,
usage: &str,
) -> Result<Config, String> {
let app = base_app(name, version, about).usage(usage);
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
Config::from(app.get_matches_from(arg_list))
}
pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static, 'a> {
App::new(name)
.version(version)
.about(about)
// Format arguments.
.arg(
Arg::with_name(options::DECODE)
.short("d")
.long(options::DECODE)
.help("decode data"),
)
.arg(
Arg::with_name(options::IGNORE_GARBAGE)
.short("i")
.long(options::IGNORE_GARBAGE)
.help("when decoding, ignore non-alphabetic characters"),
)
.arg(
Arg::with_name(options::WRAP)
.short("w")
.long(options::WRAP)
.takes_value(true)
.help(
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
),
)
// "multiple" arguments are used to check whether there is more than one
// file passed in.
.arg(Arg::with_name(options::FILE).index(1).multiple(true))
}
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> {
match &config.to_read {
Some(name) => {
let file_buf = safe_unwrap!(File::open(Path::new(name)));
Box::new(BufReader::new(file_buf)) // as Box<dyn Read>
}
None => {
Box::new(stdin_ref.lock()) // as Box<dyn Read>
}
}
}
pub fn handle_input<R: Read>(
input: &mut R,
format: Format,
line_wrap: Option<usize>,
ignore_garbage: bool,
decode: bool,
name: &str,
) {
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
if let Some(wrap) = line_wrap {
@ -88,10 +149,14 @@ fn handle_input<R: Read>(
Ok(s) => {
if stdout().write_all(&s).is_err() {
// on windows console, writing invalid utf8 returns an error
crash!(1, "Cannot write non-utf8 data");
eprintln!("{}: error: Cannot write non-utf8 data", name);
exit!(1)
}
}
Err(_) => crash!(1, "invalid input"),
Err(_) => {
eprintln!("{}: error: invalid input", name);
exit!(1)
}
}
}
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_base32); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_base32);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_base64"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "base64 ~ (uutils) decode/encode input (base64-encoding)"
@ -15,8 +15,10 @@ edition = "2018"
path = "src/base64.rs"
[dependencies]
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features = ["encoding"] }
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"}
[[bin]]
name = "base64"

View file

@ -8,13 +8,15 @@
#[macro_use]
extern crate uucore;
use uu_base32::base_common;
pub use uu_base32::uu_app;
use uucore::encoding::Format;
mod base_common;
use std::io::{stdin, Read};
static SYNTAX: &str = "[OPTION]... [FILE]";
static SUMMARY: &str = "Base64 encode or decode FILE, or standard input, to standard output.";
static LONG_HELP: &str = "
static ABOUT: &str = "
With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base64 alphabet in RFC
@ -23,13 +25,35 @@ static LONG_HELP: &str = "
to attempt to recover from any other non-alphabet bytes in the
encoded stream.
";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1;
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]", executable!())
}
pub fn uumain(args: impl uucore::Args) -> i32 {
base_common::execute(
args.collect_str(),
SYNTAX,
SUMMARY,
LONG_HELP,
Format::Base64,
)
let format = Format::Base64;
let usage = get_usage();
let name = executable!();
let config_result: Result<base_common::Config, String> =
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));
// Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args
let stdin_raw = stdin();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
base_common::handle_input(
&mut input,
format,
config.wrap_cols,
config.ignore_garbage,
config.decode,
name,
);
0
}

View file

@ -1,97 +0,0 @@
// This file is part of the uutils coreutils package.
//
// (c) Jordy Dickinson <jordy.dickinson@gmail.com>
// (c) Jian Zeng <anonymousknight96@gmail.com>
// (c) Alex Lyon <arcterus@mail.com>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
use std::fs::File;
use std::io::{stdin, stdout, BufReader, Read, Write};
use std::path::Path;
use uucore::encoding::{wrap_print, Data, Format};
pub fn execute(
args: Vec<String>,
syntax: &str,
summary: &str,
long_help: &str,
format: Format,
) -> i32 {
let matches = app!(syntax, summary, long_help)
.optflag("d", "decode", "decode data")
.optflag(
"i",
"ignore-garbage",
"when decoding, ignore non-alphabetic characters",
)
.optopt(
"w",
"wrap",
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
"COLS",
)
.parse(args);
let line_wrap = matches.opt_str("wrap").map(|s| match s.parse() {
Ok(n) => n,
Err(e) => {
crash!(1, "invalid wrap size: {}: {}", s, e);
}
});
let ignore_garbage = matches.opt_present("ignore-garbage");
let decode = matches.opt_present("decode");
if matches.free.len() > 1 {
show_usage_error!("extra operand {}", matches.free[0]);
return 1;
}
if matches.free.is_empty() || &matches.free[0][..] == "-" {
let stdin_raw = stdin();
handle_input(
&mut stdin_raw.lock(),
format,
line_wrap,
ignore_garbage,
decode,
);
} else {
let path = Path::new(matches.free[0].as_str());
let file_buf = safe_unwrap!(File::open(&path));
let mut input = BufReader::new(file_buf);
handle_input(&mut input, format, line_wrap, ignore_garbage, decode);
};
0
}
fn handle_input<R: Read>(
input: &mut R,
format: Format,
line_wrap: Option<usize>,
ignore_garbage: bool,
decode: bool,
) {
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
if let Some(wrap) = line_wrap {
data = data.line_wrap(wrap);
}
if !decode {
let encoded = data.encode();
wrap_print(&data, encoded);
} else {
match data.decode() {
Ok(s) => {
if stdout().write_all(&s).is_err() {
// on windows console, writing invalid utf8 returns an error
crash!(1, "Cannot write non-utf8 data");
}
}
Err(_) => crash!(1, "invalid input"),
}
}
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_base64); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_base64);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_basename"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "basename ~ (uutils) display PATHNAME with leading directory components removed"
@ -15,7 +15,8 @@ edition = "2018"
path = "src/basename.rs"
[dependencies]
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
clap = "2.33.2"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -10,98 +10,117 @@
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use std::path::{is_separator, PathBuf};
use uucore::InvalidEncodingHandling;
static NAME: &str = "basename";
static SYNTAX: &str = "NAME [SUFFIX]";
static SUMMARY: &str = "Print NAME with any leading directory components removed
If specified, also remove a trailing SUFFIX";
static LONG_HELP: &str = "";
If specified, also remove a trailing SUFFIX";
fn get_usage() -> String {
format!(
"{0} NAME [SUFFIX]
{0} OPTION... NAME...",
executable!()
)
}
pub mod options {
pub static MULTIPLE: &str = "multiple";
pub static NAME: &str = "name";
pub static SUFFIX: &str = "suffix";
pub static ZERO: &str = "zero";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let usage = get_usage();
//
// Argument parsing
//
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optflag(
"a",
"multiple",
"Support more than one argument. Treat every argument as a name.",
)
.optopt(
"s",
"suffix",
"Remove a trailing suffix. This option implies the -a option.",
"SUFFIX",
)
.optflag(
"z",
"zero",
"Output a zero byte (ASCII NUL) at the end of each line, rather than a newline.",
)
.parse(args);
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
// too few arguments
if matches.free.is_empty() {
if !matches.is_present(options::NAME) {
crash!(
1,
"{0}: {1}\nTry '{0} --help' for more information.",
NAME,
"{1}\nTry '{0} --help' for more information.",
executable!(),
"missing operand"
);
}
let opt_s = matches.opt_present("s");
let opt_a = matches.opt_present("a");
let opt_z = matches.opt_present("z");
let multiple_paths = opt_s || opt_a;
let opt_suffix = matches.is_present(options::SUFFIX);
let opt_multiple = matches.is_present(options::MULTIPLE);
let opt_zero = matches.is_present(options::ZERO);
let multiple_paths = opt_suffix || opt_multiple;
// too many arguments
if !multiple_paths && matches.free.len() > 2 {
if !multiple_paths && matches.occurrences_of(options::NAME) > 2 {
crash!(
1,
"{0}: extra operand '{1}'\nTry '{0} --help' for more information.",
NAME,
matches.free[2]
"extra operand '{1}'\nTry '{0} --help' for more information.",
executable!(),
matches.values_of(options::NAME).unwrap().nth(2).unwrap()
);
}
let suffix = if opt_s {
matches.opt_str("s").unwrap()
} else if !opt_a && matches.free.len() > 1 {
matches.free[1].clone()
let suffix = if opt_suffix {
matches.value_of(options::SUFFIX).unwrap()
} else if !opt_multiple && matches.occurrences_of(options::NAME) > 1 {
matches.values_of(options::NAME).unwrap().nth(1).unwrap()
} else {
"".to_owned()
""
};
//
// Main Program Processing
//
let paths = if multiple_paths {
&matches.free[..]
let paths: Vec<_> = if multiple_paths {
matches.values_of(options::NAME).unwrap().collect()
} else {
&matches.free[0..1]
matches.values_of(options::NAME).unwrap().take(1).collect()
};
let line_ending = if opt_z { "\0" } else { "\n" };
let line_ending = if opt_zero { "\0" } else { "\n" };
for path in paths {
print!("{}{}", basename(&path, &suffix), line_ending);
print!("{}{}", basename(path, suffix), line_ending);
}
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
.arg(
Arg::with_name(options::MULTIPLE)
.short("a")
.long(options::MULTIPLE)
.help("support multiple arguments and treat each as a NAME"),
)
.arg(Arg::with_name(options::NAME).multiple(true).hidden(true))
.arg(
Arg::with_name(options::SUFFIX)
.short("s")
.long(options::SUFFIX)
.value_name("SUFFIX")
.help("remove a trailing SUFFIX; implies -a"),
)
.arg(
Arg::with_name(options::ZERO)
.short("z")
.long(options::ZERO)
.help("end each output line with NUL, not newline"),
)
}
fn basename(fullname: &str, suffix: &str) -> String {
// Remove all platform-specific path separators from the end
let mut path: String = fullname
.chars()
.rev()
.skip_while(|&ch| is_separator(ch))
.collect();
// Undo reverse
path = path.chars().rev().collect();
let path = fullname.trim_end_matches(is_separator);
// Convert to path buffer and get last path component
let pb = PathBuf::from(path);
@ -111,6 +130,8 @@ fn basename(fullname: &str, suffix: &str) -> String {
}
}
// can be replaced with strip_suffix once MSRV is 1.45
#[allow(clippy::manual_strip)]
fn strip_suffix(name: &str, suffix: &str) -> String {
if name == suffix {
return name.to_owned();

View file

@ -1 +1 @@
uucore_procs::main!(uu_basename); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_basename);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_cat"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "cat ~ (uutils) concatenate and display input"
@ -15,13 +15,18 @@ edition = "2018"
path = "src/cat.rs"
[dependencies]
quick-error = "1.2.3"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] }
clap = "2.33"
thiserror = "1.0"
atty = "0.2"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(unix)'.dependencies]
unix_socket = "0.5.0"
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
nix = "0.20"
[[bin]]
name = "cat"
path = "src/main.rs"

View file

@ -3,24 +3,29 @@
// (c) Jordi Boggiano <j.boggiano@seld.be>
// (c) Evgeniy Klyuchikov <evgeniy.klyuchikov@gmail.com>
// (c) Joshua S. Miller <jsmiller@uchicago.edu>
// (c) Árni Dagur <arni@dagur.eu>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) nonprint nonblank nonprinting
#[macro_use]
extern crate quick_error;
#[cfg(unix)]
extern crate unix_socket;
#[macro_use]
extern crate uucore;
// last synced with: cat (GNU coreutils) 8.13
use quick_error::ResultExt;
use clap::{crate_version, App, Arg};
use std::fs::{metadata, File};
use std::io::{self, stderr, stdin, stdout, BufWriter, Read, Write};
use uucore::fs::is_stdin_interactive;
use std::io::{self, Read, Write};
use thiserror::Error;
/// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))]
mod splice;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::io::{AsRawFd, RawFd};
/// Unix domain socket support
#[cfg(unix)]
@ -29,11 +34,33 @@ use std::net::Shutdown;
use std::os::unix::fs::FileTypeExt;
#[cfg(unix)]
use unix_socket::UnixStream;
use uucore::InvalidEncodingHandling;
static NAME: &str = "cat";
static SYNTAX: &str = "[OPTION]... [FILE]...";
static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output
With no FILE, or when FILE is -, read standard input.";
static LONG_HELP: &str = "";
#[derive(Error, Debug)]
enum CatError {
/// Wrapper around `io::Error`
#[error("{0}")]
Io(#[from] io::Error),
/// Wrapper around `nix::Error`
#[cfg(any(target_os = "linux", target_os = "android"))]
#[error("{0}")]
Nix(#[from] nix::Error),
/// Unknown file type; it's not a regular file, socket, etc.
#[error("unknown filetype: {}", ft_debug)]
UnknownFiletype {
/// A debug print of the file type
ft_debug: String,
},
#[error("Is a directory")]
IsDirectory,
}
type CatResult<T> = Result<T, CatError>;
#[derive(PartialEq)]
enum NumberingMode {
@ -42,39 +69,6 @@ enum NumberingMode {
All,
}
quick_error! {
#[derive(Debug)]
enum CatError {
/// Wrapper for io::Error with path context
Input(err: io::Error, path: String) {
display("cat: {0}: {1}", path, err)
context(path: &'a str, err: io::Error) -> (err, path.to_owned())
cause(err)
}
/// Wrapper for io::Error with no context
Output(err: io::Error) {
display("cat: {0}", err) from()
cause(err)
}
/// Unknown Filetype classification
UnknownFiletype(path: String) {
display("cat: {0}: unknown filetype", path)
}
/// At least one error was encountered in reading or writing
EncounteredErrors(count: usize) {
display("cat: encountered {0} errors", count)
}
/// Denotes an error caused by trying to `cat` a directory
IsDirectory(path: String) {
display("cat: {0}: Is a directory", path)
}
}
}
struct OutputOptions {
/// Line numbering mode
number: NumberingMode,
@ -85,21 +79,56 @@ struct OutputOptions {
/// display TAB characters as `tab`
show_tabs: bool,
/// If `show_tabs == true`, this string will be printed in the
/// place of tabs
tab: String,
/// Can be set to show characters other than '\n' a the end of
/// each line, e.g. $
end_of_line: String,
/// Show end of lines
show_ends: bool,
/// use ^ and M- notation, except for LF (\\n) and TAB (\\t)
show_nonprint: bool,
}
impl OutputOptions {
fn tab(&self) -> &'static str {
if self.show_tabs {
"^I"
} else {
"\t"
}
}
fn end_of_line(&self) -> &'static str {
if self.show_ends {
"$\n"
} else {
"\n"
}
}
/// We can write fast if we can simply copy the contents of the file to
/// stdout, without augmenting the output with e.g. line numbers.
fn can_write_fast(&self) -> bool {
!(self.show_tabs
|| self.show_nonprint
|| self.show_ends
|| self.squeeze_blank
|| self.number != NumberingMode::None)
}
}
/// State that persists between output of each file. This struct is only used
/// when we can't write fast.
struct OutputState {
/// The current line number
line_number: usize,
/// Whether the output cursor is at the beginning of a new line
at_line_start: bool,
}
/// Represents an open file handle, stream, or other device
struct InputHandle {
reader: Box<dyn Read>,
struct InputHandle<R: Read> {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: RawFd,
reader: R,
is_interactive: bool,
}
@ -122,78 +151,74 @@ enum InputType {
Socket,
}
type CatResult<T> = Result<T, CatError>;
mod options {
pub static FILE: &str = "file";
pub static SHOW_ALL: &str = "show-all";
pub static NUMBER_NONBLANK: &str = "number-nonblank";
pub static SHOW_NONPRINTING_ENDS: &str = "e";
pub static SHOW_ENDS: &str = "show-ends";
pub static NUMBER: &str = "number";
pub static SQUEEZE_BLANK: &str = "squeeze-blank";
pub static SHOW_NONPRINTING_TABS: &str = "t";
pub static SHOW_TABS: &str = "show-tabs";
pub static SHOW_NONPRINTING: &str = "show-nonprinting";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optflag("A", "show-all", "equivalent to -vET")
.optflag(
"b",
"number-nonblank",
"number nonempty output lines, overrides -n",
)
.optflag("e", "", "equivalent to -vE")
.optflag("E", "show-ends", "display $ at end of each line")
.optflag("n", "number", "number all output lines")
.optflag("s", "squeeze-blank", "suppress repeated empty output lines")
.optflag("t", "", "equivalent to -vT")
.optflag("T", "show-tabs", "display TAB characters as ^I")
.optflag(
"v",
"show-nonprinting",
"use ^ and M- notation, except for LF (\\n) and TAB (\\t)",
)
.parse(args);
let matches = uu_app().get_matches_from(args);
let number_mode = if matches.opt_present("b") {
let number_mode = if matches.is_present(options::NUMBER_NONBLANK) {
NumberingMode::NonEmpty
} else if matches.opt_present("n") {
} else if matches.is_present(options::NUMBER) {
NumberingMode::All
} else {
NumberingMode::None
};
let show_nonprint = matches.opts_present(&[
"A".to_owned(),
"e".to_owned(),
"t".to_owned(),
"v".to_owned(),
]);
let show_ends = matches.opts_present(&["E".to_owned(), "A".to_owned(), "e".to_owned()]);
let show_tabs = matches.opts_present(&["A".to_owned(), "T".to_owned(), "t".to_owned()]);
let squeeze_blank = matches.opt_present("s");
let mut files = matches.free;
if files.is_empty() {
files.push("-".to_owned());
}
let show_nonprint = vec![
options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(),
options::SHOW_NONPRINTING.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let can_write_fast = !(show_tabs
|| show_nonprint
|| show_ends
|| squeeze_blank
|| number_mode != NumberingMode::None);
let show_ends = vec![
options::SHOW_ENDS.to_owned(),
options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let success = if can_write_fast {
write_fast(files).is_ok()
} else {
let tab = if show_tabs { "^I" } else { "\t" }.to_owned();
let show_tabs = vec![
options::SHOW_ALL.to_owned(),
options::SHOW_TABS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let end_of_line = if show_ends { "$\n" } else { "\n" }.to_owned();
let options = OutputOptions {
end_of_line,
number: number_mode,
show_nonprint,
show_tabs,
squeeze_blank,
tab,
};
write_lines(files, &options).is_ok()
let squeeze_blank = matches.is_present(options::SQUEEZE_BLANK);
let files: Vec<String> = match matches.values_of(options::FILE) {
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
None => vec!["-".to_owned()],
};
let options = OutputOptions {
show_ends,
number: number_mode,
show_nonprint,
show_tabs,
squeeze_blank,
};
let success = cat_files(files, &options).is_ok();
if success {
0
} else {
@ -201,6 +226,139 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME)
.version(crate_version!())
.usage(SYNTAX)
.about(SUMMARY)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.arg(
Arg::with_name(options::SHOW_ALL)
.short("A")
.long(options::SHOW_ALL)
.help("equivalent to -vET"),
)
.arg(
Arg::with_name(options::NUMBER_NONBLANK)
.short("b")
.long(options::NUMBER_NONBLANK)
.help("number nonempty output lines, overrides -n")
.overrides_with(options::NUMBER),
)
.arg(
Arg::with_name(options::SHOW_NONPRINTING_ENDS)
.short("e")
.help("equivalent to -vE"),
)
.arg(
Arg::with_name(options::SHOW_ENDS)
.short("E")
.long(options::SHOW_ENDS)
.help("display $ at end of each line"),
)
.arg(
Arg::with_name(options::NUMBER)
.short("n")
.long(options::NUMBER)
.help("number all output lines"),
)
.arg(
Arg::with_name(options::SQUEEZE_BLANK)
.short("s")
.long(options::SQUEEZE_BLANK)
.help("suppress repeated empty output lines"),
)
.arg(
Arg::with_name(options::SHOW_NONPRINTING_TABS)
.short("t")
.long(options::SHOW_NONPRINTING_TABS)
.help("equivalent to -vT"),
)
.arg(
Arg::with_name(options::SHOW_TABS)
.short("T")
.long(options::SHOW_TABS)
.help("display TAB characters at ^I"),
)
.arg(
Arg::with_name(options::SHOW_NONPRINTING)
.short("v")
.long(options::SHOW_NONPRINTING)
.help("use ^ and M- notation, except for LF (\\n) and TAB (\\t)"),
)
}
fn cat_handle<R: Read>(
handle: &mut InputHandle<R>,
options: &OutputOptions,
state: &mut OutputState,
) -> CatResult<()> {
if options.can_write_fast() {
write_fast(handle)
} else {
write_lines(handle, options, state)
}
}
fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> {
if path == "-" {
let stdin = io::stdin();
let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: stdin.as_raw_fd(),
reader: stdin,
is_interactive: atty::is(atty::Stream::Stdin),
};
return cat_handle(&mut handle, options, state);
}
match get_input_type(path)? {
InputType::Directory => Err(CatError::IsDirectory),
#[cfg(unix)]
InputType::Socket => {
let socket = UnixStream::connect(path)?;
socket.shutdown(Shutdown::Write)?;
let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: socket.as_raw_fd(),
reader: socket,
is_interactive: false,
};
cat_handle(&mut handle, options, state)
}
_ => {
let file = File::open(path)?;
let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: file.as_raw_fd(),
reader: file,
is_interactive: false,
};
cat_handle(&mut handle, options, state)
}
}
}
fn cat_files(files: Vec<String>, options: &OutputOptions) -> Result<(), u32> {
let mut error_count = 0;
let mut state = OutputState {
line_number: 1,
at_line_start: true,
};
for path in &files {
if let Err(err) = cat_path(path, options, &mut state) {
show_error!("{}: {}", path, err);
error_count += 1;
}
}
if error_count == 0 {
Ok(())
} else {
Err(error_count)
}
}
/// Classifies the `InputType` of file at `path` if possible
///
/// # Arguments
@ -211,7 +369,8 @@ fn get_input_type(path: &str) -> CatResult<InputType> {
return Ok(InputType::StdIn);
}
match metadata(path).context(path)?.file_type() {
let ft = metadata(path)?.file_type();
match ft {
#[cfg(unix)]
ft if ft.is_block_device() => Ok(InputType::BlockDevice),
#[cfg(unix)]
@ -223,125 +382,47 @@ fn get_input_type(path: &str) -> CatResult<InputType> {
ft if ft.is_dir() => Ok(InputType::Directory),
ft if ft.is_file() => Ok(InputType::File),
ft if ft.is_symlink() => Ok(InputType::SymLink),
_ => Err(CatError::UnknownFiletype(path.to_owned())),
_ => Err(CatError::UnknownFiletype {
ft_debug: format!("{:?}", ft),
}),
}
}
/// Returns an InputHandle from which a Reader can be accessed or an
/// error
///
/// # Arguments
///
/// * `path` - `InputHandler` will wrap a reader from this file path
fn open(path: &str) -> CatResult<InputHandle> {
if path == "-" {
let stdin = stdin();
return Ok(InputHandle {
reader: Box::new(stdin) as Box<dyn Read>,
is_interactive: is_stdin_interactive(),
});
}
match get_input_type(path)? {
InputType::Directory => Err(CatError::IsDirectory(path.to_owned())),
#[cfg(unix)]
InputType::Socket => {
let socket = UnixStream::connect(path).context(path)?;
socket.shutdown(Shutdown::Write).context(path)?;
Ok(InputHandle {
reader: Box::new(socket) as Box<dyn Read>,
is_interactive: false,
})
}
_ => {
let file = File::open(path).context(path)?;
Ok(InputHandle {
reader: Box::new(file) as Box<dyn Read>,
is_interactive: false,
})
/// Writes handle to stdout with no configuration. This allows a
/// simple memory copy.
fn write_fast<R: Read>(handle: &mut InputHandle<R>) -> CatResult<()> {
let stdout = io::stdout();
let mut stdout_lock = stdout.lock();
#[cfg(any(target_os = "linux", target_os = "android"))]
{
// If we're on Linux or Android, try to use the splice() system call
// for faster writing. If it works, we're done.
if !splice::write_fast_using_splice(handle, stdout_lock.as_raw_fd())? {
return Ok(());
}
}
}
/// Writes files to stdout with no configuration. This allows a
/// simple memory copy. Returns `Ok(())` if no errors were
/// encountered, or an error with the number of errors encountered.
///
/// # Arguments
///
/// * `files` - There is no short circuit when encountering an error
/// reading a file in this vector
fn write_fast(files: Vec<String>) -> CatResult<()> {
let mut writer = stdout();
let mut in_buf = [0; 1024 * 64];
let mut error_count = 0;
for file in files {
match open(&file[..]) {
Ok(mut handle) => {
while let Ok(n) = handle.reader.read(&mut in_buf) {
if n == 0 {
break;
}
writer.write_all(&in_buf[..n]).context(&file[..])?;
}
}
Err(error) => {
writeln!(&mut stderr(), "{}", error)?;
error_count += 1;
}
// If we're not on Linux or Android, or the splice() call failed,
// fall back on slower writing.
let mut buf = [0; 1024 * 64];
while let Ok(n) = handle.reader.read(&mut buf) {
if n == 0 {
break;
}
stdout_lock.write_all(&buf[..n])?;
}
match error_count {
0 => Ok(()),
_ => Err(CatError::EncounteredErrors(error_count)),
}
}
/// State that persists between output of each file
struct OutputState {
/// The current line number
line_number: usize,
/// Whether the output cursor is at the beginning of a new line
at_line_start: bool,
}
/// Writes files to stdout with `options` as configuration. Returns
/// `Ok(())` if no errors were encountered, or an error with the
/// number of errors encountered.
///
/// # Arguments
///
/// * `files` - There is no short circuit when encountering an error
/// reading a file in this vector
fn write_lines(files: Vec<String>, options: &OutputOptions) -> CatResult<()> {
let mut error_count = 0;
let mut state = OutputState {
line_number: 1,
at_line_start: true,
};
for file in files {
if let Err(error) = write_file_lines(&file, options, &mut state) {
writeln!(&mut stderr(), "{}", error).context(&file[..])?;
error_count += 1;
}
}
match error_count {
0 => Ok(()),
_ => Err(CatError::EncounteredErrors(error_count)),
}
Ok(())
}
/// Outputs file contents to stdout in a line-by-line fashion,
/// propagating any errors that might occur.
fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> {
let mut handle = open(file)?;
fn write_lines<R: Read>(
handle: &mut InputHandle<R>,
options: &OutputOptions,
state: &mut OutputState,
) -> CatResult<()> {
let mut in_buf = [0; 1024 * 31];
let mut writer = BufWriter::with_capacity(1024 * 64, stdout());
let stdout = io::stdout();
let mut writer = stdout.lock();
let mut one_blank_kept = false;
while let Ok(n) = handle.reader.read(&mut in_buf) {
@ -359,9 +440,9 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
write!(&mut writer, "{0:6}\t", state.line_number)?;
state.line_number += 1;
}
writer.write_all(options.end_of_line.as_bytes())?;
writer.write_all(options.end_of_line().as_bytes())?;
if handle.is_interactive {
writer.flush().context(&file[..])?;
writer.flush()?;
}
}
state.at_line_start = true;
@ -376,7 +457,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
// print to end of line or end of buffer
let offset = if options.show_nonprint {
write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab.as_bytes())
write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab().as_bytes())
} else if options.show_tabs {
write_tab_to_end(&in_buf[pos..], &mut writer)
} else {
@ -388,7 +469,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
break;
}
// print suitable end of line
writer.write_all(options.end_of_line.as_bytes())?;
writer.write_all(options.end_of_line().as_bytes())?;
if handle.is_interactive {
writer.flush()?;
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_cat); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_cat);

91
src/uu/cat/src/splice.rs Normal file
View file

@ -0,0 +1,91 @@
use super::{CatResult, InputHandle};
use nix::fcntl::{splice, SpliceFFlags};
use nix::unistd::{self, pipe};
use std::io::Read;
use std::os::unix::io::RawFd;
const BUF_SIZE: usize = 1024 * 16;
/// This function is called from `write_fast()` on Linux and Android. The
/// function `splice()` is used to move data between two file descriptors
/// without copying between kernel and user spaces. This results in a large
/// speedup.
///
/// The `bool` in the result value indicates if we need to fall back to normal
/// copying or not. False means we don't have to.
#[inline]
pub(super) fn write_fast_using_splice<R: Read>(
handle: &mut InputHandle<R>,
write_fd: RawFd,
) -> CatResult<bool> {
let (pipe_rd, pipe_wr) = match pipe() {
Ok(r) => r,
Err(_) => {
// It is very rare that creating a pipe fails, but it can happen.
return Ok(true);
}
};
loop {
match splice(
handle.file_descriptor,
None,
pipe_wr,
None,
BUF_SIZE,
SpliceFFlags::empty(),
) {
Ok(n) => {
if n == 0 {
return Ok(false);
}
if splice_exact(pipe_rd, write_fd, n).is_err() {
// If the first splice manages to copy to the intermediate
// pipe, but the second splice to stdout fails for some reason
// we can recover by copying the data that we have from the
// intermediate pipe to stdout using normal read/write. Then
// we tell the caller to fall back.
copy_exact(pipe_rd, write_fd, n)?;
return Ok(true);
}
}
Err(_) => {
return Ok(true);
}
}
}
}
/// Splice wrapper which handles short writes.
#[inline]
fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
let mut left = num_bytes;
loop {
let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?;
left -= written;
if left == 0 {
break;
}
}
Ok(())
}
/// Caller must ensure that `num_bytes <= BUF_SIZE`, otherwise this function
/// will panic. The way we use this function in `write_fast_using_splice`
/// above is safe because `splice` is set to write at most `BUF_SIZE` to the
/// pipe.
#[inline]
fn copy_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
let mut left = num_bytes;
let mut buf = [0; BUF_SIZE];
loop {
let read = unistd::read(read_fd, &mut buf[..left])?;
let written = unistd::write(write_fd, &buf[..read])?;
left -= written;
if left == 0 {
break;
}
}
Ok(())
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chgrp"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chgrp ~ (uutils) change the group ownership of FILE"
@ -15,7 +15,8 @@ edition = "2018"
path = "src/chgrp.rs"
[dependencies]
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"

View file

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

View file

@ -1 +1 @@
uucore_procs::main!(uu_chgrp); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_chgrp);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chmod"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chmod ~ (uutils) change mode of FILE"
@ -15,8 +15,9 @@ edition = "2018"
path = "src/chmod.rs"
[dependencies]
clap = "2.33.3"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs", "mode"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"

View file

@ -10,123 +10,190 @@
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
use uucore::fs::display_permissions_unix;
use uucore::libc::mode_t;
#[cfg(not(windows))]
use uucore::mode;
use uucore::InvalidEncodingHandling;
use walkdir::WalkDir;
const NAME: &str = "chmod";
static SUMMARY: &str = "Change the mode of each FILE to MODE.
static ABOUT: &str = "Change the mode of each FILE to MODE.
With --reference, change the mode of each FILE to that of RFILE.";
static LONG_HELP: &str = "
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.
";
mod options {
pub const CHANGES: &str = "changes";
pub const QUIET: &str = "quiet"; // visible_alias("silent")
pub const VERBOSE: &str = "verbose";
pub const NO_PRESERVE_ROOT: &str = "no-preserve-root";
pub const PRESERVE_ROOT: &str = "preserve-root";
pub const REFERENCE: &str = "RFILE";
pub const RECURSIVE: &str = "recursive";
pub const MODE: &str = "MODE";
pub const FILE: &str = "FILE";
}
fn get_usage() -> String {
format!(
"{0} [OPTION]... MODE[,MODE]... FILE...
or: {0} [OPTION]... OCTAL-MODE FILE...
or: {0} [OPTION]... --reference=RFILE FILE...",
executable!()
)
}
fn get_long_usage() -> String {
String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.")
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let mut args = args.collect_str();
let mut args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let syntax = format!(
"[OPTION]... MODE[,MODE]... FILE...
{0} [OPTION]... OCTAL-MODE FILE...
{0} [OPTION]... --reference=RFILE FILE...",
NAME
);
let mut opts = app!(&syntax, SUMMARY, LONG_HELP);
opts.optflag(
"c",
"changes",
"like verbose but report only when a change is made",
)
// TODO: support --silent (can be done using clap)
.optflag("f", "quiet", "suppress most error messages")
.optflag(
"v",
"verbose",
"output a diagnostic for every file processed",
)
.optflag(
"",
"no-preserve-root",
"do not treat '/' specially (the default)",
)
.optflag("", "preserve-root", "fail to operate recursively on '/'")
.optopt(
"",
"reference",
"use RFILE's mode instead of MODE values",
"RFILE",
)
.optflag("R", "recursive", "change files and directories recursively");
// Before we can parse 'args' with clap (and previously getopts),
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
let mode_had_minus_prefix = strip_minus_from_mode(&mut args);
// sanitize input for - at beginning (e.g. chmod -x test_file). Remove
// the option and save it for later, after parsing is finished.
let negative_option = sanitize_input(&mut args);
let usage = get_usage();
let after_help = get_long_usage();
let mut matches = opts.parse(args);
if matches.free.is_empty() {
show_error!("missing an argument");
show_error!("for help, try '{} --help'", NAME);
return 1;
let matches = uu_app()
.usage(&usage[..])
.after_help(&after_help[..])
.get_matches_from(args);
let changes = matches.is_present(options::CHANGES);
let quiet = matches.is_present(options::QUIET);
let verbose = matches.is_present(options::VERBOSE);
let preserve_root = matches.is_present(options::PRESERVE_ROOT);
let recursive = matches.is_present(options::RECURSIVE);
let fmode = matches
.value_of(options::REFERENCE)
.and_then(|fref| match fs::metadata(fref) {
Ok(meta) => Some(meta.mode()),
Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err),
});
let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
let cmode = if mode_had_minus_prefix {
// clap parsing is finished, now put prefix back
format!("-{}", modes)
} else {
let changes = matches.opt_present("changes");
let quiet = matches.opt_present("quiet");
let verbose = matches.opt_present("verbose");
let preserve_root = matches.opt_present("preserve-root");
let recursive = matches.opt_present("recursive");
let fmode = matches
.opt_str("reference")
.and_then(|ref fref| match fs::metadata(fref) {
Ok(meta) => Some(meta.mode()),
Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err),
});
let cmode = if fmode.is_none() {
// If there was a negative option, now it's a good time to
// use it.
if negative_option.is_some() {
negative_option
} else {
Some(matches.free.remove(0))
}
} else {
None
};
let chmoder = Chmoder {
changes,
quiet,
verbose,
preserve_root,
recursive,
fmode,
cmode,
};
match chmoder.chmod(matches.free) {
Ok(()) => {}
Err(e) => return e,
}
modes.to_string()
};
let mut files: Vec<String> = matches
.values_of(options::FILE)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let cmode = if fmode.is_some() {
// "--reference" and MODE are mutually exclusive
// if "--reference" was used MODE needs to be interpreted as another FILE
// it wasn't possible to implement this behavior directly with clap
files.push(cmode);
None
} else {
Some(cmode)
};
let chmoder = Chmoder {
changes,
quiet,
verbose,
preserve_root,
recursive,
fmode,
cmode,
};
match chmoder.chmod(files) {
Ok(()) => {}
Err(e) => return e,
}
0
}
fn sanitize_input(args: &mut Vec<String>) -> Option<String> {
for i in 0..args.len() {
let first = args[i].chars().next().unwrap();
if first != '-' {
continue;
}
if let Some(second) = args[i].chars().nth(1) {
match second {
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
return Some(args.remove(i));
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(options::CHANGES)
.long(options::CHANGES)
.short("c")
.help("like verbose but report only when a change is made"),
)
.arg(
Arg::with_name(options::QUIET)
.long(options::QUIET)
.visible_alias("silent")
.short("f")
.help("suppress most error messages"),
)
.arg(
Arg::with_name(options::VERBOSE)
.long(options::VERBOSE)
.short("v")
.help("output a diagnostic for every file processed"),
)
.arg(
Arg::with_name(options::NO_PRESERVE_ROOT)
.long(options::NO_PRESERVE_ROOT)
.help("do not treat '/' specially (the default)"),
)
.arg(
Arg::with_name(options::PRESERVE_ROOT)
.long(options::PRESERVE_ROOT)
.help("fail to operate recursively on '/'"),
)
.arg(
Arg::with_name(options::RECURSIVE)
.long(options::RECURSIVE)
.short("R")
.help("change files and directories recursively"),
)
.arg(
Arg::with_name(options::REFERENCE)
.long("reference")
.takes_value(true)
.help("use RFILE's mode instead of MODE values"),
)
.arg(
Arg::with_name(options::MODE)
.required_unless(options::REFERENCE)
.takes_value(true),
// It would be nice if clap could parse with delimiter, e.g. "g-x,u+x",
// however .multiple(true) cannot be used here because FILE already needs that.
// Only one positional argument with .multiple(true) set is allowed per command
)
.arg(
Arg::with_name(options::FILE)
.required_unless(options::MODE)
.multiple(true),
)
}
// Iterate 'args' and delete the first occurrence
// of a prefix '-' if it's associated with MODE
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
for arg in args {
if arg.starts_with('-') {
if let Some(second) = arg.chars().nth(1) {
match second {
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
// TODO: use strip_prefix() once minimum rust version reaches 1.45.0
*arg = arg[1..arg.len()].to_string();
return true;
}
_ => {}
}
_ => {}
}
}
}
None
false
}
struct Chmoder {
@ -147,7 +214,17 @@ impl Chmoder {
let filename = &filename[..];
let file = Path::new(filename);
if !file.exists() {
show_error!("no such file or directory '{}'", filename);
if is_symlink(file) {
println!(
"failed to change mode of '{}' from 0000 (---------) to 0000 (---------)",
filename
);
if !self.quiet {
show_error!("cannot operate on dangling symlink '{}'", filename);
}
} else {
show_error!("cannot access '{}': No such file or directory", filename);
}
return Err(1);
}
if self.recursive && self.preserve_root && filename == "/" {
@ -158,11 +235,11 @@ impl Chmoder {
return Err(1);
}
if !self.recursive {
r = self.chmod_file(&file).and(r);
r = self.chmod_file(file).and(r);
} else {
for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) {
let file = entry.path();
r = self.chmod_file(&file).and(r);
r = self.chmod_file(file).and(r);
}
}
}
@ -181,18 +258,18 @@ impl Chmoder {
let mut fperm = match fs::metadata(file) {
Ok(meta) => meta.mode() & 0o7777,
Err(err) => {
if !self.quiet {
if is_symlink(file) {
if self.verbose {
show_info!(
"neither symbolic link '{}' nor referent has been changed",
file.display()
);
}
return Ok(());
} else {
show_error!("{}: '{}'", err, file.display());
if is_symlink(file) {
if self.verbose {
println!(
"neither symbolic link '{}' nor referent has been changed",
file.display()
);
}
return Ok(());
} else if err.kind() == std::io::ErrorKind::PermissionDenied {
show_error!("'{}': Permission denied", file.display());
} else {
show_error!("'{}': {}", file.display(), err);
}
return Err(1);
}
@ -232,11 +309,11 @@ impl Chmoder {
fn change_file(&self, fperm: u32, mode: u32, file: &Path) -> Result<(), i32> {
if fperm == mode {
if self.verbose && !self.changes {
show_info!(
"mode of '{}' retained as {:o} ({})",
println!(
"mode of '{}' retained as {:04o} ({})",
file.display(),
fperm,
display_permissions_unix(fperm)
display_permissions_unix(fperm as mode_t, false),
);
}
Ok(())
@ -245,25 +322,25 @@ impl Chmoder {
show_error!("{}", err);
}
if self.verbose {
show_info!(
show_error!(
"failed to change mode of file '{}' from {:o} ({}) to {:o} ({})",
file.display(),
fperm,
display_permissions_unix(fperm),
display_permissions_unix(fperm as mode_t, false),
mode,
display_permissions_unix(mode)
display_permissions_unix(mode as mode_t, false)
);
}
Err(1)
} else {
if self.verbose || self.changes {
show_info!(
show_error!(
"mode of '{}' changed from {:o} ({}) to {:o} ({})",
file.display(),
fperm,
display_permissions_unix(fperm),
display_permissions_unix(fperm as mode_t, false),
mode,
display_permissions_unix(mode)
display_permissions_unix(mode as mode_t, false)
);
}
Ok(())

View file

@ -1 +1 @@
uucore_procs::main!(uu_chmod); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_chmod);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chown"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chown ~ (uutils) change the ownership of FILE"
@ -17,7 +17,7 @@ path = "src/chown.rs"
[dependencies]
clap = "2.33"
glob = "0.3.0"
uucore = { version=">=0.0.7", 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" }
walkdir = "2.2"

View file

@ -14,7 +14,7 @@ use uucore::fs::resolve_relative_path;
use uucore::libc::{gid_t, uid_t};
use uucore::perms::{wrap_chown, Verbosity};
use clap::{App, Arg};
use clap::{crate_version, App, Arg};
use walkdir::WalkDir;
@ -23,9 +23,9 @@ use std::os::unix::fs::MetadataExt;
use std::convert::AsRef;
use std::path::Path;
use uucore::InvalidEncodingHandling;
static ABOUT: &str = "change file owner and group";
static VERSION: &str = env!("CARGO_PKG_VERSION");
pub mod options {
pub mod verbosity {
@ -67,14 +67,122 @@ fn get_usage() -> String {
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let usage = get_usage();
let matches = App::new(executable!())
.version(VERSION)
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
/* First arg is the owner/group */
let owner = matches.value_of(ARG_OWNER).unwrap();
/* Then the list of files */
let files: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let preserve_root = matches.is_present(options::preserve_root::PRESERVE);
let mut derefer = if matches.is_present(options::dereference::NO_DEREFERENCE) {
1
} else {
0
};
let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) {
FTS_COMFOLLOW | FTS_PHYSICAL
} else if matches.is_present(options::traverse::EVERY) {
FTS_LOGICAL
} else {
FTS_PHYSICAL
};
let recursive = matches.is_present(options::RECURSIVE);
if recursive {
if bit_flag == FTS_PHYSICAL {
if derefer == 1 {
show_error!("-R --dereference requires -H or -L");
return 1;
}
derefer = 0;
}
} else {
bit_flag = FTS_PHYSICAL;
}
let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes
} else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET)
{
Verbosity::Silent
} else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose
} else {
Verbosity::Normal
};
let filter = if let Some(spec) = matches.value_of(options::FROM) {
match parse_spec(spec) {
Ok((Some(uid), None)) => IfFrom::User(uid),
Ok((None, Some(gid))) => IfFrom::Group(gid),
Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid),
Ok((None, None)) => IfFrom::All,
Err(e) => {
show_error!("{}", e);
return 1;
}
}
} else {
IfFrom::All
};
let dest_uid: Option<u32>;
let dest_gid: Option<u32>;
if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) {
Ok(meta) => {
dest_gid = Some(meta.gid());
dest_uid = Some(meta.uid());
}
Err(e) => {
show_error!("failed to get attributes of '{}': {}", file, e);
return 1;
}
}
} else {
match parse_spec(owner) {
Ok((u, g)) => {
dest_uid = u;
dest_gid = g;
}
Err(e) => {
show_error!("{}", e);
return 1;
}
}
}
let executor = Chowner {
bit_flag,
dest_uid,
dest_gid,
verbosity,
recursive,
dereference: derefer != 0,
filter,
preserve_root,
files,
};
executor.exec()
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(options::verbosity::CHANGES)
.short("c")
@ -165,148 +273,32 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.required(true)
.min_values(1),
)
.get_matches_from(args);
/* First arg is the owner/group */
let owner = matches.value_of(ARG_OWNER).unwrap();
/* Then the list of files */
let files: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let preserve_root = matches.is_present(options::preserve_root::PRESERVE);
let mut derefer = if matches.is_present(options::dereference::NO_DEREFERENCE) {
1
} else {
0
};
let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) {
FTS_COMFOLLOW | FTS_PHYSICAL
} else if matches.is_present(options::traverse::EVERY) {
FTS_LOGICAL
} else {
FTS_PHYSICAL
};
let recursive = matches.is_present(options::RECURSIVE);
if recursive {
if bit_flag == FTS_PHYSICAL {
if derefer == 1 {
show_info!("-R --dereference requires -H or -L");
return 1;
}
derefer = 0;
}
} else {
bit_flag = FTS_PHYSICAL;
}
let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes
} else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET)
{
Verbosity::Silent
} else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose
} else {
Verbosity::Normal
};
let filter = if let Some(spec) = matches.value_of(options::FROM) {
match parse_spec(&spec) {
Ok((Some(uid), None)) => IfFrom::User(uid),
Ok((None, Some(gid))) => IfFrom::Group(gid),
Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid),
Ok((None, None)) => IfFrom::All,
Err(e) => {
show_info!("{}", e);
return 1;
}
}
} else {
IfFrom::All
};
let dest_uid: Option<u32>;
let dest_gid: Option<u32>;
if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) {
Ok(meta) => {
dest_gid = Some(meta.gid());
dest_uid = Some(meta.uid());
}
Err(e) => {
show_info!("failed to get attributes of '{}': {}", file, e);
return 1;
}
}
} else {
match parse_spec(&owner) {
Ok((u, g)) => {
dest_uid = u;
dest_gid = g;
}
Err(e) => {
show_info!("{}", e);
return 1;
}
}
}
let executor = Chowner {
bit_flag,
dest_uid,
dest_gid,
verbosity,
recursive,
dereference: derefer != 0,
filter,
preserve_root,
files,
};
executor.exec()
}
fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
let args = spec.split(':').collect::<Vec<_>>();
let usr_only = args.len() == 1;
let grp_only = args.len() == 2 && args[0].is_empty() && !args[1].is_empty();
let args = spec.split_terminator(':').collect::<Vec<_>>();
let usr_only = args.len() == 1 && !args[0].is_empty();
let grp_only = args.len() == 2 && args[0].is_empty();
let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty();
if usr_only {
Ok((
Some(match Passwd::locate(args[0]) {
Ok(v) => v.uid(),
_ => return Err(format!("invalid user: '{}'", spec)),
}),
None,
))
} else if grp_only {
Ok((
None,
Some(match Group::locate(args[1]) {
Ok(v) => v.gid(),
_ => return Err(format!("invalid group: '{}'", spec)),
}),
))
} else if usr_grp {
Ok((
Some(match Passwd::locate(args[0]) {
Ok(v) => v.uid(),
_ => return Err(format!("invalid user: '{}'", spec)),
}),
Some(match Group::locate(args[1]) {
Ok(v) => v.gid(),
_ => return Err(format!("invalid group: '{}'", spec)),
}),
))
let uid = if usr_only || usr_grp {
Some(
Passwd::locate(args[0])
.map_err(|_| format!("invalid user: '{}'", spec))?
.uid(),
)
} else {
Ok((None, None))
}
None
};
let gid = if grp_only || usr_grp {
Some(
Group::locate(args[1])
.map_err(|_| format!("invalid group: '{}'", spec))?
.gid(),
)
} else {
None
};
Ok((uid, gid))
}
enum IfFrom {
@ -374,8 +366,8 @@ impl Chowner {
if let Some(p) = may_exist {
if p.parent().is_none() {
show_info!("it is dangerous to operate recursively on '/'");
show_info!("use --no-preserve-root to override this failsafe");
show_error!("it is dangerous to operate recursively on '/'");
show_error!("use --no-preserve-root to override this failsafe");
return 1;
}
}
@ -391,14 +383,14 @@ impl Chowner {
self.verbosity.clone(),
) {
Ok(n) => {
if n != "" {
show_info!("{}", n);
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_info!("{}", e);
show_error!("{}", e);
}
1
}
@ -421,7 +413,7 @@ impl Chowner {
for entry in WalkDir::new(root).follow_links(follow).min_depth(1) {
let entry = unwrap!(entry, e, {
ret = 1;
show_info!("{}", e);
show_error!("{}", e);
continue;
});
let path = entry.path();
@ -446,14 +438,14 @@ impl Chowner {
self.verbosity.clone(),
) {
Ok(n) => {
if n != "" {
show_info!("{}", n);
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_info!("{}", e);
show_error!("{}", e);
}
1
}
@ -469,7 +461,7 @@ impl Chowner {
unwrap!(path.metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_info!("cannot access '{}': {}", path.display(), e),
_ => show_error!("cannot access '{}': {}", path.display(), e),
}
return None;
})
@ -477,7 +469,7 @@ impl Chowner {
unwrap!(path.symlink_metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_info!("cannot dereference '{}': {}", path.display(), e),
_ => show_error!("cannot dereference '{}': {}", path.display(), e),
}
return None;
})
@ -495,3 +487,17 @@ impl Chowner {
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_spec() {
assert_eq!(parse_spec(":"), Ok((None, None)));
assert!(parse_spec("::")
.err()
.unwrap()
.starts_with("invalid group: "));
}
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_chown); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_chown);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chroot"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chroot ~ (uutils) run COMMAND under a new root directory"
@ -15,8 +15,8 @@ edition = "2018"
path = "src/chroot.rs"
[dependencies]
getopts = "0.2.18"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] }
clap= "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -10,60 +10,47 @@
#[macro_use]
extern crate uucore;
use uucore::entries;
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
use clap::{crate_version, App, Arg};
use std::ffi::CString;
use std::io::Error;
use std::path::Path;
use std::process::Command;
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
use uucore::{entries, InvalidEncodingHandling};
static NAME: &str = "chroot";
static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT.";
static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]";
static SUMMARY: &str = "Run COMMAND with root directory set to NEWROOT.";
static LONG_HELP: &str = "
If COMMAND is not specified, it defaults to '$(SHELL) -i'.
If $(SHELL) is not set, /bin/sh is used.
";
mod options {
pub const NEWROOT: &str = "newroot";
pub const USER: &str = "user";
pub const GROUP: &str = "group";
pub const GROUPS: &str = "groups";
pub const USERSPEC: &str = "userspec";
pub const COMMAND: &str = "command";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optopt(
"u",
"user",
"User (ID or name) to switch before running the program",
"USER",
)
.optopt("g", "group", "Group (ID or name) to switch to", "GROUP")
.optopt(
"G",
"groups",
"Comma-separated list of groups to switch to",
"GROUP1,GROUP2...",
)
.optopt(
"",
"userspec",
"Colon-separated user and group to switch to. \
Same as -u USER -g GROUP. \
Userspec has higher preference than -u and/or -g",
"USER:GROUP",
)
.parse(args);
if matches.free.is_empty() {
println!("Missing operand: NEWROOT");
println!("Try `{} --help` for more information.", NAME);
return 1;
}
let matches = uu_app().get_matches_from(args);
let default_shell: &'static str = "/bin/sh";
let default_option: &'static str = "-i";
let user_shell = std::env::var("SHELL");
let newroot = Path::new(&matches.free[0][..]);
let newroot: &Path = match matches.value_of(options::NEWROOT) {
Some(v) => Path::new(v),
None => crash!(
1,
"Missing operand: NEWROOT\nTry '{} --help' for more information.",
NAME
),
};
if !newroot.is_dir() {
crash!(
1,
@ -72,7 +59,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
);
}
let command: Vec<&str> = match matches.free.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 => {
let shell: &str = match user_shell {
Err(_) => default_shell,
@ -80,10 +74,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
};
vec![shell, default_option]
}
_ => matches.free[1..].iter().map(|x| &x[..]).collect(),
_ => commands,
};
set_context(&newroot, &matches);
set_context(newroot, &matches);
let pstatus = Command::new(command[0])
.args(&command[1..])
@ -97,37 +91,83 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
}
fn set_context(root: &Path, options: &getopts::Matches) {
let userspec_str = options.opt_str("userspec");
let user_str = options.opt_str("user").unwrap_or_default();
let group_str = options.opt_str("group").unwrap_or_default();
let groups_str = options.opt_str("groups").unwrap_or_default();
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(SYNTAX)
.arg(
Arg::with_name(options::NEWROOT)
.hidden(true)
.required(true)
.index(1),
)
.arg(
Arg::with_name(options::USER)
.short("u")
.long(options::USER)
.help("User (ID or name) to switch before running the program")
.value_name("USER"),
)
.arg(
Arg::with_name(options::GROUP)
.short("g")
.long(options::GROUP)
.help("Group (ID or name) to switch to")
.value_name("GROUP"),
)
.arg(
Arg::with_name(options::GROUPS)
.short("G")
.long(options::GROUPS)
.help("Comma-separated list of groups to switch to")
.value_name("GROUP1,GROUP2..."),
)
.arg(
Arg::with_name(options::USERSPEC)
.long(options::USERSPEC)
.help(
"Colon-separated user and group to switch to. \
Same as -u USER -g GROUP. \
Userspec has higher preference than -u and/or -g",
)
.value_name("USER:GROUP"),
)
.arg(
Arg::with_name(options::COMMAND)
.hidden(true)
.multiple(true)
.index(2),
)
}
fn set_context(root: &Path, options: &clap::ArgMatches) {
let userspec_str = options.value_of(options::USERSPEC);
let user_str = options.value_of(options::USER).unwrap_or_default();
let group_str = options.value_of(options::GROUP).unwrap_or_default();
let groups_str = options.value_of(options::GROUPS).unwrap_or_default();
let userspec = match userspec_str {
Some(ref u) => {
Some(u) => {
let s: Vec<&str> = u.split(':').collect();
if s.len() != 2 {
if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) {
crash!(1, "invalid userspec: `{}`", u)
};
s
}
None => Vec::new(),
};
let user = if userspec.is_empty() {
&user_str[..]
let (user, group) = if userspec.is_empty() {
(user_str, group_str)
} else {
&userspec[0][..]
};
let group = if userspec.is_empty() {
&group_str[..]
} else {
&userspec[1][..]
(userspec[0], userspec[1])
};
enter_chroot(root);
set_groups_from_str(&groups_str[..]);
set_main_group(&group[..]);
set_user(&user[..]);
set_groups_from_str(groups_str);
set_main_group(group);
set_user(user);
}
fn enter_chroot(root: &Path) {
@ -164,7 +204,7 @@ fn set_main_group(group: &str) {
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
fn set_groups(groups: Vec<libc::gid_t>) -> libc::c_int {
unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) }
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_chroot); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_chroot);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_cksum"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "cksum ~ (uutils) display CRC and size of input"
@ -15,8 +15,9 @@ edition = "2018"
path = "src/cksum.rs"
[dependencies]
clap = "2.33"
libc = "0.2.42"
uucore = { version=">=0.0.7", 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" }
[[bin]]

View file

@ -1,46 +0,0 @@
// This file is part of the uutils coreutils package.
//
// (c) Alex Lyon <arcterus@mail.com>
// (c) Michael Gehring <mg@ebfe.org>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
const CRC_TABLE_LEN: usize = 256;
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let mut table = Vec::with_capacity(CRC_TABLE_LEN);
for num in 0..CRC_TABLE_LEN {
table.push(crc_entry(num as u8) as u32);
}
let file = File::create(&Path::new(&out_dir).join("crc_table.rs")).unwrap();
write!(
&file,
"#[allow(clippy::unreadable_literal)]\nconst CRC_TABLE: [u32; {}] = {:?};",
CRC_TABLE_LEN, table
)
.unwrap();
}
#[inline]
fn crc_entry(input: u8) -> u32 {
let mut crc = (input as u32) << 24;
for _ in 0..8 {
if crc & 0x8000_0000 != 0 {
crc <<= 1;
crc ^= 0x04c1_1db7;
} else {
crc <<= 1;
}
}
crc
}

View file

@ -10,15 +10,107 @@
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use std::fs::File;
use std::io::{self, stdin, BufReader, Read};
use std::path::Path;
use uucore::InvalidEncodingHandling;
include!(concat!(env!("OUT_DIR"), "/crc_table.rs"));
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
const CRC_TABLE_LEN: usize = 256;
const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table();
static SYNTAX: &str = "[OPTIONS] [FILE]...";
static SUMMARY: &str = "Print CRC and size for each file";
static LONG_HELP: &str = "";
const NAME: &str = "cksum";
const SYNTAX: &str = "[OPTIONS] [FILE]...";
const SUMMARY: &str = "Print CRC and size for each file";
// this is basically a hack to get "loops" to work on Rust 1.33. Once we update to Rust 1.46 or
// greater, we can just use while loops
macro_rules! unroll {
(256, |$i:ident| $s:expr) => {{
unroll!(@ 32, 0 * 32, $i, $s);
unroll!(@ 32, 1 * 32, $i, $s);
unroll!(@ 32, 2 * 32, $i, $s);
unroll!(@ 32, 3 * 32, $i, $s);
unroll!(@ 32, 4 * 32, $i, $s);
unroll!(@ 32, 5 * 32, $i, $s);
unroll!(@ 32, 6 * 32, $i, $s);
unroll!(@ 32, 7 * 32, $i, $s);
}};
(8, |$i:ident| $s:expr) => {{
unroll!(@ 8, 0, $i, $s);
}};
(@ 32, $start:expr, $i:ident, $s:expr) => {{
unroll!(@ 8, $start + 0 * 8, $i, $s);
unroll!(@ 8, $start + 1 * 8, $i, $s);
unroll!(@ 8, $start + 2 * 8, $i, $s);
unroll!(@ 8, $start + 3 * 8, $i, $s);
}};
(@ 8, $start:expr, $i:ident, $s:expr) => {{
unroll!(@ 4, $start, $i, $s);
unroll!(@ 4, $start + 4, $i, $s);
}};
(@ 4, $start:expr, $i:ident, $s:expr) => {{
unroll!(@ 2, $start, $i, $s);
unroll!(@ 2, $start + 2, $i, $s);
}};
(@ 2, $start:expr, $i:ident, $s:expr) => {{
unroll!(@ 1, $start, $i, $s);
unroll!(@ 1, $start + 1, $i, $s);
}};
(@ 1, $start:expr, $i:ident, $s:expr) => {{
let $i = $start;
let _ = $s;
}};
}
const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] {
let mut table = [0; CRC_TABLE_LEN];
// NOTE: works on Rust 1.46
//let mut i = 0;
//while i < CRC_TABLE_LEN {
// table[i] = crc_entry(i as u8) as u32;
//
// i += 1;
//}
unroll!(256, |i| {
table[i] = crc_entry(i as u8) as u32;
});
table
}
const fn crc_entry(input: u8) -> u32 {
let mut crc = (input as u32) << 24;
// NOTE: this does not work on Rust 1.33, but *does* on 1.46
//let mut i = 0;
//while i < 8 {
// if crc & 0x8000_0000 != 0 {
// crc <<= 1;
// crc ^= 0x04c1_1db7;
// } else {
// crc <<= 1;
// }
//
// i += 1;
//}
unroll!(8, |_i| {
let if_condition = crc & 0x8000_0000;
let if_body = (crc << 1) ^ 0x04c1_1db7;
let else_body = crc << 1;
// NOTE: i feel like this is easier to understand than emulating an if statement in bitwise
// ops
let condition_table = [else_body, if_body];
crc = condition_table[(if_condition != 0) as usize];
});
crc
}
#[inline]
fn crc_update(crc: u32, input: u8) -> u32 {
@ -48,33 +140,52 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
let mut rd: Box<dyn Read> = match fname {
"-" => Box::new(stdin()),
_ => {
file = File::open(&Path::new(fname))?;
let path = &Path::new(fname);
if path.is_dir() {
return Err(std::io::Error::new(
io::ErrorKind::InvalidInput,
"Is a directory",
));
};
if path.metadata().is_err() {
return Err(std::io::Error::new(
io::ErrorKind::NotFound,
"No such file or directory",
));
};
file = File::open(&path)?;
Box::new(BufReader::new(file))
}
};
let mut bytes = init_byte_array();
loop {
match rd.read(&mut bytes) {
Ok(num_bytes) => {
if num_bytes == 0 {
return Ok((crc_final(crc, size), size));
}
for &b in bytes[..num_bytes].iter() {
crc = crc_update(crc, b);
}
size += num_bytes;
}
Err(err) => return Err(err),
let num_bytes = rd.read(&mut bytes)?;
if num_bytes == 0 {
return Ok((crc_final(crc, size), size));
}
for &b in bytes[..num_bytes].iter() {
crc = crc_update(crc, b);
}
size += num_bytes;
}
//Ok((0 as u32,0 as usize))
}
mod options {
pub static FILE: &str = "file";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str());
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let files = matches.free;
let matches = uu_app().get_matches_from(args);
let files: Vec<String> = match matches.values_of(options::FILE) {
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
None => vec![],
};
if files.is_empty() {
match cksum("-") {
@ -100,3 +211,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
exit_code
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME)
.version(crate_version!())
.about(SUMMARY)
.usage(SYNTAX)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_cksum); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_cksum);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_comm"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "comm ~ (uutils) compare sorted inputs"
@ -15,9 +15,9 @@ edition = "2018"
path = "src/comm.rs"
[dependencies]
getopts = "0.2.18"
clap = "2.33"
libc = "0.2.42"
uucore = { version=">=0.0.7", 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" }
[[bin]]

View file

@ -14,22 +14,35 @@ use std::cmp::Ordering;
use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Stdin};
use std::path::Path;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str = "[OPTIONS] FILE1 FILE2";
static SUMMARY: &str = "Compare sorted files line by line";
use clap::{crate_version, App, Arg, ArgMatches};
static ABOUT: &str = "compare two sorted files line by line";
static LONG_HELP: &str = "";
fn mkdelim(col: usize, opts: &getopts::Matches) -> String {
let mut s = String::new();
let delim = match opts.opt_str("output-delimiter") {
Some(d) => d,
None => "\t".to_owned(),
};
mod options {
pub const COLUMN_1: &str = "1";
pub const COLUMN_2: &str = "2";
pub const COLUMN_3: &str = "3";
pub const DELIMITER: &str = "output-delimiter";
pub const DELIMITER_DEFAULT: &str = "\t";
pub const FILE_1: &str = "FILE1";
pub const FILE_2: &str = "FILE2";
}
if col > 1 && !opts.opt_present("1") {
fn get_usage() -> String {
format!("{} [OPTION]... FILE1 FILE2", executable!())
}
fn mkdelim(col: usize, opts: &ArgMatches) -> String {
let mut s = String::new();
let delim = opts.value_of(options::DELIMITER).unwrap();
if col > 1 && !opts.is_present(options::COLUMN_1) {
s.push_str(delim.as_ref());
}
if col > 2 && !opts.opt_present("2") {
if col > 2 && !opts.is_present(options::COLUMN_2) {
s.push_str(delim.as_ref());
}
@ -37,9 +50,8 @@ fn mkdelim(col: usize, opts: &getopts::Matches) -> String {
}
fn ensure_nl(line: &mut String) {
match line.chars().last() {
Some('\n') => (),
_ => line.push('\n'),
if !line.ends_with('\n') {
line.push('\n');
}
}
@ -57,7 +69,7 @@ impl LineReader {
}
}
fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
let delim: Vec<String> = (0..4).map(|col| mkdelim(col, opts)).collect();
let ra = &mut String::new();
@ -80,7 +92,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
match ord {
Ordering::Less => {
if !opts.opt_present("1") {
if !opts.is_present(options::COLUMN_1) {
ensure_nl(ra);
print!("{}{}", delim[1], ra);
}
@ -88,7 +100,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
na = a.read_line(ra);
}
Ordering::Greater => {
if !opts.opt_present("2") {
if !opts.is_present(options::COLUMN_2) {
ensure_nl(rb);
print!("{}{}", delim[2], rb);
}
@ -96,7 +108,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
nb = b.read_line(rb);
}
Ordering::Equal => {
if !opts.opt_present("3") {
if !opts.is_present(options::COLUMN_3) {
ensure_nl(ra);
print!("{}{}", delim[3], ra);
}
@ -120,23 +132,49 @@ fn open_file(name: &str) -> io::Result<LineReader> {
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let usage = get_usage();
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optflag("1", "", "suppress column 1 (lines uniq to FILE1)")
.optflag("2", "", "suppress column 2 (lines uniq to FILE2)")
.optflag(
"3",
"",
"suppress column 3 (lines that appear in both files)",
)
.optopt("", "output-delimiter", "separate columns with STR", "STR")
.parse(args);
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
let mut f1 = open_file(matches.free[0].as_ref()).unwrap();
let mut f2 = open_file(matches.free[1].as_ref()).unwrap();
let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap();
let mut f2 = open_file(matches.value_of(options::FILE_2).unwrap()).unwrap();
comm(&mut f1, &mut f2, &matches);
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::COLUMN_1)
.short(options::COLUMN_1)
.help("suppress column 1 (lines unique to FILE1)"),
)
.arg(
Arg::with_name(options::COLUMN_2)
.short(options::COLUMN_2)
.help("suppress column 2 (lines unique to FILE2)"),
)
.arg(
Arg::with_name(options::COLUMN_3)
.short(options::COLUMN_3)
.help("suppress column 3 (lines that appear in both files)"),
)
.arg(
Arg::with_name(options::DELIMITER)
.long(options::DELIMITER)
.help("separate columns with STR")
.value_name("STR")
.default_value(options::DELIMITER_DEFAULT)
.hide_default_value(true),
)
.arg(Arg::with_name(options::FILE_1).required(true))
.arg(Arg::with_name(options::FILE_2).required(true))
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_comm); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_comm);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_cp"
version = "0.0.4"
version = "0.0.6"
authors = [
"Jordy Dickinson <jordy.dickinson@gmail.com>",
"Joshua S. Miller <jsmiller@uchicago.edu>",
@ -23,7 +23,7 @@ clap = "2.33"
filetime = "0.2"
libc = "0.2.85"
quick-error = "1.2.3"
uucore = { version=">=0.0.7", 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" }
walkdir = "2.2"

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1 +1 @@
uucore_procs::main!(uu_cp); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_cp);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_csplit"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output"
@ -15,11 +15,11 @@ edition = "2018"
path = "src/csplit.rs"
[dependencies]
getopts = "0.2.17"
clap = "2.33"
thiserror = "1.0"
regex = "1.0.0"
glob = "0.2.11"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "fs"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -2,7 +2,7 @@
#[macro_use]
extern crate uucore;
use getopts::Matches;
use clap::{crate_version, App, Arg, ArgMatches};
use regex::Regex;
use std::cmp::Ordering;
use std::io::{self, BufReader};
@ -13,22 +13,30 @@ use std::{
mod csplit_error;
mod patterns;
mod splitname;
mod split_name;
use crate::csplit_error::CsplitError;
use crate::splitname::SplitName;
use crate::split_name::SplitName;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str = "[OPTION]... FILE PATTERN...";
static SUMMARY: &str = "split a file into sections determined by context lines";
static LONG_HELP: &str = "Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output.";
static SUFFIX_FORMAT_OPT: &str = "suffix-format";
static SUPPRESS_MATCHED_OPT: &str = "suppress-matched";
static DIGITS_OPT: &str = "digits";
static PREFIX_OPT: &str = "prefix";
static KEEP_FILES_OPT: &str = "keep-files";
static QUIET_OPT: &str = "quiet";
static ELIDE_EMPTY_FILES_OPT: &str = "elide-empty-files";
mod options {
pub const SUFFIX_FORMAT: &str = "suffix-format";
pub const SUPPRESS_MATCHED: &str = "suppress-matched";
pub const DIGITS: &str = "digits";
pub const PREFIX: &str = "prefix";
pub const KEEP_FILES: &str = "keep-files";
pub const QUIET: &str = "quiet";
pub const ELIDE_EMPTY_FILES: &str = "elide-empty-files";
pub const FILE: &str = "file";
pub const PATTERN: &str = "pattern";
}
fn get_usage() -> String {
format!("{0} [OPTION]... FILE PATTERN...", executable!())
}
/// Command line options for csplit.
pub struct CsplitOptions {
@ -40,19 +48,19 @@ pub struct CsplitOptions {
}
impl CsplitOptions {
fn new(matches: &Matches) -> CsplitOptions {
let keep_files = matches.opt_present(KEEP_FILES_OPT);
let quiet = matches.opt_present(QUIET_OPT);
let elide_empty_files = matches.opt_present(ELIDE_EMPTY_FILES_OPT);
let suppress_matched = matches.opt_present(SUPPRESS_MATCHED_OPT);
fn new(matches: &ArgMatches) -> CsplitOptions {
let keep_files = matches.is_present(options::KEEP_FILES);
let quiet = matches.is_present(options::QUIET);
let elide_empty_files = matches.is_present(options::ELIDE_EMPTY_FILES);
let suppress_matched = matches.is_present(options::SUPPRESS_MATCHED);
CsplitOptions {
split_name: crash_if_err!(
1,
SplitName::new(
matches.opt_str(PREFIX_OPT),
matches.opt_str(SUFFIX_FORMAT_OPT),
matches.opt_str(DIGITS_OPT)
matches.value_of(options::PREFIX).map(str::to_string),
matches.value_of(options::SUFFIX_FORMAT).map(str::to_string),
matches.value_of(options::DIGITS).map(str::to_string)
)
),
keep_files,
@ -68,7 +76,7 @@ impl CsplitOptions {
/// # Errors
///
/// - [`io::Error`] if there is some problem reading/writing from/to a file.
/// - [`::CsplitError::LineOutOfRange`] if the linenum pattern is larger than the number of input
/// - [`::CsplitError::LineOutOfRange`] if the line number pattern is larger than the number of input
/// lines.
/// - [`::CsplitError::LineOutOfRangeOnRepetition`], like previous but after applying the pattern
/// more than once.
@ -84,7 +92,7 @@ where
T: BufRead,
{
let mut input_iter = InputSplitter::new(input.lines().enumerate());
let mut split_writer = SplitWriter::new(&options);
let mut split_writer = SplitWriter::new(options);
let ret = do_csplit(&mut split_writer, patterns, &mut input_iter);
// consume the rest
@ -115,12 +123,7 @@ where
// split the file based on patterns
for pattern in patterns.into_iter() {
let pattern_as_str = pattern.to_string();
#[allow(clippy::match_like_matches_macro)]
let is_skip = if let patterns::Pattern::SkipToMatch(_, _, _) = pattern {
true
} else {
false
};
let is_skip = matches!(pattern, patterns::Pattern::SkipToMatch(_, _, _));
match pattern {
patterns::Pattern::UpToLine(n, ex) => {
let mut up_to_line = n;
@ -479,10 +482,11 @@ where
/// Shrink the buffer so that its length is equal to the set size, returning an iterator for
/// the elements that were too much.
fn shrink_buffer_to_size(&mut self) -> impl Iterator<Item = String> + '_ {
let mut shrink_offset = 0;
if self.buffer.len() > self.size {
shrink_offset = self.buffer.len() - self.size;
}
let shrink_offset = if self.buffer.len() > self.size {
self.buffer.len() - self.size
} else {
0
};
self.buffer
.drain(..shrink_offset)
.map(|(_, line)| line.unwrap())
@ -702,45 +706,23 @@ mod tests {
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let usage = get_usage();
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optopt(
"b",
SUFFIX_FORMAT_OPT,
"use sprintf FORMAT instead of %02d",
"FORMAT",
)
.optopt("f", PREFIX_OPT, "use PREFIX instead of 'xx'", "PREFIX")
.optflag("k", KEEP_FILES_OPT, "do not remove output files on errors")
.optflag(
"",
SUPPRESS_MATCHED_OPT,
"suppress the lines matching PATTERN",
)
.optopt(
"n",
DIGITS_OPT,
"use specified number of digits instead of 2",
"DIGITS",
)
.optflag("s", QUIET_OPT, "do not print counts of output file sizes")
.optflag("z", ELIDE_EMPTY_FILES_OPT, "remove empty output files")
.parse(args);
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
// check for mandatory arguments
if matches.free.is_empty() {
show_error!("missing operand");
exit!(1);
}
if matches.free.len() == 1 {
show_error!("missing operand after '{}'", matches.free[0]);
exit!(1);
}
// get the patterns to split on
let patterns = return_if_err!(1, patterns::get_patterns(&matches.free[1..]));
// get the file to split
let file_name: &str = &matches.free[0];
let file_name = matches.value_of(options::FILE).unwrap();
// get the patterns to split on
let patterns: Vec<String> = matches
.values_of(options::PATTERN)
.unwrap()
.map(str::to_string)
.collect();
let patterns = return_if_err!(1, patterns::get_patterns(&patterns[..]));
let options = CsplitOptions::new(&matches);
if file_name == "-" {
let stdin = io::stdin();
@ -755,3 +737,62 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
};
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(SUMMARY)
.arg(
Arg::with_name(options::SUFFIX_FORMAT)
.short("b")
.long(options::SUFFIX_FORMAT)
.value_name("FORMAT")
.help("use sprintf FORMAT instead of %02d"),
)
.arg(
Arg::with_name(options::PREFIX)
.short("f")
.long(options::PREFIX)
.value_name("PREFIX")
.help("use PREFIX instead of 'xx'"),
)
.arg(
Arg::with_name(options::KEEP_FILES)
.short("k")
.long(options::KEEP_FILES)
.help("do not remove output files on errors"),
)
.arg(
Arg::with_name(options::SUPPRESS_MATCHED)
.long(options::SUPPRESS_MATCHED)
.help("suppress the lines matching PATTERN"),
)
.arg(
Arg::with_name(options::DIGITS)
.short("n")
.long(options::DIGITS)
.value_name("DIGITS")
.help("use specified number of digits instead of 2"),
)
.arg(
Arg::with_name(options::QUIET)
.short("s")
.long(options::QUIET)
.visible_alias("silent")
.help("do not print counts of output file sizes"),
)
.arg(
Arg::with_name(options::ELIDE_EMPTY_FILES)
.short("z")
.long(options::ELIDE_EMPTY_FILES)
.help("remove empty output files"),
)
.arg(Arg::with_name(options::FILE).hidden(true).required(true))
.arg(
Arg::with_name(options::PATTERN)
.hidden(true)
.multiple(true)
.required(true),
)
.after_help(LONG_HELP)
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_csplit); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_csplit);

View file

@ -1,3 +1,5 @@
// spell-checker:ignore (regex) SKIPTO UPTO ; (vars) ntimes
use crate::csplit_error::CsplitError;
use regex::Regex;
@ -131,20 +133,12 @@ fn extract_patterns(args: &[String]) -> Result<Vec<Pattern>, CsplitError> {
Some(m) => m.as_str().parse().unwrap(),
};
if let Some(up_to_match) = captures.name("UPTO") {
let pattern = match Regex::new(up_to_match.as_str()) {
Err(_) => {
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
Ok(reg) => reg,
};
let pattern = Regex::new(up_to_match.as_str())
.map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?;
patterns.push(Pattern::UpToMatch(pattern, offset, execute_ntimes));
} else if let Some(skip_to_match) = captures.name("SKIPTO") {
let pattern = match Regex::new(skip_to_match.as_str()) {
Err(_) => {
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
Ok(reg) => reg,
};
let pattern = Regex::new(skip_to_match.as_str())
.map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?;
patterns.push(Pattern::SkipToMatch(pattern, offset, execute_ntimes));
}
} else if let Ok(line_number) = arg.parse::<usize>() {
@ -167,7 +161,7 @@ fn validate_line_numbers(patterns: &[Pattern]) -> Result<(), CsplitError> {
.try_fold(0, |prev_ln, &current_ln| match (prev_ln, current_ln) {
// a line number cannot be zero
(_, 0) => Err(CsplitError::LineNumberIsZero),
// two consecutifs numbers should not be equal
// two consecutive numbers should not be equal
(n, m) if n == m => {
show_warning!("line number '{}' is the same as preceding line number", n);
Ok(n)

View file

@ -1,3 +1,5 @@
// spell-checker:ignore (regex) diuox
use regex::Regex;
use crate::csplit_error::CsplitError;
@ -31,13 +33,13 @@ impl SplitName {
// get the prefix
let prefix = prefix_opt.unwrap_or_else(|| "xx".to_string());
// the width for the split offset
let n_digits = match n_digits_opt {
None => 2,
Some(opt) => match opt.parse::<usize>() {
Ok(digits) => digits,
Err(_) => return Err(CsplitError::InvalidNumber(opt)),
},
};
let n_digits = n_digits_opt
.map(|opt| {
opt.parse::<usize>()
.map_err(|_| CsplitError::InvalidNumber(opt))
})
.transpose()?
.unwrap_or(2);
// translate the custom format into a function
let fn_split_name: Box<dyn Fn(usize) -> String> = match format_opt {
None => Box::new(move |n: usize| -> String {
@ -225,6 +227,8 @@ impl SplitName {
#[cfg(test)]
mod tests {
// spell-checker:ignore (path) xxcst
use super::*;
#[test]
@ -319,13 +323,13 @@ mod tests {
}
#[test]
fn zero_padding_lower_hexa() {
fn zero_padding_lower_hex() {
let split_name = SplitName::new(None, Some(String::from("cst-%03x-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-02a-");
}
#[test]
fn zero_padding_upper_hexa() {
fn zero_padding_upper_hex() {
let split_name = SplitName::new(None, Some(String::from("cst-%03X-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-02A-");
}
@ -337,13 +341,13 @@ mod tests {
}
#[test]
fn alternate_form_lower_hexa() {
fn alternate_form_lower_hex() {
let split_name = SplitName::new(None, Some(String::from("cst-%#10x-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst- 0x2a-");
}
#[test]
fn alternate_form_upper_hexa() {
fn alternate_form_upper_hex() {
let split_name = SplitName::new(None, Some(String::from("cst-%#10X-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst- 0x2A-");
}
@ -373,13 +377,13 @@ mod tests {
}
#[test]
fn left_adjusted_lower_hexa() {
fn left_adjusted_lower_hex() {
let split_name = SplitName::new(None, Some(String::from("cst-%-10x-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-0x2a -");
}
#[test]
fn left_adjusted_upper_hexa() {
fn left_adjusted_upper_hex() {
let split_name = SplitName::new(None, Some(String::from("cst-%-10X-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-0x2A -");
}

View file

@ -0,0 +1,46 @@
## Benchmarking cut
### Performance profile
In normal use cases a significant amount of the total execution time of `cut`
is spent performing I/O. When invoked with the `-f` option (cut fields) some
CPU time is spent on detecting fields (in `Searcher::next`). Other than that
some small amount of CPU time is spent on breaking the input stream into lines.
### How to
When fixing bugs or adding features you might want to compare
performance before and after your code changes.
- `hyperfine` can be used to accurately measure and compare the total
execution time of one or more commands.
```
$ cargo build --release --package uu_cut
$ hyperfine -w3 "./target/release/cut -f2-4,8 -d' ' input.txt" "cut -f2-4,8 -d' ' input.txt"
```
You can put those two commands in a shell script to be sure that you don't
forget to build after making any changes.
When optimizing or fixing performance regressions seeing the number of times a
function is called, and the amount of time it takes can be useful.
- `cargo flamegraph` generates flame graphs from function level metrics it records using `perf` or `dtrace`
```
$ cargo flamegraph --bin cut --package uu_cut -- -f1,3-4 input.txt > /dev/null
```
### What to benchmark
There are four different performance paths in `cut` to benchmark.
- Byte ranges `-c`/`--characters` or `-b`/`--bytes` e.g. `cut -c 2,4,6-`
- Byte ranges with output delimiters e.g. `cut -c 4- --output-delimiter=/`
- Fields e.g. `cut -f -4`
- Fields with output delimiters e.g. `cut -f 7-10 --output-delimiter=:`
Choose a test input file with large number of lines so that program startup time does not significantly affect the benchmark.

View file

@ -1,6 +1,6 @@
[package]
name = "uu_cut"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "cut ~ (uutils) display byte/field columns of input lines"
@ -15,8 +15,12 @@ edition = "2018"
path = "src/cut.rs"
[dependencies]
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
memchr = "2"
bstr = "0.2"
atty = "0.2"
[[bin]]
name = "cut"

View file

@ -1,152 +0,0 @@
// This file is part of the uutils coreutils package.
//
// (c) Rolf Morel <rolfmorel@gmail.com>
// (c) kwantam <kwantam@gmail.com>
// * substantial rewrite to use the `std::io::BufReader` trait
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) SRes Newl
use std::io::Result as IoResult;
use std::io::{BufRead, BufReader, Read, Write};
#[allow(non_snake_case)]
pub mod Bytes {
use std::io::Write;
pub trait Select {
fn select<W: Write>(&mut self, bytes: usize, out: Option<&mut W>) -> Selected;
}
#[derive(PartialEq, Eq, Debug)]
pub enum Selected {
NewlineFound,
Complete(usize),
Partial(usize),
EndOfFile,
}
}
#[derive(Debug)]
pub struct ByteReader<R>
where
R: Read,
{
inner: BufReader<R>,
newline_char: u8,
}
impl<R: Read> ByteReader<R> {
pub fn new(read: R, newline_char: u8) -> ByteReader<R> {
ByteReader {
inner: BufReader::with_capacity(4096, read),
newline_char,
}
}
}
impl<R: Read> Read for ByteReader<R> {
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
self.inner.read(buf)
}
}
impl<R: Read> BufRead for ByteReader<R> {
fn fill_buf(&mut self) -> IoResult<&[u8]> {
self.inner.fill_buf()
}
fn consume(&mut self, amt: usize) {
self.inner.consume(amt)
}
}
impl<R: Read> ByteReader<R> {
pub fn consume_line(&mut self) -> usize {
let mut bytes_consumed = 0;
let mut consume_val;
let newline_char = self.newline_char;
loop {
{
// need filled_buf to go out of scope
let filled_buf = match self.fill_buf() {
Ok(b) => {
if b.is_empty() {
return bytes_consumed;
} else {
b
}
}
Err(e) => crash!(1, "read error: {}", e),
};
if let Some(idx) = filled_buf.iter().position(|byte| *byte == newline_char) {
consume_val = idx + 1;
bytes_consumed += consume_val;
break;
}
consume_val = filled_buf.len();
}
bytes_consumed += consume_val;
self.consume(consume_val);
}
self.consume(consume_val);
bytes_consumed
}
}
impl<R: Read> self::Bytes::Select for ByteReader<R> {
fn select<W: Write>(&mut self, bytes: usize, out: Option<&mut W>) -> Bytes::Selected {
enum SRes {
Comp,
Part,
Newl,
}
use self::Bytes::Selected::*;
let newline_char = self.newline_char;
let (res, consume_val) = {
let buffer = match self.fill_buf() {
Err(e) => crash!(1, "read error: {}", e),
Ok(b) => b,
};
let (res, consume_val) = match buffer.len() {
0 => return EndOfFile,
buf_used if bytes < buf_used => {
// because the output delimiter should only be placed between
// segments check if the byte after bytes is a newline
let buf_slice = &buffer[0..=bytes];
match buf_slice.iter().position(|byte| *byte == newline_char) {
Some(idx) => (SRes::Newl, idx + 1),
None => (SRes::Comp, bytes),
}
}
_ => match buffer.iter().position(|byte| *byte == newline_char) {
Some(idx) => (SRes::Newl, idx + 1),
None => (SRes::Part, buffer.len()),
},
};
if let Some(out) = out {
crash_if_err!(1, out.write_all(&buffer[0..consume_val]));
}
(res, consume_val)
};
self.consume(consume_val);
match res {
SRes::Comp => Complete(consume_val),
SRes::Part => Partial(consume_val),
SRes::Newl => NewlineFound,
}
}
}

View file

@ -10,16 +10,19 @@
#[macro_use]
extern crate uucore;
use bstr::io::BufReadExt;
use clap::{crate_version, App, Arg};
use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, Read, Stdout, Write};
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
use std::path::Path;
use self::searcher::Searcher;
use uucore::ranges::Range;
use uucore::InvalidEncodingHandling;
mod buffer;
mod searcher;
static NAME: &str = "cut";
static SYNTAX: &str =
"[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+";
static SUMMARY: &str =
@ -122,6 +125,14 @@ enum Mode {
Fields(Vec<Range>, FieldOptions),
}
fn stdout_writer() -> Box<dyn Write> {
if atty::is(atty::Stream::Stdout) {
Box::new(stdout())
} else {
Box::new(BufWriter::new(stdout())) as Box<dyn Write>
}
}
fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
if complement {
Range::from_list(list).map(|r| uucore::ranges::complement(&r))
@ -131,72 +142,35 @@ fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
}
fn cut_bytes<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> i32 {
use self::buffer::Bytes::Select;
use self::buffer::Bytes::Selected::*;
let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' };
let mut buf_read = buffer::ByteReader::new(reader, newline_char);
let mut out = stdout();
let buf_in = BufReader::new(reader);
let mut out = stdout_writer();
let delim = opts
.out_delim
.as_ref()
.map_or("", String::as_str)
.as_bytes();
'newline: loop {
let mut cur_pos = 1;
let res = buf_in.for_byte_record(newline_char, |line| {
let mut print_delim = false;
for &Range { low, high } in ranges.iter() {
// skip up to low
let orig_pos = cur_pos;
loop {
match buf_read.select(low - cur_pos, None::<&mut Stdout>) {
NewlineFound => {
crash_if_err!(1, out.write_all(&[newline_char]));
continue 'newline;
}
Complete(len) => {
cur_pos += len;
break;
}
Partial(len) => cur_pos += len,
EndOfFile => {
if orig_pos != cur_pos {
crash_if_err!(1, out.write_all(&[newline_char]));
}
break 'newline;
}
}
for &Range { low, high } in ranges {
if low > line.len() {
break;
}
if let Some(ref delim) = opts.out_delim {
if print_delim {
crash_if_err!(1, out.write_all(delim.as_bytes()));
}
if print_delim {
out.write_all(delim)?;
} else if opts.out_delim.is_some() {
print_delim = true;
}
// write out from low to high
loop {
match buf_read.select(high - cur_pos + 1, Some(&mut out)) {
NewlineFound => continue 'newline,
Partial(len) => cur_pos += len,
Complete(_) => {
cur_pos = high + 1;
break;
}
EndOfFile => {
if cur_pos != low || low == high {
crash_if_err!(1, out.write_all(&[newline_char]));
}
break 'newline;
}
}
}
// change `low` from 1-indexed value to 0-index value
let low = low - 1;
let high = high.min(line.len());
out.write_all(&line[low..high])?;
}
buf_read.consume_line();
crash_if_err!(1, out.write_all(&[newline_char]));
}
out.write_all(&[newline_char])?;
Ok(true)
});
crash_if_err!(1, res);
0
}
@ -209,23 +183,11 @@ fn cut_fields_delimiter<R: Read>(
newline_char: u8,
out_delim: &str,
) -> i32 {
let mut buf_in = BufReader::new(reader);
let mut out = stdout();
let mut buffer = Vec::new();
let buf_in = BufReader::new(reader);
let mut out = stdout_writer();
let input_delim_len = delim.len();
'newline: loop {
buffer.clear();
match buf_in.read_until(newline_char, &mut buffer) {
Ok(n) if n == 0 => break,
Err(e) => {
if buffer.is_empty() {
crash!(1, "read error: {}", e);
}
}
_ => (),
}
let line = &buffer[..];
let result = buf_in.for_byte_record_with_terminator(newline_char, |line| {
let mut fields_pos = 1;
let mut low_idx = 0;
let mut delim_search = Searcher::new(line, delim.as_bytes()).peekable();
@ -233,46 +195,46 @@ fn cut_fields_delimiter<R: Read>(
if delim_search.peek().is_none() {
if !only_delimited {
crash_if_err!(1, out.write_all(line));
out.write_all(line)?;
if line[line.len() - 1] != newline_char {
crash_if_err!(1, out.write_all(&[newline_char]));
out.write_all(&[newline_char])?;
}
}
continue;
return Ok(true);
}
for &Range { low, high } in ranges.iter() {
for &Range { low, high } in ranges {
if low - fields_pos > 0 {
low_idx = match delim_search.nth(low - fields_pos - 1) {
Some((_, beyond_delim)) => beyond_delim,
Some(index) => index + input_delim_len,
None => break,
};
}
for _ in 0..=high - low {
if print_delim {
crash_if_err!(1, out.write_all(out_delim.as_bytes()));
out.write_all(out_delim.as_bytes())?;
} else {
print_delim = true;
}
match delim_search.next() {
Some((high_idx, next_low_idx)) => {
Some(high_idx) => {
let segment = &line[low_idx..high_idx];
crash_if_err!(1, out.write_all(segment));
out.write_all(segment)?;
print_delim = true;
low_idx = next_low_idx;
low_idx = high_idx + input_delim_len;
fields_pos = high + 1;
}
None => {
let segment = &line[low_idx..];
crash_if_err!(1, out.write_all(segment));
out.write_all(segment)?;
if line[line.len() - 1] == newline_char {
continue 'newline;
return Ok(true);
}
break;
}
@ -280,9 +242,10 @@ fn cut_fields_delimiter<R: Read>(
}
}
crash_if_err!(1, out.write_all(&[newline_char]));
}
out.write_all(&[newline_char])?;
Ok(true)
});
crash_if_err!(1, result);
0
}
@ -300,23 +263,11 @@ fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32
);
}
let mut buf_in = BufReader::new(reader);
let mut out = stdout();
let mut buffer = Vec::new();
let buf_in = BufReader::new(reader);
let mut out = stdout_writer();
let delim_len = opts.delimiter.len();
'newline: loop {
buffer.clear();
match buf_in.read_until(newline_char, &mut buffer) {
Ok(n) if n == 0 => break,
Err(e) => {
if buffer.is_empty() {
crash!(1, "read error: {}", e);
}
}
_ => (),
}
let line = &buffer[..];
let result = buf_in.for_byte_record_with_terminator(newline_char, |line| {
let mut fields_pos = 1;
let mut low_idx = 0;
let mut delim_search = Searcher::new(line, opts.delimiter.as_bytes()).peekable();
@ -324,53 +275,54 @@ fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32
if delim_search.peek().is_none() {
if !opts.only_delimited {
crash_if_err!(1, out.write_all(line));
out.write_all(line)?;
if line[line.len() - 1] != newline_char {
crash_if_err!(1, out.write_all(&[newline_char]));
out.write_all(&[newline_char])?;
}
}
continue;
return Ok(true);
}
for &Range { low, high } in ranges.iter() {
for &Range { low, high } in ranges {
if low - fields_pos > 0 {
low_idx = match delim_search.nth(low - fields_pos - 1) {
Some((_, beyond_delim)) => beyond_delim,
None => break,
};
}
if print_delim && low_idx >= opts.delimiter.as_bytes().len() {
low_idx -= opts.delimiter.as_bytes().len();
if let Some(delim_pos) = delim_search.nth(low - fields_pos - 1) {
low_idx = if print_delim {
delim_pos
} else {
delim_pos + delim_len
}
} else {
break;
}
}
match delim_search.nth(high - low) {
Some((high_idx, next_low_idx)) => {
Some(high_idx) => {
let segment = &line[low_idx..high_idx];
crash_if_err!(1, out.write_all(segment));
out.write_all(segment)?;
print_delim = true;
low_idx = next_low_idx;
low_idx = high_idx;
fields_pos = high + 1;
}
None => {
let segment = &line[low_idx..line.len()];
crash_if_err!(1, out.write_all(segment));
out.write_all(segment)?;
if line[line.len() - 1] == newline_char {
continue 'newline;
return Ok(true);
}
break;
}
}
}
crash_if_err!(1, out.write_all(&[newline_char]));
}
out.write_all(&[newline_char])?;
Ok(true)
});
crash_if_err!(1, result);
0
}
@ -398,8 +350,13 @@ fn cut_files(mut filenames: Vec<String>, mode: Mode) -> i32 {
} else {
let path = Path::new(&filename[..]);
if !path.exists() {
show_error!("{}", msg_args_nonexistent_file!(filename));
if path.is_dir() {
show_error!("{}: Is a directory", filename);
continue;
}
if path.metadata().is_err() {
show_error!("{}: No such file or directory", filename);
continue;
}
@ -422,67 +379,87 @@ fn cut_files(mut filenames: Vec<String>, mode: Mode) -> i32 {
exit_code
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
mod options {
pub const BYTES: &str = "bytes";
pub const CHARACTERS: &str = "characters";
pub const DELIMITER: &str = "delimiter";
pub const FIELDS: &str = "fields";
pub const ZERO_TERMINATED: &str = "zero-terminated";
pub const ONLY_DELIMITED: &str = "only-delimited";
pub const OUTPUT_DELIMITER: &str = "output-delimiter";
pub const COMPLEMENT: &str = "complement";
pub const FILE: &str = "file";
}
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optopt("b", "bytes", "filter byte columns from the input source", "sequence")
.optopt("c", "characters", "alias for character mode", "sequence")
.optopt("d", "delimiter", "specify the delimiter character that separates fields in the input source. Defaults to Tab.", "delimiter")
.optopt("f", "fields", "filter field columns from the input source", "sequence")
.optflag("n", "", "legacy option - has no effect.")
.optflag("", "complement", "invert the filter - instead of displaying only the filtered columns, display all but those columns")
.optflag("s", "only-delimited", "in field mode, only print lines which contain the delimiter")
.optflag("z", "zero-terminated", "instead of filtering columns based on line, filter columns based on \\0 (NULL character)")
.optopt("", "output-delimiter", "in field mode, replace the delimiter in output lines with this option's argument", "new delimiter")
.parse(args);
let complement = matches.opt_present("complement");
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = uu_app().get_matches_from(args);
let complement = matches.is_present(options::COMPLEMENT);
let mode_parse = match (
matches.opt_str("bytes"),
matches.opt_str("characters"),
matches.opt_str("fields"),
matches.value_of(options::BYTES),
matches.value_of(options::CHARACTERS),
matches.value_of(options::FIELDS),
) {
(Some(byte_ranges), None, None) => {
list_to_ranges(&byte_ranges[..], complement).map(|ranges| {
Mode::Bytes(
ranges,
Options {
out_delim: matches.opt_str("output-delimiter"),
zero_terminated: matches.opt_present("zero-terminated"),
},
)
})
}
(None, Some(char_ranges), None) => {
list_to_ranges(&char_ranges[..], complement).map(|ranges| {
Mode::Characters(
ranges,
Options {
out_delim: matches.opt_str("output-delimiter"),
zero_terminated: matches.opt_present("zero-terminated"),
},
)
})
}
(Some(byte_ranges), None, None) => list_to_ranges(byte_ranges, complement).map(|ranges| {
Mode::Bytes(
ranges,
Options {
out_delim: Some(
matches
.value_of(options::OUTPUT_DELIMITER)
.unwrap_or_default()
.to_owned(),
),
zero_terminated: matches.is_present(options::ZERO_TERMINATED),
},
)
}),
(None, Some(char_ranges), None) => list_to_ranges(char_ranges, complement).map(|ranges| {
Mode::Characters(
ranges,
Options {
out_delim: Some(
matches
.value_of(options::OUTPUT_DELIMITER)
.unwrap_or_default()
.to_owned(),
),
zero_terminated: matches.is_present(options::ZERO_TERMINATED),
},
)
}),
(None, None, Some(field_ranges)) => {
list_to_ranges(&field_ranges[..], complement).and_then(|ranges| {
let out_delim = match matches.opt_str("output-delimiter") {
list_to_ranges(field_ranges, complement).and_then(|ranges| {
let out_delim = match matches.value_of(options::OUTPUT_DELIMITER) {
Some(s) => {
if s.is_empty() {
Some("\0".to_owned())
} else {
Some(s)
Some(s.to_owned())
}
}
None => None,
};
let only_delimited = matches.opt_present("only-delimited");
let zero_terminated = matches.opt_present("zero-terminated");
let only_delimited = matches.is_present(options::ONLY_DELIMITED);
let zero_terminated = matches.is_present(options::ZERO_TERMINATED);
match matches.opt_str("delimiter") {
Some(delim) => {
match matches.value_of(options::DELIMITER) {
Some(mut delim) => {
// GNU's `cut` supports `-d=` to set the delimiter to `=`.
// Clap parsing is limited in this situation, see:
// https://github.com/uutils/coreutils/issues/2424#issuecomment-863825242
// Since clap parsing handles `-d=` as delimiter explicitly set to "" and
// an empty delimiter is not accepted by GNU's `cut` (and makes no sense),
// we can use this as basis for a simple workaround:
if delim.is_empty() {
delim = "=";
}
if delim.chars().count() > 1 {
Err(msg_opt_invalid_should_be!(
"empty or 1 character long",
@ -494,7 +471,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let delim = if delim.is_empty() {
"\0".to_owned()
} else {
delim
delim.to_owned()
};
Ok(Mode::Fields(
@ -533,10 +510,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let mode_parse = match mode_parse {
Err(_) => mode_parse,
Ok(mode) => match mode {
Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("delimiter") => Err(
msg_opt_only_usable_if!("printing a sequence of fields", "--delimiter", "-d"),
),
Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("only-delimited") => {
Mode::Bytes(_, _) | Mode::Characters(_, _)
if matches.is_present(options::DELIMITER) =>
{
Err(msg_opt_only_usable_if!(
"printing a sequence of fields",
"--delimiter",
"-d"
))
}
Mode::Bytes(_, _) | Mode::Characters(_, _)
if matches.is_present(options::ONLY_DELIMITED) =>
{
Err(msg_opt_only_usable_if!(
"printing a sequence of fields",
"--only-delimited",
@ -547,11 +532,101 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
},
};
let files: Vec<String> = matches
.values_of(options::FILE)
.unwrap_or_default()
.map(str::to_owned)
.collect();
match mode_parse {
Ok(mode) => cut_files(matches.free, mode),
Ok(mode) => cut_files(files, mode),
Err(err_msg) => {
show_error!("{}", err_msg);
1
}
}
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.name(NAME)
.version(crate_version!())
.usage(SYNTAX)
.about(SUMMARY)
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::BYTES)
.short("b")
.long(options::BYTES)
.takes_value(true)
.help("filter byte columns from the input source")
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(1),
)
.arg(
Arg::with_name(options::CHARACTERS)
.short("c")
.long(options::CHARACTERS)
.help("alias for character mode")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(2),
)
.arg(
Arg::with_name(options::DELIMITER)
.short("d")
.long(options::DELIMITER)
.help("specify the delimiter character that separates fields in the input source. Defaults to Tab.")
.takes_value(true)
.value_name("DELIM")
.display_order(3),
)
.arg(
Arg::with_name(options::FIELDS)
.short("f")
.long(options::FIELDS)
.help("filter field columns from the input source")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(4),
)
.arg(
Arg::with_name(options::COMPLEMENT)
.long(options::COMPLEMENT)
.help("invert the filter - instead of displaying only the filtered columns, display all but those columns")
.takes_value(false)
.display_order(5),
)
.arg(
Arg::with_name(options::ONLY_DELIMITED)
.short("s")
.long(options::ONLY_DELIMITED)
.help("in field mode, only print lines which contain the delimiter")
.takes_value(false)
.display_order(6),
)
.arg(
Arg::with_name(options::ZERO_TERMINATED)
.short("z")
.long(options::ZERO_TERMINATED)
.help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)")
.takes_value(false)
.display_order(8),
)
.arg(
Arg::with_name(options::OUTPUT_DELIMITER)
.long(options::OUTPUT_DELIMITER)
.help("in field mode, replace the delimiter in output lines with this option's argument")
.takes_value(true)
.value_name("NEW_DELIM")
.display_order(7),
)
.arg(
Arg::with_name(options::FILE)
.hidden(true)
.multiple(true)
)
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_cut); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_cut);

View file

@ -5,7 +5,8 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[derive(Clone)]
use memchr::memchr;
pub struct Searcher<'a> {
haystack: &'a [u8],
needle: &'a [u8],
@ -14,6 +15,7 @@ pub struct Searcher<'a> {
impl<'a> Searcher<'a> {
pub fn new(haystack: &'a [u8], needle: &'a [u8]) -> Searcher<'a> {
assert!(!needle.is_empty());
Searcher {
haystack,
needle,
@ -23,30 +25,81 @@ impl<'a> Searcher<'a> {
}
impl<'a> Iterator for Searcher<'a> {
type Item = (usize, usize);
type Item = usize;
fn next(&mut self) -> Option<(usize, usize)> {
if self.needle.len() == 1 {
for offset in self.position..self.haystack.len() {
if self.haystack[offset] == self.needle[0] {
self.position = offset + 1;
return Some((offset, offset + 1));
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(match_idx) = memchr(self.needle[0], self.haystack) {
if self.needle.len() == 1
|| self.haystack[match_idx + 1..].starts_with(&self.needle[1..])
{
let match_pos = self.position + match_idx;
let skip = match_idx + self.needle.len();
self.haystack = &self.haystack[skip..];
self.position += skip;
return Some(match_pos);
} else {
let skip = match_idx + 1;
self.haystack = &self.haystack[skip..];
self.position += skip;
// continue
}
}
self.position = self.haystack.len();
return None;
}
while self.position + self.needle.len() <= self.haystack.len() {
if &self.haystack[self.position..self.position + self.needle.len()] == self.needle {
let match_pos = self.position;
self.position += self.needle.len();
return Some((match_pos, match_pos + self.needle.len()));
} else {
self.position += 1;
return None;
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
const NEEDLE: &[u8] = "ab".as_bytes();
#[test]
fn test_normal() {
let iter = Searcher::new("a.a.a".as_bytes(), "a".as_bytes());
let items: Vec<usize> = iter.collect();
assert_eq!(vec![0, 2, 4], items);
}
#[test]
fn test_empty() {
let iter = Searcher::new("".as_bytes(), "a".as_bytes());
let items: Vec<usize> = iter.collect();
assert_eq!(vec![] as Vec<usize>, items);
}
fn test_multibyte(line: &[u8], expected: Vec<usize>) {
let iter = Searcher::new(line, NEEDLE);
let items: Vec<usize> = iter.collect();
assert_eq!(expected, items);
}
#[test]
fn test_multibyte_normal() {
test_multibyte("...ab...ab...".as_bytes(), vec![3, 8]);
}
#[test]
fn test_multibyte_needle_head_at_end() {
test_multibyte("a".as_bytes(), vec![]);
}
#[test]
fn test_multibyte_starting_needle() {
test_multibyte("ab...ab...".as_bytes(), vec![0, 5]);
}
#[test]
fn test_multibyte_trailing_needle() {
test_multibyte("...ab...ab".as_bytes(), vec![3, 8]);
}
#[test]
fn test_multibyte_first_byte_false_match() {
test_multibyte("aA..aCaC..ab..aD".as_bytes(), vec![10]);
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_date"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "date ~ (uutils) display or set the current time"
@ -17,9 +17,15 @@ path = "src/date.rs"
[dependencies]
chrono = "0.4.4"
clap = "2.33"
uucore = { version=">=0.0.7", 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" }
[target.'cfg(unix)'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["minwinbase", "sysinfoapi", "minwindef"] }
[[bin]]
name = "date"
path = "src/main.rs"

View file

@ -1,3 +1,8 @@
# `date` usage
<!-- spell-checker:ignore (format) hhmm -->
``` text
FORMAT controls the output. Interpreted sequences are:
%% a literal %
@ -70,3 +75,4 @@ Show the time on the west coast of the US (use tzselect(1) to find TZ)
Show the local time for 9AM next Friday on the west coast of the US
$ date --date='TZ="America/Los_Angeles" 09:00 next Fri'
```

View file

@ -6,19 +6,25 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (format) MMDDhhmm
// spell-checker:ignore (ToDO) DATEFILE
// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use chrono::offset::Utc;
use chrono::{DateTime, FixedOffset, Local, Offset};
use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
#[cfg(windows)]
use chrono::{Datelike, Timelike};
use clap::{crate_version, App, Arg};
#[cfg(all(unix, not(target_os = "macos")))]
use libc::{clock_settime, timespec, CLOCK_REALTIME};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
#[cfg(windows)]
use winapi::{
shared::minwindef::WORD,
um::{minwinbase::SYSTEMTIME, sysinfoapi::SetSystemTime},
};
// Options
const DATE: &str = "date";
@ -31,7 +37,6 @@ const SECOND: &str = "second";
const NS: &str = "ns";
const NAME: &str = "date";
const VERSION: &str = env!("CARGO_PKG_VERSION");
const ABOUT: &str = "print or set the system date and time";
const OPT_DATE: &str = "date";
@ -62,6 +67,11 @@ static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format.
for date and time to the indicated precision.
Example: 2006-08-14 02:34:56-06:00";
#[cfg(not(target_os = "macos"))]
static OPT_SET_HELP_STRING: &str = "set time described by STRING";
#[cfg(target_os = "macos")]
static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)";
/// Settings for this program, parsed from the command line
struct Settings {
utc: bool,
@ -132,10 +142,118 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
{0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]",
NAME
);
let matches = App::new(executable!())
.version(VERSION)
let matches = uu_app().usage(&syntax[..]).get_matches_from(args);
let format = if let Some(form) = matches.value_of(OPT_FORMAT) {
if !form.starts_with('+') {
eprintln!("date: invalid date '{}'", form);
return 1;
}
let form = form[1..].to_string();
Format::Custom(form)
} else if let Some(fmt) = matches
.values_of(OPT_ISO_8601)
.map(|mut iter| iter.next().unwrap_or(DATE).into())
{
Format::Iso8601(fmt)
} else if matches.is_present(OPT_RFC_EMAIL) {
Format::Rfc5322
} else if let Some(fmt) = matches.value_of(OPT_RFC_3339).map(Into::into) {
Format::Rfc3339(fmt)
} else {
Format::Default
};
let date_source = if let Some(date) = matches.value_of(OPT_DATE) {
DateSource::Custom(date.into())
} else if let Some(file) = matches.value_of(OPT_FILE) {
DateSource::File(file.into())
} else {
DateSource::Now
};
let set_to = match matches.value_of(OPT_SET).map(parse_date) {
None => None,
Some(Err((input, _err))) => {
eprintln!("date: invalid date '{}'", input);
return 1;
}
Some(Ok(date)) => Some(date),
};
let settings = Settings {
utc: matches.is_present(OPT_UNIVERSAL),
format,
date_source,
set_to,
};
if let Some(date) = settings.set_to {
// All set time functions expect UTC datetimes.
let date: DateTime<Utc> = if settings.utc {
date.with_timezone(&Utc)
} else {
date.into()
};
return set_system_datetime(date);
} else {
// Declare a file here because it needs to outlive the `dates` iterator.
let file: File;
// Get the current time, either in the local time zone or UTC.
let now: DateTime<FixedOffset> = if settings.utc {
let now = Utc::now();
now.with_timezone(&now.offset().fix())
} else {
let now = Local::now();
now.with_timezone(now.offset())
};
// Iterate over all dates - whether it's a single date or a file.
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
DateSource::Custom(ref input) => {
let date = parse_date(input.clone());
let iter = std::iter::once(date);
Box::new(iter)
}
DateSource::File(ref path) => {
file = File::open(path).unwrap();
let lines = BufReader::new(file).lines();
let iter = lines.filter_map(Result::ok).map(parse_date);
Box::new(iter)
}
DateSource::Now => {
let iter = std::iter::once(Ok(now));
Box::new(iter)
}
};
let format_string = make_format_string(&settings);
// Format all the dates
for date in dates {
match date {
Ok(date) => {
// GNU `date` uses `%N` for nano seconds, however crate::chrono uses `%f`
let format_string = &format_string.replace("%N", "%f");
let formatted = date.format(format_string).to_string().replace("%f", "%N");
println!("{}", formatted);
}
Err((input, _err)) => {
println!("date: invalid date '{}'", input);
}
}
}
}
0
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(&syntax[..])
.arg(
Arg::with_name(OPT_DATE)
.short("d")
@ -186,7 +304,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("s")
.long(OPT_SET)
.takes_value(true)
.help("set time described by STRING"),
.help(OPT_SET_HELP_STRING),
)
.arg(
Arg::with_name(OPT_UNIVERSAL)
@ -195,103 +313,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.alias(OPT_UNIVERSAL_2)
.help("print or set Coordinated Universal Time (UTC)"),
)
.arg(Arg::with_name(OPT_FORMAT).multiple(true))
.get_matches_from(args);
let format = if let Some(form) = matches.value_of(OPT_FORMAT) {
let form = form[1..].into();
Format::Custom(form)
} else if let Some(fmt) = matches
.values_of(OPT_ISO_8601)
.map(|mut iter| iter.next().unwrap_or(DATE).into())
{
Format::Iso8601(fmt)
} else if matches.is_present(OPT_RFC_EMAIL) {
Format::Rfc5322
} else if let Some(fmt) = matches.value_of(OPT_RFC_3339).map(Into::into) {
Format::Rfc3339(fmt)
} else {
Format::Default
};
let date_source = if let Some(date) = matches.value_of(OPT_DATE) {
DateSource::Custom(date.into())
} else if let Some(file) = matches.value_of(OPT_FILE) {
DateSource::File(file.into())
} else {
DateSource::Now
};
let settings = Settings {
utc: matches.is_present(OPT_UNIVERSAL),
format,
date_source,
// TODO: Handle this option:
set_to: None,
};
if let Some(_time) = settings.set_to {
unimplemented!();
// Probably need to use this syscall:
// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html
} else {
// Declare a file here because it needs to outlive the `dates` iterator.
let file: File;
// Get the current time, either in the local time zone or UTC.
let now: DateTime<FixedOffset> = if settings.utc {
let now = Utc::now();
now.with_timezone(&now.offset().fix())
} else {
let now = Local::now();
now.with_timezone(now.offset())
};
/// Parse a `String` into a `DateTime`.
/// If it fails, return a tuple of the `String` along with its `ParseError`.
fn parse_date(
s: String,
) -> Result<DateTime<FixedOffset>, (String, chrono::format::ParseError)> {
// TODO: The GNU date command can parse a wide variety of inputs.
s.parse().map_err(|e| (s, e))
}
// Iterate over all dates - whether it's a single date or a file.
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
DateSource::Custom(ref input) => {
let date = parse_date(input.clone());
let iter = std::iter::once(date);
Box::new(iter)
}
DateSource::File(ref path) => {
file = File::open(path).unwrap();
let lines = BufReader::new(file).lines();
let iter = lines.filter_map(Result::ok).map(parse_date);
Box::new(iter)
}
DateSource::Now => {
let iter = std::iter::once(Ok(now));
Box::new(iter)
}
};
let format_string = make_format_string(&settings);
// Format all the dates
for date in dates {
match date {
Ok(date) => {
let formatted = date.format(format_string);
println!("{}", formatted);
}
Err((input, _err)) => {
println!("date: invalid date '{}'", input);
}
}
}
}
0
.arg(Arg::with_name(OPT_FORMAT).multiple(false))
}
/// Return the appropriate format string for the given settings.
@ -314,3 +336,76 @@ fn make_format_string(settings: &Settings) -> &str {
Format::Default => "%c",
}
}
/// Parse a `String` into a `DateTime`.
/// If it fails, return a tuple of the `String` along with its `ParseError`.
fn parse_date<S: AsRef<str> + Clone>(
s: S,
) -> Result<DateTime<FixedOffset>, (String, chrono::format::ParseError)> {
// TODO: The GNU date command can parse a wide variety of inputs.
s.as_ref().parse().map_err(|e| (s.as_ref().into(), e))
}
#[cfg(not(any(unix, windows)))]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
unimplemented!("setting date not implemented (unsupported target)");
}
#[cfg(target_os = "macos")]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
eprintln!("date: setting the date is not supported by macOS");
1
}
#[cfg(all(unix, not(target_os = "macos")))]
/// System call to set date (unix).
/// See here for more:
/// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html
/// https://linux.die.net/man/3/clock_settime
/// https://www.gnu.org/software/libc/manual/html_node/Time-Types.html
fn set_system_datetime(date: DateTime<Utc>) -> i32 {
let timespec = timespec {
tv_sec: date.timestamp() as _,
tv_nsec: date.timestamp_subsec_nanos() as _,
};
let result = unsafe { clock_settime(CLOCK_REALTIME, &timespec) };
if result != 0 {
let error = std::io::Error::last_os_error();
eprintln!("date: cannot set date: {}", error);
error.raw_os_error().unwrap()
} else {
0
}
}
#[cfg(windows)]
/// System call to set date (Windows).
/// See here for more:
/// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-setsystemtime
/// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime
fn set_system_datetime(date: DateTime<Utc>) -> i32 {
let system_time = SYSTEMTIME {
wYear: date.year() as WORD,
wMonth: date.month() as WORD,
// Ignored
wDayOfWeek: 0,
wDay: date.day() as WORD,
wHour: date.hour() as WORD,
wMinute: date.minute() as WORD,
wSecond: date.second() as WORD,
// TODO: be careful of leap seconds - valid range is [0, 999] - how to handle?
wMilliseconds: ((date.nanosecond() / 1_000_000) % 1000) as WORD,
};
let result = unsafe { SetSystemTime(&system_time) };
if result == 0 {
let error = std::io::Error::last_os_error();
eprintln!("date: cannot set date: {}", error);
error.raw_os_error().unwrap()
} else {
0
}
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_date); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_date);

View file

@ -9,6 +9,7 @@
#[macro_use]
extern crate uucore;
use uucore::InvalidEncodingHandling;
#[cfg(test)]
mod dd_unit_tests;
@ -19,7 +20,6 @@ mod conversion_tables;
use conversion_tables::*;
use byte_unit::Byte;
// #[macro_use]
use debug_print::debug_println;
use gcd::Gcd;
use getopts;
@ -46,7 +46,7 @@ use std::thread;
use std::time;
const SYNTAX: &str = "dd [OPERAND]...\ndd OPTION";
const SUMMARY: &str = "convert, and optionally copy, a file";
const SUMMARY: &str = "copy, and optionally convert, a file system resource";
const LONG_HELP: &str = "";
const BUF_INIT_BYTE: u8 = 0xDD;
const RTN_SUCCESS: i32 = 0;
@ -518,6 +518,13 @@ impl Output<io::Stdout> {
let obs = parseargs::parse_obs(matches)?;
let cflags = parseargs::parse_conv_flag_output(matches)?;
let oflags = parseargs::parse_oflags(matches)?;
let seek = parseargs::parse_seek_amt(&obs, &oflags, matches)?;
if let Some(amt) = seek
{
let amt: u64 = amt.try_into()?;
dst.seek(io::SeekFrom::Start(amt))?;
}
Ok(Output {
dst: io::stdout(),
@ -695,11 +702,37 @@ impl Write for Output<File>
}
}
impl Seek for Output<io::Stdout>
{
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64>
{
self.dst.seek(pos)
}
}
impl Write for Output<io::Stdout>
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize>
{
self.dst.write(buf)
#[inline]
fn is_sparse(buf: &[u8]) -> bool
{
buf.iter()
.all(|&e| e == 0u8)
}
// -----------------------------
if self.cflags.sparse && is_sparse(buf)
{
let seek_amt: i64 = buf.len()
.try_into()
.expect("Internal dd Error: Seek amount greater than signed 64-bit integer");
self.dst.seek(io::SeekFrom::Current(seek_amt))?;
Ok(buf.len())
}
else
{
self.dst.write(buf)
}
}
fn flush(&mut self) -> io::Result<()>
@ -1375,124 +1408,6 @@ fn dd_fileout<R: Read>(mut i: Input<R>, mut o: Output<File>) -> Result<(), Box<d
Ok(())
}
#[macro_export]
macro_rules! build_app (
() =>
{
app!(SYNTAX, SUMMARY, LONG_HELP)
.optopt(
"",
"skip",
"Skip N ibs-byte blocks in the input file before copying. If iflag=skip_bytes is specified, N is interpreted as a byte count rather than a block count.",
"N"
)
.optopt(
"",
"seek",
"Skip N obs-byte blocks in the input file before copying. If oflag=skip_bytes is specified, N is interpreted as a byte count rather than a block count.",
"N"
)
.optopt(
"",
"count",
"Copy N ibs-byte blocks from the input file, instead of everything until the end of the file. if iflag=count_bytes is specified, N is interpreted as a byte count rather than a block count. Note if the input may return short reads as could be the case when reading
from a pipe for example, iflag=fullblock will ensure that count= corresponds to complete input blocks rather than the traditional POSIX specified behavior of counting input read operations.",
"BYTES"
)
.optopt(
"",
"bs",
"Set both input and output block sizes to BYTES. This makes dd read and write BYTES per block, overriding any ibs and obs settings. In addition, if no data-transforming conv option is specified, input is copied to the output as soon as its read, even
if it is smaller than the block size.",
"BYTES"
)
.optopt(
"",
"iflag",
"read as per the comma separated symbol list of flags",
"FLAG"
)
.optopt(
"",
"oflag",
"write as per the comma separated symbol list of flags",
"FLAG"
)
.optopt(
"",
"if",
"Read from FILE instead of standard input.",
"FILE"
)
.optopt(
"",
"ibs",
"Set the input block size to BYTES. This makes dd read BYTES per block. The default is 512 bytes.",
"BYTES"
)
.optopt(
"",
"of",
"Write to FILE instead of standard output. Unless conv=notrunc is given, dd truncates FILE to zero bytes (or the size specified with seek=).",
"FILE"
)
.optopt(
"",
"obs",
"Set the output block size to BYTES. This makes dd write BYTES per block. The default is 512 bytes.",
"BYTES"
)
.optopt(
"",
"conv",
"Convert the file as specified by the CONVERSION argument(s). (No spaces around any comma(s).)",
"OPT[,OPT]..."
)
.optopt(
"",
"cbs",
"Set the conversion block size to BYTES. When converting variable-length records to fixed-length ones (conv=block) or the reverse (conv=unblock), use BYTES as the fixed record length.",
"BYTES"
)
.optopt(
"",
"status",
"Specify the amount of information printed. If this operand is
given multiple times, the last one takes precedence. The LEVEL
value can be one of the following:
none
Do not print any informational or warning messages to stderr.
Error messages are output as normal.
noxfer
Do not print the final transfer rate and volume statistics
that normally make up the last status line.
progress
Print the transfer rate and volume statistics on stderr, when
processing each input block. Statistics are output on a
single line at most once every second, but updates can be
delayed when waiting on I/O.
Transfer information is normally output to stderr upon receipt of
the INFO signal or when dd exits, and defaults to the following
form in the C locale:
7287+1 records in
116608+0 records out
59703296 bytes (60 MB, 57 MiB) copied, 0.0427974 s, 1.4 GB/s
The notation W+P stands for W whole blocks and P partial blocks.
A partial block occurs when a read or write operation succeeds but
transfers less data than the block size. An additional line like
1 truncated record or 10 truncated records is output after the
records out line if conv=block processing truncated one or more
input records.",
"LEVEL"
)
}
);
fn append_dashes_if_not_present(mut acc: Vec<String>, s: &String) -> Vec<String>
{
@ -1527,7 +1442,8 @@ macro_rules! unpack_or_rtn (
pub fn uumain(args: impl uucore::Args) -> i32
{
let dashed_args = args.collect_str()
let dashed_args = args.collect_str(InvalidEncodingHandling::Ignore)
.accept_any()
.iter()
.fold(Vec::new(), append_dashes_if_not_present);
let matches = build_app!().parse(dashed_args);
@ -1573,3 +1489,175 @@ pub fn uumain(args: impl uucore::Args) -> i32
}
}
pub fn uu_app() -> App<'static, 'static>
{
build_app!()
}
#[macro_export]
macro_rules! build_app (
() =>
{
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
Arg::with_name(options::INFILE)
.long(options::INFILE)
.takes_value(true)
.help("if=FILE (alternatively --if FILE) specifies the file used for input. When not specified, stdin is used instead")
)
.arg(
Arg::with_name(options::OUTFILE)
.long(options::OUTFILE)
.takes_value(true)
.help("of=FILE (alternatively --of FILE) specifies the file used for output. When not specified, stdout is used instead")
)
.arg(
Arg::with_name(options::IBS)
.long(options::IBS)
.takes_value(true)
.help("ibs=N (alternatively --ibs N) specifies the size of buffer used for reads (default: 512). Multiplier strings permitted.")
)
.arg(
Arg::with_name(options::OBS)
.long(options::OBS)
.takes_value(true)
.help("obs=N (alternatively --obs N) specifies the size of buffer used for writes (default: 512). Multiplier strings permitted.")
)
.arg(
Arg::with_name(options::BS)
.long(options::BS)
.takes_value(true)
.help("bs=N (alternatively --bs N) specifies ibs=N and obs=N (default: 512). If ibs or obs are also specified, bs=N takes presedence. Multiplier strings permitted.")
)
.arg(
Arg::with_name(options::CBS)
.long(options::CBS)
.takes_value(true)
.help("cbs=BYTES (alternatively --cbs BYTES) specifies the 'conversion block size' in bytes. Applies to the conv=block, and conv=unblock operations. Multiplier strings permitted.")
)
.arg(
Arg::with_name(options::SKIP)
.long(options::SKIP)
.takes_value(true)
.help("skip=N (alternatively --skip N) causes N ibs-sized records of input to be skipped before beginning copy/convert operations. See iflag=count_bytes if skipping N bytes is prefered. Multiplier strings permitted.")
)
.arg(
Arg::with_name(options::SEEK)
.long(options::SEEK)
.takes_value(true)
.help("seek=N (alternatively --seek N) seeks N obs-sized records into output before beginning copy/convert operations. See oflag=seek_bytes if seeking N bytes is prefered. Multiplier strings permitted.")
)
.arg(
Arg::with_name(options::COUNT)
.long(options::COUNT)
.takes_value(true)
.help("count=N (alternatively --count N) stop reading input after N ibs-sized read operations rather than proceeding until EOF. See iflag=count_bytes if stopping after N bytes is prefered. Multiplier strings permitted.")
)
.arg(
Arg::with_name(options::STATUS)
.long(options::STATUS)
.takes_value(true)
.help("status=LEVEL (alternatively --status LEVEL) controls whether volume and performace stats are written to stderr.
When unspecified, dd will print stats upon completion. An example is below.
\t6+0 records in
\t16+0 records out
\t8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00057009 s, 14.4 MB/s
The first two lines are the 'volume' stats and the final line is the 'performance' stats.
The volume stats indicate the number of complete and partial ibs-sized reads, or obs-sized writes that took place during the copy. The format of the volume stats is <complete>+<partial>. If records have been truncated (see conv=block), the volume stats will contain the number of truncated records.
Permissable LEVEL values are:
\t- progress: Print periodic performance stats as the copy proceedes.
\t- noxfer: Print final volume stats, but not performance stats.
\t- none: Do not print any stats.
Printing performance stats is also triggered by the INFO signal (where supported), or the USR1 signal. Setting the POSIXLY_CORRECT evnironment variable to any value (including an empty value) will cause the USR1 signal to be ignored.
")
)
.arg(
Arg::with_name(options::CONV)
.long(options::CONV)
.takes_value(true)
.help("conv=CONV[,CONV] (alternatively --conv CONV[,CONV]) specifies a comma-separated list of conversion options or (for legacy reasons) file-flags. Conversion options and file flags may be intermixed.
Conversion options:
\t- One of {ascii, ebcdic, ibm} will perform an encoding conversion.
\t\t- 'ascii' converts from EBCDIC to ASCII. This is the inverse of the 'ebcdic' option.
\t\t- 'ebcdic' converts from ASCII to EBCDIC. This is the inverse of the 'ascii' option.
\t\t- 'ibm' converts from ASCII to EBCDIC, appling the conventions for '[', ']' and '~' specified in POSIX.
\t- One of {ucase, lcase} will perform a case conversion. Works in conjuction with option {ascii, ebcdic, ibm} to infer input encoding. If no other conversion option is specified, input is assumed to be ascii.
\t\t- 'ucase' converts from lower-case to upper-case
\t\t- 'lcase' converts from upper-case to lower-case.
\t- One of {block, unblock}. Convert between lines terminated by newline characters, and fixed-width lines padded by spaces (without any newlines). Both the 'block' and 'unblock' options require cbs=BYTES be specified.
\t\t- 'block' for each newline less than the size indicated by cbs=BYTES, remove the newline and pad with spaces up to cbs. Lines longer than cbs are truncated.
\t\t- 'unblock' for each block of input of the size indicated by cbs=BYTES, remove right-trailing spaces and replace with a newline character.
\t 'sparse' attempts to seek the output when an obs-sized block consists of only zeros.
\t 'swab' swaps each adjacent pair of bytes. If an odd number of bytes is present, the final byte is omitted.
\t 'sync' pad each ibs-sided block with zeros. If 'block' or 'unblock' is specified, pad with spaces instead.
Flags:
\t- One of {excl, nocreat}
\t\t- 'excl' the output file must be created. Fail if the output file is already present.
\t\t- 'nocreat' the output file will not be created. Fail if the output file in not already present.
\t- 'notrunc' the output file will not be truncated. If this option is not present, output will be truncated when opened.
\t- 'noerror' all read errors will be ignored. If this option is not present, dd will only ignore Error::Interrupted.
\t- 'fdatasync' data will be written before finishing.
\t- 'fsync' data and metadata will be written before finishing.
")
)
.arg(
Arg::with_name(options::IFLAG)
.long(options::IFLAG)
.takes_value(true)
.help("iflag=FLAG[,FLAG] (alternatively --iflag FLAG[,FLAG]) a comma separated list of input flags which specify how the input source is treated. FLAG may be any of the input-flags or general-flags specified below.
Input-Flags
\t- 'count_bytes' a value to count=N will be interpreted as bytes.
\t- 'skip_bytes' a value to skip=N will be interpreted as bytes.
\t- 'fullblock' wait for ibs bytes from each read. zero-length reads are still considered EOF.
General-Flags
\t- 'direct'
\t- 'directory'
\t- 'dsync'
\t- 'sync'
\t- 'nonblock'
\t- 'noatime'
\t- 'nocache'
\t- 'noctty'
\t- 'nofollow'
Output-Flags
\t- 'append' open file in append mode. Consider setting conv=notrunc as well.
\t- 'seek_bytes' a value to seek=N will be interpreted as bytes.
")
)
.arg(
Arg::with_name(options::OFLAG)
.long(options::OFLAG)
.takes_value(true)
.help("oflag=FLAG[,FLAG] (alternatively --oflag FLAG[,FLAG]) a comma separated list of output flags which specify how the output source is treated. FLAG may be any of the output-flags or general-flags specified below.
Output-Flags
\t- 'append' open file in append mode. Consider setting conv=notrunc as well.
\t- 'seek_bytes' a value to seek=N will be interpreted as bytes.
General-Flags
\t- 'direct'
\t- 'directory'
\t- 'dsync'
\t- 'sync'
\t- 'nonblock'
\t- 'noatime'
\t- 'nocache'
\t- 'noctty'
\t- 'nofollow'
")
)
};
);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_df"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "df ~ (uutils) display file system information"
@ -16,14 +16,10 @@ path = "src/df.rs"
[dependencies]
clap = "2.33"
libc = "0.2"
number_prefix = "0.4"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc", "fsext"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }
[[bin]]
name = "df"
path = "src/main.rs"

View file

@ -6,21 +6,13 @@
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore (ToDO) mountinfo mtab BLOCKSIZE getmntinfo fobj mptr noatime Iused overmounted
// spell-checker:ignore (libc/fs) asyncreads asyncwrites autofs bavail bfree bsize charspare cifs debugfs devfs devpts ffree frsize fsid fstypename fusectl inode inodes iosize kernfs mntbufp mntfromname mntonname mqueue namemax pipefs smbfs statfs statvfs subfs syncreads syncwrites sysfs wcslen
#[macro_use]
extern crate uucore;
#[cfg(unix)]
use uucore::fsext::statfs_fn;
use uucore::fsext::{read_fs_list, FsUsage, MountInfo};
use clap::{App, Arg};
#[cfg(windows)]
use winapi::um::errhandlingapi::GetLastError;
#[cfg(windows)]
use winapi::um::fileapi::{
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDriveTypeW, GetVolumeInformationW,
GetVolumePathNamesForVolumeNameW, QueryDosDeviceW,
};
use clap::{crate_version, App, Arg};
use number_prefix::NumberPrefix;
use std::cell::Cell;
@ -32,57 +24,18 @@ use std::ffi::CString;
#[cfg(unix)]
use std::mem;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
use libc::c_int;
#[cfg(target_os = "macos")]
use libc::statfs;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
use std::ffi::CStr;
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "windows"))]
use std::ptr;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
use std::slice;
#[cfg(target_os = "freebsd")]
use libc::{c_char, fsid_t, uid_t};
use uucore::libc::{c_char, fsid_t, uid_t};
#[cfg(target_os = "linux")]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::io::{BufRead, BufReader};
#[cfg(windows)]
use std::ffi::OsString;
#[cfg(windows)]
use std::os::windows::ffi::OsStrExt;
#[cfg(windows)]
use std::os::windows::ffi::OsStringExt;
#[cfg(windows)]
use std::path::Path;
#[cfg(windows)]
use winapi::shared::minwindef::DWORD;
#[cfg(windows)]
use winapi::um::fileapi::GetDiskFreeSpaceW;
#[cfg(windows)]
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
#[cfg(windows)]
use winapi::um::winbase::DRIVE_REMOTE;
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
or all file systems by default.";
static EXIT_OK: i32 = 0;
static EXIT_ERR: i32 = 1;
#[cfg(windows)]
const MAX_PATH: usize = 266;
#[cfg(target_os = "linux")]
static LINUX_MOUNTINFO: &str = "/proc/self/mountinfo";
#[cfg(target_os = "linux")]
static LINUX_MTAB: &str = "/etc/mtab";
static OPT_ALL: &str = "all";
static OPT_BLOCKSIZE: &str = "blocksize";
static OPT_DIRECT: &str = "direct";
@ -101,8 +54,6 @@ static OPT_TYPE: &str = "type";
static OPT_PRINT_TYPE: &str = "print-type";
static OPT_EXCLUDE_TYPE: &str = "exclude-type";
static MOUNT_OPT_BIND: &str = "bind";
/// Store names of file systems as a selector.
/// Note: `exclude` takes priority over `include`.
struct FsSelector {
@ -116,142 +67,21 @@ struct Options {
show_listed_fs: bool,
show_fs_type: bool,
show_inode_instead: bool,
print_grand_total: bool,
// block_size: usize,
human_readable_base: i64,
fs_selector: FsSelector,
}
#[derive(Debug, Clone)]
struct MountInfo {
// it stores `volume_name` in windows platform and `dev_id` in unix platform
dev_id: String,
dev_name: String,
fs_type: String,
mount_dir: String,
mount_option: String, // we only care "bind" option
mount_root: String,
remote: bool,
dummy: bool,
}
#[cfg(all(
target_os = "freebsd",
not(all(target_os = "macos", target_arch = "x86_64"))
))]
#[repr(C)]
#[derive(Copy, Clone)]
#[allow(non_camel_case_types)]
struct statfs {
f_version: u32,
f_type: u32,
f_flags: u64,
f_bsize: u64,
f_iosize: u64,
f_blocks: u64,
f_bfree: u64,
f_bavail: i64,
f_files: u64,
f_ffree: i64,
f_syncwrites: u64,
f_asyncwrites: u64,
f_syncreads: u64,
f_asyncreads: u64,
f_spare: [u64; 10usize],
f_namemax: u32,
f_owner: uid_t,
f_fsid: fsid_t,
f_charspare: [c_char; 80usize],
f_fstypename: [c_char; 16usize],
f_mntfromname: [c_char; 88usize],
f_mntonname: [c_char; 88usize],
}
#[derive(Debug, Clone)]
struct FsUsage {
blocksize: u64,
blocks: u64,
bfree: u64,
bavail: u64,
bavail_top_bit_set: bool,
files: u64,
ffree: u64,
}
#[derive(Debug, Clone)]
struct Filesystem {
mountinfo: MountInfo,
mount_info: MountInfo,
usage: FsUsage,
}
#[cfg(windows)]
macro_rules! String2LPWSTR {
($str: expr) => {
OsString::from($str.clone())
.as_os_str()
.encode_wide()
.chain(Some(0))
.collect::<Vec<u16>>()
.as_ptr()
};
}
#[cfg(windows)]
#[allow(non_snake_case)]
fn LPWSTR2String(buf: &[u16]) -> String {
let len = unsafe { libc::wcslen(buf.as_ptr()) };
OsString::from_wide(&buf[..len as usize])
.into_string()
.unwrap()
}
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!())
}
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
extern "C" {
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
#[link_name = "getmntinfo$INODE64"]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
#[cfg(any(
all(target_os = "freebsd"),
all(target_os = "macos", target_arch = "aarch64")
))]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
}
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
impl From<statfs> for MountInfo {
fn from(statfs: statfs) -> Self {
let mut info = MountInfo {
dev_id: "".to_string(),
dev_name: unsafe {
CStr::from_ptr(&statfs.f_mntfromname[0])
.to_string_lossy()
.into_owned()
},
fs_type: unsafe {
CStr::from_ptr(&statfs.f_fstypename[0])
.to_string_lossy()
.into_owned()
},
mount_dir: unsafe {
CStr::from_ptr(&statfs.f_mntonname[0])
.to_string_lossy()
.into_owned()
},
mount_root: "".to_string(),
mount_option: "".to_string(),
remote: false,
dummy: false,
};
info.set_missing_fields();
info
}
}
impl FsSelector {
fn new() -> FsSelector {
FsSelector {
@ -286,7 +116,6 @@ impl Options {
show_listed_fs: false,
show_fs_type: false,
show_inode_instead: false,
print_grand_total: false,
// block_size: match env::var("BLOCKSIZE") {
// Ok(size) => size.parse().unwrap(),
// Err(_) => 512,
@ -297,350 +126,43 @@ impl Options {
}
}
impl MountInfo {
fn set_missing_fields(&mut self) {
#[cfg(unix)]
{
// We want to keep the dev_id on Windows
// but set dev_id
let path = CString::new(self.mount_dir.clone()).unwrap();
unsafe {
let mut stat = mem::zeroed();
if libc::stat(path.as_ptr(), &mut stat) == 0 {
self.dev_id = (stat.st_dev as i32).to_string();
} else {
self.dev_id = "".to_string();
}
}
}
// set MountInfo::dummy
match self.fs_type.as_ref() {
"autofs" | "proc" | "subfs"
/* for Linux 2.6/3.x */
| "debugfs" | "devpts" | "fusectl" | "mqueue" | "rpc_pipefs" | "sysfs"
/* FreeBSD, Linux 2.4 */
| "devfs"
/* for NetBSD 3.0 */
| "kernfs"
/* for Irix 6.5 */
| "ignore" => self.dummy = true,
_ => self.dummy = self.fs_type == "none"
&& self.mount_option.find(MOUNT_OPT_BIND).is_none(),
}
// set MountInfo::remote
#[cfg(windows)]
{
self.remote = DRIVE_REMOTE == unsafe { GetDriveTypeW(String2LPWSTR!(self.mount_root)) };
}
#[cfg(unix)]
{
if self.dev_name.find(':').is_some()
|| (self.dev_name.starts_with("//") && self.fs_type == "smbfs"
|| self.fs_type == "cifs")
|| self.dev_name == "-hosts"
{
self.remote = true;
} else {
self.remote = false;
}
}
}
#[cfg(target_os = "linux")]
fn new(file_name: &str, raw: Vec<&str>) -> Option<MountInfo> {
match file_name {
// Format: 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// "man proc" for more details
"/proc/self/mountinfo" => {
let mut m = MountInfo {
dev_id: "".to_string(),
dev_name: raw[9].to_string(),
fs_type: raw[8].to_string(),
mount_root: raw[3].to_string(),
mount_dir: raw[4].to_string(),
mount_option: raw[5].to_string(),
remote: false,
dummy: false,
};
m.set_missing_fields();
Some(m)
}
"/etc/mtab" => {
let mut m = MountInfo {
dev_id: "".to_string(),
dev_name: raw[0].to_string(),
fs_type: raw[2].to_string(),
mount_root: "".to_string(),
mount_dir: raw[1].to_string(),
mount_option: raw[3].to_string(),
remote: false,
dummy: false,
};
m.set_missing_fields();
Some(m)
}
_ => None,
}
}
#[cfg(windows)]
fn new(mut volume_name: String) -> Option<MountInfo> {
let mut dev_name_buf = [0u16; MAX_PATH];
volume_name.pop();
unsafe {
QueryDosDeviceW(
OsString::from(volume_name.clone())
.as_os_str()
.encode_wide()
.chain(Some(0))
.skip(4)
.collect::<Vec<u16>>()
.as_ptr(),
dev_name_buf.as_mut_ptr(),
dev_name_buf.len() as DWORD,
)
};
volume_name.push('\\');
let dev_name = LPWSTR2String(&dev_name_buf);
let mut mount_root_buf = [0u16; MAX_PATH];
let success = unsafe {
GetVolumePathNamesForVolumeNameW(
String2LPWSTR!(volume_name),
mount_root_buf.as_mut_ptr(),
mount_root_buf.len() as DWORD,
ptr::null_mut(),
)
};
if 0 == success {
// TODO: support the case when `GetLastError()` returns `ERROR_MORE_DATA`
return None;
}
let mount_root = LPWSTR2String(&mount_root_buf);
let mut fs_type_buf = [0u16; MAX_PATH];
let success = unsafe {
GetVolumeInformationW(
String2LPWSTR!(mount_root),
ptr::null_mut(),
0 as DWORD,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
fs_type_buf.as_mut_ptr(),
fs_type_buf.len() as DWORD,
)
};
let fs_type = if 0 != success {
Some(LPWSTR2String(&fs_type_buf))
} else {
None
};
let mut mn_info = MountInfo {
dev_id: volume_name,
dev_name,
fs_type: fs_type.unwrap_or_else(|| "".to_string()),
mount_root,
mount_dir: "".to_string(),
mount_option: "".to_string(),
remote: false,
dummy: false,
};
mn_info.set_missing_fields();
Some(mn_info)
}
}
impl FsUsage {
#[cfg(unix)]
fn new(statvfs: libc::statvfs) -> FsUsage {
{
FsUsage {
blocksize: if statvfs.f_frsize != 0 {
statvfs.f_frsize as u64
} else {
statvfs.f_bsize as u64
},
blocks: statvfs.f_blocks as u64,
bfree: statvfs.f_bfree as u64,
bavail: statvfs.f_bavail as u64,
bavail_top_bit_set: ((statvfs.f_bavail as u64) & (1u64.rotate_right(1))) != 0,
files: statvfs.f_files as u64,
ffree: statvfs.f_ffree as u64,
}
}
}
#[cfg(not(unix))]
fn new(path: &Path) -> FsUsage {
let mut root_path = [0u16; MAX_PATH];
let success = unsafe {
GetVolumePathNamesForVolumeNameW(
//path_utf8.as_ptr(),
String2LPWSTR!(path.as_os_str()),
root_path.as_mut_ptr(),
root_path.len() as DWORD,
ptr::null_mut(),
)
};
if 0 == success {
crash!(
EXIT_ERR,
"GetVolumePathNamesForVolumeNameW failed: {}",
unsafe { GetLastError() }
);
}
let mut sectors_per_cluster = 0;
let mut bytes_per_sector = 0;
let mut number_of_free_clusters = 0;
let mut total_number_of_clusters = 0;
let success = unsafe {
GetDiskFreeSpaceW(
String2LPWSTR!(path.as_os_str()),
&mut sectors_per_cluster,
&mut bytes_per_sector,
&mut number_of_free_clusters,
&mut total_number_of_clusters,
)
};
if 0 == success {
// Fails in case of CD for example
//crash!(EXIT_ERR, "GetDiskFreeSpaceW failed: {}", unsafe {
//GetLastError()
//});
}
let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
FsUsage {
// f_bsize File system block size.
blocksize: bytes_per_cluster as u64,
// f_blocks - Total number of blocks on the file system, in units of f_frsize.
// frsize = Fundamental file system block size (fragment size).
blocks: total_number_of_clusters as u64,
// Total number of free blocks.
bfree: number_of_free_clusters as u64,
// Total number of free blocks available to non-privileged processes.
bavail: 0 as u64,
bavail_top_bit_set: ((bytes_per_sector as u64) & (1u64.rotate_right(1))) != 0,
// Total number of file nodes (inodes) on the file system.
files: 0 as u64, // Not available on windows
// Total number of free file nodes (inodes).
ffree: 4096 as u64, // Meaningless on Windows
}
}
}
impl Filesystem {
// TODO: resolve uuid in `mountinfo.dev_name` if exists
fn new(mountinfo: MountInfo) -> Option<Filesystem> {
let _stat_path = if !mountinfo.mount_dir.is_empty() {
mountinfo.mount_dir.clone()
// TODO: resolve uuid in `mount_info.dev_name` if exists
fn new(mount_info: MountInfo) -> Option<Filesystem> {
let _stat_path = if !mount_info.mount_dir.is_empty() {
mount_info.mount_dir.clone()
} else {
#[cfg(unix)]
{
mountinfo.dev_name.clone()
mount_info.dev_name.clone()
}
#[cfg(windows)]
{
// On windows, we expect the volume id
mountinfo.dev_id.clone()
mount_info.dev_id.clone()
}
};
#[cfg(unix)]
unsafe {
let path = CString::new(_stat_path).unwrap();
let mut statvfs = mem::zeroed();
if libc::statvfs(path.as_ptr(), &mut statvfs) < 0 {
if statfs_fn(path.as_ptr(), &mut statvfs) < 0 {
None
} else {
Some(Filesystem {
mountinfo,
mount_info,
usage: FsUsage::new(statvfs),
})
}
}
#[cfg(windows)]
Some(Filesystem {
mountinfo,
mount_info,
usage: FsUsage::new(Path::new(&_stat_path)),
})
}
}
/// Read file system list.
fn read_fs_list() -> Vec<MountInfo> {
#[cfg(target_os = "linux")]
{
let (file_name, fobj) = File::open(LINUX_MOUNTINFO)
.map(|f| (LINUX_MOUNTINFO, f))
.or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f)))
.expect("failed to find mount list files");
let reader = BufReader::new(fobj);
reader
.lines()
.filter_map(|line| line.ok())
.filter_map(|line| {
let raw_data = line.split_whitespace().collect::<Vec<&str>>();
MountInfo::new(file_name, raw_data)
})
.collect::<Vec<_>>()
}
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
{
let mut mptr: *mut statfs = ptr::null_mut();
let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) };
if len < 0 {
crash!(EXIT_ERR, "getmntinfo failed");
}
let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) };
mounts
.iter()
.map(|m| MountInfo::from(*m))
.collect::<Vec<_>>()
}
#[cfg(windows)]
{
let mut volume_name_buf = [0u16; MAX_PATH];
// As recommended in the MS documentation, retrieve the first volume before the others
let find_handle = unsafe {
FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as DWORD)
};
if INVALID_HANDLE_VALUE == find_handle {
crash!(EXIT_ERR, "FindFirstVolumeW failed: {}", unsafe {
GetLastError()
});
}
let mut mounts = Vec::<MountInfo>::new();
loop {
let volume_name = LPWSTR2String(&volume_name_buf);
if !volume_name.starts_with("\\\\?\\") || !volume_name.ends_with('\\') {
show_warning!("A bad path was skipped: {}", volume_name);
continue;
}
if let Some(m) = MountInfo::new(volume_name) {
mounts.push(m);
}
if 0 == unsafe {
FindNextVolumeW(
find_handle,
volume_name_buf.as_mut_ptr(),
volume_name_buf.len() as DWORD,
)
} {
let err = unsafe { GetLastError() };
if err != winapi::shared::winerror::ERROR_NO_MORE_FILES {
crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err);
}
break;
}
}
unsafe {
FindVolumeClose(find_handle);
}
mounts
}
}
fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> {
vmi.into_iter()
.filter_map(|mi| {
@ -679,7 +201,7 @@ fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Ve
if (!mi.dev_name.starts_with('/') || seen.dev_name.starts_with('/'))
// let points towards the root of the device win.
&& (!target_nearer_root || source_below_root)
// let an entry overmounted on a new device win...
// let an entry over-mounted on a new device win...
&& (seen.dev_name == mi.dev_name
/* ... but only when matching an existing mnt point,
to avoid problematic replacement when given
@ -736,10 +258,151 @@ fn use_size(free_size: u64, total_size: u64) -> String {
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let matches = App::new(executable!())
.version(VERSION)
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
let paths: Vec<String> = matches
.values_of(OPT_PATHS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
#[cfg(windows)]
{
if matches.is_present(OPT_INODES) {
println!("{}: doesn't support -i option", executable!());
return EXIT_OK;
}
}
let mut opt = Options::new();
if matches.is_present(OPT_LOCAL) {
opt.show_local_fs = true;
}
if matches.is_present(OPT_ALL) {
opt.show_all_fs = true;
}
if matches.is_present(OPT_INODES) {
opt.show_inode_instead = true;
}
if matches.is_present(OPT_PRINT_TYPE) {
opt.show_fs_type = true;
}
if matches.is_present(OPT_HUMAN_READABLE) {
opt.human_readable_base = 1024;
}
if matches.is_present(OPT_HUMAN_READABLE_2) {
opt.human_readable_base = 1000;
}
for fs_type in matches.values_of_lossy(OPT_TYPE).unwrap_or_default() {
opt.fs_selector.include(fs_type.to_owned());
}
for fs_type in matches
.values_of_lossy(OPT_EXCLUDE_TYPE)
.unwrap_or_default()
{
opt.fs_selector.exclude(fs_type.to_owned());
}
let fs_list = filter_mount_list(read_fs_list(), &paths, &opt)
.into_iter()
.filter_map(Filesystem::new)
.filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs)
.collect::<Vec<_>>();
// set headers
let mut header = vec!["Filesystem"];
if opt.show_fs_type {
header.push("Type");
}
header.extend_from_slice(&if opt.show_inode_instead {
// spell-checker:disable-next-line
["Inodes", "Iused", "IFree", "IUses%"]
} else {
[
if opt.human_readable_base == -1 {
"1k-blocks"
} else {
"Size"
},
"Used",
"Available",
"Use%",
]
});
if cfg!(target_os = "macos") && !opt.show_inode_instead {
header.insert(header.len() - 1, "Capacity");
}
header.push("Mounted on");
for (idx, title) in header.iter().enumerate() {
if idx == 0 || idx == header.len() - 1 {
print!("{0: <16} ", title);
} else if opt.show_fs_type && idx == 1 {
print!("{0: <5} ", title);
} else if idx == header.len() - 2 {
print!("{0: >5} ", title);
} else {
print!("{0: >12} ", title);
}
}
println!();
for fs in fs_list.iter() {
print!("{0: <16} ", fs.mount_info.dev_name);
if opt.show_fs_type {
print!("{0: <5} ", fs.mount_info.fs_type);
}
if opt.show_inode_instead {
print!(
"{0: >12} ",
human_readable(fs.usage.files, opt.human_readable_base)
);
print!(
"{0: >12} ",
human_readable(fs.usage.files - fs.usage.ffree, opt.human_readable_base)
);
print!(
"{0: >12} ",
human_readable(fs.usage.ffree, opt.human_readable_base)
);
print!(
"{0: >5} ",
format!(
"{0:.1}%",
100f64 - 100f64 * (fs.usage.ffree as f64 / fs.usage.files as f64)
)
);
} else {
let total_size = fs.usage.blocksize * fs.usage.blocks;
let free_size = fs.usage.blocksize * fs.usage.bfree;
print!(
"{0: >12} ",
human_readable(total_size, opt.human_readable_base)
);
print!(
"{0: >12} ",
human_readable(total_size - free_size, opt.human_readable_base)
);
print!(
"{0: >12} ",
human_readable(free_size, opt.human_readable_base)
);
if cfg!(target_os = "macos") {
let used = fs.usage.blocks - fs.usage.bfree;
let blocks = used + fs.usage.bavail;
print!("{0: >12} ", use_size(used, blocks));
}
print!("{0: >5} ", use_size(free_size, total_size));
}
print!("{0: <16}", fs.mount_info.mount_dir);
println!();
}
EXIT_OK
}
pub fn uu_app() -> App<'static, 'static> {
App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(OPT_ALL)
.short("a")
@ -849,137 +512,4 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
)
.arg(Arg::with_name(OPT_PATHS).multiple(true))
.help("Filesystem(s) to list")
.get_matches_from(args);
let paths: Vec<String> = matches
.values_of(OPT_PATHS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
#[cfg(windows)]
{
if matches.is_present(OPT_INODES) {
println!("{}: doesn't support -i option", executable!());
return EXIT_OK;
}
}
let mut opt = Options::new();
if matches.is_present(OPT_LOCAL) {
opt.show_local_fs = true;
}
if matches.is_present(OPT_ALL) {
opt.show_all_fs = true;
}
if matches.is_present(OPT_TOTAL) {
opt.print_grand_total = true;
}
if matches.is_present(OPT_INODES) {
opt.show_inode_instead = true;
}
if matches.is_present(OPT_PRINT_TYPE) {
opt.show_fs_type = true;
}
if matches.is_present(OPT_HUMAN_READABLE) {
opt.human_readable_base = 1024;
}
if matches.is_present(OPT_HUMAN_READABLE_2) {
opt.human_readable_base = 1000;
}
for fs_type in matches.values_of_lossy(OPT_TYPE).unwrap_or_default() {
opt.fs_selector.include(fs_type.to_owned());
}
for fs_type in matches
.values_of_lossy(OPT_EXCLUDE_TYPE)
.unwrap_or_default()
{
opt.fs_selector.exclude(fs_type.to_owned());
}
let fs_list = filter_mount_list(read_fs_list(), &paths, &opt)
.into_iter()
.filter_map(Filesystem::new)
.filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs)
.collect::<Vec<_>>();
// set headers
let mut header = vec!["Filesystem"];
if opt.show_fs_type {
header.push("Type");
}
header.extend_from_slice(&if opt.show_inode_instead {
["Inodes", "Iused", "IFree", "IUses%"]
} else {
[
if opt.human_readable_base == -1 {
"1k-blocks"
} else {
"Size"
},
"Used",
"Available",
"Use%",
]
});
header.push("Mounted on");
for (idx, title) in header.iter().enumerate() {
if idx == 0 || idx == header.len() - 1 {
print!("{0: <16} ", title);
} else if opt.show_fs_type && idx == 1 {
print!("{0: <5} ", title);
} else if idx == header.len() - 2 {
print!("{0: >5} ", title);
} else {
print!("{0: >12} ", title);
}
}
println!();
for fs in fs_list.iter() {
print!("{0: <16} ", fs.mountinfo.dev_name);
if opt.show_fs_type {
print!("{0: <5} ", fs.mountinfo.fs_type);
}
if opt.show_inode_instead {
print!(
"{0: >12} ",
human_readable(fs.usage.files, opt.human_readable_base)
);
print!(
"{0: >12} ",
human_readable(fs.usage.files - fs.usage.ffree, opt.human_readable_base)
);
print!(
"{0: >12} ",
human_readable(fs.usage.ffree, opt.human_readable_base)
);
print!(
"{0: >5} ",
format!(
"{0:.1}%",
100f64 - 100f64 * (fs.usage.ffree as f64 / fs.usage.files as f64)
)
);
} else {
let total_size = fs.usage.blocksize * fs.usage.blocks;
let free_size = fs.usage.blocksize * fs.usage.bfree;
print!(
"{0: >12} ",
human_readable(total_size, opt.human_readable_base)
);
print!(
"{0: >12} ",
human_readable(total_size - free_size, opt.human_readable_base)
);
print!(
"{0: >12} ",
human_readable(free_size, opt.human_readable_base)
);
print!("{0: >5} ", use_size(free_size, total_size));
}
print!("{0: <16}", fs.mountinfo.mount_dir);
println!();
}
EXIT_OK
}

View file

@ -1 +1 @@
uucore_procs::main!(uu_df); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_df);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_dircolors"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "dircolors ~ (uutils) display commands to set LS_COLORS"
@ -15,8 +15,9 @@ edition = "2018"
path = "src/dircolors.rs"
[dependencies]
clap = "2.33"
glob = "0.3.0"
uucore = { version=">=0.0.7", 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" }
[[bin]]

View file

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

View file

@ -1 +1 @@
uucore_procs::main!(uu_dircolors); // spell-checker:ignore procs uucore
uucore_procs::main!(uu_dircolors);

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