mirror of
https://github.com/uutils/coreutils
synced 2024-10-06 16:09:08 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
f00df3f3d8
|
@ -11,4 +11,4 @@ task:
|
|||
- cargo build
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test
|
||||
- cargo test -p uucore -p coreutils
|
||||
|
|
18
.github/stale.yml
vendored
Normal file
18
.github/stale.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
# 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
|
79
.github/workflows/CICD.yml
vendored
79
.github/workflows/CICD.yml
vendored
|
@ -1,7 +1,7 @@
|
|||
name: CICD
|
||||
|
||||
# spell-checker:ignore (acronyms) CICD MSVC musl
|
||||
# spell-checker:ignore (env/flags) Ccodegen Coverflow Cpanic RUSTDOCFLAGS RUSTFLAGS Zpanic
|
||||
# spell-checker:ignore (env/flags) Awarnings Ccodegen Coverflow Cpanic RUSTDOCFLAGS RUSTFLAGS Zpanic
|
||||
# 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
|
||||
|
@ -11,7 +11,7 @@ env:
|
|||
PROJECT_NAME: coreutils
|
||||
PROJECT_DESC: "Core universal (cross-platform) utilities"
|
||||
PROJECT_AUTH: "uutils"
|
||||
RUST_MIN_SRV: "1.32.0" ## v1.32.0 - minimum version for half, tempfile, etc
|
||||
RUST_MIN_SRV: "1.40.0" ## v1.40.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]
|
||||
|
@ -119,6 +119,12 @@ jobs:
|
|||
use-tool-cache: true
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: stable
|
||||
- name: Confirm compatible 'Cargo.lock'
|
||||
shell: bash
|
||||
run: |
|
||||
# Confirm 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 ; }
|
||||
- name: Info
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -136,17 +142,67 @@ jobs:
|
|||
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 fetch --quiet
|
||||
RUSTUP_TOOLCHAIN=stable cargo-tree tree --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
|
||||
RUSTUP_TOOLCHAIN=stable cargo-tree tree --frozen --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
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { os: ubuntu-latest }
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install `rust` toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
- name: "prepare busytest"
|
||||
shell: bash
|
||||
run: |
|
||||
make prepare-busytest
|
||||
- name: "run busybox testsuite"
|
||||
shell: bash
|
||||
run: |
|
||||
bindir=$(pwd)/target/debug
|
||||
cd tmp/busybox-*/testsuite
|
||||
S=$(bindir=$bindir ./runtest) && printf "%s\n" "$S" || { printf "%s\n" "$S" | grep "FAIL:" | sed -e "s/FAIL: /::warning ::Test failure:/g" ; }
|
||||
|
||||
makefile_build:
|
||||
name: Test the build target of the Makefile
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { os: ubuntu-latest }
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install `rust` toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
- name: "Run make build"
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get -y update ; sudo apt-get -y install python3-sphinx;
|
||||
make build
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
|
@ -348,7 +404,7 @@ jobs:
|
|||
cargo-tree tree -V
|
||||
## dependencies
|
||||
echo "## dependency list"
|
||||
cargo fetch --quiet
|
||||
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
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
|
@ -480,6 +536,17 @@ jobs:
|
|||
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}
|
||||
- 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:
|
||||
|
|
134
.github/workflows/GNU.yml
vendored
Normal file
134
.github/workflows/GNU.yml
vendored
Normal file
|
@ -0,0 +1,134 @@
|
|||
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 python3-sphinx
|
||||
pushd uutils
|
||||
make PROFILE=release
|
||||
BUILDDIR="$PWD/target/release/"
|
||||
cp "${BUILDDIR}/install" "${BUILDDIR}/ginstall" # The GNU tests rename this script before running, to avoid confusion with the make target
|
||||
# Create *sum binaries
|
||||
for sum in b2sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum
|
||||
do
|
||||
sum_path="${BUILDDIR}/${sum}"
|
||||
test -f "${sum_path}" || cp "${BUILDDIR}/hashsum" "${sum_path}"
|
||||
done
|
||||
test -f "${BUILDDIR}/[" || cp "${BUILDDIR}/test" "${BUILDDIR}/["
|
||||
popd
|
||||
GNULIB_SRCDIR="$PWD/gnulib"
|
||||
pushd gnu/
|
||||
|
||||
# Any binaries that aren't built become `false` so their tests fail
|
||||
for binary in $(./build-aux/gen-lists-of-programs.sh --list-progs)
|
||||
do
|
||||
bin_path="${BUILDDIR}/${binary}"
|
||||
test -f "${bin_path}" || { echo "'${binary}' was not built with uutils, using the 'false' program"; cp "${BUILDDIR}/false" "${bin_path}"; }
|
||||
done
|
||||
|
||||
./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR"
|
||||
./configure --quiet --disable-gcc-warnings
|
||||
#Add timeout to to protect against hangs
|
||||
sed -i 's|"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver
|
||||
# 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 {00..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 |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh
|
||||
|
||||
# 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
|
||||
|
||||
# Use the system coreutils where the test fails due to error in a util that is not the one being tested
|
||||
sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh
|
||||
sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh
|
||||
sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh
|
||||
sed -i 's|timeout \([[:digit:]]\)| /usr/bin/timeout \1|' tests/tail-2/inotify-rotate.sh tests/tail-2/inotify-dir-recreate.sh tests/tail-2/inotify-rotate-resources.sh tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.sh tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh tests/tail-2/pid.sh tests/dd/stats.sh tests/tail-2/follow-name.sh tests/misc/shuf.sh # Don't break the function called 'grep_timeout'
|
||||
sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh tests/chgrp/basic.sh tests/misc/shuf.sh
|
||||
sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh
|
||||
sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh
|
||||
sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh
|
||||
sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh tests/cp/fiemap-2.sh init.cfg
|
||||
sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh
|
||||
sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/ls/abmon-align.sh tests/ls/rt-1.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh
|
||||
sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh
|
||||
sed -i 's|printf |/usr/bin/printf |' tests/dd/ascii.sh
|
||||
sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh
|
||||
sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh
|
||||
sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh
|
||||
|
||||
#Add specific timeout to tests that currently hang to limit time spent waiting
|
||||
sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh
|
||||
sed -i 's|cat |/usr/bin/timeout 0.1 cat |' tests/misc/cat-self.sh
|
||||
|
||||
|
||||
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
|
||||
|
||||
timeout -sKILL 2h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make
|
||||
- name: Extract tests info
|
||||
shell: bash
|
||||
run: |
|
||||
if test -f gnu/tests/test-suite.log
|
||||
then
|
||||
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"
|
||||
else
|
||||
echo "::error ::Failed to get summary of test results"
|
||||
fi
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: test-report
|
||||
path: gnu/tests/**/*.log
|
8
.pre-commit-config.yaml
Normal file
8
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
# https://pre-commit.com
|
||||
repos:
|
||||
- repo: https://github.com/doublify/pre-commit-rust
|
||||
rev: v1.0
|
||||
hooks:
|
||||
- id: cargo-check
|
||||
- id: clippy
|
||||
- id: fmt
|
|
@ -18,7 +18,7 @@ matrix:
|
|||
- rust: nightly
|
||||
fast_finish: true
|
||||
include:
|
||||
- rust: 1.32.0
|
||||
- rust: 1.40.0
|
||||
env: FEATURES=unix
|
||||
# - rust: stable
|
||||
# os: linux
|
||||
|
@ -56,7 +56,7 @@ install:
|
|||
|
||||
script:
|
||||
- cargo build $CARGO_ARGS --features "$FEATURES"
|
||||
- if [ ! $REDOX ]; then cargo test $CARGO_ARGS --features "$FEATURES" --no-fail-fast; fi
|
||||
- if [ ! $REDOX ]; then cargo test $CARGO_ARGS -p uucore -p coreutils --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:
|
||||
|
|
55
.vscode/cSpell.json
vendored
55
.vscode/cSpell.json
vendored
|
@ -8,6 +8,8 @@
|
|||
"Cygwin",
|
||||
"FreeBSD",
|
||||
"Gmail",
|
||||
"GNUEABI",
|
||||
"GNUEABIhf",
|
||||
"Irix",
|
||||
"MacOS",
|
||||
"MinGW",
|
||||
|
@ -23,16 +25,31 @@
|
|||
"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",
|
||||
|
@ -64,12 +81,14 @@
|
|||
"colorize",
|
||||
"consts",
|
||||
"dedup",
|
||||
"demangle",
|
||||
"deque",
|
||||
"dequeue",
|
||||
"enqueue",
|
||||
"executable",
|
||||
"executables",
|
||||
"gibibytes",
|
||||
"hardfloat",
|
||||
"hardlink",
|
||||
"hardlinks",
|
||||
"hashsums",
|
||||
|
@ -87,9 +106,12 @@
|
|||
"primality",
|
||||
"pseudoprime",
|
||||
"pseudoprimes",
|
||||
"procs",
|
||||
"readonly",
|
||||
"seedable",
|
||||
"semver",
|
||||
"shortcode",
|
||||
"shortcodes",
|
||||
"symlink",
|
||||
"symlinks",
|
||||
"syscall",
|
||||
|
@ -144,6 +166,7 @@
|
|||
"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",
|
||||
|
@ -151,27 +174,37 @@
|
|||
"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",
|
||||
|
@ -190,16 +223,28 @@
|
|||
"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",
|
||||
|
@ -240,10 +285,16 @@
|
|||
"socktype",
|
||||
"umask",
|
||||
"waitpid",
|
||||
// vars/nix
|
||||
"iovec",
|
||||
"unistd",
|
||||
// vars/signals
|
||||
"SIGPIPE",
|
||||
// vars/sync
|
||||
"Condvar",
|
||||
// vars/stat
|
||||
"fstat",
|
||||
"stat",
|
||||
// vars/time
|
||||
"Timespec",
|
||||
"nsec",
|
||||
|
@ -265,9 +316,11 @@
|
|||
"errhandlingapi",
|
||||
"fileapi",
|
||||
"handleapi",
|
||||
"lmcons",
|
||||
"minwindef",
|
||||
"processthreadsapi",
|
||||
"synchapi",
|
||||
"sysinfoapi",
|
||||
"winbase",
|
||||
"winerror",
|
||||
"winnt",
|
||||
|
@ -285,10 +338,12 @@
|
|||
"coreopts",
|
||||
"coreutils",
|
||||
"libc",
|
||||
"libstdbuf",
|
||||
"musl",
|
||||
"ucmd",
|
||||
"utmpx",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
"uumain",
|
||||
"uutils"
|
||||
],
|
||||
|
|
2
.vscode/settings.json
vendored
Normal file
2
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
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, socio-economic 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.
|
|
@ -5,16 +5,24 @@ standard libraries are stabilized. You may *claim* an item on the to-do list by
|
|||
following these steps:
|
||||
|
||||
1. Open an issue named "Implement [the utility of your choice]", e.g. "Implement ls"
|
||||
2. State that you are working on this utility.
|
||||
3. Develop the utility.
|
||||
4. Add integration tests.
|
||||
5. Add the reference to your utility into Cargo.toml and Makefile.
|
||||
6. Remove utility from the to-do list in the README.
|
||||
7. Submit a pull request and close the issue.
|
||||
1. State that you are working on this utility.
|
||||
1. Develop the utility
|
||||
1. Add integration tests.
|
||||
1. Add the reference to your utility into Cargo.toml and Makefile.
|
||||
1. Remove utility from the to-do list in the README.
|
||||
1. Submit a pull request and close the issue.
|
||||
|
||||
The steps above imply that, before starting to work on a utility, you should
|
||||
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. 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.
|
||||
|
||||
## Commit messages
|
||||
|
||||
To help the project maintainers review pull requests from contributors across
|
||||
|
|
2677
Cargo.lock
generated
2677
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
215
Cargo.toml
215
Cargo.toml
|
@ -3,7 +3,7 @@
|
|||
|
||||
[package]
|
||||
name = "coreutils"
|
||||
version = "0.0.1" # "0.0.1.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust"
|
||||
|
@ -37,11 +37,13 @@ feat_common_core = [
|
|||
"cksum",
|
||||
"comm",
|
||||
"cp",
|
||||
"csplit",
|
||||
"cut",
|
||||
"date",
|
||||
"df",
|
||||
"dircolors",
|
||||
"dirname",
|
||||
"du",
|
||||
"echo",
|
||||
"env",
|
||||
"expand",
|
||||
|
@ -148,7 +150,6 @@ feat_require_unix = [
|
|||
"chmod",
|
||||
"chown",
|
||||
"chroot",
|
||||
"du",
|
||||
"groups",
|
||||
"hostid",
|
||||
"id",
|
||||
|
@ -225,122 +226,132 @@ test = [ "uu_test" ]
|
|||
[dependencies]
|
||||
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.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="src/uucore" }
|
||||
# * uutils
|
||||
uu_test = { optional=true, version="0.0.1", 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.1", package="uu_arch", path="src/uu/arch" }
|
||||
base32 = { optional=true, version="0.0.1", package="uu_base32", path="src/uu/base32" }
|
||||
base64 = { optional=true, version="0.0.1", package="uu_base64", path="src/uu/base64" }
|
||||
basename = { optional=true, version="0.0.1", package="uu_basename", path="src/uu/basename" }
|
||||
cat = { optional=true, version="0.0.1", package="uu_cat", path="src/uu/cat" }
|
||||
chgrp = { optional=true, version="0.0.1", package="uu_chgrp", path="src/uu/chgrp" }
|
||||
chmod = { optional=true, version="0.0.1", package="uu_chmod", path="src/uu/chmod" }
|
||||
chown = { optional=true, version="0.0.1", package="uu_chown", path="src/uu/chown" }
|
||||
chroot = { optional=true, version="0.0.1", package="uu_chroot", path="src/uu/chroot" }
|
||||
cksum = { optional=true, version="0.0.1", package="uu_cksum", path="src/uu/cksum" }
|
||||
comm = { optional=true, version="0.0.1", package="uu_comm", path="src/uu/comm" }
|
||||
cp = { optional=true, version="0.0.1", package="uu_cp", path="src/uu/cp" }
|
||||
cut = { optional=true, version="0.0.1", package="uu_cut", path="src/uu/cut" }
|
||||
date = { optional=true, version="0.0.1", package="uu_date", path="src/uu/date" }
|
||||
df = { optional=true, version="0.0.1", package="uu_df", path="src/uu/df" }
|
||||
dircolors= { optional=true, version="0.0.1", package="uu_dircolors", path="src/uu/dircolors" }
|
||||
dirname = { optional=true, version="0.0.1", package="uu_dirname", path="src/uu/dirname" }
|
||||
du = { optional=true, version="0.0.1", package="uu_du", path="src/uu/du" }
|
||||
echo = { optional=true, version="0.0.1", package="uu_echo", path="src/uu/echo" }
|
||||
env = { optional=true, version="0.0.1", package="uu_env", path="src/uu/env" }
|
||||
expand = { optional=true, version="0.0.1", package="uu_expand", path="src/uu/expand" }
|
||||
expr = { optional=true, version="0.0.1", package="uu_expr", path="src/uu/expr" }
|
||||
factor = { optional=true, version="0.0.1", package="uu_factor", path="src/uu/factor" }
|
||||
false = { optional=true, version="0.0.1", package="uu_false", path="src/uu/false" }
|
||||
fmt = { optional=true, version="0.0.1", package="uu_fmt", path="src/uu/fmt" }
|
||||
fold = { optional=true, version="0.0.1", package="uu_fold", path="src/uu/fold" }
|
||||
groups = { optional=true, version="0.0.1", package="uu_groups", path="src/uu/groups" }
|
||||
hashsum = { optional=true, version="0.0.1", package="uu_hashsum", path="src/uu/hashsum" }
|
||||
head = { optional=true, version="0.0.1", package="uu_head", path="src/uu/head" }
|
||||
hostid = { optional=true, version="0.0.1", package="uu_hostid", path="src/uu/hostid" }
|
||||
hostname = { optional=true, version="0.0.1", package="uu_hostname", path="src/uu/hostname" }
|
||||
id = { optional=true, version="0.0.1", package="uu_id", path="src/uu/id" }
|
||||
install = { optional=true, version="0.0.1", package="uu_install", path="src/uu/install" }
|
||||
join = { optional=true, version="0.0.1", package="uu_join", path="src/uu/join" }
|
||||
kill = { optional=true, version="0.0.1", package="uu_kill", path="src/uu/kill" }
|
||||
link = { optional=true, version="0.0.1", package="uu_link", path="src/uu/link" }
|
||||
ln = { optional=true, version="0.0.1", package="uu_ln", path="src/uu/ln" }
|
||||
ls = { optional=true, version="0.0.1", package="uu_ls", path="src/uu/ls" }
|
||||
logname = { optional=true, version="0.0.1", package="uu_logname", path="src/uu/logname" }
|
||||
mkdir = { optional=true, version="0.0.1", package="uu_mkdir", path="src/uu/mkdir" }
|
||||
mkfifo = { optional=true, version="0.0.1", package="uu_mkfifo", path="src/uu/mkfifo" }
|
||||
mknod = { optional=true, version="0.0.1", package="uu_mknod", path="src/uu/mknod" }
|
||||
mktemp = { optional=true, version="0.0.1", package="uu_mktemp", path="src/uu/mktemp" }
|
||||
more = { optional=true, version="0.0.1", package="uu_more", path="src/uu/more" }
|
||||
mv = { optional=true, version="0.0.1", package="uu_mv", path="src/uu/mv" }
|
||||
nice = { optional=true, version="0.0.1", package="uu_nice", path="src/uu/nice" }
|
||||
nl = { optional=true, version="0.0.1", package="uu_nl", path="src/uu/nl" }
|
||||
nohup = { optional=true, version="0.0.1", package="uu_nohup", path="src/uu/nohup" }
|
||||
nproc = { optional=true, version="0.0.1", package="uu_nproc", path="src/uu/nproc" }
|
||||
numfmt = { optional=true, version="0.0.1", package="uu_numfmt", path="src/uu/numfmt" }
|
||||
od = { optional=true, version="0.0.1", package="uu_od", path="src/uu/od" }
|
||||
paste = { optional=true, version="0.0.1", package="uu_paste", path="src/uu/paste" }
|
||||
pathchk = { optional=true, version="0.0.1", package="uu_pathchk", path="src/uu/pathchk" }
|
||||
pinky = { optional=true, version="0.0.1", package="uu_pinky", path="src/uu/pinky" }
|
||||
printenv = { optional=true, version="0.0.1", package="uu_printenv", path="src/uu/printenv" }
|
||||
printf = { optional=true, version="0.0.1", package="uu_printf", path="src/uu/printf" }
|
||||
ptx = { optional=true, version="0.0.1", package="uu_ptx", path="src/uu/ptx" }
|
||||
pwd = { optional=true, version="0.0.1", package="uu_pwd", path="src/uu/pwd" }
|
||||
readlink = { optional=true, version="0.0.1", package="uu_readlink", path="src/uu/readlink" }
|
||||
realpath = { optional=true, version="0.0.1", package="uu_realpath", path="src/uu/realpath" }
|
||||
relpath = { optional=true, version="0.0.1", package="uu_relpath", path="src/uu/relpath" }
|
||||
rm = { optional=true, version="0.0.1", package="uu_rm", path="src/uu/rm" }
|
||||
rmdir = { optional=true, version="0.0.1", package="uu_rmdir", path="src/uu/rmdir" }
|
||||
seq = { optional=true, version="0.0.1", package="uu_seq", path="src/uu/seq" }
|
||||
shred = { optional=true, version="0.0.1", package="uu_shred", path="src/uu/shred" }
|
||||
shuf = { optional=true, version="0.0.1", package="uu_shuf", path="src/uu/shuf" }
|
||||
sleep = { optional=true, version="0.0.1", package="uu_sleep", path="src/uu/sleep" }
|
||||
sort = { optional=true, version="0.0.1", package="uu_sort", path="src/uu/sort" }
|
||||
split = { optional=true, version="0.0.1", package="uu_split", path="src/uu/split" }
|
||||
stat = { optional=true, version="0.0.1", package="uu_stat", path="src/uu/stat" }
|
||||
stdbuf = { optional=true, version="0.0.1", package="uu_stdbuf", path="src/uu/stdbuf" }
|
||||
sum = { optional=true, version="0.0.1", package="uu_sum", path="src/uu/sum" }
|
||||
sync = { optional=true, version="0.0.1", package="uu_sync", path="src/uu/sync" }
|
||||
tac = { optional=true, version="0.0.1", package="uu_tac", path="src/uu/tac" }
|
||||
tail = { optional=true, version="0.0.1", package="uu_tail", path="src/uu/tail" }
|
||||
tee = { optional=true, version="0.0.1", package="uu_tee", path="src/uu/tee" }
|
||||
timeout = { optional=true, version="0.0.1", package="uu_timeout", path="src/uu/timeout" }
|
||||
touch = { optional=true, version="0.0.1", package="uu_touch", path="src/uu/touch" }
|
||||
tr = { optional=true, version="0.0.1", package="uu_tr", path="src/uu/tr" }
|
||||
true = { optional=true, version="0.0.1", package="uu_true", path="src/uu/true" }
|
||||
truncate = { optional=true, version="0.0.1", package="uu_truncate", path="src/uu/truncate" }
|
||||
tsort = { optional=true, version="0.0.1", package="uu_tsort", path="src/uu/tsort" }
|
||||
tty = { optional=true, version="0.0.1", package="uu_tty", path="src/uu/tty" }
|
||||
uname = { optional=true, version="0.0.1", package="uu_uname", path="src/uu/uname" }
|
||||
unexpand = { optional=true, version="0.0.1", package="uu_unexpand", path="src/uu/unexpand" }
|
||||
uniq = { optional=true, version="0.0.1", package="uu_uniq", path="src/uu/uniq" }
|
||||
unlink = { optional=true, version="0.0.1", package="uu_unlink", path="src/uu/unlink" }
|
||||
uptime = { optional=true, version="0.0.1", package="uu_uptime", path="src/uu/uptime" }
|
||||
users = { optional=true, version="0.0.1", package="uu_users", path="src/uu/users" }
|
||||
wc = { optional=true, version="0.0.1", package="uu_wc", path="src/uu/wc" }
|
||||
who = { optional=true, version="0.0.1", package="uu_who", path="src/uu/who" }
|
||||
whoami = { optional=true, version="0.0.1", package="uu_whoami", path="src/uu/whoami" }
|
||||
yes = { optional=true, version="0.0.1", package="uu_yes", path="src/uu/yes" }
|
||||
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" }
|
||||
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" }
|
||||
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" }
|
||||
#
|
||||
# * pinned transitive dependencies
|
||||
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
|
||||
# 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]
|
||||
conv = "0.3"
|
||||
filetime = "0.2"
|
||||
glob = "0.3.0"
|
||||
libc = "0.2"
|
||||
rand = "0.6"
|
||||
nix = "0.20.0"
|
||||
rand = "0.7"
|
||||
regex = "1.0"
|
||||
tempfile = "3.1"
|
||||
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"
|
||||
time = "0.1"
|
||||
unindent = "0.1"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["entries"] }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] }
|
||||
walkdir = "2.2"
|
||||
tempdir = "0.3"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
rust-users = { version="0.10", package="users" }
|
||||
unix_socket = "0.5.0"
|
||||
|
||||
|
||||
[[bin]]
|
||||
name = "coreutils"
|
||||
path = "src/bin/coreutils.rs"
|
||||
|
|
38
DEVELOPER_INSTRUCTIONS.md
Normal file
38
DEVELOPER_INSTRUCTIONS.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
Code Coverage Report Generation
|
||||
---------------------------------
|
||||
|
||||
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.
|
48
GNUmakefile
48
GNUmakefile
|
@ -30,7 +30,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
|
||||
|
@ -41,7 +41,7 @@ PKG_BUILDDIR := $(BUILDDIR)/deps
|
|||
DOCSDIR := $(BASEDIR)/docs
|
||||
|
||||
BUSYBOX_ROOT := $(BASEDIR)/tmp
|
||||
BUSYBOX_VER := 1.24.1
|
||||
BUSYBOX_VER := 1.32.1
|
||||
BUSYBOX_SRC := $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER)
|
||||
|
||||
# Possible programs
|
||||
|
@ -53,7 +53,9 @@ PROGS := \
|
|||
cksum \
|
||||
comm \
|
||||
cp \
|
||||
csplit \
|
||||
cut \
|
||||
date \
|
||||
df \
|
||||
dircolors \
|
||||
dirname \
|
||||
|
@ -160,7 +162,9 @@ TEST_PROGS := \
|
|||
cksum \
|
||||
comm \
|
||||
cp \
|
||||
csplit \
|
||||
cut \
|
||||
date \
|
||||
dircolors \
|
||||
dirname \
|
||||
echo \
|
||||
|
@ -224,7 +228,7 @@ endif
|
|||
|
||||
define TEST_BUSYBOX
|
||||
test_busybox_$(1):
|
||||
(cd $(BUSYBOX_SRC)/testsuite && bindir=$(BUILDDIR) ./runtest $(RUNTEST_ARGS) $(1) )
|
||||
-(cd $(BUSYBOX_SRC)/testsuite && bindir=$(BUILDDIR) ./runtest $(RUNTEST_ARGS) $(1))
|
||||
endef
|
||||
|
||||
# Output names
|
||||
|
@ -233,19 +237,7 @@ EXES := \
|
|||
|
||||
INSTALLEES := ${EXES}
|
||||
ifeq (${MULTICALL}, y)
|
||||
INSTALLEES := ${INSTALLEES} uutils
|
||||
endif
|
||||
|
||||
# Shared library extension
|
||||
SYSTEM := $(shell uname)
|
||||
DYLIB_EXT :=
|
||||
ifeq ($(SYSTEM),Linux)
|
||||
DYLIB_EXT := so
|
||||
DYLIB_FLAGS := -shared
|
||||
endif
|
||||
ifeq ($(SYSTEM),Darwin)
|
||||
DYLIB_EXT := dylib
|
||||
DYLIB_FLAGS := -dynamiclib -undefined dynamic_lookup
|
||||
INSTALLEES := ${INSTALLEES} coreutils
|
||||
endif
|
||||
|
||||
all: build
|
||||
|
@ -258,13 +250,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))))
|
||||
|
||||
|
@ -283,10 +275,12 @@ $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
|
|||
cp $< $@
|
||||
|
||||
# Test under the busybox testsuite
|
||||
$(BUILDDIR)/busybox: busybox-src build-uutils $(BUILDDIR)/.config
|
||||
cp $(BUILDDIR)/uutils $(BUILDDIR)/busybox; \
|
||||
$(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config
|
||||
cp $(BUILDDIR)/coreutils $(BUILDDIR)/busybox; \
|
||||
chmod +x $@;
|
||||
|
||||
prepare-busytest: $(BUILDDIR)/busybox
|
||||
|
||||
ifeq ($(EXES),)
|
||||
busytest:
|
||||
else
|
||||
|
@ -304,10 +298,10 @@ 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);)
|
||||
|
@ -317,10 +311,10 @@ endif
|
|||
|
||||
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 $(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
|
||||
|
|
67
README.md
67
README.md
|
@ -1,14 +1,13 @@
|
|||
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)
|
||||
[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/coreutils/blob/master/LICENSE)
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils?ref=badge_shield)
|
||||
[![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 (Windows)](https://ci.appveyor.com/api/projects/status/787ltcxgy86r20le?svg=true)](https://ci.appveyor.com/project/Arcterus/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)
|
||||
|
||||
|
@ -24,9 +23,8 @@ 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.
|
||||
|
@ -42,7 +40,7 @@ Requirements
|
|||
### 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.32.0`.
|
||||
The current oldest supported version of the Rust compiler is `1.40.0`.
|
||||
|
||||
On both Windows and Redox, only the nightly version is tested currently.
|
||||
|
||||
|
@ -115,7 +113,7 @@ Installation Instructions
|
|||
|
||||
Likewise, installing can simply be done using:
|
||||
```bash
|
||||
$ cargo install
|
||||
$ cargo install --path .
|
||||
```
|
||||
|
||||
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
|
||||
|
@ -228,6 +226,11 @@ If you would prefer to test a select few utilities:
|
|||
$ 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
|
||||
|
@ -289,19 +292,20 @@ Utilities
|
|||
| Done | Semi-Done | To Do |
|
||||
|-----------|-----------|--------|
|
||||
| arch | cp | chcon |
|
||||
| base32 | expr | csplit |
|
||||
| base64 | install | dd |
|
||||
| basename | ls | numfmt |
|
||||
| cat | more | pr |
|
||||
| chgrp | od (`--strings` and 128-bit data types missing) | runcon |
|
||||
| chmod | printf | stty |
|
||||
| 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 | |
|
||||
| cut | date | |
|
||||
| dircolors | join | |
|
||||
| dirname | df | |
|
||||
| csplit | date | |
|
||||
| cut | join | |
|
||||
| dircolors | df | |
|
||||
| dirname | tac | |
|
||||
| du | | |
|
||||
| echo | | |
|
||||
| env | | |
|
||||
|
@ -354,7 +358,6 @@ Utilities
|
|||
| stdbuf | | |
|
||||
| sum | | |
|
||||
| sync | | |
|
||||
| tac | | |
|
||||
| tee | | |
|
||||
| timeout | | |
|
||||
| touch | | |
|
||||
|
@ -374,9 +377,37 @@ Utilities
|
|||
| whoami | | |
|
||||
| yes | | |
|
||||
|
||||
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|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
|
||||
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils?ref=badge_large)
|
||||
GNU Coreutils is licensed under the GPL 3.0 or later.
|
||||
|
|
38
build.rs
38
build.rs
|
@ -44,10 +44,10 @@ pub fn main() {
|
|||
|
||||
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\
|
||||
"
|
||||
\n\
|
||||
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n\
|
||||
\tlet mut map = UtilityMap::new();\n\
|
||||
"
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -97,21 +97,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);\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\
|
||||
",
|
||||
krate = krate
|
||||
)
|
||||
.as_bytes(),
|
||||
|
|
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
|
|
234
docs/compiles_table.py
Normal file
234
docs/compiles_table.py
Normal file
|
@ -0,0 +1,234 @@
|
|||
#!/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
|
||||
|
||||
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
|
||||
"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.platfrom, 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)
|
13
docs/conf.py
13
docs/conf.py
|
@ -23,6 +23,7 @@
|
|||
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
|
@ -55,11 +56,14 @@ author = 'uutils developers'
|
|||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# * take version from project "Cargo.toml"
|
||||
version_file = open(os.path.join("..","Cargo.toml"), "r")
|
||||
version_file_content = version_file.read()
|
||||
v = re.search("^\s*version\s*=\s*\"([0-9.]+)\"", version_file_content, re.IGNORECASE | re.MULTILINE)
|
||||
# The short X.Y version.
|
||||
version = '0.0.1'
|
||||
version = v.groups()[0]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.0.1'
|
||||
release = version
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@ -179,6 +183,3 @@ texinfo_documents = [
|
|||
author, 'uutils', 'A cross-platform reimplementation of GNU coreutils in Rust.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
extern crate textwrap;
|
||||
extern crate uucore;
|
||||
|
||||
use std::cmp;
|
||||
use std::collections::hash_map::HashMap;
|
||||
use std::ffi::OsString;
|
||||
|
@ -22,10 +19,10 @@ include!(concat!(env!("OUT_DIR"), "/uutils_map.rs"));
|
|||
fn usage<T>(utils: &UtilityMap<T>, name: &str) {
|
||||
println!("{} {} (multi-call binary)\n", name, VERSION);
|
||||
println!("Usage: {} [function [arguments...]]\n", name);
|
||||
println!("Currently defined functions/utilities:\n");
|
||||
println!("Currently defined functions:\n");
|
||||
#[allow(clippy::map_clone)]
|
||||
let mut utils: Vec<&str> = utils.keys().map(|&s| s).collect();
|
||||
utils.sort();
|
||||
utils.sort_unstable();
|
||||
let display_list = utils.join(", ");
|
||||
let width = cmp::min(textwrap::termwidth(), 100) - 4 * 2; // (opinion/heuristic) max 100 chars wide with 4 character side indentions
|
||||
println!(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_arch"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "arch ~ (uutils) display machine architecture"
|
||||
|
@ -15,9 +15,9 @@ edition = "2018"
|
|||
path = "src/arch.rs"
|
||||
|
||||
[dependencies]
|
||||
platform-info = "0.0.1"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
platform-info = "0.1"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "arch"
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
extern crate platform_info;
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_base32"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "base32 ~ (uutils) decode/encode input (base32-encoding)"
|
||||
|
@ -15,8 +15,8 @@ edition = "2018"
|
|||
path = "src/base32.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features = ["encoding"] }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "base32"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// that was distributed with this source code.
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, BufReader, Read};
|
||||
use std::io::{stdin, stdout, BufReader, Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use uucore::encoding::{wrap_print, Data, Format};
|
||||
|
@ -85,7 +85,12 @@ fn handle_input<R: Read>(
|
|||
wrap_print(&data, encoded);
|
||||
} else {
|
||||
match data.decode() {
|
||||
Ok(s) => print!("{}", String::from_utf8(s).unwrap()),
|
||||
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,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_base64"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "base64 ~ (uutils) decode/encode input (base64-encoding)"
|
||||
|
@ -15,8 +15,8 @@ edition = "2018"
|
|||
path = "src/base64.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features = ["encoding"] }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "base64"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// that was distributed with this source code.
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, BufReader, Read};
|
||||
use std::io::{stdin, stdout, BufReader, Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use uucore::encoding::{wrap_print, Data, Format};
|
||||
|
@ -85,7 +85,12 @@ fn handle_input<R: Read>(
|
|||
wrap_print(&data, encoded);
|
||||
} else {
|
||||
match data.decode() {
|
||||
Ok(s) => print!("{}", String::from_utf8(s).unwrap()),
|
||||
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,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_basename"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "basename ~ (uutils) display PATHNAME with leading directory components removed"
|
||||
|
@ -15,8 +15,8 @@ edition = "2018"
|
|||
path = "src/basename.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "basename"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_cat"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "cat ~ (uutils) concatenate and display input"
|
||||
|
@ -15,9 +15,10 @@ edition = "2018"
|
|||
path = "src/cat.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
quick-error = "1.2.3"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["fs"] }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
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"
|
||||
|
|
|
@ -17,6 +17,7 @@ extern crate unix_socket;
|
|||
extern crate uucore;
|
||||
|
||||
// last synced with: cat (GNU coreutils) 8.13
|
||||
use clap::{App, Arg};
|
||||
use quick_error::ResultExt;
|
||||
use std::fs::{metadata, File};
|
||||
use std::io::{self, stderr, stdin, stdout, BufWriter, Read, Write};
|
||||
|
@ -30,10 +31,11 @@ use std::os::unix::fs::FileTypeExt;
|
|||
#[cfg(unix)]
|
||||
use unix_socket::UnixStream;
|
||||
|
||||
static NAME: &str = "cat";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
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(PartialEq)]
|
||||
enum NumberingMode {
|
||||
|
@ -124,50 +126,122 @@ enum InputType {
|
|||
|
||||
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 matches = app!(SYNTAX, SUMMARY, LONG_HELP)
|
||||
.optflag("A", "show-all", "equivalent to -vET")
|
||||
.optflag(
|
||||
"b",
|
||||
"number-nonblank",
|
||||
"number nonempty output lines, overrides -n",
|
||||
let matches = App::new(executable!())
|
||||
.name(NAME)
|
||||
.version(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"),
|
||||
)
|
||||
.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)",
|
||||
.arg(
|
||||
Arg::with_name(options::NUMBER_NONBLANK)
|
||||
.short("b")
|
||||
.long(options::NUMBER_NONBLANK)
|
||||
.help("number nonempty output lines, overrides -n")
|
||||
.overrides_with(options::NUMBER),
|
||||
)
|
||||
.parse(args);
|
||||
.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)"),
|
||||
)
|
||||
.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 show_ends = vec![
|
||||
options::SHOW_ENDS.to_owned(),
|
||||
options::SHOW_ALL.to_owned(),
|
||||
options::SHOW_NONPRINTING_ENDS.to_owned(),
|
||||
]
|
||||
.iter()
|
||||
.any(|v| matches.is_present(v));
|
||||
|
||||
let show_tabs = vec![
|
||||
options::SHOW_ALL.to_owned(),
|
||||
options::SHOW_TABS.to_owned(),
|
||||
options::SHOW_NONPRINTING_TABS.to_owned(),
|
||||
]
|
||||
.iter()
|
||||
.any(|v| matches.is_present(v));
|
||||
|
||||
let squeeze_blank = matches.is_present(options::SQUEEZE_BLANK);
|
||||
let files: Vec<String> = match matches.values_of(options::FILE) {
|
||||
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
|
||||
None => vec!["-".to_owned()],
|
||||
};
|
||||
|
||||
let can_write_fast = !(show_tabs
|
||||
|| show_nonprint
|
||||
|
@ -361,7 +435,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
|
|||
}
|
||||
writer.write_all(options.end_of_line.as_bytes())?;
|
||||
if handle.is_interactive {
|
||||
writer.flush().context(&file[..])?;
|
||||
writer.flush().context(file)?;
|
||||
}
|
||||
}
|
||||
state.at_line_start = true;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_chgrp"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "chgrp ~ (uutils) change the group ownership of FILE"
|
||||
|
@ -15,9 +15,9 @@ edition = "2018"
|
|||
path = "src/chgrp.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["entries", "fs"] }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
walkdir = "2.2.8"
|
||||
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"
|
||||
|
||||
[[bin]]
|
||||
name = "chgrp"
|
||||
|
|
|
@ -11,23 +11,18 @@
|
|||
extern crate uucore;
|
||||
pub use uucore::entries;
|
||||
use uucore::fs::resolve_relative_path;
|
||||
use uucore::libc::{self, gid_t, lchown};
|
||||
use uucore::libc::gid_t;
|
||||
use uucore::perms::{wrap_chgrp, Verbosity};
|
||||
|
||||
extern crate walkdir;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use std::io::Error as IOError;
|
||||
use std::io::Result as IOResult;
|
||||
|
||||
use std::fs;
|
||||
use std::fs::Metadata;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
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.";
|
||||
|
@ -165,14 +160,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
executor.exec()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum Verbosity {
|
||||
Silent,
|
||||
Changes,
|
||||
Verbose,
|
||||
Normal,
|
||||
}
|
||||
|
||||
struct Chgrper {
|
||||
dest_gid: gid_t,
|
||||
bit_flag: u8,
|
||||
|
@ -201,23 +188,6 @@ impl Chgrper {
|
|||
ret
|
||||
}
|
||||
|
||||
fn chgrp<P: AsRef<Path>>(&self, path: P, dgid: gid_t, follow: bool) -> IOResult<()> {
|
||||
let path = path.as_ref();
|
||||
let s = CString::new(path.as_os_str().as_bytes()).unwrap();
|
||||
let ret = unsafe {
|
||||
if follow {
|
||||
libc::chown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid)
|
||||
} else {
|
||||
lchown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid)
|
||||
}
|
||||
};
|
||||
if ret == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(IOError::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_bind_root<P: AsRef<Path>>(&self, root: P) -> bool {
|
||||
// TODO: is there an equivalent on Windows?
|
||||
|
@ -269,7 +239,24 @@ impl Chgrper {
|
|||
}
|
||||
}
|
||||
|
||||
let ret = self.wrap_chgrp(path, &meta, follow_arg);
|
||||
let ret = match wrap_chgrp(
|
||||
path,
|
||||
&meta,
|
||||
self.dest_gid,
|
||||
follow_arg,
|
||||
self.verbosity.clone(),
|
||||
) {
|
||||
Ok(n) => {
|
||||
show_info!("{}", n);
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
if self.verbosity != Verbosity::Silent {
|
||||
show_info!("{}", e);
|
||||
}
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
if !self.recursive {
|
||||
ret
|
||||
|
@ -297,8 +284,22 @@ impl Chgrper {
|
|||
}
|
||||
};
|
||||
|
||||
ret = self.wrap_chgrp(path, &meta, follow);
|
||||
ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) {
|
||||
Ok(n) => {
|
||||
if n != "" {
|
||||
show_info!("{}", n);
|
||||
}
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
if self.verbosity != Verbosity::Silent {
|
||||
show_info!("{}", e);
|
||||
}
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
|
@ -324,50 +325,4 @@ impl Chgrper {
|
|||
};
|
||||
Some(meta)
|
||||
}
|
||||
|
||||
fn wrap_chgrp<P: AsRef<Path>>(&self, path: P, meta: &Metadata, follow: bool) -> i32 {
|
||||
use self::Verbosity::*;
|
||||
let mut ret = 0;
|
||||
let dest_gid = self.dest_gid;
|
||||
let path = path.as_ref();
|
||||
if let Err(e) = self.chgrp(path, dest_gid, follow) {
|
||||
match self.verbosity {
|
||||
Silent => (),
|
||||
_ => {
|
||||
show_info!("changing group of '{}': {}", path.display(), e);
|
||||
if self.verbosity == Verbose {
|
||||
println!(
|
||||
"failed to change group of {} from {} to {}",
|
||||
path.display(),
|
||||
entries::gid2grp(meta.gid()).unwrap(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
ret = 1;
|
||||
} else {
|
||||
let changed = dest_gid != meta.gid();
|
||||
if changed {
|
||||
match self.verbosity {
|
||||
Changes | Verbose => {
|
||||
println!(
|
||||
"changed group of {} from {} to {}",
|
||||
path.display(),
|
||||
entries::gid2grp(meta.gid()).unwrap(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
} else if self.verbosity == Verbose {
|
||||
println!(
|
||||
"group of {} retained as {}",
|
||||
path.display(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_chmod"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "chmod ~ (uutils) change mode of FILE"
|
||||
|
@ -15,10 +15,11 @@ edition = "2018"
|
|||
path = "src/chmod.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.3"
|
||||
libc = "0.2.42"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["fs", "mode"] }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
walker = "1.0.0"
|
||||
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"
|
||||
|
||||
[[bin]]
|
||||
name = "chmod"
|
||||
|
|
|
@ -7,130 +7,185 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's
|
||||
|
||||
#[cfg(unix)]
|
||||
extern crate libc;
|
||||
extern crate walker;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::fs;
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
use std::path::Path;
|
||||
use uucore::fs::display_permissions_unix;
|
||||
#[cfg(not(windows))]
|
||||
use uucore::mode;
|
||||
use walker::Walker;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
const NAME: &str = "chmod";
|
||||
static SUMMARY: &str = "Change the mode of each FILE to MODE.
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
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 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;
|
||||
} 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")
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.after_help(&after_help[..])
|
||||
.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 delimeter, 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),
|
||||
)
|
||||
.get_matches_from(args);
|
||||
|
||||
let changes = matches.is_present(options::CHANGES);
|
||||
let quiet = matches.is_present(options::QUIET);
|
||||
let verbose = matches.is_present(options::VERBOSE);
|
||||
let preserve_root = matches.is_present(options::PRESERVE_ROOT);
|
||||
let recursive = matches.is_present(options::RECURSIVE);
|
||||
let fmode =
|
||||
matches
|
||||
.value_of(options::REFERENCE)
|
||||
.and_then(|ref fref| match fs::metadata(fref) {
|
||||
Ok(meta) => Some(meta.mode()),
|
||||
Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err),
|
||||
});
|
||||
let 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,
|
||||
}
|
||||
let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
|
||||
let mut cmode = if mode_had_minus_prefix {
|
||||
// clap parsing is finished, now put prefix back
|
||||
Some(format!("-{}", modes))
|
||||
} else {
|
||||
Some(modes.to_string())
|
||||
};
|
||||
let mut files: Vec<String> = matches
|
||||
.values_of(options::FILE)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
if fmode.is_some() {
|
||||
// "--reference" and MODE are mutually exclusive
|
||||
// if "--reference" was used MODE needs to be interpreted as another FILE
|
||||
// it wasn't possible to implement this behavior directly with clap
|
||||
files.push(cmode.unwrap());
|
||||
cmode = None;
|
||||
}
|
||||
|
||||
let chmoder = Chmoder {
|
||||
changes,
|
||||
quiet,
|
||||
verbose,
|
||||
preserve_root,
|
||||
recursive,
|
||||
fmode,
|
||||
cmode,
|
||||
};
|
||||
match chmoder.chmod(files) {
|
||||
Ok(()) => {}
|
||||
Err(e) => return e,
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn sanitize_input(args: &mut Vec<String>) -> Option<String> {
|
||||
// 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 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));
|
||||
if args[i].starts_with("-") {
|
||||
if let Some(second) = args[i].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
|
||||
args[i] = args[i][1..args[i].len()].to_string();
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
false
|
||||
}
|
||||
|
||||
struct Chmoder {
|
||||
|
@ -150,72 +205,67 @@ impl Chmoder {
|
|||
for filename in &files {
|
||||
let filename = &filename[..];
|
||||
let file = Path::new(filename);
|
||||
if file.exists() {
|
||||
if file.is_dir() {
|
||||
if !self.preserve_root || filename != "/" {
|
||||
if self.recursive {
|
||||
let walk_dir = match Walker::new(&file) {
|
||||
Ok(m) => m,
|
||||
Err(f) => {
|
||||
crash!(1, "{}", f.to_string());
|
||||
}
|
||||
};
|
||||
// XXX: here (and elsewhere) we see that this impl will have issues
|
||||
// with non-UTF-8 filenames. Using OsString won't fix this because
|
||||
// on Windows OsStrings cannot be built out of non-UTF-8 chars. One
|
||||
// possible fix is to use CStrings rather than Strings in the args
|
||||
// to chmod() and chmod_file().
|
||||
r = self
|
||||
.chmod(
|
||||
walk_dir
|
||||
.filter_map(|x| match x {
|
||||
Ok(o) => match o.path().into_os_string().to_str() {
|
||||
Some(s) => Some(s.to_owned()),
|
||||
None => None,
|
||||
},
|
||||
Err(_) => None,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.and(r);
|
||||
r = self.chmod_file(&file, filename).and(r);
|
||||
}
|
||||
} else {
|
||||
show_error!("could not change permissions of directory '{}'", filename);
|
||||
r = Err(1);
|
||||
if !file.exists() {
|
||||
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 {
|
||||
r = self.chmod_file(&file, filename).and(r);
|
||||
show_error!("cannot access '{}': No such file or directory", filename);
|
||||
}
|
||||
return Err(1);
|
||||
}
|
||||
if self.recursive && self.preserve_root && filename == "/" {
|
||||
show_error!(
|
||||
"it is dangerous to operate recursively on '{}'\nuse --no-preserve-root to override this failsafe",
|
||||
filename
|
||||
);
|
||||
return Err(1);
|
||||
}
|
||||
if !self.recursive {
|
||||
r = self.chmod_file(&file).and(r);
|
||||
} else {
|
||||
show_error!("no such file or directory '{}'", filename);
|
||||
r = Err(1);
|
||||
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
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn chmod_file(&self, file: &Path, name: &str) -> Result<(), i32> {
|
||||
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
|
||||
// chmod is useless on Windows
|
||||
// it doesn't set any permissions at all
|
||||
// instead it just sets the readonly attribute on the file
|
||||
Err(0)
|
||||
}
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
fn chmod_file(&self, file: &Path, name: &str) -> Result<(), i32> {
|
||||
let mut fperm = match fs::metadata(name) {
|
||||
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
|
||||
let mut fperm = match fs::metadata(file) {
|
||||
Ok(meta) => meta.mode() & 0o7777,
|
||||
Err(err) => {
|
||||
if !self.quiet {
|
||||
show_error!("{}", err);
|
||||
if is_symlink(file) {
|
||||
if self.verbose {
|
||||
println!(
|
||||
"neither symbolic link '{}' nor referent has been changed",
|
||||
file.display()
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
} else {
|
||||
show_error!("{}: '{}'", err, file.display());
|
||||
}
|
||||
return Err(1);
|
||||
}
|
||||
};
|
||||
match self.fmode {
|
||||
Some(mode) => self.change_file(fperm, mode, file, name)?,
|
||||
Some(mode) => self.change_file(fperm, mode, file)?,
|
||||
None => {
|
||||
let cmode_unwrapped = self.cmode.clone().unwrap();
|
||||
for mode in cmode_unwrapped.split(',') {
|
||||
|
@ -228,7 +278,7 @@ impl Chmoder {
|
|||
};
|
||||
match result {
|
||||
Ok(mode) => {
|
||||
self.change_file(fperm, mode, file, name)?;
|
||||
self.change_file(fperm, mode, file)?;
|
||||
fperm = mode;
|
||||
}
|
||||
Err(f) => {
|
||||
|
@ -246,20 +296,18 @@ impl Chmoder {
|
|||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn change_file(&self, fperm: u32, mode: u32, file: &Path, path: &str) -> Result<(), i32> {
|
||||
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),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
} else if let Err(err) =
|
||||
fs::set_permissions(Path::new(path), fs::Permissions::from_mode(mode))
|
||||
{
|
||||
} else if let Err(err) = fs::set_permissions(file, fs::Permissions::from_mode(mode)) {
|
||||
if !self.quiet {
|
||||
show_error!("{}", err);
|
||||
}
|
||||
|
@ -289,3 +337,10 @@ impl Chmoder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_symlink<P: AsRef<Path>>(path: P) -> bool {
|
||||
match fs::symlink_metadata(path) {
|
||||
Ok(m) => m.file_type().is_symlink(),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_chown"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "chown ~ (uutils) change the ownership of FILE"
|
||||
|
@ -15,9 +15,10 @@ edition = "2018"
|
|||
path = "src/chown.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
glob = "0.3.0"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["entries", "fs"] }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
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"
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -11,94 +11,188 @@
|
|||
extern crate uucore;
|
||||
pub use uucore::entries::{self, Group, Locate, Passwd};
|
||||
use uucore::fs::resolve_relative_path;
|
||||
use uucore::libc::{self, gid_t, lchown, uid_t};
|
||||
use uucore::libc::{gid_t, uid_t};
|
||||
use uucore::perms::{wrap_chown, Verbosity};
|
||||
|
||||
use clap::{App, Arg};
|
||||
|
||||
extern crate walkdir;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use std::fs::{self, Metadata};
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use std::io;
|
||||
use std::io::Result as IOResult;
|
||||
|
||||
use std::convert::AsRef;
|
||||
use std::path::Path;
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
static ABOUT: &str = "change file owner and group";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
static SYNTAX: &str =
|
||||
"[OPTION]... [OWNER][:[GROUP]] FILE...\n chown [OPTION]... --reference=RFILE FILE...";
|
||||
static SUMMARY: &str = "change file owner and group";
|
||||
pub mod options {
|
||||
pub mod verbosity {
|
||||
pub static CHANGES: &str = "changes";
|
||||
pub static QUIET: &str = "quiet";
|
||||
pub static SILENT: &str = "silent";
|
||||
pub static VERBOSE: &str = "verbose";
|
||||
}
|
||||
pub mod preserve_root {
|
||||
pub static PRESERVE: &str = "preserve-root";
|
||||
pub static NO_PRESERVE: &str = "no-preserve-root";
|
||||
}
|
||||
pub mod dereference {
|
||||
pub static DEREFERENCE: &str = "dereference";
|
||||
pub static NO_DEREFERENCE: &str = "no-dereference";
|
||||
}
|
||||
pub static FROM: &str = "from";
|
||||
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";
|
||||
}
|
||||
|
||||
static ARG_OWNER: &str = "owner";
|
||||
static ARG_FILES: &str = "files";
|
||||
|
||||
const FTS_COMFOLLOW: u8 = 1;
|
||||
const FTS_PHYSICAL: u8 = 1 << 1;
|
||||
const FTS_LOGICAL: u8 = 1 << 2;
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!(
|
||||
"{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...",
|
||||
executable!()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
|
||||
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)")
|
||||
let usage = get_usage();
|
||||
|
||||
.optopt("", "from", "change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute", "CURRENT_OWNER:CURRENT_GROUP")
|
||||
.optopt("",
|
||||
"reference",
|
||||
"use RFILE's owner and group rather than specifying OWNER:GROUP values",
|
||||
"RFILE")
|
||||
.optflag("",
|
||||
"no-preserve-root",
|
||||
"do not treat '/' specially (the default)")
|
||||
.optflag("", "preserve-root", "fail to operate recursively on '/'")
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.arg(
|
||||
Arg::with_name(options::verbosity::CHANGES)
|
||||
.short("c")
|
||||
.long(options::verbosity::CHANGES)
|
||||
.help("like verbose but report only when a change is made"),
|
||||
)
|
||||
.arg(Arg::with_name(options::dereference::DEREFERENCE).long(options::dereference::DEREFERENCE).help(
|
||||
"affect the referent of each symbolic link (this is the default), rather than the symbolic link itself",
|
||||
))
|
||||
.arg(
|
||||
Arg::with_name(options::dereference::NO_DEREFERENCE)
|
||||
.short("h")
|
||||
.long(options::dereference::NO_DEREFERENCE)
|
||||
.help(
|
||||
"affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::FROM)
|
||||
.long(options::FROM)
|
||||
.help(
|
||||
"change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute",
|
||||
)
|
||||
.value_name("CURRENT_OWNER:CURRENT_GROUP"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::preserve_root::PRESERVE)
|
||||
.long(options::preserve_root::PRESERVE)
|
||||
.help("fail to operate recursively on '/'"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::preserve_root::NO_PRESERVE)
|
||||
.long(options::preserve_root::NO_PRESERVE)
|
||||
.help("do not treat '/' specially (the default)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::verbosity::QUIET)
|
||||
.long(options::verbosity::QUIET)
|
||||
.help("suppress most error messages"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::RECURSIVE)
|
||||
.short("R")
|
||||
.long(options::RECURSIVE)
|
||||
.help("operate on files and directories recursively"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::REFERENCE)
|
||||
.long(options::REFERENCE)
|
||||
.help("use RFILE's owner and group rather than specifying OWNER:GROUP values")
|
||||
.value_name("RFILE")
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(Arg::with_name(options::verbosity::SILENT).short("f").long(options::verbosity::SILENT))
|
||||
.arg(
|
||||
Arg::with_name(options::traverse::TRAVERSE)
|
||||
.short(options::traverse::TRAVERSE)
|
||||
.help("if a command line argument is a symbolic link to a directory, traverse it")
|
||||
.overrides_with_all(&[options::traverse::EVERY, options::traverse::NO_TRAVERSE]),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::traverse::EVERY)
|
||||
.short(options::traverse::EVERY)
|
||||
.help("traverse every symbolic link to a directory encountered")
|
||||
.overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::NO_TRAVERSE]),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::traverse::NO_TRAVERSE)
|
||||
.short(options::traverse::NO_TRAVERSE)
|
||||
.help("do not traverse any symbolic links (default)")
|
||||
.overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::verbosity::VERBOSE)
|
||||
.long(options::verbosity::VERBOSE)
|
||||
.help("output a diagnostic for every file processed"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(ARG_OWNER)
|
||||
.multiple(false)
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(ARG_FILES)
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.get_matches_from(args);
|
||||
|
||||
.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)");
|
||||
/* First arg is the owner/group */
|
||||
let owner = matches.value_of(ARG_OWNER).unwrap();
|
||||
|
||||
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,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
/* 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 matches = opts.parse(args);
|
||||
let recursive = matches.opt_present("recursive");
|
||||
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 {
|
||||
|
@ -111,17 +205,19 @@ 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
|
||||
};
|
||||
|
||||
let filter = if let Some(spec) = matches.opt_str("from") {
|
||||
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),
|
||||
|
@ -136,18 +232,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
IfFrom::All
|
||||
};
|
||||
|
||||
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 mut files;
|
||||
let dest_uid: Option<u32>;
|
||||
let dest_gid: Option<u32>;
|
||||
if let Some(file) = matches.opt_str("reference") {
|
||||
if let Some(file) = matches.value_of(options::REFERENCE) {
|
||||
match fs::metadata(&file) {
|
||||
Ok(meta) => {
|
||||
dest_gid = Some(meta.gid());
|
||||
|
@ -158,9 +245,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
return 1;
|
||||
}
|
||||
}
|
||||
files = matches.free;
|
||||
} else {
|
||||
match parse_spec(&matches.free[0]) {
|
||||
match parse_spec(&owner) {
|
||||
Ok((u, g)) => {
|
||||
dest_uid = u;
|
||||
dest_gid = g;
|
||||
|
@ -170,8 +256,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
return 1;
|
||||
}
|
||||
}
|
||||
files = matches.free;
|
||||
files.remove(0);
|
||||
}
|
||||
let executor = Chowner {
|
||||
bit_flag,
|
||||
|
@ -197,7 +281,7 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
|
|||
Ok((
|
||||
Some(match Passwd::locate(args[0]) {
|
||||
Ok(v) => v.uid(),
|
||||
_ => return Err(format!("invalid user: ‘{}’", spec)),
|
||||
_ => return Err(format!("invalid user: '{}'", spec)),
|
||||
}),
|
||||
None,
|
||||
))
|
||||
|
@ -206,18 +290,18 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
|
|||
None,
|
||||
Some(match Group::locate(args[1]) {
|
||||
Ok(v) => v.gid(),
|
||||
_ => return Err(format!("invalid group: ‘{}’", spec)),
|
||||
_ => 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)),
|
||||
_ => return Err(format!("invalid user: '{}'", spec)),
|
||||
}),
|
||||
Some(match Group::locate(args[1]) {
|
||||
Ok(v) => v.gid(),
|
||||
_ => return Err(format!("invalid group: ‘{}’", spec)),
|
||||
_ => return Err(format!("invalid group: '{}'", spec)),
|
||||
}),
|
||||
))
|
||||
} else {
|
||||
|
@ -225,14 +309,6 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum Verbosity {
|
||||
Silent,
|
||||
Changes,
|
||||
Verbose,
|
||||
Normal,
|
||||
}
|
||||
|
||||
enum IfFrom {
|
||||
All,
|
||||
User(u32),
|
||||
|
@ -270,29 +346,6 @@ impl Chowner {
|
|||
ret
|
||||
}
|
||||
|
||||
fn chown<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
duid: uid_t,
|
||||
dgid: gid_t,
|
||||
follow: bool,
|
||||
) -> IOResult<()> {
|
||||
let path = path.as_ref();
|
||||
let s = CString::new(path.as_os_str().as_bytes()).unwrap();
|
||||
let ret = unsafe {
|
||||
if follow {
|
||||
libc::chown(s.as_ptr(), duid, dgid)
|
||||
} else {
|
||||
lchown(s.as_ptr(), duid, dgid)
|
||||
}
|
||||
};
|
||||
if ret == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 {
|
||||
let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL;
|
||||
let path = root.as_ref();
|
||||
|
@ -329,7 +382,27 @@ impl Chowner {
|
|||
}
|
||||
|
||||
let ret = if self.matched(meta.uid(), meta.gid()) {
|
||||
self.wrap_chown(path, &meta, follow_arg)
|
||||
match wrap_chown(
|
||||
path,
|
||||
&meta,
|
||||
self.dest_uid,
|
||||
self.dest_gid,
|
||||
follow_arg,
|
||||
self.verbosity.clone(),
|
||||
) {
|
||||
Ok(n) => {
|
||||
if n != "" {
|
||||
show_info!("{}", n);
|
||||
}
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
if self.verbosity != Verbosity::Silent {
|
||||
show_info!("{}", e);
|
||||
}
|
||||
1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
@ -364,7 +437,27 @@ impl Chowner {
|
|||
continue;
|
||||
}
|
||||
|
||||
ret = self.wrap_chown(path, &meta, follow);
|
||||
ret = match wrap_chown(
|
||||
path,
|
||||
&meta,
|
||||
self.dest_uid,
|
||||
self.dest_gid,
|
||||
follow,
|
||||
self.verbosity.clone(),
|
||||
) {
|
||||
Ok(n) => {
|
||||
if n != "" {
|
||||
show_info!("{}", n);
|
||||
}
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
if self.verbosity != Verbosity::Silent {
|
||||
show_info!("{}", e);
|
||||
}
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
@ -392,58 +485,6 @@ impl Chowner {
|
|||
Some(meta)
|
||||
}
|
||||
|
||||
fn wrap_chown<P: AsRef<Path>>(&self, path: P, meta: &Metadata, follow: bool) -> i32 {
|
||||
use self::Verbosity::*;
|
||||
let mut ret = 0;
|
||||
let dest_uid = self.dest_uid.unwrap_or_else(|| meta.uid());
|
||||
let dest_gid = self.dest_gid.unwrap_or_else(|| meta.gid());
|
||||
let path = path.as_ref();
|
||||
if let Err(e) = self.chown(path, dest_uid, dest_gid, follow) {
|
||||
match self.verbosity {
|
||||
Silent => (),
|
||||
_ => {
|
||||
show_info!("changing ownership of '{}': {}", path.display(), e);
|
||||
if self.verbosity == Verbose {
|
||||
println!(
|
||||
"failed to change ownership of {} from {}:{} to {}:{}",
|
||||
path.display(),
|
||||
entries::uid2usr(meta.uid()).unwrap(),
|
||||
entries::gid2grp(meta.gid()).unwrap(),
|
||||
entries::uid2usr(dest_uid).unwrap(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
ret = 1;
|
||||
} else {
|
||||
let changed = dest_uid != meta.uid() || dest_gid != meta.gid();
|
||||
if changed {
|
||||
match self.verbosity {
|
||||
Changes | Verbose => {
|
||||
println!(
|
||||
"changed ownership of {} from {}:{} to {}:{}",
|
||||
path.display(),
|
||||
entries::uid2usr(meta.uid()).unwrap(),
|
||||
entries::gid2grp(meta.gid()).unwrap(),
|
||||
entries::uid2usr(dest_uid).unwrap(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
} else if self.verbosity == Verbose {
|
||||
println!(
|
||||
"ownership of {} retained as {}:{}",
|
||||
path.display(),
|
||||
entries::uid2usr(dest_uid).unwrap(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn matched(&self, uid: uid_t, gid: gid_t) -> bool {
|
||||
match self.filter {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_chroot"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "chroot ~ (uutils) run COMMAND under a new root directory"
|
||||
|
@ -15,9 +15,9 @@ edition = "2018"
|
|||
path = "src/chroot.rs"
|
||||
|
||||
[dependencies]
|
||||
getopts = "0.2.18"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["entries"] }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
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]]
|
||||
name = "chroot"
|
||||
|
|
|
@ -8,65 +8,83 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) NEWROOT Userspec pstatus
|
||||
|
||||
extern crate getopts;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
use clap::{App, Arg};
|
||||
use std::ffi::CString;
|
||||
use std::io::Error;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use uucore::entries;
|
||||
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::io::Error;
|
||||
use std::iter::FromIterator;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
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 fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
|
||||
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
|
||||
.optopt(
|
||||
"u",
|
||||
"user",
|
||||
"User (ID or name) to switch before running the program",
|
||||
"USER",
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(SYNTAX)
|
||||
.arg(Arg::with_name(options::NEWROOT).hidden(true).required(true))
|
||||
.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"),
|
||||
)
|
||||
.optopt("g", "group", "Group (ID or name) to switch to", "GROUP")
|
||||
.optopt(
|
||||
"G",
|
||||
"groups",
|
||||
"Comma-separated list of groups to switch to",
|
||||
"GROUP1,GROUP2...",
|
||||
.arg(
|
||||
Arg::with_name(options::GROUP)
|
||||
.short("g")
|
||||
.long(options::GROUP)
|
||||
.help("Group (ID or name) to switch to")
|
||||
.value_name("GROUP"),
|
||||
)
|
||||
.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",
|
||||
.arg(
|
||||
Arg::with_name(options::GROUPS)
|
||||
.short("G")
|
||||
.long(options::GROUPS)
|
||||
.help("Comma-separated list of groups to switch to")
|
||||
.value_name("GROUP1,GROUP2..."),
|
||||
)
|
||||
.parse(args);
|
||||
|
||||
if matches.free.is_empty() {
|
||||
println!("Missing operand: NEWROOT");
|
||||
println!("Try `{} --help` for more information.", NAME);
|
||||
return 1;
|
||||
}
|
||||
.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"),
|
||||
)
|
||||
.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,
|
||||
|
@ -75,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
);
|
||||
}
|
||||
|
||||
let command: Vec<&str> = match matches.free.len() {
|
||||
let command: Vec<&str> = match matches.args.len() {
|
||||
1 => {
|
||||
let shell: &str = match user_shell {
|
||||
Err(_) => default_shell,
|
||||
|
@ -83,7 +101,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
};
|
||||
vec![shell, default_option]
|
||||
}
|
||||
_ => matches.free[1..].iter().map(|x| &x[..]).collect(),
|
||||
_ => {
|
||||
let mut vector: Vec<&str> = Vec::new();
|
||||
for (&k, v) in matches.args.iter() {
|
||||
vector.push(k.clone());
|
||||
vector.push(&v.vals[0].to_str().unwrap());
|
||||
}
|
||||
vector
|
||||
}
|
||||
};
|
||||
|
||||
set_context(&newroot, &matches);
|
||||
|
@ -96,37 +121,30 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
if pstatus.success() {
|
||||
0
|
||||
} else {
|
||||
match pstatus.code() {
|
||||
Some(i) => i,
|
||||
None => -1,
|
||||
}
|
||||
pstatus.code().unwrap_or(-1)
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
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) => {
|
||||
let s: Vec<&str> = u.split(':').collect();
|
||||
if s.len() != 2 {
|
||||
if s.len() != 2 || s.iter().any(|&spec| spec == "") {
|
||||
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);
|
||||
|
@ -170,7 +188,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()) }
|
||||
}
|
||||
|
@ -182,11 +200,13 @@ fn set_groups(groups: Vec<libc::gid_t>) -> libc::c_int {
|
|||
|
||||
fn set_groups_from_str(groups: &str) {
|
||||
if !groups.is_empty() {
|
||||
let groups_vec: Vec<libc::gid_t> =
|
||||
FromIterator::from_iter(groups.split(',').map(|x| match entries::grp2gid(x) {
|
||||
let groups_vec: Vec<libc::gid_t> = groups
|
||||
.split(',')
|
||||
.map(|x| match entries::grp2gid(x) {
|
||||
Ok(g) => g,
|
||||
_ => crash!(1, "no such group: {}", x),
|
||||
}));
|
||||
})
|
||||
.collect();
|
||||
let err = set_groups(groups_vec);
|
||||
if err != 0 {
|
||||
crash!(1, "cannot set groups: {}", Error::last_os_error())
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_cksum"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "cksum ~ (uutils) display CRC and size of input"
|
||||
|
@ -15,9 +15,10 @@ edition = "2018"
|
|||
path = "src/cksum.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
libc = "0.2.42"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "cksum"
|
||||
|
|
|
@ -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::{App, Arg};
|
||||
use std::fs::File;
|
||||
use std::io::{self, stdin, BufReader, Read};
|
||||
use std::path::Path;
|
||||
|
||||
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 VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
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_cond = 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 cond_table = [else_body, if_body];
|
||||
|
||||
crc = cond_table[(if_cond != 0) as usize];
|
||||
});
|
||||
|
||||
crc
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn crc_update(crc: u32, input: u8) -> u32 {
|
||||
|
@ -48,7 +140,20 @@ 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))
|
||||
}
|
||||
};
|
||||
|
@ -68,13 +173,27 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
|
|||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
//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();
|
||||
|
||||
let files = matches.free;
|
||||
let matches = App::new(executable!())
|
||||
.name(NAME)
|
||||
.version(VERSION)
|
||||
.about(SUMMARY)
|
||||
.usage(SYNTAX)
|
||||
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
|
||||
.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("-") {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_comm"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "comm ~ (uutils) compare sorted inputs"
|
||||
|
@ -15,10 +15,10 @@ edition = "2018"
|
|||
path = "src/comm.rs"
|
||||
|
||||
[dependencies]
|
||||
getopts = "0.2.18"
|
||||
clap = "2.33"
|
||||
libc = "0.2.42"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "comm"
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) delim mkdelim
|
||||
|
||||
extern crate getopts;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
|
@ -17,21 +15,34 @@ use std::fs::File;
|
|||
use std::io::{self, stdin, BufRead, BufReader, Stdin};
|
||||
use std::path::Path;
|
||||
|
||||
static SYNTAX: &str = "[OPTIONS] FILE1 FILE2";
|
||||
static SUMMARY: &str = "Compare sorted files line by line";
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -41,7 +52,7 @@ fn mkdelim(col: usize, opts: &getopts::Matches) -> String {
|
|||
fn ensure_nl(line: &mut String) {
|
||||
match line.chars().last() {
|
||||
Some('\n') => (),
|
||||
_ => line.push_str("\n"),
|
||||
_ => line.push('\n'),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +70,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();
|
||||
|
@ -82,7 +93,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);
|
||||
}
|
||||
|
@ -90,7 +101,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);
|
||||
}
|
||||
|
@ -98,7 +109,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);
|
||||
}
|
||||
|
@ -122,21 +133,42 @@ 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 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)",
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.after_help(LONG_HELP)
|
||||
.arg(
|
||||
Arg::with_name(options::COLUMN_1)
|
||||
.short(options::COLUMN_1)
|
||||
.help("suppress column 1 (lines unique to FILE1)"),
|
||||
)
|
||||
.optopt("", "output-delimiter", "separate columns with STR", "STR")
|
||||
.parse(args);
|
||||
.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))
|
||||
.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);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_cp"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = [
|
||||
"Jordy Dickinson <jordy.dickinson@gmail.com>",
|
||||
"Joshua S. Miller <jsmiller@uchicago.edu>",
|
||||
|
@ -19,13 +19,13 @@ edition = "2018"
|
|||
path = "src/cp.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.32"
|
||||
clap = "2.33"
|
||||
filetime = "0.2"
|
||||
libc = "0.2.42"
|
||||
libc = "0.2.85"
|
||||
quick-error = "1.2.3"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["fs"] }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
walkdir = "2.2.8"
|
||||
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"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
ioctl-sys = "0.5.2"
|
||||
|
|
|
@ -10,22 +10,14 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) ficlone linkgs lstat nlink nlinks pathbuf reflink strs xattrs
|
||||
|
||||
extern crate clap;
|
||||
extern crate filetime;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[macro_use]
|
||||
extern crate ioctl_sys;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate quick_error;
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
extern crate walkdir;
|
||||
#[cfg(unix)]
|
||||
extern crate xattr;
|
||||
|
||||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::fileapi::CreateFileW;
|
||||
#[cfg(windows)]
|
||||
|
@ -43,7 +35,6 @@ use std::ffi::CString;
|
|||
#[cfg(windows)]
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io;
|
||||
|
@ -121,7 +112,7 @@ macro_rules! or_continue(
|
|||
})
|
||||
);
|
||||
|
||||
/// Prompts the user yes/no and returns `true` they if successfully
|
||||
/// Prompts the user yes/no and returns `true` if they successfully
|
||||
/// answered yes.
|
||||
macro_rules! prompt_yes(
|
||||
($($args:tt)+) => ({
|
||||
|
@ -216,6 +207,7 @@ pub struct Options {
|
|||
one_file_system: bool,
|
||||
overwrite: OverwriteMode,
|
||||
parents: bool,
|
||||
strip_trailing_slashes: bool,
|
||||
reflink: bool,
|
||||
reflink_mode: ReflinkMode,
|
||||
preserve_attributes: Vec<Attribute>,
|
||||
|
@ -231,11 +223,6 @@ static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
|
|||
static EXIT_OK: i32 = 0;
|
||||
static EXIT_ERR: i32 = 1;
|
||||
|
||||
/// Prints the version
|
||||
fn print_version() {
|
||||
println!("{} {}", executable!(), VERSION);
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!(
|
||||
"{0} [OPTION]... [-T] SOURCE DEST
|
||||
|
@ -262,6 +249,7 @@ static OPT_NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs
|
|||
static OPT_NO_PRESERVE: &str = "no-preserve";
|
||||
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory";
|
||||
static OPT_ONE_FILE_SYSTEM: &str = "one-file-system";
|
||||
static OPT_PARENT: &str = "parent";
|
||||
static OPT_PARENTS: &str = "parents";
|
||||
static OPT_PATHS: &str = "paths";
|
||||
static OPT_PRESERVE: &str = "preserve";
|
||||
|
@ -277,7 +265,6 @@ static OPT_SYMBOLIC_LINK: &str = "symbolic-link";
|
|||
static OPT_TARGET_DIRECTORY: &str = "target-directory";
|
||||
static OPT_UPDATE: &str = "update";
|
||||
static OPT_VERBOSE: &str = "verbose";
|
||||
static OPT_VERSION: &str = "version";
|
||||
|
||||
#[cfg(unix)]
|
||||
static PRESERVABLE_ATTRIBUTES: &[&str] = &[
|
||||
|
@ -325,10 +312,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(OPT_NO_TARGET_DIRECTORY)
|
||||
.conflicts_with(OPT_TARGET_DIRECTORY)
|
||||
.help("Treat DEST as a regular file and not a directory"))
|
||||
.arg(Arg::with_name(OPT_VERSION)
|
||||
.short("V")
|
||||
.long(OPT_VERSION)
|
||||
.help("output version information and exit"))
|
||||
.arg(Arg::with_name(OPT_INTERACTIVE)
|
||||
.short("i")
|
||||
.long(OPT_INTERACTIVE)
|
||||
|
@ -347,10 +330,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.arg(Arg::with_name(OPT_RECURSIVE)
|
||||
.short("r")
|
||||
.long(OPT_RECURSIVE)
|
||||
.help("copy directories recursively"))
|
||||
// --archive sets this option
|
||||
.help("copy directories recursively"))
|
||||
.arg(Arg::with_name(OPT_RECURSIVE_ALIAS)
|
||||
.short("R")
|
||||
.help("same as -r"))
|
||||
.arg(Arg::with_name(OPT_STRIP_TRAILING_SLASHES)
|
||||
.long(OPT_STRIP_TRAILING_SLASHES)
|
||||
.help("remove any trailing slashes from each SOURCE argument"))
|
||||
.arg(Arg::with_name(OPT_VERBOSE)
|
||||
.short("v")
|
||||
.long(OPT_VERBOSE)
|
||||
|
@ -405,7 +392,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.use_delimiter(true)
|
||||
.possible_values(PRESERVABLE_ATTRIBUTES)
|
||||
.value_name("ATTR_LIST")
|
||||
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE, OPT_ARCHIVE])
|
||||
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE])
|
||||
// -d sets this option
|
||||
// --archive sets this option
|
||||
.help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\
|
||||
if possible additional attributes: context, links, xattr, all"))
|
||||
.arg(Arg::with_name(OPT_PRESERVE_DEFAULT_ATTRIBUTES)
|
||||
|
@ -419,45 +408,44 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.value_name("ATTR_LIST")
|
||||
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_ARCHIVE])
|
||||
.help("don't preserve the specified attributes"))
|
||||
.arg(Arg::with_name(OPT_PARENTS)
|
||||
.long(OPT_PARENTS)
|
||||
.alias(OPT_PARENT)
|
||||
.help("use full source file name under DIRECTORY"))
|
||||
.arg(Arg::with_name(OPT_NO_DEREFERENCE)
|
||||
.short("-P")
|
||||
.long(OPT_NO_DEREFERENCE)
|
||||
.conflicts_with(OPT_DEREFERENCE)
|
||||
// -d sets this option
|
||||
.help("never follow symbolic links in SOURCE"))
|
||||
|
||||
// TODO: implement the following args
|
||||
.arg(Arg::with_name(OPT_ARCHIVE)
|
||||
.short("a")
|
||||
.long(OPT_ARCHIVE)
|
||||
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE])
|
||||
.help("NotImplemented: same as -dR --preserve=all"))
|
||||
.arg(Arg::with_name(OPT_COPY_CONTENTS)
|
||||
.long(OPT_COPY_CONTENTS)
|
||||
.conflicts_with(OPT_ATTRIBUTES_ONLY)
|
||||
.help("NotImplemented: copy contents of special files when recursive"))
|
||||
.arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
|
||||
.short("d")
|
||||
.help("NotImplemented: same as --no-dereference --preserve=links"))
|
||||
.arg(Arg::with_name(OPT_DEREFERENCE)
|
||||
.short("L")
|
||||
.long(OPT_DEREFERENCE)
|
||||
.conflicts_with(OPT_NO_DEREFERENCE)
|
||||
.help("NotImplemented: always follow symbolic links in SOURCE"))
|
||||
.arg(Arg::with_name(OPT_PARENTS)
|
||||
.long(OPT_PARENTS)
|
||||
.help("NotImplemented: use full source file name under DIRECTORY"))
|
||||
.help("always follow symbolic links in SOURCE"))
|
||||
.arg(Arg::with_name(OPT_ARCHIVE)
|
||||
.short("a")
|
||||
.long(OPT_ARCHIVE)
|
||||
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE])
|
||||
.help("Same as -dR --preserve=all"))
|
||||
.arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
|
||||
.short("d")
|
||||
.help("same as --no-dereference --preserve=links"))
|
||||
.arg(Arg::with_name(OPT_ONE_FILE_SYSTEM)
|
||||
.short("x")
|
||||
.long(OPT_ONE_FILE_SYSTEM)
|
||||
.help("stay on this file system"))
|
||||
|
||||
// TODO: implement the following args
|
||||
.arg(Arg::with_name(OPT_COPY_CONTENTS)
|
||||
.long(OPT_COPY_CONTENTS)
|
||||
.conflicts_with(OPT_ATTRIBUTES_ONLY)
|
||||
.help("NotImplemented: copy contents of special files when recursive"))
|
||||
.arg(Arg::with_name(OPT_SPARSE)
|
||||
.long(OPT_SPARSE)
|
||||
.takes_value(true)
|
||||
.value_name("WHEN")
|
||||
.help("NotImplemented: control creation of sparse files. See below"))
|
||||
.arg(Arg::with_name(OPT_STRIP_TRAILING_SLASHES)
|
||||
.long(OPT_STRIP_TRAILING_SLASHES)
|
||||
.help("NotImplemented: remove any trailing slashes from each SOURCE argument"))
|
||||
.arg(Arg::with_name(OPT_ONE_FILE_SYSTEM)
|
||||
.short("x")
|
||||
.long(OPT_ONE_FILE_SYSTEM)
|
||||
.help("NotImplemented: stay on this file system"))
|
||||
.arg(Arg::with_name(OPT_CONTEXT)
|
||||
.long(OPT_CONTEXT)
|
||||
.takes_value(true)
|
||||
|
@ -472,14 +460,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.multiple(true))
|
||||
.get_matches_from(args);
|
||||
|
||||
if matches.is_present(OPT_VERSION) {
|
||||
print_version();
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches));
|
||||
let paths: Vec<String> = matches
|
||||
.values_of("paths")
|
||||
.values_of(OPT_PATHS)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
|
@ -557,22 +540,30 @@ impl FromStr for Attribute {
|
|||
return Err(Error::InvalidArgument(format!(
|
||||
"invalid attribute '{}'",
|
||||
value
|
||||
)))
|
||||
)));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn add_all_attributes() -> Vec<Attribute> {
|
||||
let mut attr = Vec::new();
|
||||
#[cfg(unix)]
|
||||
attr.push(Attribute::Mode);
|
||||
attr.push(Attribute::Ownership);
|
||||
attr.push(Attribute::Timestamps);
|
||||
attr.push(Attribute::Context);
|
||||
attr.push(Attribute::Xattr);
|
||||
attr.push(Attribute::Links);
|
||||
attr
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn from_matches(matches: &ArgMatches) -> CopyResult<Options> {
|
||||
let not_implemented_opts = vec![
|
||||
OPT_ARCHIVE,
|
||||
OPT_COPY_CONTENTS,
|
||||
OPT_NO_DEREFERENCE_PRESERVE_LINKS,
|
||||
OPT_DEREFERENCE,
|
||||
OPT_PARENTS,
|
||||
OPT_SPARSE,
|
||||
OPT_STRIP_TRAILING_SLASHES,
|
||||
#[cfg(not(any(windows, unix)))]
|
||||
OPT_ONE_FILE_SYSTEM,
|
||||
OPT_CONTEXT,
|
||||
#[cfg(windows)]
|
||||
|
@ -605,13 +596,7 @@ impl Options {
|
|||
let mut attributes = Vec::new();
|
||||
for attribute_str in attribute_strs {
|
||||
if attribute_str == "all" {
|
||||
#[cfg(unix)]
|
||||
attributes.push(Attribute::Mode);
|
||||
attributes.push(Attribute::Ownership);
|
||||
attributes.push(Attribute::Timestamps);
|
||||
attributes.push(Attribute::Context);
|
||||
attributes.push(Attribute::Xattr);
|
||||
attributes.push(Attribute::Links);
|
||||
attributes = add_all_attributes();
|
||||
break;
|
||||
} else {
|
||||
attributes.push(Attribute::from_str(attribute_str)?);
|
||||
|
@ -620,6 +605,11 @@ impl Options {
|
|||
attributes
|
||||
}
|
||||
}
|
||||
} else if matches.is_present(OPT_ARCHIVE) {
|
||||
// --archive is used. Same as --preserve=all
|
||||
add_all_attributes()
|
||||
} else if matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) {
|
||||
vec![Attribute::Links]
|
||||
} else if matches.is_present(OPT_PRESERVE_DEFAULT_ATTRIBUTES) {
|
||||
DEFAULT_ATTRIBUTES.to_vec()
|
||||
} else {
|
||||
|
@ -631,13 +621,17 @@ impl Options {
|
|||
copy_contents: matches.is_present(OPT_COPY_CONTENTS),
|
||||
copy_mode: CopyMode::from_matches(matches),
|
||||
dereference: matches.is_present(OPT_DEREFERENCE),
|
||||
no_dereference: matches.is_present(OPT_NO_DEREFERENCE),
|
||||
// No dereference is set with -p, -d and --archive
|
||||
no_dereference: matches.is_present(OPT_NO_DEREFERENCE)
|
||||
|| matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
|
||||
|| matches.is_present(OPT_ARCHIVE),
|
||||
one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM),
|
||||
overwrite: OverwriteMode::from_matches(matches),
|
||||
parents: matches.is_present(OPT_PARENTS),
|
||||
backup_suffix: matches.value_of(OPT_SUFFIX).unwrap().to_string(),
|
||||
update: matches.is_present(OPT_UPDATE),
|
||||
verbose: matches.is_present(OPT_VERBOSE),
|
||||
strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES),
|
||||
reflink: matches.is_present(OPT_REFLINK),
|
||||
reflink_mode: {
|
||||
if let Some(reflink) = matches.value_of(OPT_REFLINK) {
|
||||
|
@ -648,7 +642,7 @@ impl Options {
|
|||
return Err(Error::InvalidArgument(format!(
|
||||
"invalid argument '{}' for \'reflink\'",
|
||||
value
|
||||
)))
|
||||
)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -695,7 +689,7 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
|
|||
return Err(format!("extra operand {:?}", paths[2]).into());
|
||||
}
|
||||
|
||||
let (sources, target) = match options.target_dir {
|
||||
let (mut sources, target) = match options.target_dir {
|
||||
Some(ref target) => {
|
||||
// All path args are sources, and the target dir was
|
||||
// specified separately
|
||||
|
@ -709,6 +703,12 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
|
|||
}
|
||||
};
|
||||
|
||||
if options.strip_trailing_slashes {
|
||||
for source in sources.iter_mut() {
|
||||
*source = source.components().as_path().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
Ok((sources, target))
|
||||
}
|
||||
|
||||
|
@ -812,13 +812,19 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()
|
|||
let dest = construct_dest_path(source, target, &target_type, options)?;
|
||||
preserve_hardlinks(&mut hard_links, source, dest, &mut found_hard_link).unwrap();
|
||||
}
|
||||
|
||||
if !found_hard_link {
|
||||
if let Err(error) = copy_source(source, target, &target_type, options) {
|
||||
show_error!("{}", error);
|
||||
match error {
|
||||
Error::Skipped(_) => (),
|
||||
_ => non_fatal_errors = true,
|
||||
// When using --no-clobber, we don't want to show
|
||||
// an error message
|
||||
Error::NotAllFilesCopied => (),
|
||||
Error::Skipped(_) => {
|
||||
show_error!("{}", error);
|
||||
}
|
||||
_ => {
|
||||
show_error!("{}", error);
|
||||
non_fatal_errors = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -846,9 +852,17 @@ fn construct_dest_path(
|
|||
.into());
|
||||
}
|
||||
|
||||
if options.parents && !target.is_dir() {
|
||||
return Err("with --parents, the destination must be a directory".into());
|
||||
}
|
||||
|
||||
Ok(match *target_type {
|
||||
TargetType::Directory => {
|
||||
let root = source_path.parent().unwrap_or(source_path);
|
||||
let root = if options.parents {
|
||||
Path::new("")
|
||||
} else {
|
||||
source_path.parent().unwrap_or(source_path)
|
||||
};
|
||||
localize_to_target(root, source_path, target)?
|
||||
}
|
||||
TargetType::File => target.to_path_buf(),
|
||||
|
@ -924,10 +938,10 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
|
|||
#[cfg(any(windows, target_os = "redox"))]
|
||||
let mut hard_links: Vec<(String, u64)> = vec![];
|
||||
|
||||
for path in WalkDir::new(root) {
|
||||
for path in WalkDir::new(root).same_file_system(options.one_file_system) {
|
||||
let p = or_continue!(path);
|
||||
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
|
||||
let path = if options.no_dereference && is_symlink {
|
||||
let path = if (options.no_dereference || options.dereference) && is_symlink {
|
||||
// we are dealing with a symlink. Don't follow it
|
||||
match env::current_dir() {
|
||||
Ok(cwd) => cwd.join(resolve_relative_path(p.path())),
|
||||
|
@ -941,7 +955,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
|
|||
Some(parent) => {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// On Windows, some pathes are starting with \\?
|
||||
// On Windows, some paths are starting with \\?
|
||||
// but not always, so, make sure that we are consistent for strip_prefix
|
||||
// See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file for more info
|
||||
let parent_can = adjust_canonicalization(parent);
|
||||
|
@ -968,7 +982,19 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
|
|||
let dest = local_to_target.as_path().to_path_buf();
|
||||
preserve_hardlinks(&mut hard_links, &source, dest, &mut found_hard_link).unwrap();
|
||||
if !found_hard_link {
|
||||
copy_file(path.as_path(), local_to_target.as_path(), options)?;
|
||||
match copy_file(path.as_path(), local_to_target.as_path(), options) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
if fs::symlink_metadata(&source)?.file_type().is_symlink() {
|
||||
// silent the error with a symlink
|
||||
// In case we do --archive, we might copy the symlink
|
||||
// before the file itself
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}?;
|
||||
}
|
||||
} else {
|
||||
copy_file(path.as_path(), local_to_target.as_path(), options)?;
|
||||
|
@ -982,11 +1008,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
|
|||
impl OverwriteMode {
|
||||
fn verify(&self, path: &Path) -> CopyResult<()> {
|
||||
match *self {
|
||||
OverwriteMode::NoClobber => Err(Error::Skipped(format!(
|
||||
"Not overwriting {} because of option '{}'",
|
||||
path.display(),
|
||||
OPT_NO_CLOBBER
|
||||
))),
|
||||
OverwriteMode::NoClobber => Err(Error::NotAllFilesCopied),
|
||||
OverwriteMode::Interactive(_) => {
|
||||
if prompt_yes!("{}: overwrite {}? ", executable!(), path.display()) {
|
||||
Ok(())
|
||||
|
@ -1041,12 +1063,16 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
|
||||
Ok(std::os::unix::fs::symlink(source, dest).context(context)?)
|
||||
match std::os::unix::fs::symlink(source, dest).context(context) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
@ -1159,11 +1185,9 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
|||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
for attribute in &options.preserve_attributes {
|
||||
copy_attribute(source, dest, attribute)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1224,7 +1248,16 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
|
|||
dest.into()
|
||||
};
|
||||
symlink_file(&link, &dest, &*context_for(&link, &dest))?;
|
||||
} else if source.to_string_lossy() == "/dev/null" {
|
||||
/* workaround a limitation of fs::copy
|
||||
* https://github.com/rust-lang/rust/issues/79390
|
||||
*/
|
||||
File::create(dest)?;
|
||||
} else {
|
||||
if options.parents {
|
||||
let parent = dest.parent().unwrap_or(dest);
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
fs::copy(source, dest).context(&*context_for(source, dest))?;
|
||||
}
|
||||
|
||||
|
|
27
src/uu/csplit/Cargo.toml
Normal file
27
src/uu/csplit/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
name = "uu_csplit"
|
||||
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"
|
||||
|
||||
homepage = "https://github.com/uutils/coreutils"
|
||||
repository = "https://github.com/uutils/coreutils/tree/master/src/uu/ls"
|
||||
keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"]
|
||||
categories = ["command-line-utilities"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
path = "src/csplit.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
thiserror = "1.0"
|
||||
regex = "1.0.0"
|
||||
glob = "0.2.11"
|
||||
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]]
|
||||
name = "csplit"
|
||||
path = "src/main.rs"
|
798
src/uu/csplit/src/csplit.rs
Normal file
798
src/uu/csplit/src/csplit.rs
Normal file
|
@ -0,0 +1,798 @@
|
|||
#![crate_name = "uu_csplit"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use regex::Regex;
|
||||
use std::cmp::Ordering;
|
||||
use std::io::{self, BufReader};
|
||||
use std::{
|
||||
fs::{remove_file, File},
|
||||
io::{BufRead, BufWriter, Write},
|
||||
};
|
||||
|
||||
mod csplit_error;
|
||||
mod patterns;
|
||||
mod splitname;
|
||||
|
||||
use crate::csplit_error::CsplitError;
|
||||
use crate::splitname::SplitName;
|
||||
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
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.";
|
||||
|
||||
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 {
|
||||
split_name: crate::SplitName,
|
||||
keep_files: bool,
|
||||
quiet: bool,
|
||||
elide_empty_files: bool,
|
||||
suppress_matched: bool,
|
||||
}
|
||||
|
||||
impl CsplitOptions {
|
||||
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.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,
|
||||
quiet,
|
||||
elide_empty_files,
|
||||
suppress_matched,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits a file into severals according to the command line patterns.
|
||||
///
|
||||
/// # 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
|
||||
/// lines.
|
||||
/// - [`::CsplitError::LineOutOfRangeOnRepetition`], like previous but after applying the pattern
|
||||
/// more than once.
|
||||
/// - [`::CsplitError::MatchNotFound`] if no line matched a regular expression.
|
||||
/// - [`::CsplitError::MatchNotFoundOnRepetition`], like previous but after applying the pattern
|
||||
/// more than once.
|
||||
pub fn csplit<T>(
|
||||
options: &CsplitOptions,
|
||||
patterns: Vec<patterns::Pattern>,
|
||||
input: T,
|
||||
) -> Result<(), CsplitError>
|
||||
where
|
||||
T: BufRead,
|
||||
{
|
||||
let mut input_iter = InputSplitter::new(input.lines().enumerate());
|
||||
let mut split_writer = SplitWriter::new(&options);
|
||||
let ret = do_csplit(&mut split_writer, patterns, &mut input_iter);
|
||||
|
||||
// consume the rest
|
||||
input_iter.rewind_buffer();
|
||||
if let Some((_, line)) = input_iter.next() {
|
||||
split_writer.new_writer()?;
|
||||
split_writer.writeln(line?)?;
|
||||
for (_, line) in input_iter {
|
||||
split_writer.writeln(line?)?;
|
||||
}
|
||||
split_writer.finish_split();
|
||||
}
|
||||
// delete files on error by default
|
||||
if ret.is_err() && !options.keep_files {
|
||||
split_writer.delete_all_splits()?;
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn do_csplit<I>(
|
||||
split_writer: &mut SplitWriter,
|
||||
patterns: Vec<patterns::Pattern>,
|
||||
input_iter: &mut InputSplitter<I>,
|
||||
) -> Result<(), CsplitError>
|
||||
where
|
||||
I: Iterator<Item = (usize, io::Result<String>)>,
|
||||
{
|
||||
// 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
|
||||
};
|
||||
match pattern {
|
||||
patterns::Pattern::UpToLine(n, ex) => {
|
||||
let mut up_to_line = n;
|
||||
for (_, ith) in ex.iter() {
|
||||
split_writer.new_writer()?;
|
||||
match split_writer.do_to_line(&pattern_as_str, up_to_line, input_iter) {
|
||||
// the error happened when applying the pattern more than once
|
||||
Err(CsplitError::LineOutOfRange(_)) if ith != 1 => {
|
||||
return Err(CsplitError::LineOutOfRangeOnRepetition(
|
||||
pattern_as_str.to_string(),
|
||||
ith - 1,
|
||||
));
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
// continue the splitting process
|
||||
Ok(()) => (),
|
||||
}
|
||||
up_to_line += n;
|
||||
}
|
||||
}
|
||||
patterns::Pattern::UpToMatch(regex, offset, ex)
|
||||
| patterns::Pattern::SkipToMatch(regex, offset, ex) => {
|
||||
for (max, ith) in ex.iter() {
|
||||
if is_skip {
|
||||
// when skipping a part of the input, no writer is created
|
||||
split_writer.as_dev_null();
|
||||
} else {
|
||||
split_writer.new_writer()?;
|
||||
}
|
||||
match (
|
||||
split_writer.do_to_match(&pattern_as_str, ®ex, offset, input_iter),
|
||||
max,
|
||||
) {
|
||||
// in case of ::pattern::ExecutePattern::Always, then it's fine not to find a
|
||||
// matching line
|
||||
(Err(CsplitError::MatchNotFound(_)), None) => {
|
||||
return Ok(());
|
||||
}
|
||||
// the error happened when applying the pattern more than once
|
||||
(Err(CsplitError::MatchNotFound(_)), Some(m)) if m != 1 && ith != 1 => {
|
||||
return Err(CsplitError::MatchNotFoundOnRepetition(
|
||||
pattern_as_str.to_string(),
|
||||
ith - 1,
|
||||
));
|
||||
}
|
||||
(Err(err), _) => return Err(err),
|
||||
// continue the splitting process
|
||||
(Ok(()), _) => (),
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a portion of the input file into a split which filename is based on an incrementing
|
||||
/// counter.
|
||||
struct SplitWriter<'a> {
|
||||
/// the options set through the command line
|
||||
options: &'a CsplitOptions,
|
||||
/// a split counter
|
||||
counter: usize,
|
||||
/// the writer to the current split
|
||||
current_writer: Option<BufWriter<File>>,
|
||||
/// the size in bytes of the current split
|
||||
size: usize,
|
||||
/// flag to indicate that no content should be written to a split
|
||||
dev_null: bool,
|
||||
}
|
||||
|
||||
impl<'a> Drop for SplitWriter<'a> {
|
||||
fn drop(&mut self) {
|
||||
if self.options.elide_empty_files && self.size == 0 {
|
||||
let file_name = self.options.split_name.get(self.counter);
|
||||
remove_file(file_name).expect("Failed to elide split");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SplitWriter<'a> {
|
||||
fn new(options: &CsplitOptions) -> SplitWriter {
|
||||
SplitWriter {
|
||||
options,
|
||||
counter: 0,
|
||||
current_writer: None,
|
||||
size: 0,
|
||||
dev_null: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new split and returns its filename.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The creation of the split file may fail with some [`io::Error`].
|
||||
fn new_writer(&mut self) -> io::Result<()> {
|
||||
let file_name = self.options.split_name.get(self.counter);
|
||||
let file = File::create(&file_name)?;
|
||||
self.current_writer = Some(BufWriter::new(file));
|
||||
self.counter += 1;
|
||||
self.size = 0;
|
||||
self.dev_null = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The current split will not keep any of the read input lines.
|
||||
fn as_dev_null(&mut self) {
|
||||
self.dev_null = true;
|
||||
}
|
||||
|
||||
/// Writes the line to the current split, appending a newline character.
|
||||
/// If [`dev_null`] is true, then the line is discarded.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Some [`io::Error`] may occur when attempting to write the line.
|
||||
fn writeln(&mut self, line: String) -> io::Result<()> {
|
||||
if !self.dev_null {
|
||||
match self.current_writer {
|
||||
Some(ref mut current_writer) => {
|
||||
let bytes = line.as_bytes();
|
||||
current_writer.write_all(bytes)?;
|
||||
current_writer.write_all(b"\n")?;
|
||||
self.size += bytes.len() + 1;
|
||||
}
|
||||
None => panic!("trying to write to a split that was not created"),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform some operations after completing a split, i.e., either remove it
|
||||
/// if the [`::ELIDE_EMPTY_FILES_OPT`] option is enabled, or print how much bytes were written
|
||||
/// to it if [`::QUIET_OPT`] is disabled.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Some [`io::Error`] if the split could not be removed in case it should be elided.
|
||||
fn finish_split(&mut self) {
|
||||
if !self.dev_null {
|
||||
if self.options.elide_empty_files && self.size == 0 {
|
||||
self.counter -= 1;
|
||||
} else if !self.options.quiet {
|
||||
println!("{}", self.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes all the split files that were created.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an [`io::Error`] if there was a problem removing a split.
|
||||
fn delete_all_splits(&self) -> io::Result<()> {
|
||||
let mut ret = Ok(());
|
||||
for ith in 0..self.counter {
|
||||
let file_name = self.options.split_name.get(ith);
|
||||
if let Err(err) = remove_file(file_name) {
|
||||
ret = Err(err);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/// Split the input stream up to the line number `n`.
|
||||
///
|
||||
/// If the line number `n` is smaller than the current position in the input, then an empty
|
||||
/// split is created.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// In addition to errors reading/writing from/to a file, if the line number
|
||||
/// `n` is greater than the total available lines, then a
|
||||
/// [`::CsplitError::LineOutOfRange`] error is returned.
|
||||
fn do_to_line<I>(
|
||||
&mut self,
|
||||
pattern_as_str: &str,
|
||||
n: usize,
|
||||
input_iter: &mut InputSplitter<I>,
|
||||
) -> Result<(), CsplitError>
|
||||
where
|
||||
I: Iterator<Item = (usize, io::Result<String>)>,
|
||||
{
|
||||
input_iter.rewind_buffer();
|
||||
input_iter.set_size_of_buffer(1);
|
||||
|
||||
let mut ret = Err(CsplitError::LineOutOfRange(pattern_as_str.to_string()));
|
||||
while let Some((ln, line)) = input_iter.next() {
|
||||
let l = line?;
|
||||
match n.cmp(&(&ln + 1)) {
|
||||
Ordering::Less => {
|
||||
if input_iter.add_line_to_buffer(ln, l).is_some() {
|
||||
panic!("the buffer is big enough to contain 1 line");
|
||||
}
|
||||
ret = Ok(());
|
||||
break;
|
||||
}
|
||||
Ordering::Equal => {
|
||||
if !self.options.suppress_matched
|
||||
&& input_iter.add_line_to_buffer(ln, l).is_some()
|
||||
{
|
||||
panic!("the buffer is big enough to contain 1 line");
|
||||
}
|
||||
ret = Ok(());
|
||||
break;
|
||||
}
|
||||
Ordering::Greater => (),
|
||||
}
|
||||
self.writeln(l)?;
|
||||
}
|
||||
self.finish_split();
|
||||
ret
|
||||
}
|
||||
|
||||
/// Read lines up to the line matching a [`Regex`]. With a non-zero offset,
|
||||
/// the block of relevant lines can be extended (if positive), or reduced
|
||||
/// (if negative).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// In addition to errors reading/writing from/to a file, the following errors may be returned:
|
||||
/// - if no line matched, an [`::CsplitError::MatchNotFound`].
|
||||
/// - if there are not enough lines to accommodate the offset, an
|
||||
/// [`::CsplitError::LineOutOfRange`].
|
||||
fn do_to_match<I>(
|
||||
&mut self,
|
||||
pattern_as_str: &str,
|
||||
regex: &Regex,
|
||||
mut offset: i32,
|
||||
input_iter: &mut InputSplitter<I>,
|
||||
) -> Result<(), CsplitError>
|
||||
where
|
||||
I: Iterator<Item = (usize, io::Result<String>)>,
|
||||
{
|
||||
if offset >= 0 {
|
||||
// The offset is zero or positive, no need for a buffer on the lines read.
|
||||
// NOTE: drain the buffer of input_iter, no match should be done within.
|
||||
for line in input_iter.drain_buffer() {
|
||||
self.writeln(line)?;
|
||||
}
|
||||
// retain the matching line
|
||||
input_iter.set_size_of_buffer(1);
|
||||
|
||||
while let Some((ln, line)) = input_iter.next() {
|
||||
let l = line?;
|
||||
if regex.is_match(&l) {
|
||||
match (self.options.suppress_matched, offset) {
|
||||
// no offset, add the line to the next split
|
||||
(false, 0) => {
|
||||
if input_iter.add_line_to_buffer(ln, l).is_some() {
|
||||
panic!("the buffer is big enough to contain 1 line");
|
||||
}
|
||||
}
|
||||
// a positive offset, some more lines need to be added to the current split
|
||||
(false, _) => self.writeln(l)?,
|
||||
_ => (),
|
||||
};
|
||||
offset -= 1;
|
||||
|
||||
// write the extra lines required by the offset
|
||||
while offset > 0 {
|
||||
match input_iter.next() {
|
||||
Some((_, line)) => {
|
||||
self.writeln(line?)?;
|
||||
}
|
||||
None => {
|
||||
self.finish_split();
|
||||
return Err(CsplitError::LineOutOfRange(
|
||||
pattern_as_str.to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
offset -= 1;
|
||||
}
|
||||
self.finish_split();
|
||||
return Ok(());
|
||||
}
|
||||
self.writeln(l)?;
|
||||
}
|
||||
} else {
|
||||
// With a negative offset we use a buffer to keep the lines within the offset.
|
||||
// NOTE: do not drain the buffer of input_iter, in case of an LineOutOfRange error
|
||||
// but do not rewind it either since no match should be done within.
|
||||
// The consequence is that the buffer may already be full with lines from a previous
|
||||
// split, which is taken care of when calling `shrink_buffer_to_size`.
|
||||
let offset_usize = -offset as usize;
|
||||
input_iter.set_size_of_buffer(offset_usize);
|
||||
while let Some((ln, line)) = input_iter.next() {
|
||||
let l = line?;
|
||||
if regex.is_match(&l) {
|
||||
for line in input_iter.shrink_buffer_to_size() {
|
||||
self.writeln(line)?;
|
||||
}
|
||||
if !self.options.suppress_matched {
|
||||
// add 1 to the buffer size to make place for the matched line
|
||||
input_iter.set_size_of_buffer(offset_usize + 1);
|
||||
if input_iter.add_line_to_buffer(ln, l).is_some() {
|
||||
panic!("should be big enough to hold every lines");
|
||||
}
|
||||
}
|
||||
self.finish_split();
|
||||
if input_iter.buffer_len() < offset_usize {
|
||||
return Err(CsplitError::LineOutOfRange(pattern_as_str.to_string()));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(line) = input_iter.add_line_to_buffer(ln, l) {
|
||||
self.writeln(line)?;
|
||||
}
|
||||
}
|
||||
// no match, drain the buffer into the current split
|
||||
for line in input_iter.drain_buffer() {
|
||||
self.writeln(line)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_split();
|
||||
Err(CsplitError::MatchNotFound(pattern_as_str.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator which can output items from a buffer filled externally.
|
||||
/// This is used to pass matching lines to the next split and to support patterns with a negative offset.
|
||||
struct InputSplitter<I>
|
||||
where
|
||||
I: Iterator<Item = (usize, io::Result<String>)>,
|
||||
{
|
||||
iter: I,
|
||||
buffer: Vec<<I as Iterator>::Item>,
|
||||
/// the number of elements the buffer may hold
|
||||
size: usize,
|
||||
/// flag to indicate content off the buffer should be returned instead of off the wrapped
|
||||
/// iterator
|
||||
rewind: bool,
|
||||
}
|
||||
|
||||
impl<I> InputSplitter<I>
|
||||
where
|
||||
I: Iterator<Item = (usize, io::Result<String>)>,
|
||||
{
|
||||
fn new(iter: I) -> InputSplitter<I> {
|
||||
InputSplitter {
|
||||
iter,
|
||||
buffer: Vec::new(),
|
||||
rewind: false,
|
||||
size: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Rewind the iteration by outputting the buffer's content.
|
||||
fn rewind_buffer(&mut self) {
|
||||
self.rewind = true;
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
self.buffer
|
||||
.drain(..shrink_offset)
|
||||
.map(|(_, line)| line.unwrap())
|
||||
}
|
||||
|
||||
/// Drain the content of the buffer.
|
||||
fn drain_buffer(&mut self) -> impl Iterator<Item = String> + '_ {
|
||||
self.buffer.drain(..).map(|(_, line)| line.unwrap())
|
||||
}
|
||||
|
||||
/// Set the maximum number of lines to keep.
|
||||
fn set_size_of_buffer(&mut self, size: usize) {
|
||||
self.size = size;
|
||||
}
|
||||
|
||||
/// Add a line to the buffer. If the buffer has [`size`] elements, then its head is removed and
|
||||
/// the new line is pushed to the buffer. The removed head is then available in the returned
|
||||
/// option.
|
||||
fn add_line_to_buffer(&mut self, ln: usize, line: String) -> Option<String> {
|
||||
if self.rewind {
|
||||
self.buffer.insert(0, (ln, Ok(line)));
|
||||
None
|
||||
} else if self.buffer.len() >= self.size {
|
||||
let (_, head_line) = self.buffer.remove(0);
|
||||
self.buffer.push((ln, Ok(line)));
|
||||
Some(head_line.unwrap())
|
||||
} else {
|
||||
self.buffer.push((ln, Ok(line)));
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of lines stored in the buffer
|
||||
fn buffer_len(&self) -> usize {
|
||||
self.buffer.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for InputSplitter<I>
|
||||
where
|
||||
I: Iterator<Item = (usize, io::Result<String>)>,
|
||||
{
|
||||
type Item = <I as Iterator>::Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.rewind {
|
||||
if !self.buffer.is_empty() {
|
||||
return Some(self.buffer.remove(0));
|
||||
}
|
||||
self.rewind = false;
|
||||
}
|
||||
self.iter.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn input_splitter() {
|
||||
let input = vec![
|
||||
Ok(String::from("aaa")),
|
||||
Ok(String::from("bbb")),
|
||||
Ok(String::from("ccc")),
|
||||
Ok(String::from("ddd")),
|
||||
];
|
||||
let mut input_splitter = InputSplitter::new(input.into_iter().enumerate());
|
||||
|
||||
input_splitter.set_size_of_buffer(2);
|
||||
assert_eq!(input_splitter.buffer_len(), 0);
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((0, Ok(line))) => {
|
||||
assert_eq!(line, String::from("aaa"));
|
||||
assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 1);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((1, Ok(line))) => {
|
||||
assert_eq!(line, String::from("bbb"));
|
||||
assert_eq!(input_splitter.add_line_to_buffer(1, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 2);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((2, Ok(line))) => {
|
||||
assert_eq!(line, String::from("ccc"));
|
||||
assert_eq!(
|
||||
input_splitter.add_line_to_buffer(2, line),
|
||||
Some(String::from("aaa"))
|
||||
);
|
||||
assert_eq!(input_splitter.buffer_len(), 2);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
input_splitter.rewind_buffer();
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((1, Ok(line))) => {
|
||||
assert_eq!(line, String::from("bbb"));
|
||||
assert_eq!(input_splitter.buffer_len(), 1);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((2, Ok(line))) => {
|
||||
assert_eq!(line, String::from("ccc"));
|
||||
assert_eq!(input_splitter.buffer_len(), 0);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((3, Ok(line))) => {
|
||||
assert_eq!(line, String::from("ddd"));
|
||||
assert_eq!(input_splitter.buffer_len(), 0);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
assert!(input_splitter.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_splitter_interrupt_rewind() {
|
||||
let input = vec![
|
||||
Ok(String::from("aaa")),
|
||||
Ok(String::from("bbb")),
|
||||
Ok(String::from("ccc")),
|
||||
Ok(String::from("ddd")),
|
||||
];
|
||||
let mut input_splitter = InputSplitter::new(input.into_iter().enumerate());
|
||||
|
||||
input_splitter.set_size_of_buffer(3);
|
||||
assert_eq!(input_splitter.buffer_len(), 0);
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((0, Ok(line))) => {
|
||||
assert_eq!(line, String::from("aaa"));
|
||||
assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 1);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((1, Ok(line))) => {
|
||||
assert_eq!(line, String::from("bbb"));
|
||||
assert_eq!(input_splitter.add_line_to_buffer(1, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 2);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((2, Ok(line))) => {
|
||||
assert_eq!(line, String::from("ccc"));
|
||||
assert_eq!(input_splitter.add_line_to_buffer(2, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 3);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
input_splitter.rewind_buffer();
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((0, Ok(line))) => {
|
||||
assert_eq!(line, String::from("aaa"));
|
||||
assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 3);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((0, Ok(line))) => {
|
||||
assert_eq!(line, String::from("aaa"));
|
||||
assert_eq!(input_splitter.buffer_len(), 2);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((1, Ok(line))) => {
|
||||
assert_eq!(line, String::from("bbb"));
|
||||
assert_eq!(input_splitter.buffer_len(), 1);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((2, Ok(line))) => {
|
||||
assert_eq!(line, String::from("ccc"));
|
||||
assert_eq!(input_splitter.buffer_len(), 0);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
Some((3, Ok(line))) => {
|
||||
assert_eq!(line, String::from("ddd"));
|
||||
assert_eq!(input_splitter.buffer_len(), 0);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
assert!(input_splitter.next().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let usage = get_usage();
|
||||
let args = args.collect_str();
|
||||
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(SUMMARY)
|
||||
.usage(&usage[..])
|
||||
.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)
|
||||
.get_matches_from(args);
|
||||
|
||||
// get the file to split
|
||||
let file_name = matches.value_of(options::FILE).unwrap();
|
||||
|
||||
// get the patterns to split on
|
||||
let patterns: Vec<String> = matches
|
||||
.values_of(options::PATTERN)
|
||||
.unwrap()
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
let patterns = return_if_err!(1, patterns::get_patterns(&patterns[..]));
|
||||
let options = CsplitOptions::new(&matches);
|
||||
if file_name == "-" {
|
||||
let stdin = io::stdin();
|
||||
crash_if_err!(1, csplit(&options, patterns, stdin.lock()));
|
||||
} else {
|
||||
let file = return_if_err!(1, File::open(file_name));
|
||||
let file_metadata = return_if_err!(1, file.metadata());
|
||||
if !file_metadata.is_file() {
|
||||
crash!(1, "'{}' is not a regular file", file_name);
|
||||
}
|
||||
crash_if_err!(1, csplit(&options, patterns, BufReader::new(file)));
|
||||
};
|
||||
0
|
||||
}
|
35
src/uu/csplit/src/csplit_error.rs
Normal file
35
src/uu/csplit/src/csplit_error.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors thrown by the csplit command
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CsplitError {
|
||||
#[error("IO error: {}", _0)]
|
||||
IoError(io::Error),
|
||||
#[error("'{}': line number out of range", _0)]
|
||||
LineOutOfRange(String),
|
||||
#[error("'{}': line number out of range on repetition {}", _0, _1)]
|
||||
LineOutOfRangeOnRepetition(String, usize),
|
||||
#[error("'{}': match not found", _0)]
|
||||
MatchNotFound(String),
|
||||
#[error("'{}': match not found on repetition {}", _0, _1)]
|
||||
MatchNotFoundOnRepetition(String, usize),
|
||||
#[error("line number must be greater than zero")]
|
||||
LineNumberIsZero,
|
||||
#[error("line number '{}' is smaller than preceding line number, {}", _0, _1)]
|
||||
LineNumberSmallerThanPrevious(usize, usize),
|
||||
#[error("invalid pattern: {}", _0)]
|
||||
InvalidPattern(String),
|
||||
#[error("invalid number: '{}'", _0)]
|
||||
InvalidNumber(String),
|
||||
#[error("incorrect conversion specification in suffix")]
|
||||
SuffixFormatIncorrect,
|
||||
#[error("too many % conversion specifications in suffix")]
|
||||
SuffixFormatTooManyPercents,
|
||||
}
|
||||
|
||||
impl From<io::Error> for CsplitError {
|
||||
fn from(error: io::Error) -> Self {
|
||||
CsplitError::IoError(error)
|
||||
}
|
||||
}
|
1
src/uu/csplit/src/main.rs
Normal file
1
src/uu/csplit/src/main.rs
Normal file
|
@ -0,0 +1 @@
|
|||
uucore_procs::main!(uu_csplit); // spell-checker:ignore procs uucore
|
355
src/uu/csplit/src/patterns.rs
Normal file
355
src/uu/csplit/src/patterns.rs
Normal file
|
@ -0,0 +1,355 @@
|
|||
use crate::csplit_error::CsplitError;
|
||||
use regex::Regex;
|
||||
|
||||
/// The definition of a pattern to match on a line.
|
||||
#[derive(Debug)]
|
||||
pub enum Pattern {
|
||||
/// Copy the file's content to a split up to, not including, the given line number. The number
|
||||
/// of times the pattern is executed is detailed in [`ExecutePattern`].
|
||||
UpToLine(usize, ExecutePattern),
|
||||
/// Copy the file's content to a split up to, not including, the line matching the regex. The
|
||||
/// integer is an offset relative to the matched line of what to include (if positive) or
|
||||
/// to exclude (if negative). The number of times the pattern is executed is detailed in
|
||||
/// [`ExecutePattern`].
|
||||
UpToMatch(Regex, i32, ExecutePattern),
|
||||
/// Skip the file's content up to, not including, the line matching the regex. The integer
|
||||
/// is an offset relative to the matched line of what to include (if positive) or to exclude
|
||||
/// (if negative). The number of times the pattern is executed is detailed in [`ExecutePattern`].
|
||||
SkipToMatch(Regex, i32, ExecutePattern),
|
||||
}
|
||||
|
||||
impl ToString for Pattern {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Pattern::UpToLine(n, _) => n.to_string(),
|
||||
Pattern::UpToMatch(regex, 0, _) => format!("/{}/", regex.as_str()),
|
||||
Pattern::UpToMatch(regex, offset, _) => format!("/{}/{:+}", regex.as_str(), offset),
|
||||
Pattern::SkipToMatch(regex, 0, _) => format!("%{}%", regex.as_str()),
|
||||
Pattern::SkipToMatch(regex, offset, _) => format!("%{}%{:+}", regex.as_str(), offset),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of times a pattern can be used.
|
||||
#[derive(Debug)]
|
||||
pub enum ExecutePattern {
|
||||
/// Execute the pattern as many times as possible
|
||||
Always,
|
||||
/// Execute the pattern a fixed number of times
|
||||
Times(usize),
|
||||
}
|
||||
|
||||
impl ExecutePattern {
|
||||
pub fn iter(&self) -> ExecutePatternIter {
|
||||
match self {
|
||||
ExecutePattern::Times(n) => ExecutePatternIter::new(Some(*n)),
|
||||
ExecutePattern::Always => ExecutePatternIter::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExecutePatternIter {
|
||||
max: Option<usize>,
|
||||
cur: usize,
|
||||
}
|
||||
|
||||
impl ExecutePatternIter {
|
||||
fn new(max: Option<usize>) -> ExecutePatternIter {
|
||||
ExecutePatternIter { max, cur: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ExecutePatternIter {
|
||||
type Item = (Option<usize>, usize);
|
||||
|
||||
fn next(&mut self) -> Option<(Option<usize>, usize)> {
|
||||
match self.max {
|
||||
// iterate until m is reached
|
||||
Some(m) => {
|
||||
if self.cur == m {
|
||||
None
|
||||
} else {
|
||||
self.cur += 1;
|
||||
Some((self.max, self.cur))
|
||||
}
|
||||
}
|
||||
// no limit, just increment a counter
|
||||
None => {
|
||||
self.cur += 1;
|
||||
Some((None, self.cur))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the definitions of patterns given on the command line into a list of [`Pattern`]s.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If a pattern is incorrect, a [`::CsplitError::InvalidPattern`] error is returned, which may be
|
||||
/// due to, e.g.,:
|
||||
/// - an invalid regular expression;
|
||||
/// - an invalid number for, e.g., the offset.
|
||||
pub fn get_patterns(args: &[String]) -> Result<Vec<Pattern>, CsplitError> {
|
||||
let patterns = extract_patterns(args)?;
|
||||
validate_line_numbers(&patterns)?;
|
||||
Ok(patterns)
|
||||
}
|
||||
|
||||
fn extract_patterns(args: &[String]) -> Result<Vec<Pattern>, CsplitError> {
|
||||
let mut patterns = Vec::with_capacity(args.len());
|
||||
let to_match_reg =
|
||||
Regex::new(r"^(/(?P<UPTO>.+)/|%(?P<SKIPTO>.+)%)(?P<OFFSET>[\+-]\d+)?$").unwrap();
|
||||
let execute_ntimes_reg = Regex::new(r"^\{(?P<TIMES>\d+)|\*\}$").unwrap();
|
||||
let mut iter = args.iter().peekable();
|
||||
|
||||
while let Some(arg) = iter.next() {
|
||||
// get the number of times a pattern is repeated, which is at least once plus whatever is
|
||||
// in the quantifier.
|
||||
let execute_ntimes = match iter.peek() {
|
||||
None => ExecutePattern::Times(1),
|
||||
Some(&next_item) => {
|
||||
match execute_ntimes_reg.captures(next_item) {
|
||||
None => ExecutePattern::Times(1),
|
||||
Some(r) => {
|
||||
// skip the next item
|
||||
iter.next();
|
||||
if let Some(times) = r.name("TIMES") {
|
||||
ExecutePattern::Times(times.as_str().parse::<usize>().unwrap() + 1)
|
||||
} else {
|
||||
ExecutePattern::Always
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// get the pattern definition
|
||||
if let Some(captures) = to_match_reg.captures(arg) {
|
||||
let offset = match captures.name("OFFSET") {
|
||||
None => 0,
|
||||
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,
|
||||
};
|
||||
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,
|
||||
};
|
||||
patterns.push(Pattern::SkipToMatch(pattern, offset, execute_ntimes));
|
||||
}
|
||||
} else if let Ok(line_number) = arg.parse::<usize>() {
|
||||
patterns.push(Pattern::UpToLine(line_number, execute_ntimes));
|
||||
} else {
|
||||
return Err(CsplitError::InvalidPattern(arg.to_string()));
|
||||
}
|
||||
}
|
||||
Ok(patterns)
|
||||
}
|
||||
|
||||
/// Asserts the line numbers are in increasing order, starting at 1.
|
||||
fn validate_line_numbers(patterns: &[Pattern]) -> Result<(), CsplitError> {
|
||||
patterns
|
||||
.iter()
|
||||
.filter_map(|pattern| match pattern {
|
||||
Pattern::UpToLine(line_number, _) => Some(line_number),
|
||||
_ => None,
|
||||
})
|
||||
.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
|
||||
(n, m) if n == m => {
|
||||
show_warning!("line number '{}' is the same as preceding line number", n);
|
||||
Ok(n)
|
||||
}
|
||||
// a number cannot be greater than the one that follows
|
||||
(n, m) if n > m => Err(CsplitError::LineNumberSmallerThanPrevious(m, n)),
|
||||
(_, m) => Ok(m),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bad_pattern() {
|
||||
let input = vec!["bad".to_string()];
|
||||
assert!(get_patterns(input.as_slice()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn up_to_line_pattern() {
|
||||
let input: Vec<String> = vec!["24", "42", "{*}", "50", "{4}"]
|
||||
.into_iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect();
|
||||
let patterns = get_patterns(input.as_slice()).unwrap();
|
||||
assert_eq!(patterns.len(), 3);
|
||||
match patterns.get(0) {
|
||||
Some(Pattern::UpToLine(24, ExecutePattern::Times(1))) => (),
|
||||
_ => panic!("expected UpToLine pattern"),
|
||||
};
|
||||
match patterns.get(1) {
|
||||
Some(Pattern::UpToLine(42, ExecutePattern::Always)) => (),
|
||||
_ => panic!("expected UpToLine pattern"),
|
||||
};
|
||||
match patterns.get(2) {
|
||||
Some(Pattern::UpToLine(50, ExecutePattern::Times(5))) => (),
|
||||
_ => panic!("expected UpToLine pattern"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn up_to_match_pattern() {
|
||||
let input: Vec<String> = vec![
|
||||
"/test1.*end$/",
|
||||
"/test2.*end$/",
|
||||
"{*}",
|
||||
"/test3.*end$/",
|
||||
"{4}",
|
||||
"/test4.*end$/+3",
|
||||
"/test5.*end$/-3",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect();
|
||||
let patterns = get_patterns(input.as_slice()).unwrap();
|
||||
assert_eq!(patterns.len(), 5);
|
||||
match patterns.get(0) {
|
||||
Some(Pattern::UpToMatch(reg, 0, ExecutePattern::Times(1))) => {
|
||||
let parsed_reg = format!("{}", reg);
|
||||
assert_eq!(parsed_reg, "test1.*end$");
|
||||
}
|
||||
_ => panic!("expected UpToMatch pattern"),
|
||||
};
|
||||
match patterns.get(1) {
|
||||
Some(Pattern::UpToMatch(reg, 0, ExecutePattern::Always)) => {
|
||||
let parsed_reg = format!("{}", reg);
|
||||
assert_eq!(parsed_reg, "test2.*end$");
|
||||
}
|
||||
_ => panic!("expected UpToMatch pattern"),
|
||||
};
|
||||
match patterns.get(2) {
|
||||
Some(Pattern::UpToMatch(reg, 0, ExecutePattern::Times(5))) => {
|
||||
let parsed_reg = format!("{}", reg);
|
||||
assert_eq!(parsed_reg, "test3.*end$");
|
||||
}
|
||||
_ => panic!("expected UpToMatch pattern"),
|
||||
};
|
||||
match patterns.get(3) {
|
||||
Some(Pattern::UpToMatch(reg, 3, ExecutePattern::Times(1))) => {
|
||||
let parsed_reg = format!("{}", reg);
|
||||
assert_eq!(parsed_reg, "test4.*end$");
|
||||
}
|
||||
_ => panic!("expected UpToMatch pattern"),
|
||||
};
|
||||
match patterns.get(4) {
|
||||
Some(Pattern::UpToMatch(reg, -3, ExecutePattern::Times(1))) => {
|
||||
let parsed_reg = format!("{}", reg);
|
||||
assert_eq!(parsed_reg, "test5.*end$");
|
||||
}
|
||||
_ => panic!("expected UpToMatch pattern"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_to_match_pattern() {
|
||||
let input: Vec<String> = vec![
|
||||
"%test1.*end$%",
|
||||
"%test2.*end$%",
|
||||
"{*}",
|
||||
"%test3.*end$%",
|
||||
"{4}",
|
||||
"%test4.*end$%+3",
|
||||
"%test5.*end$%-3",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect();
|
||||
let patterns = get_patterns(input.as_slice()).unwrap();
|
||||
assert_eq!(patterns.len(), 5);
|
||||
match patterns.get(0) {
|
||||
Some(Pattern::SkipToMatch(reg, 0, ExecutePattern::Times(1))) => {
|
||||
let parsed_reg = format!("{}", reg);
|
||||
assert_eq!(parsed_reg, "test1.*end$");
|
||||
}
|
||||
_ => panic!("expected SkipToMatch pattern"),
|
||||
};
|
||||
match patterns.get(1) {
|
||||
Some(Pattern::SkipToMatch(reg, 0, ExecutePattern::Always)) => {
|
||||
let parsed_reg = format!("{}", reg);
|
||||
assert_eq!(parsed_reg, "test2.*end$");
|
||||
}
|
||||
_ => panic!("expected SkipToMatch pattern"),
|
||||
};
|
||||
match patterns.get(2) {
|
||||
Some(Pattern::SkipToMatch(reg, 0, ExecutePattern::Times(5))) => {
|
||||
let parsed_reg = format!("{}", reg);
|
||||
assert_eq!(parsed_reg, "test3.*end$");
|
||||
}
|
||||
_ => panic!("expected SkipToMatch pattern"),
|
||||
};
|
||||
match patterns.get(3) {
|
||||
Some(Pattern::SkipToMatch(reg, 3, ExecutePattern::Times(1))) => {
|
||||
let parsed_reg = format!("{}", reg);
|
||||
assert_eq!(parsed_reg, "test4.*end$");
|
||||
}
|
||||
_ => panic!("expected SkipToMatch pattern"),
|
||||
};
|
||||
match patterns.get(4) {
|
||||
Some(Pattern::SkipToMatch(reg, -3, ExecutePattern::Times(1))) => {
|
||||
let parsed_reg = format!("{}", reg);
|
||||
assert_eq!(parsed_reg, "test5.*end$");
|
||||
}
|
||||
_ => panic!("expected SkipToMatch pattern"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_number_zero() {
|
||||
let patterns = vec![Pattern::UpToLine(0, ExecutePattern::Times(1))];
|
||||
match validate_line_numbers(&patterns) {
|
||||
Err(CsplitError::LineNumberIsZero) => (),
|
||||
_ => panic!("expected LineNumberIsZero error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_number_smaller_than_previous() {
|
||||
let input: Vec<String> = vec!["10".to_string(), "5".to_string()];
|
||||
match get_patterns(input.as_slice()) {
|
||||
Err(CsplitError::LineNumberSmallerThanPrevious(5, 10)) => (),
|
||||
_ => panic!("expected LineNumberSmallerThanPrevious error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_number_smaller_than_previous_separate() {
|
||||
let input: Vec<String> = vec!["10".to_string(), "/20/".to_string(), "5".to_string()];
|
||||
match get_patterns(input.as_slice()) {
|
||||
Err(CsplitError::LineNumberSmallerThanPrevious(5, 10)) => (),
|
||||
_ => panic!("expected LineNumberSmallerThanPrevious error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_number_zero_separate() {
|
||||
let input: Vec<String> = vec!["10".to_string(), "/20/".to_string(), "0".to_string()];
|
||||
match get_patterns(input.as_slice()) {
|
||||
Err(CsplitError::LineNumberIsZero) => (),
|
||||
_ => panic!("expected LineNumberIsZero error"),
|
||||
}
|
||||
}
|
||||
}
|
395
src/uu/csplit/src/splitname.rs
Normal file
395
src/uu/csplit/src/splitname.rs
Normal file
|
@ -0,0 +1,395 @@
|
|||
use regex::Regex;
|
||||
|
||||
use crate::csplit_error::CsplitError;
|
||||
|
||||
/// Computes the filename of a split, taking into consideration a possible user-defined suffix
|
||||
/// format.
|
||||
pub struct SplitName {
|
||||
fn_split_name: Box<dyn Fn(usize) -> String>,
|
||||
}
|
||||
|
||||
impl SplitName {
|
||||
/// Creates a new SplitName with the given user-defined options:
|
||||
/// - `prefix_opt` specifies a prefix for all splits.
|
||||
/// - `format_opt` specifies a custom format for the suffix part of the filename, using the
|
||||
/// `sprintf` format notation.
|
||||
/// - `n_digits_opt` defines the width of the split number.
|
||||
///
|
||||
/// # Caveats
|
||||
///
|
||||
/// If `prefix_opt` and `format_opt` are defined, and the `format_opt` has some string appearing
|
||||
/// before the conversion pattern (e.g., "here-%05d"), then it is appended to the passed prefix
|
||||
/// via `prefix_opt`.
|
||||
///
|
||||
/// If `n_digits_opt` and `format_opt` are defined, then width defined in `format_opt` is
|
||||
/// taken.
|
||||
pub fn new(
|
||||
prefix_opt: Option<String>,
|
||||
format_opt: Option<String>,
|
||||
n_digits_opt: Option<String>,
|
||||
) -> Result<SplitName, CsplitError> {
|
||||
// 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)),
|
||||
},
|
||||
};
|
||||
// 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 {
|
||||
format!("{}{:0width$}", prefix, n, width = n_digits)
|
||||
}),
|
||||
Some(custom) => {
|
||||
let spec =
|
||||
Regex::new(r"(?P<ALL>%(?P<FLAG>[0#-])(?P<WIDTH>\d+)?(?P<TYPE>[diuoxX]))")
|
||||
.unwrap();
|
||||
let mut captures_iter = spec.captures_iter(&custom);
|
||||
let custom_fn: Box<dyn Fn(usize) -> String> = match captures_iter.next() {
|
||||
Some(captures) => {
|
||||
let all = captures.name("ALL").unwrap();
|
||||
let before = custom[0..all.start()].to_owned();
|
||||
let after = custom[all.end()..].to_owned();
|
||||
let n_digits = match captures.name("WIDTH") {
|
||||
None => 0,
|
||||
Some(m) => m.as_str().parse::<usize>().unwrap(),
|
||||
};
|
||||
match (captures.name("FLAG"), captures.name("TYPE")) {
|
||||
(Some(ref f), Some(ref t)) => {
|
||||
match (f.as_str(), t.as_str()) {
|
||||
/*
|
||||
* zero padding
|
||||
*/
|
||||
// decimal
|
||||
("0", "d") | ("0", "i") | ("0", "u") => {
|
||||
Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:0width$}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
})
|
||||
}
|
||||
// octal
|
||||
("0", "o") => Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:0width$o}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
}),
|
||||
// lower hexadecimal
|
||||
("0", "x") => Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:0width$x}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
}),
|
||||
// upper hexadecimal
|
||||
("0", "X") => Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:0width$X}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
}),
|
||||
|
||||
/*
|
||||
* Alternate form
|
||||
*/
|
||||
// octal
|
||||
("#", "o") => Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:>#width$o}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
}),
|
||||
// lower hexadecimal
|
||||
("#", "x") => Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:>#width$x}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
}),
|
||||
// upper hexadecimal
|
||||
("#", "X") => Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:>#width$X}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
}),
|
||||
|
||||
/*
|
||||
* Left adjusted
|
||||
*/
|
||||
// decimal
|
||||
("-", "d") | ("-", "i") | ("-", "u") => {
|
||||
Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:<#width$}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
})
|
||||
}
|
||||
// octal
|
||||
("-", "o") => Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:<#width$o}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
}),
|
||||
// lower hexadecimal
|
||||
("-", "x") => Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:<#width$x}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
}),
|
||||
// upper hexadecimal
|
||||
("-", "X") => Box::new(move |n: usize| -> String {
|
||||
format!(
|
||||
"{}{}{:<#width$X}{}",
|
||||
prefix,
|
||||
before,
|
||||
n,
|
||||
after,
|
||||
width = n_digits
|
||||
)
|
||||
}),
|
||||
|
||||
_ => return Err(CsplitError::SuffixFormatIncorrect),
|
||||
}
|
||||
}
|
||||
_ => return Err(CsplitError::SuffixFormatIncorrect),
|
||||
}
|
||||
}
|
||||
None => return Err(CsplitError::SuffixFormatIncorrect),
|
||||
};
|
||||
|
||||
// there cannot be more than one format pattern
|
||||
if captures_iter.next().is_some() {
|
||||
return Err(CsplitError::SuffixFormatTooManyPercents);
|
||||
}
|
||||
custom_fn
|
||||
}
|
||||
};
|
||||
|
||||
Ok(SplitName { fn_split_name })
|
||||
}
|
||||
|
||||
/// Returns the filename of the i-th split.
|
||||
pub fn get(&self, n: usize) -> String {
|
||||
(self.fn_split_name)(n)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn invalid_number() {
|
||||
let split_name = SplitName::new(None, None, Some(String::from("bad")));
|
||||
match split_name {
|
||||
Err(CsplitError::InvalidNumber(_)) => (),
|
||||
_ => panic!("should fail with InvalidNumber"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_suffix_format1() {
|
||||
let split_name = SplitName::new(None, Some(String::from("no conversion string")), None);
|
||||
match split_name {
|
||||
Err(CsplitError::SuffixFormatIncorrect) => (),
|
||||
_ => panic!("should fail with SuffixFormatIncorrect"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_suffix_format2() {
|
||||
let split_name = SplitName::new(None, Some(String::from("%042a")), None);
|
||||
match split_name {
|
||||
Err(CsplitError::SuffixFormatIncorrect) => (),
|
||||
_ => panic!("should fail with SuffixFormatIncorrect"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_formatter() {
|
||||
let split_name = SplitName::new(None, None, None).unwrap();
|
||||
assert_eq!(split_name.get(2), "xx02");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_formatter_with_prefix() {
|
||||
let split_name = SplitName::new(Some(String::from("aaa")), None, None).unwrap();
|
||||
assert_eq!(split_name.get(2), "aaa02");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_formatter_with_width() {
|
||||
let split_name = SplitName::new(None, None, Some(String::from("5"))).unwrap();
|
||||
assert_eq!(split_name.get(2), "xx00002");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_padding_decimal1() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%03d-")), None).unwrap();
|
||||
assert_eq!(split_name.get(2), "xxcst-002-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_padding_decimal2() {
|
||||
let split_name = SplitName::new(
|
||||
Some(String::from("pre-")),
|
||||
Some(String::from("cst-%03d-post")),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(split_name.get(2), "pre-cst-002-post");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_padding_decimal3() {
|
||||
let split_name = SplitName::new(
|
||||
None,
|
||||
Some(String::from("cst-%03d-")),
|
||||
Some(String::from("42")),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(split_name.get(2), "xxcst-002-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_padding_decimal4() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%03i-")), None).unwrap();
|
||||
assert_eq!(split_name.get(2), "xxcst-002-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_padding_decimal5() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%03u-")), None).unwrap();
|
||||
assert_eq!(split_name.get(2), "xxcst-002-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_padding_octal() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%03o-")), None).unwrap();
|
||||
assert_eq!(split_name.get(42), "xxcst-052-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_padding_lower_hexa() {
|
||||
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() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%03X-")), None).unwrap();
|
||||
assert_eq!(split_name.get(42), "xxcst-02A-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alternate_form_octal() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%#10o-")), None).unwrap();
|
||||
assert_eq!(split_name.get(42), "xxcst- 0o52-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alternate_form_lower_hexa() {
|
||||
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() {
|
||||
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_decimal1() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%-10d-")), None).unwrap();
|
||||
assert_eq!(split_name.get(42), "xxcst-42 -");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_adjusted_decimal2() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%-10i-")), None).unwrap();
|
||||
assert_eq!(split_name.get(42), "xxcst-42 -");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_adjusted_decimal3() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%-10u-")), None).unwrap();
|
||||
assert_eq!(split_name.get(42), "xxcst-42 -");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_adjusted_octal() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%-10o-")), None).unwrap();
|
||||
assert_eq!(split_name.get(42), "xxcst-0o52 -");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_adjusted_lower_hexa() {
|
||||
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() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%-10X-")), None).unwrap();
|
||||
assert_eq!(split_name.get(42), "xxcst-0x2A -");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_percent() {
|
||||
let split_name = SplitName::new(None, Some(String::from("%02d-%-3x")), None);
|
||||
match split_name {
|
||||
Err(CsplitError::SuffixFormatTooManyPercents) => (),
|
||||
_ => panic!("should fail with SuffixFormatTooManyPercents"),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_cut"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "cut ~ (uutils) display byte/field columns of input lines"
|
||||
|
@ -15,8 +15,9 @@ edition = "2018"
|
|||
path = "src/cut.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
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]]
|
||||
name = "cut"
|
||||
|
|
|
@ -107,7 +107,7 @@ impl<R: Read> self::Bytes::Select for ByteReader<R> {
|
|||
Comp,
|
||||
Part,
|
||||
Newl,
|
||||
};
|
||||
}
|
||||
|
||||
use self::Bytes::Selected::*;
|
||||
|
||||
|
|
|
@ -10,17 +10,19 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, stdout, BufRead, BufReader, Read, Stdout, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use self::ranges::Range;
|
||||
use self::searcher::Searcher;
|
||||
use uucore::ranges::Range;
|
||||
|
||||
mod buffer;
|
||||
mod ranges;
|
||||
mod searcher;
|
||||
|
||||
static NAME: &str = "cut";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static SYNTAX: &str =
|
||||
"[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+";
|
||||
static SUMMARY: &str =
|
||||
|
@ -125,7 +127,7 @@ enum Mode {
|
|||
|
||||
fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
|
||||
if complement {
|
||||
Range::from_list(list).map(|r| ranges::complement(&r))
|
||||
Range::from_list(list).map(|r| uucore::ranges::complement(&r))
|
||||
} else {
|
||||
Range::from_list(list)
|
||||
}
|
||||
|
@ -399,8 +401,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_ok() {
|
||||
show_error!("{}: No such file or directory", filename);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -423,34 +430,123 @@ fn cut_files(mut filenames: Vec<String>, mode: Mode) -> i32 {
|
|||
exit_code
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
|
||||
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");
|
||||
let matches = App::new(executable!())
|
||||
.name(NAME)
|
||||
.version(VERSION)
|
||||
.usage(SYNTAX)
|
||||
.about(SUMMARY)
|
||||
.after_help(LONG_HELP)
|
||||
.arg(
|
||||
Arg::with_name(options::BYTES)
|
||||
.short("b")
|
||||
.long(options::BYTES)
|
||||
.takes_value(true)
|
||||
.help("filter byte columns from the input source")
|
||||
.allow_hyphen_values(true)
|
||||
.value_name("LIST")
|
||||
.display_order(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::CHARACTERS)
|
||||
.short("c")
|
||||
.long(options::CHARACTERS)
|
||||
.help("alias for character mode")
|
||||
.takes_value(true)
|
||||
.allow_hyphen_values(true)
|
||||
.value_name("LIST")
|
||||
.display_order(2),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::DELIMITER)
|
||||
.short("d")
|
||||
.long(options::DELIMITER)
|
||||
.help("specify the delimiter character that separates fields in the input source. Defaults to Tab.")
|
||||
.takes_value(true)
|
||||
.value_name("DELIM")
|
||||
.display_order(3),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::FIELDS)
|
||||
.short("f")
|
||||
.long(options::FIELDS)
|
||||
.help("filter field columns from the input source")
|
||||
.takes_value(true)
|
||||
.allow_hyphen_values(true)
|
||||
.value_name("LIST")
|
||||
.display_order(4),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::COMPLEMENT)
|
||||
.long(options::COMPLEMENT)
|
||||
.help("invert the filter - instead of displaying only the filtered columns, display all but those columns")
|
||||
.takes_value(false)
|
||||
.display_order(5),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::ONLY_DELIMITED)
|
||||
.short("s")
|
||||
.long(options::ONLY_DELIMITED)
|
||||
.help("in field mode, only print lines which contain the delimiter")
|
||||
.takes_value(false)
|
||||
.display_order(6),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::ZERO_TERMINATED)
|
||||
.short("z")
|
||||
.long(options::ZERO_TERMINATED)
|
||||
.help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)")
|
||||
.takes_value(false)
|
||||
.display_order(8),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::OUTPUT_DELIMITER)
|
||||
.long(options::OUTPUT_DELIMITER)
|
||||
.help("in field mode, replace the delimiter in output lines with this option's argument")
|
||||
.takes_value(true)
|
||||
.value_name("NEW_DELIM")
|
||||
.display_order(7),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::FILE)
|
||||
.hidden(true)
|
||||
.multiple(true)
|
||||
)
|
||||
.get_matches_from(args);
|
||||
|
||||
let 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"),
|
||||
out_delim: Some(
|
||||
matches
|
||||
.value_of(options::OUTPUT_DELIMITER)
|
||||
.unwrap_or_default()
|
||||
.to_owned(),
|
||||
),
|
||||
zero_terminated: matches.is_present(options::ZERO_TERMINATED),
|
||||
},
|
||||
)
|
||||
})
|
||||
|
@ -460,29 +556,34 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
Mode::Characters(
|
||||
ranges,
|
||||
Options {
|
||||
out_delim: matches.opt_str("output-delimiter"),
|
||||
zero_terminated: matches.opt_present("zero-terminated"),
|
||||
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") {
|
||||
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") {
|
||||
match matches.value_of(options::DELIMITER) {
|
||||
Some(delim) => {
|
||||
if delim.chars().count() > 1 {
|
||||
Err(msg_opt_invalid_should_be!(
|
||||
|
@ -495,7 +596,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(
|
||||
|
@ -534,10 +635,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",
|
||||
|
@ -548,8 +657,14 @@ 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_date"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "date ~ (uutils) display or set the current time"
|
||||
|
@ -16,9 +16,15 @@ path = "src/date.rs"
|
|||
|
||||
[dependencies]
|
||||
chrono = "0.4.4"
|
||||
clap = "2.32"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
clap = "2.33"
|
||||
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"
|
||||
|
|
|
@ -9,19 +9,23 @@
|
|||
// spell-checker:ignore (format) MMDDhhmm
|
||||
// spell-checker:ignore (ToDO) DATEFILE
|
||||
|
||||
extern crate chrono;
|
||||
|
||||
extern crate clap;
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
|
||||
#[cfg(windows)]
|
||||
use chrono::{Datelike, Timelike};
|
||||
use clap::{App, Arg};
|
||||
|
||||
use chrono::offset::Utc;
|
||||
use chrono::{DateTime, FixedOffset, Local, Offset};
|
||||
#[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";
|
||||
|
@ -65,6 +69,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,
|
||||
|
@ -189,7 +198,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)
|
||||
|
@ -225,18 +234,31 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
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,
|
||||
// TODO: Handle this option:
|
||||
set_to: None,
|
||||
set_to,
|
||||
};
|
||||
|
||||
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
|
||||
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;
|
||||
|
@ -250,15 +272,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
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) => {
|
||||
|
@ -317,3 +330,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");
|
||||
return 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,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_df"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "df ~ (uutils) display file system information"
|
||||
|
@ -15,11 +15,11 @@ edition = "2018"
|
|||
path = "src/df.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.32"
|
||||
clap = "2.33"
|
||||
libc = "0.2"
|
||||
number_prefix = "0.2"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
number_prefix = "0.4"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }
|
||||
|
|
|
@ -9,17 +9,11 @@
|
|||
// 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
|
||||
|
||||
extern crate clap;
|
||||
extern crate libc;
|
||||
extern crate number_prefix;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
|
||||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
#[cfg(windows)]
|
||||
|
@ -28,7 +22,7 @@ use winapi::um::fileapi::{
|
|||
GetVolumePathNamesForVolumeNameW, QueryDosDeviceW,
|
||||
};
|
||||
|
||||
use number_prefix::{binary_prefix, decimal_prefix, PrefixNames, Prefixed, Standalone};
|
||||
use number_prefix::NumberPrefix;
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
@ -38,15 +32,15 @@ use std::ffi::CString;
|
|||
#[cfg(unix)]
|
||||
use std::mem;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
|
||||
use libc::c_int;
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg(target_vendor = "apple")]
|
||||
use libc::statfs;
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
|
||||
use std::ffi::CStr;
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "windows"))]
|
||||
#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "windows"))]
|
||||
use std::ptr;
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
|
||||
use std::slice;
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
|
@ -106,7 +100,6 @@ static OPT_SYNC: &str = "sync";
|
|||
static OPT_TYPE: &str = "type";
|
||||
static OPT_PRINT_TYPE: &str = "print-type";
|
||||
static OPT_EXCLUDE_TYPE: &str = "exclude-type";
|
||||
static OPT_VERSION: &str = "version";
|
||||
|
||||
static MOUNT_OPT_BIND: &str = "bind";
|
||||
|
||||
|
@ -144,7 +137,7 @@ struct MountInfo {
|
|||
|
||||
#[cfg(all(
|
||||
target_os = "freebsd",
|
||||
not(all(target_os = "macos", target_arch = "x86_64"))
|
||||
not(all(target_vendor = "apple", target_arch = "x86_64"))
|
||||
))]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -216,20 +209,20 @@ fn get_usage() -> String {
|
|||
format!("{0} [OPTION]... [FILE]...", executable!())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
|
||||
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
|
||||
extern "C" {
|
||||
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
|
||||
#[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
|
||||
#[link_name = "getmntinfo$INODE64"]
|
||||
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
|
||||
|
||||
#[cfg(all(
|
||||
target_os = "freebsd",
|
||||
not(all(target_os = "macos", target_arch = "x86_64"))
|
||||
#[cfg(any(
|
||||
all(target_os = "freebsd"),
|
||||
all(target_vendor = "apple", target_arch = "aarch64")
|
||||
))]
|
||||
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
|
||||
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
|
||||
impl From<statfs> for MountInfo {
|
||||
fn from(statfs: statfs) -> Self {
|
||||
let mut info = MountInfo {
|
||||
|
@ -592,7 +585,7 @@ fn read_fs_list() -> Vec<MountInfo> {
|
|||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
|
||||
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
|
||||
{
|
||||
let mut mptr: *mut statfs = ptr::null_mut();
|
||||
let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) };
|
||||
|
@ -676,7 +669,7 @@ fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Ve
|
|||
#[allow(clippy::map_entry)]
|
||||
{
|
||||
if acc.contains_key(&id) {
|
||||
let seen = acc.get(&id).unwrap().replace(mi.clone());
|
||||
let seen = acc[&id].replace(mi.clone());
|
||||
let target_nearer_root = seen.mount_dir.len() > mi.mount_dir.len();
|
||||
// With bind mounts, prefer items nearer the root of the source
|
||||
let source_below_root = !seen.mount_root.is_empty()
|
||||
|
@ -694,7 +687,7 @@ fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Ve
|
|||
environments for example. */
|
||||
|| seen.mount_dir != mi.mount_dir)
|
||||
{
|
||||
acc.get(&id).unwrap().replace(seen);
|
||||
acc[&id].replace(seen);
|
||||
}
|
||||
} else {
|
||||
acc.insert(id, Cell::new(mi));
|
||||
|
@ -717,14 +710,14 @@ fn human_readable(value: u64, base: i64) -> String {
|
|||
|
||||
// ref: [Binary prefix](https://en.wikipedia.org/wiki/Binary_prefix) @@ <https://archive.is/cnwmF>
|
||||
// ref: [SI/metric prefix](https://en.wikipedia.org/wiki/Metric_prefix) @@ <https://archive.is/QIuLj>
|
||||
1000 => match decimal_prefix(value as f64) {
|
||||
Standalone(bytes) => bytes.to_string(),
|
||||
Prefixed(prefix, bytes) => format!("{:.1}{}", bytes, prefix.symbol()),
|
||||
1000 => match NumberPrefix::decimal(value as f64) {
|
||||
NumberPrefix::Standalone(bytes) => bytes.to_string(),
|
||||
NumberPrefix::Prefixed(prefix, bytes) => format!("{:.1}{}", bytes, prefix.symbol()),
|
||||
},
|
||||
|
||||
1024 => match binary_prefix(value as f64) {
|
||||
Standalone(bytes) => bytes.to_string(),
|
||||
Prefixed(prefix, bytes) => format!("{:.1}{}", bytes, prefix.symbol()),
|
||||
1024 => match NumberPrefix::binary(value as f64) {
|
||||
NumberPrefix::Standalone(bytes) => bytes.to_string(),
|
||||
NumberPrefix::Prefixed(prefix, bytes) => format!("{:.1}{}", bytes, prefix.symbol()),
|
||||
},
|
||||
|
||||
_ => crash!(EXIT_ERR, "Internal error: Unknown base value {}", base),
|
||||
|
@ -760,7 +753,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.takes_value(true)
|
||||
.help(
|
||||
"scale sizes by SIZE before printing them; e.g.\
|
||||
'-BM' prints sizes in units of 1,048,576 bytes",
|
||||
'-BM' prints sizes in units of 1,048,576 bytes",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
|
@ -817,7 +810,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.use_delimiter(true)
|
||||
.help(
|
||||
"use the output format defined by FIELD_LIST,\
|
||||
or print all fields if FIELD_LIST is omitted.",
|
||||
or print all fields if FIELD_LIST is omitted.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
|
@ -854,21 +847,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.use_delimiter(true)
|
||||
.help("limit listing to file systems not of type TYPE"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_VERSION)
|
||||
.short("v")
|
||||
.long("version")
|
||||
.help("output version information and exit"),
|
||||
)
|
||||
.arg(Arg::with_name(OPT_PATHS).multiple(true))
|
||||
.help("Filesystem(s) to list")
|
||||
.get_matches_from(args);
|
||||
|
||||
if matches.is_present(OPT_VERSION) {
|
||||
println!("{} {}", executable!(), VERSION);
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
let paths: Vec<String> = matches
|
||||
.values_of(OPT_PATHS)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_dircolors"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "dircolors ~ (uutils) display commands to set LS_COLORS"
|
||||
|
@ -16,8 +16,8 @@ path = "src/dircolors.rs"
|
|||
|
||||
[dependencies]
|
||||
glob = "0.3.0"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "dircolors"
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) clrtoeol dircolors eightbit endcode fnmatch leftcode multihardlink rightcode setenv sgid suid
|
||||
|
||||
extern crate glob;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_dirname"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "dirname ~ (uutils) display parent directory of PATHNAME"
|
||||
|
@ -15,9 +15,10 @@ edition = "2018"
|
|||
path = "src/dirname.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
libc = "0.2.42"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "dirname"
|
||||
|
|
|
@ -8,32 +8,57 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::path::Path;
|
||||
|
||||
static NAME: &str = "dirname";
|
||||
static SYNTAX: &str = "[OPTION] NAME...";
|
||||
static SUMMARY: &str = "strip last component from file name";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static LONG_HELP: &str = "
|
||||
Output each NAME with its last non-slash component and trailing slashes
|
||||
removed; if NAME contains no /'s, output '.' (meaning the current
|
||||
directory).
|
||||
";
|
||||
|
||||
mod options {
|
||||
pub const ZERO: &str = "zero";
|
||||
pub const DIR: &str = "dir";
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
|
||||
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
|
||||
.optflag("z", "zero", "separate output with NUL rather than newline")
|
||||
.parse(args);
|
||||
let matches = App::new(executable!())
|
||||
.name(NAME)
|
||||
.usage(SYNTAX)
|
||||
.about(SUMMARY)
|
||||
.after_help(LONG_HELP)
|
||||
.version(VERSION)
|
||||
.arg(
|
||||
Arg::with_name(options::ZERO)
|
||||
.short(options::ZERO)
|
||||
.short("z")
|
||||
.takes_value(false)
|
||||
.help("separate output with NUL rather than newline"),
|
||||
)
|
||||
.arg(Arg::with_name(options::DIR).hidden(true).multiple(true))
|
||||
.get_matches_from(args);
|
||||
|
||||
let separator = if matches.opt_present("zero") {
|
||||
let separator = if matches.is_present(options::ZERO) {
|
||||
"\0"
|
||||
} else {
|
||||
"\n"
|
||||
};
|
||||
|
||||
if !matches.free.is_empty() {
|
||||
for path in &matches.free {
|
||||
let dirnames: Vec<String> = matches
|
||||
.values_of(options::DIR)
|
||||
.unwrap_or_default()
|
||||
.map(str::to_owned)
|
||||
.collect();
|
||||
|
||||
if !dirnames.is_empty() {
|
||||
for path in dirnames.iter() {
|
||||
let p = Path::new(path);
|
||||
match p.parent() {
|
||||
Some(d) => {
|
||||
|
@ -54,8 +79,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
print!("{}", separator);
|
||||
}
|
||||
} else {
|
||||
println!("{0}: missing operand", NAME);
|
||||
println!("Try '{0} --help' for more information.", NAME);
|
||||
show_usage_error!("missing operand");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_du"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "du ~ (uutils) display disk usage"
|
||||
|
@ -16,8 +16,9 @@ path = "src/du.rs"
|
|||
|
||||
[dependencies]
|
||||
time = "0.1.40"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
winapi = { version="0.3", features=[] }
|
||||
|
||||
[[bin]]
|
||||
name = "du"
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) BLOCKSIZE inode inodes ment strs
|
||||
|
||||
extern crate time;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
|
@ -17,9 +15,24 @@ use std::env;
|
|||
use std::fs;
|
||||
use std::io::{stderr, Result, Write};
|
||||
use std::iter;
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use std::path::PathBuf;
|
||||
use time::Timespec;
|
||||
#[cfg(windows)]
|
||||
use winapi::shared::minwindef::{DWORD, LPVOID};
|
||||
#[cfg(windows)]
|
||||
use winapi::um::fileapi::{FILE_ID_INFO, FILE_STANDARD_INFO};
|
||||
#[cfg(windows)]
|
||||
use winapi::um::minwinbase::{FileIdInfo, FileStandardInfo};
|
||||
#[cfg(windows)]
|
||||
use winapi::um::winbase::GetFileInformationByHandleEx;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::winnt::{FILE_ID_128, ULONGLONG};
|
||||
|
||||
const NAME: &str = "du";
|
||||
const SUMMARY: &str = "estimate file space usage";
|
||||
|
@ -45,12 +58,18 @@ struct Options {
|
|||
separate_dirs: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
struct FileInfo {
|
||||
file_id: u128,
|
||||
dev_id: u64,
|
||||
}
|
||||
|
||||
struct Stat {
|
||||
path: PathBuf,
|
||||
is_dir: bool,
|
||||
size: u64,
|
||||
blocks: u64,
|
||||
inode: u64,
|
||||
inode: Option<FileInfo>,
|
||||
created: u64,
|
||||
accessed: u64,
|
||||
modified: u64,
|
||||
|
@ -59,19 +78,114 @@ struct Stat {
|
|||
impl Stat {
|
||||
fn new(path: PathBuf) -> Result<Stat> {
|
||||
let metadata = fs::symlink_metadata(&path)?;
|
||||
Ok(Stat {
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let file_info = FileInfo {
|
||||
file_id: metadata.ino() as u128,
|
||||
dev_id: metadata.dev(),
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
return Ok(Stat {
|
||||
path,
|
||||
is_dir: metadata.is_dir(),
|
||||
size: metadata.len(),
|
||||
blocks: metadata.blocks() as u64,
|
||||
inode: metadata.ino() as u64,
|
||||
inode: Some(file_info),
|
||||
created: metadata.mtime() as u64,
|
||||
accessed: metadata.atime() as u64,
|
||||
modified: metadata.mtime() as u64,
|
||||
});
|
||||
|
||||
#[cfg(windows)]
|
||||
let size_on_disk = get_size_on_disk(&path);
|
||||
#[cfg(windows)]
|
||||
let file_info = get_file_info(&path);
|
||||
#[cfg(windows)]
|
||||
Ok(Stat {
|
||||
path,
|
||||
is_dir: metadata.is_dir(),
|
||||
size: metadata.len(),
|
||||
blocks: size_on_disk / 1024 * 2,
|
||||
inode: file_info,
|
||||
created: windows_time_to_unix_time(metadata.creation_time()),
|
||||
accessed: windows_time_to_unix_time(metadata.last_access_time()),
|
||||
modified: windows_time_to_unix_time(metadata.last_write_time()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
// https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.creation_time
|
||||
// "The returned 64-bit value [...] which represents the number of 100-nanosecond intervals since January 1, 1601 (UTC)."
|
||||
fn windows_time_to_unix_time(win_time: u64) -> u64 {
|
||||
win_time / 10_000 - 11_644_473_600_000
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_size_on_disk(path: &PathBuf) -> u64 {
|
||||
let mut size_on_disk = 0;
|
||||
|
||||
// bind file so it stays in scope until end of function
|
||||
// if it goes out of scope the handle below becomes invalid
|
||||
let file = match fs::File::open(path) {
|
||||
Ok(file) => file,
|
||||
Err(_) => return size_on_disk, // opening directories will fail
|
||||
};
|
||||
|
||||
let handle = file.as_raw_handle();
|
||||
|
||||
unsafe {
|
||||
let mut file_info: FILE_STANDARD_INFO = core::mem::zeroed();
|
||||
let file_info_ptr: *mut FILE_STANDARD_INFO = &mut file_info;
|
||||
|
||||
let success = GetFileInformationByHandleEx(
|
||||
handle,
|
||||
FileStandardInfo,
|
||||
file_info_ptr as LPVOID,
|
||||
std::mem::size_of::<FILE_STANDARD_INFO>() as DWORD,
|
||||
);
|
||||
|
||||
if success != 0 {
|
||||
size_on_disk = *file_info.AllocationSize.QuadPart() as u64;
|
||||
}
|
||||
}
|
||||
|
||||
size_on_disk
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_file_info(path: &PathBuf) -> Option<FileInfo> {
|
||||
let mut result = None;
|
||||
|
||||
let file = match fs::File::open(path) {
|
||||
Ok(file) => file,
|
||||
Err(_) => return result,
|
||||
};
|
||||
|
||||
let handle = file.as_raw_handle();
|
||||
|
||||
unsafe {
|
||||
let mut file_info: FILE_ID_INFO = core::mem::zeroed();
|
||||
let file_info_ptr: *mut FILE_ID_INFO = &mut file_info;
|
||||
|
||||
let success = GetFileInformationByHandleEx(
|
||||
handle,
|
||||
FileIdInfo,
|
||||
file_info_ptr as LPVOID,
|
||||
std::mem::size_of::<FILE_ID_INFO>() as DWORD,
|
||||
);
|
||||
|
||||
if success != 0 {
|
||||
result = Some(FileInfo {
|
||||
file_id: std::mem::transmute::<FILE_ID_128, u128>(file_info.FileId),
|
||||
dev_id: std::mem::transmute::<ULONGLONG, u64>(file_info.VolumeSerialNumber),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn unit_string_to_number(s: &str) -> Option<u64> {
|
||||
let mut offset = 0;
|
||||
let mut s_chars = s.chars().rev();
|
||||
|
@ -139,7 +253,7 @@ fn du(
|
|||
mut my_stat: Stat,
|
||||
options: &Options,
|
||||
depth: usize,
|
||||
inodes: &mut HashSet<u64>,
|
||||
inodes: &mut HashSet<FileInfo>,
|
||||
) -> Box<dyn DoubleEndedIterator<Item = Stat>> {
|
||||
let mut stats = vec![];
|
||||
let mut futures = vec![];
|
||||
|
@ -166,10 +280,13 @@ fn du(
|
|||
if this_stat.is_dir {
|
||||
futures.push(du(this_stat, options, depth + 1, inodes));
|
||||
} else {
|
||||
if inodes.contains(&this_stat.inode) {
|
||||
continue;
|
||||
if this_stat.inode.is_some() {
|
||||
let inode = this_stat.inode.unwrap();
|
||||
if inodes.contains(&inode) {
|
||||
continue;
|
||||
}
|
||||
inodes.insert(inode);
|
||||
}
|
||||
inodes.insert(this_stat.inode);
|
||||
my_stat.size += this_stat.size;
|
||||
my_stat.blocks += this_stat.blocks;
|
||||
if options.all {
|
||||
|
@ -202,6 +319,9 @@ fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String {
|
|||
return format!("{:.1}{}", (size as f64) / (limit as f64), unit);
|
||||
}
|
||||
}
|
||||
if size == 0 {
|
||||
return format!("0");
|
||||
}
|
||||
format!("{}B", size)
|
||||
}
|
||||
|
||||
|
@ -420,7 +540,7 @@ Try '{} --help' for more information.",
|
|||
let path = PathBuf::from(&path_str);
|
||||
match Stat::new(path) {
|
||||
Ok(stat) => {
|
||||
let mut inodes: HashSet<u64> = HashSet::new();
|
||||
let mut inodes: HashSet<FileInfo> = HashSet::new();
|
||||
|
||||
let iter = du(stat, &options, 0, &mut inodes);
|
||||
let (_, len) = iter.size_hint();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_echo"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "echo ~ (uutils) display TEXT"
|
||||
|
@ -15,8 +15,9 @@ edition = "2018"
|
|||
path = "src/echo.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
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]]
|
||||
name = "echo"
|
||||
|
|
|
@ -9,14 +9,17 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use std::io::{self, Write};
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
|
||||
const SYNTAX: &str = "[OPTIONS]... [STRING]...";
|
||||
const NAME: &str = "echo";
|
||||
const SUMMARY: &str = "display a line of text";
|
||||
const HELP: &str = r#"
|
||||
const USAGE: &str = "[OPTIONS]... [STRING]...";
|
||||
const AFTER_HELP: &str = r#"
|
||||
Echo the STRING(s) to standard output.
|
||||
|
||||
If -e is in effect, the following sequences are recognized:
|
||||
|
||||
\\\\ backslash
|
||||
|
@ -33,6 +36,13 @@ const HELP: &str = r#"
|
|||
\\xHH byte with hexadecimal value HH (1 to 2 digits)
|
||||
"#;
|
||||
|
||||
mod options {
|
||||
pub const STRING: &str = "STRING";
|
||||
pub const NO_NEWLINE: &str = "no_newline";
|
||||
pub const ENABLE_BACKSLASH_ESCAPE: &str = "enable_backslash_escape";
|
||||
pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape";
|
||||
}
|
||||
|
||||
fn parse_code(
|
||||
input: &mut Peekable<Chars>,
|
||||
base: u32,
|
||||
|
@ -103,22 +113,53 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result<bool> {
|
|||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
|
||||
let matches = app!(SYNTAX, SUMMARY, HELP)
|
||||
.optflag("n", "", "do not output the trailing newline")
|
||||
.optflag("e", "", "enable interpretation of backslash escapes")
|
||||
.optflag(
|
||||
"E",
|
||||
"",
|
||||
"disable interpretation of backslash escapes (default)",
|
||||
let matches = App::new(executable!())
|
||||
.name(NAME)
|
||||
// TrailingVarArg specifies the final positional argument is a VarArg
|
||||
// and it doesn't attempts the parse any further args.
|
||||
// Final argument must have multiple(true) or the usage string equivalent.
|
||||
.setting(clap::AppSettings::TrailingVarArg)
|
||||
.setting(clap::AppSettings::AllowLeadingHyphen)
|
||||
.version(crate_version!())
|
||||
.about(SUMMARY)
|
||||
.after_help(AFTER_HELP)
|
||||
.usage(USAGE)
|
||||
.arg(
|
||||
Arg::with_name(options::NO_NEWLINE)
|
||||
.short("n")
|
||||
.help("do not output the trailing newline")
|
||||
.takes_value(false)
|
||||
.display_order(1),
|
||||
)
|
||||
.parse(args);
|
||||
.arg(
|
||||
Arg::with_name(options::ENABLE_BACKSLASH_ESCAPE)
|
||||
.short("e")
|
||||
.help("enable interpretation of backslash escapes")
|
||||
.takes_value(false)
|
||||
.display_order(2),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::DISABLE_BACKSLASH_ESCAPE)
|
||||
.short("E")
|
||||
.help("disable interpretation of backslash escapes (default)")
|
||||
.takes_value(false)
|
||||
.display_order(3),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::STRING)
|
||||
.multiple(true)
|
||||
.allow_hyphen_values(true),
|
||||
)
|
||||
.get_matches_from(args);
|
||||
|
||||
let no_newline = matches.opt_present("n");
|
||||
let escaped = matches.opt_present("e");
|
||||
let no_newline = matches.is_present(options::NO_NEWLINE);
|
||||
let escaped = matches.is_present(options::ENABLE_BACKSLASH_ESCAPE);
|
||||
let values: Vec<String> = match matches.values_of(options::STRING) {
|
||||
Some(s) => s.map(|s| s.to_string()).collect(),
|
||||
None => vec!["".to_string()],
|
||||
};
|
||||
|
||||
match execute(no_newline, escaped, matches.free) {
|
||||
match execute(no_newline, escaped, values) {
|
||||
Ok(_) => 0,
|
||||
Err(f) => {
|
||||
show_error!("{}", f);
|
||||
|
|
6
src/uu/env/Cargo.toml
vendored
6
src/uu/env/Cargo.toml
vendored
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_env"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND"
|
||||
|
@ -18,8 +18,8 @@ path = "src/env.rs"
|
|||
clap = "2.33"
|
||||
libc = "0.2.42"
|
||||
rust-ini = "0.13.0"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "env"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_expand"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "expand ~ (uutils) convert input tabs to spaces"
|
||||
|
@ -15,10 +15,10 @@ edition = "2018"
|
|||
path = "src/expand.rs"
|
||||
|
||||
[dependencies]
|
||||
getopts = "0.2.18"
|
||||
clap = "2.33"
|
||||
unicode-width = "0.1.5"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "expand"
|
||||
|
|
|
@ -9,30 +9,39 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) ctype cwidth iflag nbytes nspaces nums tspaces uflag
|
||||
|
||||
extern crate getopts;
|
||||
extern crate unicode_width;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
|
||||
use std::iter::repeat;
|
||||
use std::str::from_utf8;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
static SYNTAX: &str = "[OPTION]... [FILE]...";
|
||||
static SUMMARY: &str = "Convert tabs in each FILE to spaces, writing to standard output.
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output.
|
||||
With no FILE, or when FILE is -, read standard input.";
|
||||
|
||||
pub mod options {
|
||||
pub static TABS: &str = "tabs";
|
||||
pub static INITIAL: &str = "initial";
|
||||
pub static NO_UTF8: &str = "no-utf8";
|
||||
pub static FILES: &str = "FILES";
|
||||
}
|
||||
|
||||
static LONG_HELP: &str = "";
|
||||
|
||||
static DEFAULT_TABSTOP: usize = 8;
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION]... [FILE]...", executable!())
|
||||
}
|
||||
|
||||
fn tabstops_parse(s: String) -> Vec<usize> {
|
||||
let words = s.split(',').collect::<Vec<&str>>();
|
||||
let words = s.split(',');
|
||||
|
||||
let nums = words
|
||||
.into_iter()
|
||||
.map(|sn| {
|
||||
sn.parse::<usize>()
|
||||
.unwrap_or_else(|_| crash!(1, "{}\n", "tab size contains invalid character(s)"))
|
||||
|
@ -62,14 +71,14 @@ struct Options {
|
|||
}
|
||||
|
||||
impl Options {
|
||||
fn new(matches: getopts::Matches) -> Options {
|
||||
let tabstops = match matches.opt_str("t") {
|
||||
fn new(matches: &ArgMatches) -> Options {
|
||||
let tabstops = match matches.value_of(options::TABS) {
|
||||
Some(s) => tabstops_parse(s.to_string()),
|
||||
None => vec![DEFAULT_TABSTOP],
|
||||
Some(s) => tabstops_parse(s),
|
||||
};
|
||||
|
||||
let iflag = matches.opt_present("i");
|
||||
let uflag = !matches.opt_present("U");
|
||||
let iflag = matches.is_present(options::INITIAL);
|
||||
let uflag = !matches.is_present(options::NO_UTF8);
|
||||
|
||||
// avoid allocations when dumping out long sequences of spaces
|
||||
// by precomputing the longest string of spaces we will ever need
|
||||
|
@ -84,10 +93,9 @@ impl Options {
|
|||
.unwrap(); // length of tabstops is guaranteed >= 1
|
||||
let tspaces = repeat(' ').take(nspaces).collect();
|
||||
|
||||
let files = if matches.free.is_empty() {
|
||||
vec!["-".to_owned()]
|
||||
} else {
|
||||
matches.free
|
||||
let files: Vec<String> = match matches.values_of(options::FILES) {
|
||||
Some(s) => s.map(|v| v.to_string()).collect(),
|
||||
None => vec!["-".to_owned()],
|
||||
};
|
||||
|
||||
Options {
|
||||
|
@ -101,31 +109,40 @@ impl Options {
|
|||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
|
||||
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
|
||||
.optflag("i", "initial", "do not convert tabs after non blanks")
|
||||
.optopt(
|
||||
"t",
|
||||
"tabs",
|
||||
"have tabs NUMBER characters apart, not 8",
|
||||
"NUMBER",
|
||||
let usage = get_usage();
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.after_help(LONG_HELP)
|
||||
.arg(
|
||||
Arg::with_name(options::INITIAL)
|
||||
.long(options::INITIAL)
|
||||
.short("i")
|
||||
.help("do not convert tabs after non blanks"),
|
||||
)
|
||||
.optopt(
|
||||
"t",
|
||||
"tabs",
|
||||
"use comma separated list of explicit tab positions",
|
||||
"LIST",
|
||||
.arg(
|
||||
Arg::with_name(options::TABS)
|
||||
.long(options::TABS)
|
||||
.short("t")
|
||||
.value_name("N, LIST")
|
||||
.takes_value(true)
|
||||
.help("have tabs N characters apart, not 8 or use comma separated list of explicit tab positions"),
|
||||
)
|
||||
.optflag(
|
||||
"U",
|
||||
"no-utf8",
|
||||
"interpret input file as 8-bit ASCII rather than UTF-8",
|
||||
.arg(
|
||||
Arg::with_name(options::NO_UTF8)
|
||||
.long(options::NO_UTF8)
|
||||
.short("U")
|
||||
.help("interpret input file as 8-bit ASCII rather than UTF-8"),
|
||||
).arg(
|
||||
Arg::with_name(options::FILES)
|
||||
.multiple(true)
|
||||
.hidden(true)
|
||||
.takes_value(true)
|
||||
)
|
||||
.parse(args);
|
||||
|
||||
expand(Options::new(matches));
|
||||
.get_matches_from(args);
|
||||
|
||||
expand(Options::new(&matches));
|
||||
0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_expr"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "expr ~ (uutils) display the value of EXPRESSION"
|
||||
|
@ -17,8 +17,8 @@ path = "src/expr.rs"
|
|||
[dependencies]
|
||||
libc = "0.2.42"
|
||||
onig = "~4.3.2"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "expr"
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
//* For the full copyright and license information, please view the LICENSE
|
||||
//* file that was distributed with this source code.
|
||||
|
||||
extern crate onig;
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
|
@ -41,7 +40,7 @@ fn process_expr(token_strings: &[String]) -> Result<String, String> {
|
|||
|
||||
fn print_expr_ok(expr_result: &str) -> i32 {
|
||||
println!("{}", expr_result);
|
||||
if expr_result == "0" || expr_result == "" {
|
||||
if expr_result == "0" || expr_result.is_empty() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
|
|
|
@ -148,11 +148,11 @@ impl ASTNode {
|
|||
|a: &String, b: &String| Ok(bool_as_string(a >= b)),
|
||||
&operand_values,
|
||||
),
|
||||
"|" => infix_operator_or(&operand_values),
|
||||
"&" => infix_operator_and(&operand_values),
|
||||
"|" => Ok(infix_operator_or(&operand_values)),
|
||||
"&" => Ok(infix_operator_and(&operand_values)),
|
||||
":" | "match" => operator_match(&operand_values),
|
||||
"length" => prefix_operator_length(&operand_values),
|
||||
"index" => prefix_operator_index(&operand_values),
|
||||
"length" => Ok(prefix_operator_length(&operand_values)),
|
||||
"index" => Ok(prefix_operator_index(&operand_values)),
|
||||
"substr" => prefix_operator_substr(&operand_values),
|
||||
|
||||
_ => Err(format!("operation not implemented: {}", op_type)),
|
||||
|
@ -465,20 +465,20 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn infix_operator_or(values: &[String]) -> Result<String, String> {
|
||||
fn infix_operator_or(values: &[String]) -> String {
|
||||
assert!(values.len() == 2);
|
||||
if value_as_bool(&values[0]) {
|
||||
Ok(values[0].clone())
|
||||
values[0].clone()
|
||||
} else {
|
||||
Ok(values[1].clone())
|
||||
values[1].clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn infix_operator_and(values: &[String]) -> Result<String, String> {
|
||||
fn infix_operator_and(values: &[String]) -> String {
|
||||
if value_as_bool(&values[0]) && value_as_bool(&values[1]) {
|
||||
Ok(values[0].clone())
|
||||
values[0].clone()
|
||||
} else {
|
||||
Ok(0.to_string())
|
||||
0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -502,12 +502,12 @@ fn operator_match(values: &[String]) -> Result<String, String> {
|
|||
}
|
||||
}
|
||||
|
||||
fn prefix_operator_length(values: &[String]) -> Result<String, String> {
|
||||
fn prefix_operator_length(values: &[String]) -> String {
|
||||
assert!(values.len() == 1);
|
||||
Ok(values[0].len().to_string())
|
||||
values[0].len().to_string()
|
||||
}
|
||||
|
||||
fn prefix_operator_index(values: &[String]) -> Result<String, String> {
|
||||
fn prefix_operator_index(values: &[String]) -> String {
|
||||
assert!(values.len() == 2);
|
||||
let haystack = &values[0];
|
||||
let needles = &values[1];
|
||||
|
@ -515,11 +515,11 @@ fn prefix_operator_index(values: &[String]) -> Result<String, String> {
|
|||
for (current_idx, ch_h) in haystack.chars().enumerate() {
|
||||
for ch_n in needles.chars() {
|
||||
if ch_n == ch_h {
|
||||
return Ok(current_idx.to_string());
|
||||
return current_idx.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok("0".to_string())
|
||||
"0".to_string()
|
||||
}
|
||||
|
||||
fn prefix_operator_substr(values: &[String]) -> Result<String, String> {
|
||||
|
|
|
@ -63,6 +63,8 @@ impl Token {
|
|||
}
|
||||
}
|
||||
fn is_a_close_paren(&self) -> bool {
|
||||
#[allow(clippy::match_like_matches_macro)]
|
||||
// `matches!(...)` macro not stabilized until rust v1.42
|
||||
match *self {
|
||||
Token::ParClose => true,
|
||||
_ => false,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_factor"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "factor ~ (uutils) display the prime factors of each NUMBER"
|
||||
|
@ -12,14 +12,15 @@ categories = ["command-line-utilities"]
|
|||
edition = "2018"
|
||||
|
||||
[build-dependencies]
|
||||
num-traits = "0.2" # used in src/numerics.rs, which is included by build.rs
|
||||
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
|
||||
|
||||
|
||||
[dependencies]
|
||||
num-traits = "0.2"
|
||||
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
|
||||
rand = { version="0.7", features=["small_rng"] }
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
smallvec = { version="0.6.14, < 1.0" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
|
|
@ -29,6 +29,7 @@ use miller_rabin::is_prime;
|
|||
|
||||
#[path = "src/numeric/modular_inverse.rs"]
|
||||
mod modular_inverse;
|
||||
#[allow(unused_imports)] // imports there are used, but invisible from build.rs
|
||||
#[path = "src/numeric/traits.rs"]
|
||||
mod traits;
|
||||
use modular_inverse::modular_inverse;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) filts, minidx, minkey paridx
|
||||
|
||||
use std::iter::{Chain, Cycle, Map};
|
||||
use std::iter::{Chain, Copied, Cycle};
|
||||
use std::slice::Iter;
|
||||
|
||||
/// A lazy Sieve of Eratosthenes.
|
||||
|
@ -68,27 +68,17 @@ impl Sieve {
|
|||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn primes() -> PrimeSieve {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
fn deref(x: &u64) -> u64 {
|
||||
*x
|
||||
}
|
||||
let deref = deref as fn(&u64) -> u64;
|
||||
INIT_PRIMES.iter().map(deref).chain(Sieve::new())
|
||||
INIT_PRIMES.iter().copied().chain(Sieve::new())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn odd_primes() -> PrimeSieve {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
fn deref(x: &u64) -> u64 {
|
||||
*x
|
||||
}
|
||||
let deref = deref as fn(&u64) -> u64;
|
||||
(&INIT_PRIMES[1..]).iter().map(deref).chain(Sieve::new())
|
||||
(&INIT_PRIMES[1..]).iter().copied().chain(Sieve::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub type PrimeSieve = Chain<Map<Iter<'static, u64>, fn(&u64) -> u64>, Sieve>;
|
||||
pub type PrimeSieve = Chain<Copied<Iter<'static, u64>>, Sieve>;
|
||||
|
||||
/// An iterator that generates an infinite list of numbers that are
|
||||
/// not divisible by any of 2, 3, 5, or 7.
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
// * For the full copyright and license information, please view the LICENSE file
|
||||
// * that was distributed with this source code.
|
||||
|
||||
extern crate rand;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
|
||||
|
@ -16,11 +15,16 @@ use crate::{miller_rabin, rho, table};
|
|||
type Exponent = u8;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Decomposition(Vec<(u64, Exponent)>);
|
||||
struct Decomposition(SmallVec<[(u64, Exponent); NUM_FACTORS_INLINE]>);
|
||||
|
||||
// The number of factors to inline directly into a `Decomposition` object.
|
||||
// As a consequence of the Erdős–Kac theorem, the average number of prime factors
|
||||
// of integers < 10²⁵ ≃ 2⁸³ is 4, so we can use a slightly higher value.
|
||||
const NUM_FACTORS_INLINE: usize = 5;
|
||||
|
||||
impl Decomposition {
|
||||
fn one() -> Decomposition {
|
||||
Decomposition(Vec::new())
|
||||
Decomposition(SmallVec::new())
|
||||
}
|
||||
|
||||
fn add(&mut self, factor: u64, exp: Exponent) {
|
||||
|
@ -141,10 +145,10 @@ pub fn factor(mut n: u64) -> Factors {
|
|||
return factors;
|
||||
}
|
||||
|
||||
let z = n.trailing_zeros();
|
||||
if z > 0 {
|
||||
factors.add(2, z as Exponent);
|
||||
n >>= z;
|
||||
let n_zeros = n.trailing_zeros();
|
||||
if n_zeros > 0 {
|
||||
factors.add(2, n_zeros as Exponent);
|
||||
n >>= n_zeros;
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
|
@ -162,8 +166,20 @@ pub fn factor(mut n: u64) -> Factors {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{factor, Factors};
|
||||
use super::{factor, Decomposition, Exponent, Factors};
|
||||
use quickcheck::quickcheck;
|
||||
use smallvec::smallvec;
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[test]
|
||||
fn factor_2044854919485649() {
|
||||
let f = Factors(RefCell::new(Decomposition(smallvec![
|
||||
(503, 1),
|
||||
(2423, 1),
|
||||
(40961, 2)
|
||||
])));
|
||||
assert_eq!(factor(f.product()), f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factor_recombines_small() {
|
||||
|
@ -182,7 +198,7 @@ mod tests {
|
|||
#[test]
|
||||
fn factor_recombines_strong_pseudoprime() {
|
||||
// This is a strong pseudoprime (wrt. miller_rabin::BASIS)
|
||||
// and triggered a bug in rho::factor's codepath handling
|
||||
// and triggered a bug in rho::factor's code path handling
|
||||
// miller_rabbin::Result::Composite
|
||||
let pseudoprime = 17179869183;
|
||||
for _ in 0..20 {
|
||||
|
@ -197,9 +213,15 @@ mod tests {
|
|||
i == 0 || factor(i).product() == i
|
||||
}
|
||||
|
||||
fn recombines_factors(f: Factors) -> bool {
|
||||
fn recombines_factors(f: Factors) -> () {
|
||||
assert_eq!(factor(f.product()), f);
|
||||
true
|
||||
}
|
||||
|
||||
fn exponentiate_factors(f: Factors, e: Exponent) -> () {
|
||||
if e == 0 { return; }
|
||||
if let Some(fe) = f.product().checked_pow(e.into()) {
|
||||
assert_eq!(factor(fe), f ^ e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +235,7 @@ impl quickcheck::Arbitrary for Factors {
|
|||
let mut n = u64::MAX;
|
||||
|
||||
// Adam Kalai's algorithm for generating uniformly-distributed
|
||||
// integers and their factorisation.
|
||||
// integers and their factorization.
|
||||
//
|
||||
// See Generating Random Factored Numbers, Easily, J. Cryptology (2003)
|
||||
'attempt: loop {
|
||||
|
@ -234,3 +256,19 @@ impl quickcheck::Arbitrary for Factors {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl std::ops::BitXor<Exponent> for Factors {
|
||||
type Output = Self;
|
||||
|
||||
fn bitxor(self, rhs: Exponent) -> Factors {
|
||||
debug_assert_ne!(rhs, 0);
|
||||
let mut r = Factors::one();
|
||||
for (p, e) in self.0.borrow().0.iter() {
|
||||
r.add(*p, rhs * e);
|
||||
}
|
||||
|
||||
debug_assert_eq!(r.product(), self.product().pow(rhs.into()));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,10 +76,13 @@ mod tests {
|
|||
gcd(0, a) == a
|
||||
}
|
||||
|
||||
fn divisor(a: u64, b: u64) -> bool {
|
||||
// Test that gcd(a, b) divides a and b
|
||||
fn divisor(a: u64, b: u64) -> () {
|
||||
// Test that gcd(a, b) divides a and b, unless a == b == 0
|
||||
if a == 0 && b == 0 { return; }
|
||||
|
||||
let g = gcd(a, b);
|
||||
(g != 0 && a % g == 0 && b % g == 0) || (g == 0 && a == 0 && b == 0)
|
||||
assert_eq!(a % g, 0);
|
||||
assert_eq!(b % g, 0);
|
||||
}
|
||||
|
||||
fn commutative(a: u64, b: u64) -> bool {
|
||||
|
|
|
@ -9,7 +9,6 @@ mod gcd;
|
|||
pub use gcd::gcd;
|
||||
|
||||
pub(crate) mod traits;
|
||||
use traits::{DoubleInt, Int, OverflowingAdd};
|
||||
|
||||
mod modular_inverse;
|
||||
pub(crate) use modular_inverse::modular_inverse;
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
// * that was distributed with this source code.
|
||||
|
||||
use super::*;
|
||||
use num_traits::identities::{One, Zero};
|
||||
|
||||
use traits::{DoubleInt, Int, One, OverflowingAdd, Zero};
|
||||
|
||||
pub(crate) trait Arithmetic: Copy + Sized {
|
||||
// The type of integers mod m, in some opaque representation
|
||||
|
@ -72,7 +73,7 @@ impl<T: DoubleInt> Montgomery<T> {
|
|||
let Montgomery { a, n } = self;
|
||||
let m = T::from_double_width(x).wrapping_mul(a);
|
||||
let nm = (n.as_double_width()) * (m.as_double_width());
|
||||
let (xnm, overflow) = x.overflowing_add_(nm); // x + n*m
|
||||
let (xnm, overflow) = x.overflowing_add(&nm); // x + n*m
|
||||
debug_assert_eq!(
|
||||
xnm % (T::DoubleWidth::one() << T::zero().count_zeros() as usize),
|
||||
T::DoubleWidth::zero()
|
||||
|
@ -128,7 +129,7 @@ impl<T: DoubleInt> Arithmetic for Montgomery<T> {
|
|||
}
|
||||
|
||||
fn add(&self, a: Self::ModInt, b: Self::ModInt) -> Self::ModInt {
|
||||
let (r, overflow) = a.overflowing_add_(b);
|
||||
let (r, overflow) = a.overflowing_add(&b);
|
||||
|
||||
// In case of overflow, a+b = 2⁶⁴ + r = (2⁶⁴ - n) + r (working mod n)
|
||||
let r = if !overflow {
|
||||
|
|
|
@ -6,31 +6,16 @@
|
|||
// * For the full copyright and license information, please view the LICENSE file
|
||||
// * that was distributed with this source code.
|
||||
|
||||
pub(crate) use num_traits::{
|
||||
identities::{One, Zero},
|
||||
ops::overflowing::OverflowingAdd,
|
||||
};
|
||||
use num_traits::{
|
||||
int::PrimInt,
|
||||
ops::wrapping::{WrappingMul, WrappingNeg, WrappingSub},
|
||||
};
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
// NOTE: Trait can be removed once num-traits adds a similar one;
|
||||
// see https://github.com/rust-num/num-traits/issues/168
|
||||
pub(crate) trait OverflowingAdd: Sized {
|
||||
fn overflowing_add_(self, n: Self) -> (Self, bool);
|
||||
}
|
||||
|
||||
macro_rules! overflowing {
|
||||
($x:ty) => {
|
||||
impl OverflowingAdd for $x {
|
||||
fn overflowing_add_(self, n: Self) -> (Self, bool) {
|
||||
self.overflowing_add(n)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
overflowing!(u32);
|
||||
overflowing!(u64);
|
||||
overflowing!(u128);
|
||||
|
||||
pub(crate) trait Int:
|
||||
Display + Debug + PrimInt + OverflowingAdd + WrappingNeg + WrappingSub + WrappingMul
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_false"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "false ~ (uutils) do nothing and fail"
|
||||
|
@ -15,8 +15,8 @@ edition = "2018"
|
|||
path = "src/false.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "false"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_fmt"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "fmt ~ (uutils) reformat each paragraph of input"
|
||||
|
@ -15,10 +15,11 @@ edition = "2018"
|
|||
path = "src/fmt.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
libc = "0.2.42"
|
||||
unicode-width = "0.1.5"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "fmt"
|
||||
|
|
|
@ -7,11 +7,10 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) PSKIP linebreak ostream parasplit tabwidth xanti xprefix
|
||||
|
||||
extern crate unicode_width;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::cmp;
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, stdout, Write};
|
||||
|
@ -32,10 +31,29 @@ macro_rules! silent_unwrap(
|
|||
mod linebreak;
|
||||
mod parasplit;
|
||||
|
||||
// program's NAME and VERSION are used for -V and -h
|
||||
static SYNTAX: &str = "[OPTION]... [FILE]...";
|
||||
static SUMMARY: &str = "Reformat paragraphs from input files (or stdin) to stdout.";
|
||||
static LONG_HELP: &str = "";
|
||||
static ABOUT: &str = "Reformat paragraphs from input files (or stdin) to stdout.";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static MAX_WIDTH: usize = 2500;
|
||||
|
||||
static OPT_CROWN_MARGIN: &str = "crown-margin";
|
||||
static OPT_TAGGED_PARAGRAPH: &str = "tagged-paragraph";
|
||||
static OPT_PRESERVE_HEADERS: &str = "preserve-headers";
|
||||
static OPT_SPLIT_ONLY: &str = "split-only";
|
||||
static OPT_UNIFORM_SPACING: &str = "uniform-spacing";
|
||||
static OPT_PREFIX: &str = "prefix";
|
||||
static OPT_SKIP_PREFIX: &str = "skip-prefix";
|
||||
static OPT_EXACT_PREFIX: &str = "exact-prefix";
|
||||
static OPT_EXACT_SKIP_PREFIX: &str = "exact-skip-prefix";
|
||||
static OPT_WIDTH: &str = "width";
|
||||
static OPT_GOAL: &str = "goal";
|
||||
static OPT_QUICK: &str = "quick";
|
||||
static OPT_TAB_WIDTH: &str = "tab-width";
|
||||
|
||||
static ARG_FILES: &str = "files";
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{} [OPTION]... [FILE]...", executable!())
|
||||
}
|
||||
|
||||
pub type FileOrStdReader = BufReader<Box<dyn Read + 'static>>;
|
||||
pub struct FmtOptions {
|
||||
|
@ -58,23 +76,136 @@ pub struct FmtOptions {
|
|||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
let usage = get_usage();
|
||||
|
||||
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
|
||||
.optflag("c", "crown-margin", "First and second line of paragraph may have different indentations, in which case the first line's indentation is preserved, and each subsequent line's indentation matches the second line.")
|
||||
.optflag("t", "tagged-paragraph", "Like -c, except that the first and second line of a paragraph *must* have different indentation or they are treated as separate paragraphs.")
|
||||
.optflag("m", "preserve-headers", "Attempt to detect and preserve mail headers in the input. Be careful when combining this flag with -p.")
|
||||
.optflag("s", "split-only", "Split lines only, do not reflow.")
|
||||
.optflag("u", "uniform-spacing", "Insert exactly one space between words, and two between sentences. Sentence breaks in the input are detected as [?!.] followed by two spaces or a newline; other punctuation is not interpreted as a sentence break.")
|
||||
.optopt("p", "prefix", "Reformat only lines beginning with PREFIX, reattaching PREFIX to reformatted lines. Unless -x is specified, leading whitespace will be ignored when matching PREFIX.", "PREFIX")
|
||||
.optopt("P", "skip-prefix", "Do not reformat lines beginning with PSKIP. Unless -X is specified, leading whitespace will be ignored when matching PSKIP", "PSKIP")
|
||||
.optflag("x", "exact-prefix", "PREFIX must match at the beginning of the line with no preceding whitespace.")
|
||||
.optflag("X", "exact-skip-prefix", "PSKIP must match at the beginning of the line with no preceding whitespace.")
|
||||
.optopt("w", "width", "Fill output lines up to a maximum of WIDTH columns, default 79.", "WIDTH")
|
||||
.optopt("g", "goal", "Goal width, default ~0.94*WIDTH. Must be less than WIDTH.", "GOAL")
|
||||
.optflag("q", "quick", "Break lines more quickly at the expense of a potentially more ragged appearance.")
|
||||
.optopt("T", "tab-width", "Treat tabs as TABWIDTH spaces for determining line length, default 8. Note that this is used only for calculating line lengths; tabs are preserved in the output.", "TABWIDTH")
|
||||
.parse(args);
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.arg(
|
||||
Arg::with_name(OPT_CROWN_MARGIN)
|
||||
.short("c")
|
||||
.long(OPT_CROWN_MARGIN)
|
||||
.help(
|
||||
"First and second line of paragraph
|
||||
may have different indentations, in which
|
||||
case the first line's indentation is preserved,
|
||||
and each subsequent line's indentation matches the second line.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_TAGGED_PARAGRAPH)
|
||||
.short("t")
|
||||
.long("tagged-paragraph")
|
||||
.help(
|
||||
"Like -c, except that the first and second line of a paragraph *must*
|
||||
have different indentation or they are treated as separate paragraphs.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_PRESERVE_HEADERS)
|
||||
.short("m")
|
||||
.long("preserve-headers")
|
||||
.help(
|
||||
"Attempt to detect and preserve mail headers in the input.
|
||||
Be careful when combining this flag with -p.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_SPLIT_ONLY)
|
||||
.short("s")
|
||||
.long("split-only")
|
||||
.help("Split lines only, do not reflow."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_UNIFORM_SPACING)
|
||||
.short("u")
|
||||
.long("uniform-spacing")
|
||||
.help(
|
||||
"Insert exactly one
|
||||
space between words, and two between sentences.
|
||||
Sentence breaks in the input are detected as [?!.]
|
||||
followed by two spaces or a newline; other punctuation
|
||||
is not interpreted as a sentence break.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_PREFIX)
|
||||
.short("p")
|
||||
.long("prefix")
|
||||
.help(
|
||||
"Reformat only lines
|
||||
beginning with PREFIX, reattaching PREFIX to reformatted lines.
|
||||
Unless -x is specified, leading whitespace will be ignored
|
||||
when matching PREFIX.",
|
||||
)
|
||||
.value_name("PREFIX"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_SKIP_PREFIX)
|
||||
.short("P")
|
||||
.long("skip-prefix")
|
||||
.help(
|
||||
"Do not reformat lines
|
||||
beginning with PSKIP. Unless -X is specified, leading whitespace
|
||||
will be ignored when matching PSKIP",
|
||||
)
|
||||
.value_name("PSKIP"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_EXACT_PREFIX)
|
||||
.short("x")
|
||||
.long("exact-prefix")
|
||||
.help(
|
||||
"PREFIX must match at the
|
||||
beginning of the line with no preceding whitespace.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_EXACT_SKIP_PREFIX)
|
||||
.short("X")
|
||||
.long("exact-skip-prefix")
|
||||
.help(
|
||||
"PSKIP must match at the
|
||||
beginning of the line with no preceding whitespace.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_WIDTH)
|
||||
.short("w")
|
||||
.long("width")
|
||||
.help("Fill output lines up to a maximum of WIDTH columns, default 79.")
|
||||
.value_name("WIDTH"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_GOAL)
|
||||
.short("g")
|
||||
.long("goal")
|
||||
.help("Goal width, default ~0.94*WIDTH. Must be less than WIDTH.")
|
||||
.value_name("GOAL"),
|
||||
)
|
||||
.arg(Arg::with_name(OPT_QUICK).short("q").long("quick").help(
|
||||
"Break lines more quickly at the
|
||||
expense of a potentially more ragged appearance.",
|
||||
))
|
||||
.arg(
|
||||
Arg::with_name(OPT_TAB_WIDTH)
|
||||
.short("T")
|
||||
.long("tab-width")
|
||||
.help(
|
||||
"Treat tabs as TABWIDTH spaces for
|
||||
determining line length, default 8. Note that this is used only for
|
||||
calculating line lengths; tabs are preserved in the output.",
|
||||
)
|
||||
.value_name("TABWIDTH"),
|
||||
)
|
||||
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
|
||||
.get_matches_from(args);
|
||||
|
||||
let mut files: Vec<String> = matches
|
||||
.values_of(ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut fmt_opts = FmtOptions {
|
||||
crown: false,
|
||||
|
@ -94,69 +225,64 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
tabwidth: 8,
|
||||
};
|
||||
|
||||
if matches.opt_present("t") {
|
||||
fmt_opts.tagged = true;
|
||||
}
|
||||
if matches.opt_present("c") {
|
||||
fmt_opts.tagged = matches.is_present(OPT_TAGGED_PARAGRAPH);
|
||||
if matches.is_present(OPT_CROWN_MARGIN) {
|
||||
fmt_opts.crown = true;
|
||||
fmt_opts.tagged = false;
|
||||
}
|
||||
if matches.opt_present("m") {
|
||||
fmt_opts.mail = true;
|
||||
}
|
||||
if matches.opt_present("u") {
|
||||
fmt_opts.uniform = true;
|
||||
}
|
||||
if matches.opt_present("q") {
|
||||
fmt_opts.quick = true;
|
||||
}
|
||||
if matches.opt_present("s") {
|
||||
fmt_opts.mail = matches.is_present(OPT_PRESERVE_HEADERS);
|
||||
fmt_opts.uniform = matches.is_present(OPT_UNIFORM_SPACING);
|
||||
fmt_opts.quick = matches.is_present(OPT_QUICK);
|
||||
if matches.is_present(OPT_SPLIT_ONLY) {
|
||||
fmt_opts.split_only = true;
|
||||
fmt_opts.crown = false;
|
||||
fmt_opts.tagged = false;
|
||||
}
|
||||
if matches.opt_present("x") {
|
||||
fmt_opts.xprefix = true;
|
||||
}
|
||||
if matches.opt_present("X") {
|
||||
fmt_opts.xanti_prefix = true;
|
||||
}
|
||||
fmt_opts.xprefix = matches.is_present(OPT_EXACT_PREFIX);
|
||||
fmt_opts.xanti_prefix = matches.is_present(OPT_SKIP_PREFIX);
|
||||
|
||||
if let Some(s) = matches.opt_str("p") {
|
||||
if let Some(s) = matches.value_of(OPT_PREFIX).map(String::from) {
|
||||
fmt_opts.prefix = s;
|
||||
fmt_opts.use_prefix = true;
|
||||
};
|
||||
|
||||
if let Some(s) = matches.opt_str("P") {
|
||||
if let Some(s) = matches.value_of(OPT_SKIP_PREFIX).map(String::from) {
|
||||
fmt_opts.anti_prefix = s;
|
||||
fmt_opts.use_anti_prefix = true;
|
||||
};
|
||||
|
||||
if let Some(s) = matches.opt_str("w") {
|
||||
if let Some(s) = matches.value_of(OPT_WIDTH) {
|
||||
fmt_opts.width = match s.parse::<usize>() {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
crash!(1, "Invalid WIDTH specification: `{}': {}", s, e);
|
||||
}
|
||||
};
|
||||
if fmt_opts.width > MAX_WIDTH {
|
||||
crash!(
|
||||
1,
|
||||
"invalid width: '{}': Numerical result out of range",
|
||||
fmt_opts.width
|
||||
);
|
||||
}
|
||||
fmt_opts.goal = cmp::min(fmt_opts.width * 94 / 100, fmt_opts.width - 3);
|
||||
};
|
||||
|
||||
if let Some(s) = matches.opt_str("g") {
|
||||
if let Some(s) = matches.value_of(OPT_GOAL) {
|
||||
fmt_opts.goal = match s.parse::<usize>() {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
crash!(1, "Invalid GOAL specification: `{}': {}", s, e);
|
||||
}
|
||||
};
|
||||
if !matches.opt_present("w") {
|
||||
if !matches.is_present(OPT_WIDTH) {
|
||||
fmt_opts.width = cmp::max(fmt_opts.goal * 100 / 94, fmt_opts.goal + 3);
|
||||
} else if fmt_opts.goal > fmt_opts.width {
|
||||
crash!(1, "GOAL cannot be greater than WIDTH.");
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(s) = matches.opt_str("T") {
|
||||
if let Some(s) = matches.value_of(OPT_TAB_WIDTH) {
|
||||
fmt_opts.tabwidth = match s.parse::<usize>() {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
|
@ -172,7 +298,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
// immutable now
|
||||
let fmt_opts = fmt_opts;
|
||||
|
||||
let mut files = matches.free;
|
||||
if files.is_empty() {
|
||||
files.push("-".to_owned());
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ pub fn break_lines(para: &Paragraph, opts: &FmtOptions, ostream: &mut BufWriter<
|
|||
let mut break_args = BreakArgs {
|
||||
opts,
|
||||
init_len: p_init_len,
|
||||
indent_str: &p_indent[..],
|
||||
indent_str: p_indent,
|
||||
indent_len: p_indent_len,
|
||||
uniform,
|
||||
ostream,
|
||||
|
|
|
@ -264,6 +264,8 @@ impl<'a> ParagraphStream<'a> {
|
|||
return false;
|
||||
}
|
||||
|
||||
#[allow(clippy::match_like_matches_macro)]
|
||||
// `matches!(...)` macro not stabilized until rust v1.42
|
||||
l_slice[..colon_posn].chars().all(|x| match x as usize {
|
||||
y if y < 33 || y > 126 => false,
|
||||
_ => true,
|
||||
|
@ -539,6 +541,8 @@ impl<'a> WordSplit<'a> {
|
|||
}
|
||||
|
||||
fn is_punctuation(c: char) -> bool {
|
||||
#[allow(clippy::match_like_matches_macro)]
|
||||
// `matches!(...)` macro not stabilized until rust v1.42
|
||||
match c {
|
||||
'!' | '.' | '?' => true,
|
||||
_ => false,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_fold"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "fold ~ (uutils) wrap each line of input"
|
||||
|
@ -15,8 +15,8 @@ edition = "2018"
|
|||
path = "src/fold.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "fold"
|
||||
|
|
|
@ -14,6 +14,8 @@ use std::fs::File;
|
|||
use std::io::{stdin, BufRead, BufReader, Read};
|
||||
use std::path::Path;
|
||||
|
||||
const TAB_WIDTH: usize = 8;
|
||||
|
||||
static SYNTAX: &str = "[OPTION]... [FILE]...";
|
||||
static SUMMARY: &str = "Writes each file (or standard input if no files are given)
|
||||
to standard output whilst breaking long lines";
|
||||
|
@ -79,7 +81,6 @@ fn handle_obsolete(args: &[String]) -> (Vec<String>, Option<String>) {
|
|||
(args.to_vec(), None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fold(filenames: Vec<String>, bytes: bool, spaces: bool, width: usize) {
|
||||
for filename in &filenames {
|
||||
let filename: &str = &filename;
|
||||
|
@ -92,124 +93,176 @@ fn fold(filenames: Vec<String>, bytes: bool, spaces: bool, width: usize) {
|
|||
file_buf = safe_unwrap!(File::open(Path::new(filename)));
|
||||
&mut file_buf as &mut dyn Read
|
||||
});
|
||||
fold_file(buffer, bytes, spaces, width);
|
||||
|
||||
if bytes {
|
||||
fold_file_bytewise(buffer, spaces, width);
|
||||
} else {
|
||||
fold_file(buffer, spaces, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fold_file<T: Read>(mut file: BufReader<T>, bytes: bool, spaces: bool, width: usize) {
|
||||
/// Fold `file` to fit `width` (number of columns), counting all characters as
|
||||
/// one column.
|
||||
///
|
||||
/// This function handles folding for the `-b`/`--bytes` option, counting
|
||||
/// tab, backspace, and carriage return as occupying one column, identically
|
||||
/// to all other characters in the stream.
|
||||
///
|
||||
/// If `spaces` is `true`, attempt to break lines at whitespace boundaries.
|
||||
fn fold_file_bytewise<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) {
|
||||
let mut line = String::new();
|
||||
while safe_unwrap!(file.read_line(&mut line)) > 0 {
|
||||
if bytes {
|
||||
let len = line.len();
|
||||
let mut i = 0;
|
||||
while i < len {
|
||||
let width = if len - i >= width { width } else { len - i };
|
||||
let slice = {
|
||||
let slice = &line[i..i + width];
|
||||
if spaces && i + width < len {
|
||||
match slice.rfind(char::is_whitespace) {
|
||||
Some(m) => &slice[..=m],
|
||||
None => slice,
|
||||
}
|
||||
} else {
|
||||
slice
|
||||
|
||||
loop {
|
||||
if let Ok(0) = file.read_line(&mut line) {
|
||||
break;
|
||||
}
|
||||
|
||||
if line == "\n" {
|
||||
println!();
|
||||
line.truncate(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
let len = line.len();
|
||||
let mut i = 0;
|
||||
|
||||
while i < len {
|
||||
let width = if len - i >= width { width } else { len - i };
|
||||
let slice = {
|
||||
let slice = &line[i..i + width];
|
||||
if spaces && i + width < len {
|
||||
match slice.rfind(char::is_whitespace) {
|
||||
Some(m) => &slice[..=m],
|
||||
None => slice,
|
||||
}
|
||||
};
|
||||
print!("{}", slice);
|
||||
i += slice.len();
|
||||
} else {
|
||||
slice
|
||||
}
|
||||
};
|
||||
|
||||
// Don't duplicate trailing newlines: if the slice is "\n", the
|
||||
// previous iteration folded just before the end of the line and
|
||||
// has already printed this newline.
|
||||
if slice == "\n" {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
let mut len = line.chars().count();
|
||||
let newline = line.ends_with('\n');
|
||||
if newline {
|
||||
if len == 1 {
|
||||
println!();
|
||||
|
||||
i += slice.len();
|
||||
|
||||
let at_eol = i >= len;
|
||||
|
||||
if at_eol {
|
||||
print!("{}", slice);
|
||||
} else {
|
||||
println!("{}", slice);
|
||||
}
|
||||
}
|
||||
|
||||
line.truncate(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fold `file` to fit `width` (number of columns).
|
||||
///
|
||||
/// By default `fold` treats tab, backspace, and carriage return specially:
|
||||
/// tab characters count as 8 columns, backspace decreases the
|
||||
/// column count, and carriage return resets the column count to 0.
|
||||
///
|
||||
/// If `spaces` is `true`, attempt to break lines at whitespace boundaries.
|
||||
#[allow(unused_assignments)]
|
||||
fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) {
|
||||
let mut line = String::new();
|
||||
let mut output = String::new();
|
||||
let mut col_count = 0;
|
||||
let mut char_count = 0;
|
||||
let mut last_space = None;
|
||||
|
||||
/// Print the output line, resetting the column and character counts.
|
||||
///
|
||||
/// If `spaces` is `true`, print the output line up to the last
|
||||
/// encountered whitespace character (inclusive) and set the remaining
|
||||
/// characters as the start of the next line.
|
||||
macro_rules! emit_output {
|
||||
() => {
|
||||
let consume = match last_space {
|
||||
Some(i) => i + 1,
|
||||
None => output.len(),
|
||||
};
|
||||
|
||||
println!("{}", &output[..consume]);
|
||||
output.replace_range(..consume, "");
|
||||
char_count = output.len();
|
||||
|
||||
// we know there are no tabs left in output, so each char counts
|
||||
// as 1 column
|
||||
col_count = char_count;
|
||||
|
||||
last_space = None;
|
||||
};
|
||||
}
|
||||
|
||||
loop {
|
||||
if let Ok(0) = file.read_line(&mut line) {
|
||||
break;
|
||||
}
|
||||
|
||||
for ch in line.chars() {
|
||||
if ch == '\n' {
|
||||
// make sure to _not_ split output at whitespace, since we
|
||||
// know the entire output will fit
|
||||
last_space = None;
|
||||
emit_output!();
|
||||
break;
|
||||
}
|
||||
|
||||
if col_count >= width {
|
||||
emit_output!();
|
||||
}
|
||||
|
||||
match ch {
|
||||
'\t' => {
|
||||
let next_tab_stop = col_count + TAB_WIDTH - col_count % TAB_WIDTH;
|
||||
|
||||
if next_tab_stop > width && !output.is_empty() {
|
||||
emit_output!();
|
||||
}
|
||||
|
||||
col_count = next_tab_stop;
|
||||
last_space = if spaces { Some(char_count) } else { None };
|
||||
}
|
||||
'\x08' => {
|
||||
// FIXME: does not match GNU's handling of backspace
|
||||
if col_count > 0 {
|
||||
col_count -= 1;
|
||||
char_count -= 1;
|
||||
output.truncate(char_count);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
len -= 1;
|
||||
line.truncate(len);
|
||||
}
|
||||
let mut output = String::new();
|
||||
let mut count = 0;
|
||||
for (i, ch) in line.chars().enumerate() {
|
||||
if count >= width {
|
||||
let (val, ncount) = {
|
||||
let slice = &output[..];
|
||||
let (out, val, ncount) = if spaces && i + 1 < len {
|
||||
match rfind_whitespace(slice) {
|
||||
Some(m) => {
|
||||
let routput = &slice[m + 1..slice.chars().count()];
|
||||
let ncount = routput.chars().fold(0, |out, ch: char| {
|
||||
out + match ch {
|
||||
'\t' => 8,
|
||||
'\x08' => {
|
||||
if out > 0 {
|
||||
!0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
'\r' => return 0,
|
||||
_ => 1,
|
||||
}
|
||||
});
|
||||
(&slice[0..=m], routput, ncount)
|
||||
}
|
||||
None => (slice, "", 0),
|
||||
}
|
||||
} else {
|
||||
(slice, "", 0)
|
||||
};
|
||||
println!("{}", out);
|
||||
(val.to_owned(), ncount)
|
||||
};
|
||||
output = val;
|
||||
count = ncount;
|
||||
'\r' => {
|
||||
// FIXME: does not match GNU's handling of carriage return
|
||||
output.truncate(0);
|
||||
col_count = 0;
|
||||
char_count = 0;
|
||||
continue;
|
||||
}
|
||||
match ch {
|
||||
'\t' => {
|
||||
count += 8;
|
||||
if count > width {
|
||||
println!("{}", output);
|
||||
output.truncate(0);
|
||||
count = 8;
|
||||
}
|
||||
}
|
||||
'\x08' => {
|
||||
if count > 0 {
|
||||
count -= 1;
|
||||
let len = output.len() - 1;
|
||||
output.truncate(len);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
'\r' => {
|
||||
output.truncate(0);
|
||||
count = 0;
|
||||
continue;
|
||||
}
|
||||
_ => count += 1,
|
||||
};
|
||||
output.push(ch);
|
||||
}
|
||||
if count > 0 {
|
||||
if newline {
|
||||
println!("{}", output);
|
||||
} else {
|
||||
print!("{}", output);
|
||||
_ if spaces && ch.is_whitespace() => {
|
||||
last_space = Some(char_count);
|
||||
col_count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => col_count += 1,
|
||||
};
|
||||
|
||||
#[inline]
|
||||
fn rfind_whitespace(slice: &str) -> Option<usize> {
|
||||
for (i, ch) in slice.chars().rev().enumerate() {
|
||||
if ch.is_whitespace() {
|
||||
return Some(slice.chars().count() - (i + 1));
|
||||
output.push(ch);
|
||||
char_count += 1;
|
||||
}
|
||||
|
||||
if col_count > 0 {
|
||||
print!("{}", output);
|
||||
output.truncate(0);
|
||||
}
|
||||
|
||||
line.truncate(0);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_groups"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "groups ~ (uutils) display group memberships for USERNAME"
|
||||
|
@ -15,9 +15,9 @@ edition = "2018"
|
|||
path = "src/groups.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["entries"] }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
clap = "2.32"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries"] }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
clap = "2.33"
|
||||
|
||||
[[bin]]
|
||||
name = "groups"
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
extern crate uucore;
|
||||
use uucore::entries::{get_groups, gid2grp, Locate, Passwd};
|
||||
|
||||
extern crate clap;
|
||||
use clap::{App, Arg};
|
||||
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_hashsum"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "hashsum ~ (uutils) display or check input digests"
|
||||
|
@ -16,7 +16,7 @@ path = "src/hashsum.rs"
|
|||
|
||||
[dependencies]
|
||||
digest = "0.6.2"
|
||||
clap = "2"
|
||||
clap = "2.33"
|
||||
hex = "0.2.0"
|
||||
libc = "0.2.42"
|
||||
md5 = "0.3.5"
|
||||
|
@ -25,8 +25,9 @@ regex-syntax = "0.6.7"
|
|||
sha1 = "0.6.0"
|
||||
sha2 = "0.6.0"
|
||||
sha3 = "0.6.0"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
blake2-rfc = "0.2.18"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "hashsum"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
extern crate blake2_rfc;
|
||||
extern crate digest;
|
||||
extern crate md5;
|
||||
extern crate sha1;
|
||||
|
@ -48,6 +49,29 @@ impl Digest for md5::Context {
|
|||
}
|
||||
}
|
||||
|
||||
impl Digest for blake2_rfc::blake2b::Blake2b {
|
||||
fn new() -> Self {
|
||||
blake2_rfc::blake2b::Blake2b::new(64)
|
||||
}
|
||||
|
||||
fn input(&mut self, input: &[u8]) {
|
||||
self.update(input);
|
||||
}
|
||||
|
||||
fn result(&mut self, out: &mut [u8]) {
|
||||
let hash_result = &self.clone().finalize();
|
||||
out.copy_from_slice(&hash_result.as_bytes());
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
*self = blake2_rfc::blake2b::Blake2b::new(64);
|
||||
}
|
||||
|
||||
fn output_bits(&self) -> usize {
|
||||
512
|
||||
}
|
||||
}
|
||||
|
||||
impl Digest for sha1::Sha1 {
|
||||
fn new() -> Self {
|
||||
sha1::Sha1::new()
|
||||
|
|
|
@ -11,13 +11,6 @@
|
|||
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
extern crate hex;
|
||||
extern crate md5;
|
||||
extern crate regex;
|
||||
extern crate regex_syntax;
|
||||
extern crate sha1;
|
||||
extern crate sha2;
|
||||
extern crate sha3;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
@ -26,6 +19,7 @@ mod digest;
|
|||
|
||||
use self::digest::Digest;
|
||||
|
||||
use blake2_rfc::blake2b::Blake2b;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use hex::ToHex;
|
||||
use md5::Context as Md5;
|
||||
|
@ -57,10 +51,12 @@ struct Options {
|
|||
}
|
||||
|
||||
fn is_custom_binary(program: &str) -> bool {
|
||||
#[allow(clippy::match_like_matches_macro)]
|
||||
// `matches!(...)` macro not stabilized until rust v1.42
|
||||
match program {
|
||||
"md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum"
|
||||
| "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum"
|
||||
| "shake128sum" | "shake256sum" => true,
|
||||
| "shake128sum" | "shake256sum" | "b2sum" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +76,7 @@ fn detect_algo<'a>(
|
|||
"sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box<dyn Digest>, 256),
|
||||
"sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box<dyn Digest>, 384),
|
||||
"sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512),
|
||||
"b2sum" => ("BLAKE2", Box::new(Blake2b::new(64)) as Box<dyn Digest>, 512),
|
||||
"sha3sum" => match matches.value_of("bits") {
|
||||
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) {
|
||||
Ok(224) => (
|
||||
|
@ -180,6 +177,9 @@ fn detect_algo<'a>(
|
|||
if matches.is_present("sha512") {
|
||||
set_or_crash("SHA512", Box::new(Sha512::new()), 512)
|
||||
}
|
||||
if matches.is_present("b2sum") {
|
||||
set_or_crash("BLAKE2", Box::new(Blake2b::new(64)), 512)
|
||||
}
|
||||
if matches.is_present("sha3") {
|
||||
match matches.value_of("bits") {
|
||||
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) {
|
||||
|
@ -381,6 +381,7 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 {
|
|||
"shake256",
|
||||
"work with SHAKE256 using BITS for the output size",
|
||||
),
|
||||
("b2sum", "work with BLAKE2"),
|
||||
];
|
||||
|
||||
for (name, desc) in algos {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_head"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "head ~ (uutils) display the first lines of input"
|
||||
|
@ -15,9 +15,9 @@ edition = "2018"
|
|||
path = "src/head.rs"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.42"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
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]]
|
||||
name = "head"
|
||||
|
|
|
@ -1,223 +1,642 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * (c) Alan Andrade <alan.andradec@gmail.com>
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
// *
|
||||
// * Synced with: https://raw.github.com/avsm/src/master/usr.bin/head/head.c
|
||||
use clap::{App, Arg};
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::OsString;
|
||||
use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
|
||||
use uucore::{crash, executable, show_error};
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
const EXIT_FAILURE: i32 = 1;
|
||||
const EXIT_SUCCESS: i32 = 0;
|
||||
const BUF_SIZE: usize = 65536;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, BufRead, BufReader, Read};
|
||||
use std::path::Path;
|
||||
use std::str::from_utf8;
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const ABOUT: &str = "\
|
||||
Print the first 10 lines of each FILE to standard output.\n\
|
||||
With more than one FILE, precede each with a header giving the file name.\n\
|
||||
\n\
|
||||
With no FILE, or when FILE is -, read standard input.\n\
|
||||
\n\
|
||||
Mandatory arguments to long flags are mandatory for short flags too.\
|
||||
";
|
||||
const USAGE: &str = "head [FLAG]... [FILE]...";
|
||||
|
||||
static SYNTAX: &str = "";
|
||||
static SUMMARY: &str = "";
|
||||
static LONG_HELP: &str = "";
|
||||
mod options {
|
||||
pub const BYTES_NAME: &str = "BYTES";
|
||||
pub const LINES_NAME: &str = "LINES";
|
||||
pub const QUIET_NAME: &str = "QUIET";
|
||||
pub const VERBOSE_NAME: &str = "VERBOSE";
|
||||
pub const ZERO_NAME: &str = "ZERO";
|
||||
pub const FILES_NAME: &str = "FILE";
|
||||
}
|
||||
mod parse;
|
||||
mod split;
|
||||
|
||||
enum FilterMode {
|
||||
Bytes(usize),
|
||||
fn app<'a>() -> App<'a, 'a> {
|
||||
App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(USAGE)
|
||||
.arg(
|
||||
Arg::with_name(options::BYTES_NAME)
|
||||
.short("c")
|
||||
.long("bytes")
|
||||
.value_name("[-]NUM")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"\
|
||||
print the first NUM bytes of each file;\n\
|
||||
with the leading '-', print all but the last\n\
|
||||
NUM bytes of each file\
|
||||
",
|
||||
)
|
||||
.overrides_with_all(&[options::BYTES_NAME, options::LINES_NAME])
|
||||
.allow_hyphen_values(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::LINES_NAME)
|
||||
.short("n")
|
||||
.long("lines")
|
||||
.value_name("[-]NUM")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"\
|
||||
print the first NUM lines instead of the first 10;\n\
|
||||
with the leading '-', print all but the last\n\
|
||||
NUM lines of each file\
|
||||
",
|
||||
)
|
||||
.overrides_with_all(&[options::LINES_NAME, options::BYTES_NAME])
|
||||
.allow_hyphen_values(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::QUIET_NAME)
|
||||
.short("q")
|
||||
.long("--quiet")
|
||||
.visible_alias("silent")
|
||||
.help("never print headers giving file names")
|
||||
.overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::VERBOSE_NAME)
|
||||
.short("v")
|
||||
.long("verbose")
|
||||
.help("always print headers giving file names")
|
||||
.overrides_with_all(&[options::QUIET_NAME, options::VERBOSE_NAME]),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::ZERO_NAME)
|
||||
.short("z")
|
||||
.long("zero-terminated")
|
||||
.help("line delimiter is NUL, not newline")
|
||||
.overrides_with(options::ZERO_NAME),
|
||||
)
|
||||
.arg(Arg::with_name(options::FILES_NAME).multiple(true))
|
||||
}
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
enum Modes {
|
||||
Lines(usize),
|
||||
NLines(usize),
|
||||
Bytes(usize),
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
mode: FilterMode,
|
||||
verbose: bool,
|
||||
fn parse_mode<F>(src: &str, closure: F) -> Result<(Modes, bool), String>
|
||||
where
|
||||
F: FnOnce(usize) -> Modes,
|
||||
{
|
||||
match parse::parse_num(src) {
|
||||
Ok((n, last)) => Ok((closure(n), last)),
|
||||
Err(reason) => match reason {
|
||||
parse::ParseError::Syntax => Err(format!("'{}'", src)),
|
||||
parse::ParseError::Overflow => {
|
||||
Err(format!("'{}': Value too large for defined datatype", src))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
mode: FilterMode::Lines(10),
|
||||
verbose: false,
|
||||
fn arg_iterate<'a>(
|
||||
mut args: impl uucore::Args + 'a,
|
||||
) -> Result<Box<dyn Iterator<Item = OsString> + 'a>, String> {
|
||||
// argv[0] is always present
|
||||
let first = args.next().unwrap();
|
||||
if let Some(second) = args.next() {
|
||||
if let Some(s) = second.to_str() {
|
||||
match parse::parse_obsolete(s) {
|
||||
Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
|
||||
Some(Err(e)) => match e {
|
||||
parse::ParseError::Syntax => Err(format!("bad argument format: '{}'", s)),
|
||||
parse::ParseError::Overflow => Err(format!(
|
||||
"invalid argument: '{}' Value too large for defined datatype",
|
||||
s
|
||||
)),
|
||||
},
|
||||
None => Ok(Box::new(vec![first, second].into_iter().chain(args))),
|
||||
}
|
||||
} else {
|
||||
Err("bad argument encoding".to_owned())
|
||||
}
|
||||
} else {
|
||||
Ok(Box::new(vec![first].into_iter()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct HeadOptions {
|
||||
pub quiet: bool,
|
||||
pub verbose: bool,
|
||||
pub zeroed: bool,
|
||||
pub all_but_last: bool,
|
||||
pub mode: Modes,
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
impl HeadOptions {
|
||||
pub fn new() -> HeadOptions {
|
||||
HeadOptions {
|
||||
quiet: false,
|
||||
verbose: false,
|
||||
zeroed: false,
|
||||
all_but_last: false,
|
||||
mode: Modes::Lines(10),
|
||||
files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
///Construct options from matches
|
||||
pub fn get_from(args: impl uucore::Args) -> Result<Self, String> {
|
||||
let matches = app().get_matches_from(arg_iterate(args)?);
|
||||
|
||||
let mut options = HeadOptions::new();
|
||||
|
||||
options.quiet = matches.is_present(options::QUIET_NAME);
|
||||
options.verbose = matches.is_present(options::VERBOSE_NAME);
|
||||
options.zeroed = matches.is_present(options::ZERO_NAME);
|
||||
|
||||
let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) {
|
||||
match parse_mode(v, Modes::Bytes) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return Err(format!("invalid number of bytes: {}", err));
|
||||
}
|
||||
}
|
||||
} else if let Some(v) = matches.value_of(options::LINES_NAME) {
|
||||
match parse_mode(v, Modes::Lines) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return Err(format!("invalid number of lines: {}", err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(Modes::Lines(10), false)
|
||||
};
|
||||
|
||||
options.mode = mode_and_from_end.0;
|
||||
options.all_but_last = mode_and_from_end.1;
|
||||
|
||||
options.files = match matches.values_of(options::FILES_NAME) {
|
||||
Some(v) => v.map(|s| s.to_owned()).collect(),
|
||||
None => vec!["-".to_owned()],
|
||||
};
|
||||
//println!("{:#?}", options);
|
||||
Ok(options)
|
||||
}
|
||||
}
|
||||
// to make clippy shut up
|
||||
impl Default for HeadOptions {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn rbuf_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> {
|
||||
if n == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let mut readbuf = [0u8; BUF_SIZE];
|
||||
let mut i = 0usize;
|
||||
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
loop {
|
||||
let read = loop {
|
||||
match input.read(&mut readbuf) {
|
||||
Ok(n) => break n,
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::Interrupted => {}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
};
|
||||
if read == 0 {
|
||||
// might be unexpected if
|
||||
// we haven't read `n` bytes
|
||||
// but this mirrors GNU's behavior
|
||||
return Ok(());
|
||||
}
|
||||
stdout.write_all(&readbuf[..read.min(n - i)])?;
|
||||
i += read.min(n - i);
|
||||
if i == n {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rbuf_n_lines(input: &mut impl std::io::BufRead, n: usize, zero: bool) -> std::io::Result<()> {
|
||||
if n == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let mut lines = 0usize;
|
||||
split::walk_lines(input, zero, |e| match e {
|
||||
split::Event::Data(dat) => {
|
||||
stdout.write_all(dat)?;
|
||||
Ok(true)
|
||||
}
|
||||
split::Event::Line => {
|
||||
lines += 1;
|
||||
if lines == n {
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn rbuf_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> {
|
||||
if n == 0 {
|
||||
//prints everything
|
||||
return rbuf_n_bytes(input, std::usize::MAX);
|
||||
}
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
let mut ringbuf = vec![0u8; n];
|
||||
|
||||
// first we fill the ring buffer
|
||||
if let Err(e) = input.read_exact(&mut ringbuf) {
|
||||
if e.kind() == ErrorKind::UnexpectedEof {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
let mut buffer = [0u8; BUF_SIZE];
|
||||
loop {
|
||||
let read = loop {
|
||||
match input.read(&mut buffer) {
|
||||
Ok(n) => break n,
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::Interrupted => {}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
};
|
||||
if read == 0 {
|
||||
return Ok(());
|
||||
} else if read >= n {
|
||||
stdout.write_all(&ringbuf)?;
|
||||
stdout.write_all(&buffer[..read - n])?;
|
||||
for i in 0..n {
|
||||
ringbuf[i] = buffer[read - n + i];
|
||||
}
|
||||
} else {
|
||||
stdout.write_all(&ringbuf[..read])?;
|
||||
for i in 0..n - read {
|
||||
ringbuf[i] = ringbuf[read + i];
|
||||
}
|
||||
ringbuf[n - read..].copy_from_slice(&buffer[..read]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rbuf_but_last_n_lines(
|
||||
input: &mut impl std::io::BufRead,
|
||||
n: usize,
|
||||
zero: bool,
|
||||
) -> std::io::Result<()> {
|
||||
if n == 0 {
|
||||
//prints everything
|
||||
return rbuf_n_bytes(input, std::usize::MAX);
|
||||
}
|
||||
let mut ringbuf = vec![Vec::new(); n];
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let mut line = Vec::new();
|
||||
let mut lines = 0usize;
|
||||
split::walk_lines(input, zero, |e| match e {
|
||||
split::Event::Data(dat) => {
|
||||
line.extend_from_slice(dat);
|
||||
Ok(true)
|
||||
}
|
||||
split::Event::Line => {
|
||||
if lines < n {
|
||||
ringbuf[lines] = std::mem::replace(&mut line, Vec::new());
|
||||
lines += 1;
|
||||
} else {
|
||||
stdout.write_all(&ringbuf[0])?;
|
||||
ringbuf.rotate_left(1);
|
||||
ringbuf[n - 1] = std::mem::replace(&mut line, Vec::new());
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {
|
||||
assert!(options.all_but_last);
|
||||
let size = input.seek(SeekFrom::End(0))?;
|
||||
let size = usize::try_from(size).unwrap();
|
||||
match options.mode {
|
||||
Modes::Bytes(n) => {
|
||||
if n >= size {
|
||||
return Ok(());
|
||||
} else {
|
||||
input.seek(SeekFrom::Start(0))?;
|
||||
rbuf_n_bytes(
|
||||
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
|
||||
size - n,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Modes::Lines(n) => {
|
||||
let mut buffer = [0u8; BUF_SIZE];
|
||||
let buffer = &mut buffer[..BUF_SIZE.min(size)];
|
||||
let mut i = 0usize;
|
||||
let mut lines = 0usize;
|
||||
|
||||
let found = 'o: loop {
|
||||
// the casts here are ok, `buffer.len()` should never be above a few k
|
||||
input.seek(SeekFrom::Current(
|
||||
-((buffer.len() as i64).min((size - i) as i64)),
|
||||
))?;
|
||||
input.read_exact(buffer)?;
|
||||
for byte in buffer.iter().rev() {
|
||||
match byte {
|
||||
b'\n' if !options.zeroed => {
|
||||
lines += 1;
|
||||
}
|
||||
0u8 if options.zeroed => {
|
||||
lines += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// if it were just `n`,
|
||||
if lines == n + 1 {
|
||||
break 'o i;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if size - i == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
input.seek(SeekFrom::Start(0))?;
|
||||
rbuf_n_bytes(
|
||||
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
|
||||
size - found,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {
|
||||
if options.all_but_last {
|
||||
head_backwards_file(input, options)
|
||||
} else {
|
||||
match options.mode {
|
||||
Modes::Bytes(n) => {
|
||||
rbuf_n_bytes(&mut std::io::BufReader::with_capacity(BUF_SIZE, input), n)
|
||||
}
|
||||
Modes::Lines(n) => rbuf_n_lines(
|
||||
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
|
||||
n,
|
||||
options.zeroed,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn uu_head(options: &HeadOptions) {
|
||||
let mut first = true;
|
||||
for fname in &options.files {
|
||||
let res = match fname.as_str() {
|
||||
"-" => {
|
||||
if options.verbose {
|
||||
if !first {
|
||||
println!();
|
||||
}
|
||||
println!("==> standard input <==")
|
||||
}
|
||||
let stdin = std::io::stdin();
|
||||
let mut stdin = stdin.lock();
|
||||
match options.mode {
|
||||
Modes::Bytes(n) => {
|
||||
if options.all_but_last {
|
||||
rbuf_but_last_n_bytes(&mut stdin, n)
|
||||
} else {
|
||||
rbuf_n_bytes(&mut stdin, n)
|
||||
}
|
||||
}
|
||||
Modes::Lines(n) => {
|
||||
if options.all_but_last {
|
||||
rbuf_but_last_n_lines(&mut stdin, n, options.zeroed)
|
||||
} else {
|
||||
rbuf_n_lines(&mut stdin, n, options.zeroed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
name => {
|
||||
let mut file = match std::fs::File::open(name) {
|
||||
Ok(f) => f,
|
||||
Err(err) => match err.kind() {
|
||||
ErrorKind::NotFound => {
|
||||
crash!(
|
||||
EXIT_FAILURE,
|
||||
"head: cannot open '{}' for reading: No such file or directory",
|
||||
name
|
||||
);
|
||||
}
|
||||
ErrorKind::PermissionDenied => {
|
||||
crash!(
|
||||
EXIT_FAILURE,
|
||||
"head: cannot open '{}' for reading: Permission denied",
|
||||
name
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
crash!(
|
||||
EXIT_FAILURE,
|
||||
"head: cannot open '{}' for reading: {}",
|
||||
name,
|
||||
err
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
if (options.files.len() > 1 && !options.quiet) || options.verbose {
|
||||
println!("==> {} <==", name)
|
||||
}
|
||||
head_file(&mut file, options)
|
||||
}
|
||||
};
|
||||
if res.is_err() {
|
||||
if fname.as_str() == "-" {
|
||||
crash!(
|
||||
EXIT_FAILURE,
|
||||
"head: error reading standard input: Input/output error"
|
||||
);
|
||||
} else {
|
||||
crash!(
|
||||
EXIT_FAILURE,
|
||||
"head: error reading {}: Input/output error",
|
||||
fname
|
||||
);
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
|
||||
let mut settings: Settings = Default::default();
|
||||
|
||||
// handle obsolete -number syntax
|
||||
let new_args = match obsolete(&args[0..]) {
|
||||
(args, Some(n)) => {
|
||||
settings.mode = FilterMode::Lines(n);
|
||||
args
|
||||
}
|
||||
(args, None) => args,
|
||||
};
|
||||
|
||||
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
|
||||
.optopt(
|
||||
"c",
|
||||
"bytes",
|
||||
"Print the first K bytes. With the leading '-', print all but the last K bytes",
|
||||
"[-]K",
|
||||
)
|
||||
.optopt(
|
||||
"n",
|
||||
"lines",
|
||||
"Print the first K lines. With the leading '-', print all but the last K lines",
|
||||
"[-]K",
|
||||
)
|
||||
.optflag("q", "quiet", "never print headers giving file names")
|
||||
.optflag("v", "verbose", "always print headers giving file names")
|
||||
.optflag("h", "help", "display this help and exit")
|
||||
.optflag("V", "version", "output version information and exit")
|
||||
.parse(new_args);
|
||||
|
||||
let use_bytes = matches.opt_present("c");
|
||||
// TODO: suffixes (e.g. b, kB, etc.)
|
||||
match matches.opt_str("n") {
|
||||
Some(n) => {
|
||||
if use_bytes {
|
||||
show_error!("cannot specify both --bytes and --lines.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
match n.parse::<isize>() {
|
||||
Ok(m) => {
|
||||
settings.mode = if m < 0 {
|
||||
let m: usize = m.abs() as usize;
|
||||
FilterMode::NLines(m)
|
||||
} else {
|
||||
let m: usize = m.abs() as usize;
|
||||
FilterMode::Lines(m)
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
show_error!("invalid line count '{}': {}", n, e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if let Some(count) = matches.opt_str("c") {
|
||||
match count.parse::<usize>() {
|
||||
Ok(m) => settings.mode = FilterMode::Bytes(m),
|
||||
Err(e) => {
|
||||
show_error!("invalid byte count '{}': {}", count, e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let args = match HeadOptions::get_from(args) {
|
||||
Ok(o) => o,
|
||||
Err(s) => {
|
||||
crash!(EXIT_FAILURE, "head: {}", s);
|
||||
}
|
||||
};
|
||||
uu_head(&args);
|
||||
|
||||
let quiet = matches.opt_present("q");
|
||||
let verbose = matches.opt_present("v");
|
||||
let files = matches.free;
|
||||
|
||||
// GNU implementation allows multiple declarations of "-q" and "-v" with the
|
||||
// last flag winning. This can't be simulated with the getopts cargo unless
|
||||
// we manually parse the arguments. Given the declaration of both flags,
|
||||
// verbose mode always wins. This is a potential future improvement.
|
||||
if files.len() > 1 && !quiet && !verbose {
|
||||
settings.verbose = true;
|
||||
}
|
||||
if quiet {
|
||||
settings.verbose = false;
|
||||
}
|
||||
if verbose {
|
||||
settings.verbose = true;
|
||||
}
|
||||
|
||||
if files.is_empty() {
|
||||
let mut buffer = BufReader::new(stdin());
|
||||
head(&mut buffer, &settings);
|
||||
} else {
|
||||
let mut first_time = true;
|
||||
|
||||
for file in &files {
|
||||
if settings.verbose {
|
||||
if !first_time {
|
||||
println!();
|
||||
}
|
||||
println!("==> {} <==", file);
|
||||
}
|
||||
first_time = false;
|
||||
|
||||
let path = Path::new(file);
|
||||
let reader = File::open(&path).unwrap();
|
||||
let mut buffer = BufReader::new(reader);
|
||||
if !head(&mut buffer, &settings) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
EXIT_SUCCESS
|
||||
}
|
||||
|
||||
// It searches for an option in the form of -123123
|
||||
//
|
||||
// In case is found, the options vector will get rid of that object so that
|
||||
// getopts works correctly.
|
||||
fn obsolete(options: &[String]) -> (Vec<String>, Option<usize>) {
|
||||
let mut options: Vec<String> = options.to_vec();
|
||||
let mut a = 1;
|
||||
let b = options.len();
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::OsString;
|
||||
|
||||
while a < b {
|
||||
let previous = options[a - 1].clone();
|
||||
let current = options[a].clone();
|
||||
let current = current.as_bytes();
|
||||
|
||||
if previous != "-n" && current.len() > 1 && current[0] == b'-' {
|
||||
let len = current.len();
|
||||
for pos in 1..len {
|
||||
// Ensure that the argument is only made out of digits
|
||||
if !(current[pos] as char).is_numeric() {
|
||||
break;
|
||||
}
|
||||
|
||||
// If this is the last number
|
||||
if pos == len - 1 {
|
||||
options.remove(a);
|
||||
let number: Option<usize> =
|
||||
from_utf8(¤t[1..len]).unwrap().parse::<usize>().ok();
|
||||
return (options, Some(number.unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a += 1;
|
||||
use super::*;
|
||||
fn options(args: &str) -> Result<HeadOptions, String> {
|
||||
let combined = "head ".to_owned() + args;
|
||||
let args = combined.split_whitespace();
|
||||
HeadOptions::get_from(args.map(|s| OsString::from(s)))
|
||||
}
|
||||
#[test]
|
||||
fn test_args_modes() {
|
||||
let args = options("-n -10M -vz").unwrap();
|
||||
assert!(args.zeroed);
|
||||
assert!(args.verbose);
|
||||
assert!(args.all_but_last);
|
||||
assert_eq!(args.mode, Modes::Lines(10 * 1024 * 1024));
|
||||
}
|
||||
#[test]
|
||||
fn test_gnu_compatibility() {
|
||||
let args = options("-n 1 -c 1 -n 5 -c kiB -vqvqv").unwrap();
|
||||
assert!(args.mode == Modes::Bytes(1024));
|
||||
assert!(args.verbose);
|
||||
assert_eq!(options("-5").unwrap().mode, Modes::Lines(5));
|
||||
assert_eq!(options("-2b").unwrap().mode, Modes::Bytes(1024));
|
||||
assert_eq!(options("-5 -c 1").unwrap().mode, Modes::Bytes(1));
|
||||
}
|
||||
#[test]
|
||||
fn all_args_test() {
|
||||
assert!(options("--silent").unwrap().quiet);
|
||||
assert!(options("--quiet").unwrap().quiet);
|
||||
assert!(options("-q").unwrap().quiet);
|
||||
assert!(options("--verbose").unwrap().verbose);
|
||||
assert!(options("-v").unwrap().verbose);
|
||||
assert!(options("--zero-terminated").unwrap().zeroed);
|
||||
assert!(options("-z").unwrap().zeroed);
|
||||
assert_eq!(options("--lines 15").unwrap().mode, Modes::Lines(15));
|
||||
assert_eq!(options("-n 15").unwrap().mode, Modes::Lines(15));
|
||||
assert_eq!(options("--bytes 15").unwrap().mode, Modes::Bytes(15));
|
||||
assert_eq!(options("-c 15").unwrap().mode, Modes::Bytes(15));
|
||||
}
|
||||
#[test]
|
||||
fn test_options_errors() {
|
||||
assert!(options("-n IsThisTheRealLife?").is_err());
|
||||
assert!(options("-c IsThisJustFantasy").is_err());
|
||||
}
|
||||
#[test]
|
||||
fn test_options_correct_defaults() {
|
||||
let opts = HeadOptions::new();
|
||||
let opts2: HeadOptions = Default::default();
|
||||
|
||||
(options, None)
|
||||
}
|
||||
assert_eq!(opts, opts2);
|
||||
|
||||
// TODO: handle errors on read
|
||||
fn head<T: Read>(reader: &mut BufReader<T>, settings: &Settings) -> bool {
|
||||
match settings.mode {
|
||||
FilterMode::Bytes(count) => {
|
||||
for byte in reader.bytes().take(count) {
|
||||
print!("{}", byte.unwrap() as char);
|
||||
}
|
||||
}
|
||||
FilterMode::Lines(count) => {
|
||||
for line in reader.lines().take(count) {
|
||||
println!("{}", line.unwrap());
|
||||
}
|
||||
}
|
||||
FilterMode::NLines(count) => {
|
||||
let mut vector: VecDeque<String> = VecDeque::new();
|
||||
|
||||
for line in reader.lines() {
|
||||
vector.push_back(line.unwrap());
|
||||
if vector.len() <= count {
|
||||
continue;
|
||||
}
|
||||
println!("{}", vector.pop_front().unwrap());
|
||||
assert!(opts.verbose == false);
|
||||
assert!(opts.quiet == false);
|
||||
assert!(opts.zeroed == false);
|
||||
assert!(opts.all_but_last == false);
|
||||
assert_eq!(opts.mode, Modes::Lines(10));
|
||||
assert!(opts.files.is_empty());
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_mode() {
|
||||
assert_eq!(
|
||||
parse_mode("123", Modes::Lines),
|
||||
Ok((Modes::Lines(123), false))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_mode("-456", Modes::Bytes),
|
||||
Ok((Modes::Bytes(456), true))
|
||||
);
|
||||
assert!(parse_mode("Nonsensical Nonsense", Modes::Bytes).is_err());
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
assert!(parse_mode("1Y", Modes::Lines).is_err());
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
assert!(parse_mode("1T", Modes::Bytes).is_err());
|
||||
}
|
||||
fn arg_outputs(src: &str) -> Result<String, String> {
|
||||
let split = src.split_whitespace().map(|x| OsString::from(x));
|
||||
match arg_iterate(split) {
|
||||
Ok(args) => {
|
||||
let vec = args
|
||||
.map(|s| s.to_str().unwrap().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
Ok(vec.join(" "))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
true
|
||||
#[test]
|
||||
fn test_arg_iterate() {
|
||||
// test that normal args remain unchanged
|
||||
assert_eq!(
|
||||
arg_outputs("head -n -5 -zv"),
|
||||
Ok("head -n -5 -zv".to_owned())
|
||||
);
|
||||
// tests that nonsensical args are unchanged
|
||||
assert_eq!(
|
||||
arg_outputs("head -to_be_or_not_to_be,..."),
|
||||
Ok("head -to_be_or_not_to_be,...".to_owned())
|
||||
);
|
||||
//test that the obsolete syntax is unrolled
|
||||
assert_eq!(
|
||||
arg_outputs("head -123qvqvqzc"),
|
||||
Ok("head -q -z -c 123".to_owned())
|
||||
);
|
||||
//test that bad obsoletes are an error
|
||||
assert!(arg_outputs("head -123FooBar").is_err());
|
||||
//test overflow
|
||||
assert!(arg_outputs("head -100000000000000000000000000000000000000000").is_err());
|
||||
//test that empty args remain unchanged
|
||||
assert_eq!(arg_outputs("head"), Ok("head".to_owned()));
|
||||
}
|
||||
#[test]
|
||||
#[cfg(linux)]
|
||||
fn test_arg_iterate_bad_encoding() {
|
||||
let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") };
|
||||
// this arises from a conversion from OsString to &str
|
||||
assert!(
|
||||
arg_iterate(vec![OsString::from("head"), OsString::from(invalid)].into_iter()).is_err()
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn rbuf_early_exit() {
|
||||
let mut empty = std::io::BufReader::new(std::io::Cursor::new(Vec::new()));
|
||||
assert!(rbuf_n_bytes(&mut empty, 0).is_ok());
|
||||
assert!(rbuf_n_lines(&mut empty, 0, false).is_ok());
|
||||
}
|
||||
}
|
||||
|
|
282
src/uu/head/src/parse.rs
Normal file
282
src/uu/head/src/parse.rs
Normal file
|
@ -0,0 +1,282 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum ParseError {
|
||||
Syntax,
|
||||
Overflow,
|
||||
}
|
||||
/// Parses obsolete syntax
|
||||
/// head -NUM[kmzv]
|
||||
pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>, ParseError>> {
|
||||
let mut chars = src.char_indices();
|
||||
if let Some((_, '-')) = chars.next() {
|
||||
let mut num_end = 0usize;
|
||||
let mut has_num = false;
|
||||
let mut last_char = 0 as char;
|
||||
while let Some((n, c)) = chars.next() {
|
||||
if c.is_numeric() {
|
||||
has_num = true;
|
||||
num_end = n;
|
||||
} else {
|
||||
last_char = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if has_num {
|
||||
match src[1..=num_end].parse::<usize>() {
|
||||
Ok(num) => {
|
||||
let mut quiet = false;
|
||||
let mut verbose = false;
|
||||
let mut zero_terminated = false;
|
||||
let mut multiplier = None;
|
||||
let mut c = last_char;
|
||||
loop {
|
||||
// not that here, we only match lower case 'k', 'c', and 'm'
|
||||
match c {
|
||||
// we want to preserve order
|
||||
// this also saves us 1 heap allocation
|
||||
'q' => {
|
||||
quiet = true;
|
||||
verbose = false
|
||||
}
|
||||
'v' => {
|
||||
verbose = true;
|
||||
quiet = false
|
||||
}
|
||||
'z' => zero_terminated = true,
|
||||
'c' => multiplier = Some(1),
|
||||
'b' => multiplier = Some(512),
|
||||
'k' => multiplier = Some(1024),
|
||||
'm' => multiplier = Some(1024 * 1024),
|
||||
'\0' => {}
|
||||
_ => return Some(Err(ParseError::Syntax)),
|
||||
}
|
||||
if let Some((_, next)) = chars.next() {
|
||||
c = next
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut options = Vec::new();
|
||||
if quiet {
|
||||
options.push(OsString::from("-q"))
|
||||
}
|
||||
if verbose {
|
||||
options.push(OsString::from("-v"))
|
||||
}
|
||||
if zero_terminated {
|
||||
options.push(OsString::from("-z"))
|
||||
}
|
||||
if let Some(n) = multiplier {
|
||||
options.push(OsString::from("-c"));
|
||||
let num = match num.checked_mul(n) {
|
||||
Some(n) => n,
|
||||
None => return Some(Err(ParseError::Overflow)),
|
||||
};
|
||||
options.push(OsString::from(format!("{}", num)));
|
||||
} else {
|
||||
options.push(OsString::from("-n"));
|
||||
options.push(OsString::from(format!("{}", num)));
|
||||
}
|
||||
Some(Ok(options.into_iter()))
|
||||
}
|
||||
Err(_) => Some(Err(ParseError::Overflow)),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// Parses an -c or -n argument,
|
||||
/// the bool specifies whether to read from the end
|
||||
pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> {
|
||||
let mut num_start = 0;
|
||||
let mut chars = src.char_indices();
|
||||
let (mut chars, all_but_last) = match chars.next() {
|
||||
Some((_, c)) => {
|
||||
if c == '-' {
|
||||
num_start += 1;
|
||||
(chars, true)
|
||||
} else {
|
||||
(src.char_indices(), false)
|
||||
}
|
||||
}
|
||||
None => return Err(ParseError::Syntax),
|
||||
};
|
||||
let mut num_end = 0usize;
|
||||
let mut last_char = 0 as char;
|
||||
let mut num_count = 0usize;
|
||||
while let Some((n, c)) = chars.next() {
|
||||
if c.is_numeric() {
|
||||
num_end = n;
|
||||
num_count += 1;
|
||||
} else {
|
||||
last_char = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let num = if num_count > 0 {
|
||||
match src[num_start..=num_end].parse::<usize>() {
|
||||
Ok(n) => Some(n),
|
||||
Err(_) => return Err(ParseError::Overflow),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if last_char == 0 as char {
|
||||
if let Some(n) = num {
|
||||
Ok((n, all_but_last))
|
||||
} else {
|
||||
Err(ParseError::Syntax)
|
||||
}
|
||||
} else {
|
||||
let base: u128 = match chars.next() {
|
||||
Some((_, c)) => {
|
||||
let b = match c {
|
||||
'B' if last_char != 'b' => 1000,
|
||||
'i' if last_char != 'b' => {
|
||||
if let Some((_, 'B')) = chars.next() {
|
||||
1024
|
||||
} else {
|
||||
return Err(ParseError::Syntax);
|
||||
}
|
||||
}
|
||||
_ => return Err(ParseError::Syntax),
|
||||
};
|
||||
if chars.next().is_some() {
|
||||
return Err(ParseError::Syntax);
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
None => 1024,
|
||||
};
|
||||
let mul = match last_char.to_lowercase().next().unwrap() {
|
||||
'b' => 512,
|
||||
'k' => base.pow(1),
|
||||
'm' => base.pow(2),
|
||||
'g' => base.pow(3),
|
||||
't' => base.pow(4),
|
||||
'p' => base.pow(5),
|
||||
'e' => base.pow(6),
|
||||
'z' => base.pow(7),
|
||||
'y' => base.pow(8),
|
||||
_ => return Err(ParseError::Syntax),
|
||||
};
|
||||
let mul = match usize::try_from(mul) {
|
||||
Ok(n) => n,
|
||||
Err(_) => return Err(ParseError::Overflow),
|
||||
};
|
||||
match num.unwrap_or(1).checked_mul(mul) {
|
||||
Some(n) => Ok((n, all_but_last)),
|
||||
None => Err(ParseError::Overflow),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn obsolete(src: &str) -> Option<Result<Vec<String>, ParseError>> {
|
||||
let r = parse_obsolete(src);
|
||||
match r {
|
||||
Some(s) => match s {
|
||||
Ok(v) => Some(Ok(v.map(|s| s.to_str().unwrap().to_owned()).collect())),
|
||||
Err(e) => Some(Err(e)),
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
fn obsolete_result(src: &[&str]) -> Option<Result<Vec<String>, ParseError>> {
|
||||
Some(Ok(src.iter().map(|s| s.to_string()).collect()))
|
||||
}
|
||||
#[test]
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
fn test_parse_overflow_x64() {
|
||||
assert_eq!(parse_num("1Y"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("1Z"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("100E"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("100000P"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("1000000000T"), Err(ParseError::Overflow));
|
||||
assert_eq!(
|
||||
parse_num("10000000000000000000000"),
|
||||
Err(ParseError::Overflow)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
fn test_parse_overflow_x32() {
|
||||
assert_eq!(parse_num("1T"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("1000G"), Err(ParseError::Overflow));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_bad_syntax() {
|
||||
assert_eq!(parse_num("5MiB nonsense"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("Nonsense string"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("5mib"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("biB"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("-"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num(""), Err(ParseError::Syntax));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_numbers() {
|
||||
assert_eq!(parse_num("k"), Ok((1024, false)));
|
||||
assert_eq!(parse_num("MiB"), Ok((1024 * 1024, false)));
|
||||
assert_eq!(parse_num("-5"), Ok((5, true)));
|
||||
assert_eq!(parse_num("b"), Ok((512, false)));
|
||||
assert_eq!(parse_num("-2GiB"), Ok((2 * 1024 * 1024 * 1024, true)));
|
||||
assert_eq!(parse_num("5M"), Ok((5 * 1024 * 1024, false)));
|
||||
assert_eq!(parse_num("5MB"), Ok((5 * 1000 * 1000, false)));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_numbers_obsolete() {
|
||||
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
|
||||
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));
|
||||
assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "5242880"]));
|
||||
assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "1024"]));
|
||||
assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "1024"]));
|
||||
assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "1024"]));
|
||||
assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "1"]));
|
||||
assert_eq!(
|
||||
obsolete("-1vzqvq"),
|
||||
obsolete_result(&["-q", "-z", "-n", "1"])
|
||||
);
|
||||
assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "1"]));
|
||||
assert_eq!(
|
||||
obsolete("-105kzm"),
|
||||
obsolete_result(&["-z", "-c", "110100480"])
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_errors_obsolete() {
|
||||
assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax)));
|
||||
assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax)));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_obsolete_nomatch() {
|
||||
assert_eq!(obsolete("-k"), None);
|
||||
assert_eq!(obsolete("asd"), None);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn test_parse_obsolete_overflow_x64() {
|
||||
assert_eq!(
|
||||
obsolete("-1000000000000000m"),
|
||||
Some(Err(ParseError::Overflow))
|
||||
);
|
||||
assert_eq!(
|
||||
obsolete("-10000000000000000000000"),
|
||||
Some(Err(ParseError::Overflow))
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
fn test_parse_obsolete_overflow_x32() {
|
||||
assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow)));
|
||||
assert_eq!(obsolete("-42949672k"), Some(Err(ParseError::Overflow)));
|
||||
}
|
||||
}
|
60
src/uu/head/src/split.rs
Normal file
60
src/uu/head/src/split.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
#[derive(Debug)]
|
||||
pub enum Event<'a> {
|
||||
Data(&'a [u8]),
|
||||
Line,
|
||||
}
|
||||
/// Loops over the lines read from a BufRead.
|
||||
/// # Arguments
|
||||
/// * `input` the ReadBuf to read from
|
||||
/// * `zero` whether to use 0u8 as a line delimiter
|
||||
/// * `on_event` a closure receiving some bytes read in a slice, or
|
||||
/// event signalling a line was just read.
|
||||
/// this is guaranteed to be signalled *directly* after the
|
||||
/// slice containing the (CR on win)LF / 0 is passed
|
||||
///
|
||||
/// Return whether to continue
|
||||
pub fn walk_lines<F>(
|
||||
input: &mut impl std::io::BufRead,
|
||||
zero: bool,
|
||||
mut on_event: F,
|
||||
) -> std::io::Result<()>
|
||||
where
|
||||
F: FnMut(Event) -> std::io::Result<bool>,
|
||||
{
|
||||
let mut buffer = [0u8; super::BUF_SIZE];
|
||||
loop {
|
||||
let read = loop {
|
||||
match input.read(&mut buffer) {
|
||||
Ok(n) => break n,
|
||||
Err(e) => match e.kind() {
|
||||
std::io::ErrorKind::Interrupted => {}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
};
|
||||
if read == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let mut base = 0usize;
|
||||
for (i, byte) in buffer[..read].iter().enumerate() {
|
||||
match byte {
|
||||
b'\n' if !zero => {
|
||||
on_event(Event::Data(&buffer[base..=i]))?;
|
||||
base = i + 1;
|
||||
if !on_event(Event::Line)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
0u8 if zero => {
|
||||
on_event(Event::Data(&buffer[base..=i]))?;
|
||||
base = i + 1;
|
||||
if !on_event(Event::Line)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
on_event(Event::Data(&buffer[base..read]))?;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_hostid"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "hostid ~ (uutils) display the numeric identifier of the current host"
|
||||
|
@ -16,8 +16,8 @@ path = "src/hostid.rs"
|
|||
|
||||
[dependencies]
|
||||
libc = "0.2.42"
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "hostid"
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) gethostid
|
||||
|
||||
extern crate libc;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_hostname"
|
||||
version = "0.0.1"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "hostname ~ (uutils) display or set the host name of the current host"
|
||||
|
@ -15,11 +15,11 @@ edition = "2018"
|
|||
path = "src/hostname.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.32"
|
||||
clap = "2.33"
|
||||
libc = "0.2.42"
|
||||
hostname = { version = "0.3", features = ["set"] }
|
||||
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["wide"] }
|
||||
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["wide"] }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
winapi = { version="0.3", features=["sysinfoapi", "winsock2"] }
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -7,12 +7,6 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) MAKEWORD addrs hashset
|
||||
|
||||
extern crate clap;
|
||||
extern crate hostname;
|
||||
extern crate libc;
|
||||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
|
@ -84,7 +78,7 @@ fn execute(args: impl uucore::Args) -> i32 {
|
|||
)
|
||||
.arg(Arg::with_name(OPT_SHORT).short("s").long("short").help(
|
||||
"Display the short hostname (the portion before the first dot) if \
|
||||
possible",
|
||||
possible",
|
||||
))
|
||||
.arg(Arg::with_name(OPT_HOST))
|
||||
.get_matches_from(args);
|
||||
|
@ -123,7 +117,7 @@ fn display_hostname(matches: &ArgMatches) -> i32 {
|
|||
ip.truncate(len - 2);
|
||||
}
|
||||
output.push_str(&ip);
|
||||
output.push_str(" ");
|
||||
output.push(' ');
|
||||
hashset.insert(addr);
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue