mirror of
https://github.com/uutils/coreutils
synced 2024-07-22 10:24:54 +00:00
Merge branch 'master' of https://github.com/uutils/coreutils into uutils-master
This commit is contained in:
commit
92281585a7
11
.cirrus.yml
11
.cirrus.yml
|
@ -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
3
.codespell.rc
Normal file
|
@ -0,0 +1,3 @@
|
|||
[codespell]
|
||||
ignore-words-list = crate
|
||||
skip = ./.git/**,./.vscode/cspell.dictionaries/**,./target/**,./tests/fixtures/**
|
|
@ -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
19
.github/stale.yml
vendored
Normal 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
|
309
.github/workflows/CICD.yml
vendored
309
.github/workflows/CICD.yml
vendored
|
@ -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
135
.github/workflows/FixPR.yml
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
name: FixPR
|
||||
|
||||
# Trigger automated fixes for PRs being merged (with associated commits)
|
||||
|
||||
# ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45
|
||||
|
||||
env:
|
||||
BRANCH_TARGET: master
|
||||
|
||||
on:
|
||||
# * only trigger on pull request closed to specific branches
|
||||
# ref: https://github.community/t/trigger-workflow-only-on-pull-request-merge/17359/9
|
||||
pull_request:
|
||||
branches:
|
||||
- master # == env.BRANCH_TARGET ## unfortunately, env context variables are only available in jobs/steps (see <https://github.community/t/how-to-use-env-context/16975/2>)
|
||||
types: [ closed ]
|
||||
|
||||
jobs:
|
||||
code_deps:
|
||||
# Refresh dependencies (ie, 'Cargo.lock') and show updated dependency tree
|
||||
if: github.event.pull_request.merged == true ## only for PR merges
|
||||
name: Update/dependencies
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
job:
|
||||
- { os: ubuntu-latest , features: feat_os_unix }
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Initialize job variables
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
## VARs setup
|
||||
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
|
||||
# surface MSRV from CICD workflow
|
||||
RUST_MIN_SRV=$(grep -P "^\s+RUST_MIN_SRV:" .github/workflows/CICD.yml | grep -Po "(?<=\x22)\d+[.]\d+(?:[.]\d+)?(?=\x22)" )
|
||||
outputs RUST_MIN_SRV
|
||||
- name: Install `rust` toolchain (v${{ steps.vars.outputs.RUST_MIN_SRV }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ steps.vars.outputs.RUST_MIN_SRV }}
|
||||
default: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
- name: Install `cargo-tree` # for dependency information
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: cargo-tree
|
||||
version: latest
|
||||
use-tool-cache: true
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: stable
|
||||
- name: Ensure updated 'Cargo.lock'
|
||||
shell: bash
|
||||
run: |
|
||||
# Ensure updated 'Cargo.lock'
|
||||
# * 'Cargo.lock' is required to be in a format that `cargo` of MinSRV can interpret (eg, v1-format for MinSRV < v1.38)
|
||||
cargo fetch --locked --quiet || cargo +${{ steps.vars.outputs.RUST_MIN_SRV }} update
|
||||
- name: Info
|
||||
shell: bash
|
||||
run: |
|
||||
# Info
|
||||
## environment
|
||||
echo "## environment"
|
||||
echo "CI='${CI}'"
|
||||
## tooling info display
|
||||
echo "## tooling"
|
||||
which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
|
||||
rustup -V 2>/dev/null
|
||||
rustup show active-toolchain
|
||||
cargo -V
|
||||
rustc -V
|
||||
cargo-tree tree -V
|
||||
## dependencies
|
||||
echo "## dependency list"
|
||||
cargo fetch --locked --quiet
|
||||
## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors
|
||||
RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
|
||||
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
|
||||
uses: EndBug/add-and-commit@v7
|
||||
with:
|
||||
branch: ${{ env.BRANCH_TARGET }}
|
||||
default_author: github_actions
|
||||
message: "maint ~ refresh 'Cargo.lock'"
|
||||
add: Cargo.lock
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
code_format:
|
||||
# Recheck/refresh code formatting
|
||||
if: github.event.pull_request.merged == true ## only for PR merges
|
||||
name: Update/format
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { os: ubuntu-latest , features: feat_os_unix }
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Initialize job variables
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
## VARs setup
|
||||
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
|
||||
# target-specific options
|
||||
# * CARGO_FEATURES_OPTION
|
||||
CARGO_FEATURES_OPTION='' ;
|
||||
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
|
||||
outputs CARGO_FEATURES_OPTION
|
||||
- name: Install `rust` toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
components: rustfmt
|
||||
- name: "`cargo fmt`"
|
||||
shell: bash
|
||||
run: |
|
||||
cargo fmt
|
||||
- name: "`cargo fmt` tests"
|
||||
shell: bash
|
||||
run: |
|
||||
# `cargo fmt` of tests
|
||||
find tests -name "*.rs" -print0 | xargs -0 cargo fmt --
|
||||
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
|
||||
uses: EndBug/add-and-commit@v7
|
||||
with:
|
||||
branch: ${{ env.BRANCH_TARGET }}
|
||||
default_author: github_actions
|
||||
message: "maint ~ rustfmt (`cargo fmt`)"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
101
.github/workflows/GNU.yml
vendored
101
.github/workflows/GNU.yml
vendored
|
@ -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
92
.github/workflows/GnuTests.yml
vendored
Normal 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
3
.gitignore
vendored
|
@ -12,3 +12,6 @@ target/
|
|||
Cargo.lock
|
||||
lib*.a
|
||||
/docs/_build
|
||||
*.iml
|
||||
### macOS ###
|
||||
.DS_Store
|
||||
|
|
17
.pre-commit-config.yaml
Normal file
17
.pre-commit-config.yaml
Normal 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
|
72
.travis.yml
72
.travis.yml
|
@ -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
365
.vscode/cSpell.json
vendored
|
@ -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": []
|
||||
}
|
||||
|
|
66
.vscode/cspell.dictionaries/acronyms+names.wordlist.txt
vendored
Normal file
66
.vscode/cspell.dictionaries/acronyms+names.wordlist.txt
vendored
Normal 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
|
111
.vscode/cspell.dictionaries/jargon.wordlist.txt
vendored
Normal file
111
.vscode/cspell.dictionaries/jargon.wordlist.txt
vendored
Normal 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
|
178
.vscode/cspell.dictionaries/people.wordlist.txt
vendored
Normal file
178
.vscode/cspell.dictionaries/people.wordlist.txt
vendored
Normal 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
|
93
.vscode/cspell.dictionaries/shell.wordlist.txt
vendored
Normal file
93
.vscode/cspell.dictionaries/shell.wordlist.txt
vendored
Normal 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
|
300
.vscode/cspell.dictionaries/workspace.wordlist.txt
vendored
Normal file
300
.vscode/cspell.dictionaries/workspace.wordlist.txt
vendored
Normal 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
|
8
.vscode/extensions.json
vendored
8
.vscode/extensions.json
vendored
|
@ -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
128
CODE_OF_CONDUCT.md
Normal 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.
|
|
@ -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
1228
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
226
Cargo.toml
226
Cargo.toml
|
@ -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
44
DEVELOPER_INSTRUCTIONS.md
Normal 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`.
|
54
GNUmakefile
54
GNUmakefile
|
@ -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
|
||||
|
|
6
Makefile
6
Makefile
|
@ -1,5 +1,5 @@
|
|||
USEGNU=gmake $*
|
||||
UseGNU=gmake $*
|
||||
all:
|
||||
@$(USEGNU)
|
||||
@$(UseGNU)
|
||||
.DEFAULT:
|
||||
@$(USEGNU)
|
||||
@$(UseGNU)
|
||||
|
|
264
README.md
264
README.md
|
@ -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
|
||||
|
||||
|
|
69
build.rs
69
build.rs
|
@ -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
1
clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
msrv = "1.43.1"
|
|
@ -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 =
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
USEGNU=gmake $*
|
||||
UseGNU=gmake $*
|
||||
all:
|
||||
@$(USEGNU)
|
||||
@$(UseGNU)
|
||||
.DEFAULT:
|
||||
@$(USEGNU)
|
||||
@$(UseGNU)
|
||||
|
|
21
docs/compiles_table.csv
Normal file
21
docs/compiles_table.csv
Normal 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
|
|
237
docs/compiles_table.py
Normal file
237
docs/compiles_table.py
Normal 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)
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,7 +16,7 @@ Synopsis
|
|||
Description
|
||||
-----------
|
||||
|
||||
``uutils`` is a program that contains that other coreutils commands, somewhat
|
||||
``uutils`` is a program that contains other coreutils commands, somewhat
|
||||
similar to Busybox.
|
||||
|
||||
--help, -h print a help menu for PROGRAM displaying accepted options and
|
||||
|
|
|
@ -5,6 +5,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
|
||||
}
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_arch); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_arch);
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_base32); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_base32);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_base64); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_base64);
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_basename); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_basename);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()?;
|
||||
}
|
||||
|
|
|
@ -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
91
src/uu/cat/src/splice.rs
Normal 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(())
|
||||
}
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
})
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_chgrp); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_chgrp);
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_chmod); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_chmod);
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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: "));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_chown); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_chown);
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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()) }
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_chroot); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_chroot);
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_cksum); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_cksum);
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_comm); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_comm);
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_cp); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_cp);
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_csplit); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_csplit);
|
||||
|
|
|
@ -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, ¤t_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)
|
||||
|
|
|
@ -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 -");
|
||||
}
|
46
src/uu/cut/BENCHMARKING.md
Normal file
46
src/uu/cut/BENCHMARKING.md
Normal 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.
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_cut); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_cut);
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'
|
||||
```
|
|
@ -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, ×pec) };
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_date); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_date);
|
||||
|
|
|
@ -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 it’s 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'
|
||||
")
|
||||
)
|
||||
};
|
||||
);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
uucore_procs::main!(uu_df); // spell-checker:ignore procs uucore
|
||||
uucore_procs::main!(uu_df);
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue