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

Merge branch 'master' into iss1769

This commit is contained in:
Jan Scheer 2021-03-25 23:23:08 +01:00 committed by GitHub
commit aac79d13b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 3802 additions and 1438 deletions

View file

@ -34,46 +34,74 @@ jobs:
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify expect python3-sphinx sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx
pushd uutils pushd uutils
make PROFILE=release make PROFILE=release
BUILDDIR="$PWD/target/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 popd
GNULIB_SRCDIR="$PWD/gnulib" GNULIB_SRCDIR="$PWD/gnulib"
pushd gnu/ 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" ./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR"
./configure --quiet --disable-gcc-warnings ./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 # 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/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile
sed -i 's| tr | /usr/bin/tr |' tests/init.sh sed -i 's| tr | /usr/bin/tr |' tests/init.sh
make make
# Generate the factor tests, so they can be fixed # Generate the factor tests, so they can be fixed
for i in $(seq -w 1 36) for i in {00..36}
do do
make tests/factor/t${i}.sh make tests/factor/t${i}.sh
done done
grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||' grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||'
sed -i -e 's|^seq |/usr/bin/seq |' tests/factor/t*sh sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh
sed -i -e '/tests\/misc\/cat-self.sh/ D' Makefile # issue #1707
sed -i -e '/tests\/misc\/numfmt.pl/ D' Makefile # issue #1708
sed -i -e '/tests\/chown\/preserve-root.sh/ D' Makefile # issue #1709
sed -i -e '/tests\/cp\/file-perm-race.sh/ D' Makefile # issue #1710
sed -i -e '/tests\/cp\/special-f.sh/ D' Makefile # issue #1710
sed -i -e '/tests\/mv\/mv-special-1.sh/ D' Makefile # issue #1710
sed -i -e '/tests\/dd\/stats.sh/ D' Makefile # issue #1710
sed -i -e '/tests\/cp\/existing-perm-race.sh/ D' Makefile # hangs, cp doesn't implement --copy-contents
# Remove tests that rely on seq with large numbers. See issue #1703
sed -i -e '/tests\/misc\/seq.pl/ D' \
-e '/tests\/misc\/seq-precision.sh/D' \
Makefile
# Remove tests checking for --version & --help # Remove tests checking for --version & --help
# Not really interesting for us and logs are too big # Not really interesting for us and logs are too big
sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \ sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \
-e '/tests\/misc\/help-version.sh/ D' \ -e '/tests\/misc\/help-version.sh/ D' \
-e '/tests\/misc\/help-version-getopt.sh/ D' \ -e '/tests\/misc\/help-version-getopt.sh/ D' \
Makefile Makefile
sed -i -e '/tests\/tail-2\/pid.sh/ D' Makefile # hangs on github, tail doesn't support -f
sed -i -e'/incompat4/ D' -e"/options '-co' are incompatible/ d" tests/misc/sort.pl # Sort doesn't correctly check for incompatible options, waits for input # 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 # 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
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}" test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}"
- name: Run GNU tests - name: Run GNU tests
@ -83,17 +111,22 @@ jobs:
GNULIB_DIR="${PWD}/gnulib" GNULIB_DIR="${PWD}/gnulib"
pushd gnu pushd gnu
unbuffer timeout -sKILL 3600 make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : 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 - name: Extract tests info
shell: bash shell: bash
run: | run: |
TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-) if test -f gnu/tests/test-suite.log
PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-) then
SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-) TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-) PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
XPASS=$( grep "# XPASS:" gnu/tests/test-suite.log|cut -d' ' -f2-) SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-)
ERROR=$( grep "# ERROR:" gnu/tests/test-suite.log|cut -d' ' -f2-) FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
echo "::warning ::GNU testsuite = $TOTAL / $PASS / $FAIL / $ERROR" 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 - uses: actions/upload-artifact@v2
with: with:

144
Cargo.lock generated
View file

@ -59,11 +59,6 @@ name = "bit-vec"
version = "0.6.3" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
@ -95,7 +90,7 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -193,7 +188,7 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -300,6 +295,7 @@ dependencies = [
"uu_whoami 0.0.4", "uu_whoami 0.0.4",
"uu_yes 0.0.4", "uu_yes 0.0.4",
"uucore 0.0.7", "uucore 0.0.7",
"walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -404,13 +400,13 @@ dependencies = [
"oorandom 11.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "oorandom 11.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"plotters 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "plotters 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"tinytemplate 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "tinytemplate 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -472,7 +468,7 @@ dependencies = [
"csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -517,7 +513,7 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -657,10 +653,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.48" version = "0.3.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"wasm-bindgen 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -726,17 +722,6 @@ dependencies = [
"autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "nix"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.13.1" version = "0.13.1"
@ -854,8 +839,8 @@ dependencies = [
"num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"plotters-backend 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "plotters-backend 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"plotters-svg 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "plotters-svg 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
"web-sys 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)", "web-sys 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1035,7 +1020,7 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.4.4" version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", "aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1115,7 +1100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.124" version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -1124,12 +1109,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"half 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "half 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.124" version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1144,7 +1129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1301,7 +1286,7 @@ name = "tinytemplate"
version = "1.2.1" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1397,7 +1382,7 @@ version = "0.0.4"
dependencies = [ dependencies = [
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1407,7 +1392,7 @@ dependencies = [
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1418,14 +1403,14 @@ dependencies = [
"glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "uu_chroot" name = "uu_chroot"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
] ]
@ -1460,7 +1445,7 @@ dependencies = [
"quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1471,7 +1456,7 @@ version = "0.0.4"
dependencies = [ dependencies = [
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
@ -1491,8 +1476,10 @@ version = "0.0.4"
dependencies = [ dependencies = [
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1538,6 +1525,7 @@ dependencies = [
name = "uu_echo" name = "uu_echo"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
] ]
@ -1557,7 +1545,7 @@ dependencies = [
name = "uu_expand" name = "uu_expand"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
@ -1634,7 +1622,7 @@ dependencies = [
"hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)",
"sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1745,7 +1733,7 @@ name = "uu_ls"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"number_prefix 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1802,7 +1790,7 @@ name = "uu_more"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
@ -1838,7 +1826,7 @@ dependencies = [
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
@ -1848,7 +1836,7 @@ dependencies = [
name = "uu_nohup" name = "uu_nohup"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
@ -1879,7 +1867,7 @@ name = "uu_od"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"half 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "half 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
@ -1939,7 +1927,7 @@ dependencies = [
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
@ -1990,7 +1978,7 @@ dependencies = [
"remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2140,7 +2128,7 @@ dependencies = [
name = "uu_tee" name = "uu_tee"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
@ -2209,7 +2197,7 @@ dependencies = [
name = "uu_tsort" name = "uu_tsort"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
] ]
@ -2238,7 +2226,7 @@ dependencies = [
name = "uu_unexpand" name = "uu_unexpand"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",
"uucore_procs 0.0.5", "uucore_procs 0.0.5",
@ -2357,7 +2345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.3.1" version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2372,16 +2360,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.71" version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-macro 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.71" version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"bumpalo 3.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "bumpalo 3.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2390,42 +2378,42 @@ dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-shared 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.71" version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro-support 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-macro-support 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.71" version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-backend 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-shared 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.71" version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.48" version = "0.3.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"js-sys 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2490,7 +2478,6 @@ dependencies = [
"checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" "checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
"checksum bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" "checksum bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
"checksum bit-vec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" "checksum bit-vec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400"
"checksum block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814" "checksum block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814"
@ -2549,7 +2536,7 @@ dependencies = [
"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" "checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" "checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
"checksum itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" "checksum itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
"checksum js-sys 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)" = "dc9f84f9b115ce7843d60706df1422a916680bfdfcbdb0447c5614ff9d7e4d78" "checksum js-sys 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)" = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)" = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" "checksum libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)" = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
@ -2561,7 +2548,6 @@ dependencies = [
"checksum memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" "checksum memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
"checksum memoffset 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" "checksum memoffset 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
"checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b"
"checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487"
"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
"checksum num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" "checksum num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
"checksum num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" "checksum num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
@ -2598,7 +2584,7 @@ dependencies = [
"checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" "checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
"checksum redox_syscall 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" "checksum redox_syscall 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
"checksum redox_termios 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" "checksum redox_termios 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
"checksum regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "54fd1046a3107eb58f42de31d656fee6853e5d276c455fd943742dce89fc3dd3" "checksum regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
"checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" "checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
"checksum regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)" = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" "checksum regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)" = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
"checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" "checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
@ -2610,9 +2596,9 @@ dependencies = [
"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)" = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f" "checksum serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)" = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
"checksum serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" "checksum serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622"
"checksum serde_derive 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)" = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" "checksum serde_derive 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)" = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
"checksum serde_json 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)" = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" "checksum serde_json 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)" = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
"checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
"checksum sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d963c78ce367df26d7ea8b8cc655c651b42e8a1e584e869c1e17dae3ccb116a" "checksum sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d963c78ce367df26d7ea8b8cc655c651b42e8a1e584e869c1e17dae3ccb116a"
@ -2640,14 +2626,14 @@ dependencies = [
"checksum users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486" "checksum users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486"
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" "checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" "checksum walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
"checksum wasm-bindgen 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7" "checksum wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe"
"checksum wasm-bindgen-backend 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8" "checksum wasm-bindgen-backend 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3"
"checksum wasm-bindgen-macro 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b" "checksum wasm-bindgen-macro 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b"
"checksum wasm-bindgen-macro-support 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" "checksum wasm-bindgen-macro-support 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d"
"checksum wasm-bindgen-shared 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "7d6f8ec44822dd71f5f221a5847fb34acd9060535c1211b70a05844c0f6383b1" "checksum wasm-bindgen-shared 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa"
"checksum web-sys 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)" = "ec600b26223b2948cedfde2a0aa6756dcf1fef616f43d7b3097aaf53a6c4d92b" "checksum web-sys 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)" = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310"
"checksum wild 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020" "checksum wild 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" "checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"

View file

@ -347,6 +347,7 @@ tempfile = "= 3.1.0"
time = "0.1" time = "0.1"
unindent = "0.1" unindent = "0.1"
uucore = { version=">=0.0.7", package="uucore", path="src/uucore", features=["entries"] } uucore = { version=">=0.0.7", package="uucore", path="src/uucore", features=["entries"] }
walkdir = "2.2"
[target.'cfg(unix)'.dev-dependencies] [target.'cfg(unix)'.dev-dependencies]
rust-users = { version="0.10", package="users" } rust-users = { version="0.10", package="users" }

View file

@ -23,9 +23,8 @@ Why?
Many GNU, Linux and other utilities are useful, and obviously Many GNU, Linux and other utilities are useful, and obviously
[some](http://gnuwin32.sourceforge.net) [effort](http://unxutils.sourceforge.net) [some](http://gnuwin32.sourceforge.net) [effort](http://unxutils.sourceforge.net)
has been spent in the past to port them to Windows. However, those projects 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 are written in platform-specific C, a language considered unsafe compared to Rust, and
for new contributors to contribute to them), are written in platform-specific C, or have other issues.
suffer from other issues.
Rust provides a good, platform-agnostic way of writing systems utilities that are easy 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. to compile anywhere, and this is as good a way as any to try and learn it.
@ -301,7 +300,7 @@ Utilities
| csplit | date | | | csplit | date | |
| cut | join | | | cut | join | |
| dircolors | df | | | dircolors | df | |
| dirname | | | | dirname | tac | |
| du | | | | du | | |
| echo | | | | echo | | |
| env | | | | env | | |
@ -354,7 +353,6 @@ Utilities
| stdbuf | | | | stdbuf | | |
| sum | | | | sum | | |
| sync | | | | sync | | |
| tac | | |
| tee | | | | tee | | |
| timeout | | | | timeout | | |
| touch | | | | touch | | |

View file

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

View file

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

View file

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

View file

@ -14,11 +14,101 @@ use std::fs::File;
use std::io::{self, stdin, BufReader, Read}; use std::io::{self, stdin, BufReader, Read};
use std::path::Path; 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]..."; const SYNTAX: &str = "[OPTIONS] [FILE]...";
static SUMMARY: &str = "Print CRC and size for each file"; const SUMMARY: &str = "Print CRC and size for each file";
static LONG_HELP: &str = ""; const LONG_HELP: &str = "";
// 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] #[inline]
fn crc_update(crc: u32, input: u8) -> u32 { fn crc_update(crc: u32, input: u8) -> u32 {
@ -68,7 +158,6 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
Err(err) => return Err(err), Err(err) => return Err(err),
} }
} }
//Ok((0 as u32,0 as usize))
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {

View file

@ -112,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. /// answered yes.
macro_rules! prompt_yes( macro_rules! prompt_yes(
($($args:tt)+) => ({ ($($args:tt)+) => ({
@ -431,6 +431,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS) .arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
.short("d") .short("d")
.help("same as --no-dereference --preserve=links")) .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 // TODO: implement the following args
.arg(Arg::with_name(OPT_COPY_CONTENTS) .arg(Arg::with_name(OPT_COPY_CONTENTS)
@ -442,10 +446,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(true) .takes_value(true)
.value_name("WHEN") .value_name("WHEN")
.help("NotImplemented: control creation of sparse files. See below")) .help("NotImplemented: control creation of sparse files. See below"))
.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) .arg(Arg::with_name(OPT_CONTEXT)
.long(OPT_CONTEXT) .long(OPT_CONTEXT)
.takes_value(true) .takes_value(true)
@ -563,6 +563,7 @@ impl Options {
let not_implemented_opts = vec![ let not_implemented_opts = vec![
OPT_COPY_CONTENTS, OPT_COPY_CONTENTS,
OPT_SPARSE, OPT_SPARSE,
#[cfg(not(any(windows, unix)))]
OPT_ONE_FILE_SYSTEM, OPT_ONE_FILE_SYSTEM,
OPT_CONTEXT, OPT_CONTEXT,
#[cfg(windows)] #[cfg(windows)]
@ -937,7 +938,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
#[cfg(any(windows, target_os = "redox"))] #[cfg(any(windows, target_os = "redox"))]
let mut hard_links: Vec<(String, u64)> = vec![]; 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 p = or_continue!(path);
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink(); let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
let path = if (options.no_dereference || options.dereference) && is_symlink { let path = if (options.no_dereference || options.dereference) && is_symlink {

View file

@ -20,6 +20,12 @@ clap = "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(unix)'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["minwinbase", "sysinfoapi", "minwindef"] }
[[bin]] [[bin]]
name = "date" name = "date"
path = "src/main.rs" path = "src/main.rs"

View file

@ -12,13 +12,20 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
#[cfg(windows)]
use chrono::{Datelike, Timelike};
use clap::{App, Arg}; use clap::{App, Arg};
#[cfg(all(unix, not(target_os = "macos")))]
use chrono::offset::Utc; use libc::{clock_settime, timespec, CLOCK_REALTIME};
use chrono::{DateTime, FixedOffset, Local, Offset};
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(windows)]
use winapi::{
shared::minwindef::WORD,
um::{minwinbase::SYSTEMTIME, sysinfoapi::SetSystemTime},
};
// Options // Options
const DATE: &str = "date"; const DATE: &str = "date";
@ -62,6 +69,11 @@ static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format.
for date and time to the indicated precision. for date and time to the indicated precision.
Example: 2006-08-14 02:34:56-06:00"; 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 /// Settings for this program, parsed from the command line
struct Settings { struct Settings {
utc: bool, utc: bool,
@ -186,7 +198,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("s") .short("s")
.long(OPT_SET) .long(OPT_SET)
.takes_value(true) .takes_value(true)
.help("set time described by STRING"), .help(OPT_SET_HELP_STRING),
) )
.arg( .arg(
Arg::with_name(OPT_UNIVERSAL) Arg::with_name(OPT_UNIVERSAL)
@ -222,18 +234,31 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
DateSource::Now 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 { let settings = Settings {
utc: matches.is_present(OPT_UNIVERSAL), utc: matches.is_present(OPT_UNIVERSAL),
format, format,
date_source, date_source,
// TODO: Handle this option: set_to,
set_to: None,
}; };
if let Some(_time) = settings.set_to { if let Some(date) = settings.set_to {
unimplemented!(); // All set time functions expect UTC datetimes.
// Probably need to use this syscall: let date: DateTime<Utc> = if settings.utc {
// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html date.with_timezone(&Utc)
} else {
date.into()
};
return set_system_datetime(date);
} else { } else {
// Declare a file here because it needs to outlive the `dates` iterator. // Declare a file here because it needs to outlive the `dates` iterator.
let file: File; let file: File;
@ -247,15 +272,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
now.with_timezone(now.offset()) 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. // Iterate over all dates - whether it's a single date or a file.
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source { let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
DateSource::Custom(ref input) => { DateSource::Custom(ref input) => {
@ -314,3 +330,76 @@ fn make_format_string(settings: &Settings) -> &str {
Format::Default => "%c", 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, &timespec) };
if result != 0 {
let error = std::io::Error::last_os_error();
eprintln!("date: cannot set date: {}", error);
error.raw_os_error().unwrap()
} else {
0
}
}
#[cfg(windows)]
/// System call to set date (Windows).
/// See here for more:
/// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-setsystemtime
/// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime
fn set_system_datetime(date: DateTime<Utc>) -> i32 {
let system_time = SYSTEMTIME {
wYear: date.year() as WORD,
wMonth: date.month() as WORD,
// Ignored
wDayOfWeek: 0,
wDay: date.day() as WORD,
wHour: date.hour() as WORD,
wMinute: date.minute() as WORD,
wSecond: date.second() as WORD,
// TODO: be careful of leap seconds - valid range is [0, 999] - how to handle?
wMilliseconds: ((date.nanosecond() / 1_000_000) % 1000) as WORD,
};
let result = unsafe { SetSystemTime(&system_time) };
if result == 0 {
let error = std::io::Error::last_os_error();
eprintln!("date: cannot set date: {}", error);
error.raw_os_error().unwrap()
} else {
0
}
}

View file

@ -32,15 +32,15 @@ use std::ffi::CString;
#[cfg(unix)] #[cfg(unix)]
use std::mem; use std::mem;
#[cfg(any(target_os = "macos", target_os = "freebsd"))] #[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use libc::c_int; use libc::c_int;
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
use libc::statfs; use libc::statfs;
#[cfg(any(target_os = "macos", target_os = "freebsd"))] #[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::ffi::CStr; 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; use std::ptr;
#[cfg(any(target_os = "macos", target_os = "freebsd"))] #[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::slice; use std::slice;
#[cfg(target_os = "freebsd")] #[cfg(target_os = "freebsd")]
@ -137,7 +137,7 @@ struct MountInfo {
#[cfg(all( #[cfg(all(
target_os = "freebsd", target_os = "freebsd",
not(all(target_os = "macos", target_arch = "x86_64")) not(all(target_vendor = "apple", target_arch = "x86_64"))
))] ))]
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -209,20 +209,20 @@ fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!()) format!("{0} [OPTION]... [FILE]...", executable!())
} }
#[cfg(any(target_os = "freebsd", target_os = "macos"))] #[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
extern "C" { extern "C" {
#[cfg(all(target_os = "macos", target_arch = "x86_64"))] #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
#[link_name = "getmntinfo$INODE64"] #[link_name = "getmntinfo$INODE64"]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
#[cfg(any( #[cfg(any(
all(target_os = "freebsd"), all(target_os = "freebsd"),
all(target_os = "macos", target_arch = "aarch64") all(target_vendor = "apple", target_arch = "aarch64")
))] ))]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; 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 { impl From<statfs> for MountInfo {
fn from(statfs: statfs) -> Self { fn from(statfs: statfs) -> Self {
let mut info = MountInfo { let mut info = MountInfo {
@ -585,7 +585,7 @@ fn read_fs_list() -> Vec<MountInfo> {
}) })
.collect::<Vec<_>>() .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 mut mptr: *mut statfs = ptr::null_mut();
let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) }; let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) };

View file

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

View file

@ -9,14 +9,17 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg};
use std::io::{self, Write}; use std::io::{self, Write};
use std::iter::Peekable; use std::iter::Peekable;
use std::str::Chars; use std::str::Chars;
const SYNTAX: &str = "[OPTIONS]... [STRING]..."; static NAME: &str = "echo";
const SUMMARY: &str = "display a line of text"; static USAGE: &str = "[OPTIONS]... [STRING]...";
const HELP: &str = r#" static SUMMARY: &str = "display a line of text";
static AFTER_HELP: &str = r#"
Echo the STRING(s) to standard output. Echo the STRING(s) to standard output.
If -e is in effect, the following sequences are recognized: If -e is in effect, the following sequences are recognized:
\\\\ backslash \\\\ backslash
@ -33,6 +36,13 @@ const HELP: &str = r#"
\\xHH byte with hexadecimal value HH (1 to 2 digits) \\xHH byte with hexadecimal value HH (1 to 2 digits)
"#; "#;
mod options {
pub static STRING: &str = "STRING";
pub static NO_NEWLINE: &str = "no_newline";
pub static ENABLE_BACKSLASH_ESCAPE: &str = "enable_backslash_escape";
pub static DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape";
}
fn parse_code( fn parse_code(
input: &mut Peekable<Chars>, input: &mut Peekable<Chars>,
base: u32, base: u32,
@ -105,20 +115,54 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result<bool> {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args.collect_str();
let matches = app!(SYNTAX, SUMMARY, HELP) let matches = App::new(executable!())
.optflag("n", "", "do not output the trailing newline") .name(NAME)
.optflag("e", "", "enable interpretation of backslash escapes") // TrailingVarArg specifies the final positional argument is a VarArg
.optflag( // and it doesn't attempts the parse any further args.
"E", // Final argument must have multiple(true) or the usage string equivalent.
"", .setting(clap::AppSettings::TrailingVarArg)
"disable interpretation of backslash escapes (default)", .setting(clap::AppSettings::AllowLeadingHyphen)
.version(crate_version!())
.usage(USAGE)
.about(SUMMARY)
.after_help(AFTER_HELP)
.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)
.hidden(true)
.multiple(true)
.allow_hyphen_values(true),
)
.get_matches_from(args);
let no_newline = matches.opt_present("n"); let no_newline = matches.is_present(options::NO_NEWLINE);
let escaped = matches.opt_present("e"); 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, Ok(_) => 0,
Err(f) => { Err(f) => {
show_error!("{}", f); show_error!("{}", f);

View file

@ -148,11 +148,11 @@ impl ASTNode {
|a: &String, b: &String| Ok(bool_as_string(a >= b)), |a: &String, b: &String| Ok(bool_as_string(a >= b)),
&operand_values, &operand_values,
), ),
"|" => infix_operator_or(&operand_values), "|" => Ok(infix_operator_or(&operand_values)),
"&" => infix_operator_and(&operand_values), "&" => Ok(infix_operator_and(&operand_values)),
":" | "match" => operator_match(&operand_values), ":" | "match" => operator_match(&operand_values),
"length" => prefix_operator_length(&operand_values), "length" => Ok(prefix_operator_length(&operand_values)),
"index" => prefix_operator_index(&operand_values), "index" => Ok(prefix_operator_index(&operand_values)),
"substr" => prefix_operator_substr(&operand_values), "substr" => prefix_operator_substr(&operand_values),
_ => Err(format!("operation not implemented: {}", op_type)), _ => 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); assert!(values.len() == 2);
if value_as_bool(&values[0]) { if value_as_bool(&values[0]) {
Ok(values[0].clone()) values[0].clone()
} else { } 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]) { if value_as_bool(&values[0]) && value_as_bool(&values[1]) {
Ok(values[0].clone()) values[0].clone()
} else { } 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); 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); assert!(values.len() == 2);
let haystack = &values[0]; let haystack = &values[0];
let needles = &values[1]; 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 (current_idx, ch_h) in haystack.chars().enumerate() {
for ch_n in needles.chars() { for ch_n in needles.chars() {
if ch_n == ch_h { 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> { fn prefix_operator_substr(values: &[String]) -> Result<String, String> {

View file

@ -9,7 +9,7 @@ use smallvec::SmallVec;
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt; use std::fmt;
use crate::numeric::{gcd, Arithmetic, Montgomery}; use crate::numeric::{Arithmetic, Montgomery};
use crate::{miller_rabin, rho, table}; use crate::{miller_rabin, rho, table};
type Exponent = u8; type Exponent = u8;
@ -29,20 +29,15 @@ impl Decomposition {
fn add(&mut self, factor: u64, exp: Exponent) { fn add(&mut self, factor: u64, exp: Exponent) {
debug_assert!(exp > 0); debug_assert!(exp > 0);
// Assert the factor doesn't already exist in the Decomposition object
debug_assert_eq!(self.0.iter_mut().find(|(f, _)| *f == factor), None);
self.0.push((factor, exp)) if let Some((_, e)) = self.0.iter_mut().find(|(f, _)| *f == factor) {
} *e += exp;
} else {
fn is_one(&self) -> bool { self.0.push((factor, exp))
self.0.is_empty() }
}
fn pop(&mut self) -> Option<(u64, Exponent)> {
self.0.pop()
} }
#[cfg(test)]
fn product(&self) -> u64 { fn product(&self) -> u64 {
self.0 self.0
.iter() .iter()
@ -86,11 +81,11 @@ impl Factors {
self.0.borrow_mut().add(prime, exp) self.0.borrow_mut().add(prime, exp)
} }
#[cfg(test)]
pub fn push(&mut self, prime: u64) { pub fn push(&mut self, prime: u64) {
self.add(prime, 1) self.add(prime, 1)
} }
#[cfg(test)]
fn product(&self) -> u64 { fn product(&self) -> u64 {
self.0.borrow().product() self.0.borrow().product()
} }
@ -111,116 +106,62 @@ impl fmt::Display for Factors {
} }
} }
fn _find_factor<A: Arithmetic + miller_rabin::Basis>(num: u64) -> Option<u64> { fn _factor<A: Arithmetic + miller_rabin::Basis>(num: u64, f: Factors) -> Factors {
use miller_rabin::Result::*; use miller_rabin::Result::*;
// Shadow the name, so the recursion automatically goes from “Big” arithmetic to small.
let _factor = |n, f| {
if n < (1 << 32) {
_factor::<Montgomery<u32>>(n, f)
} else {
_factor::<A>(n, f)
}
};
if num == 1 {
return f;
}
let n = A::new(num); let n = A::new(num);
match miller_rabin::test::<A>(n) { let divisor = match miller_rabin::test::<A>(n) {
Prime => None, Prime => {
Composite(d) => Some(d), let mut r = f;
Pseudoprime => Some(rho::find_divisor::<A>(n)), r.push(num);
} return r;
}
Composite(d) => d,
Pseudoprime => rho::find_divisor::<A>(n),
};
let f = _factor(divisor, f);
_factor(num / divisor, f)
} }
fn find_factor(num: u64) -> Option<u64> { pub fn factor(mut n: u64) -> Factors {
if num < (1 << 32) {
_find_factor::<Montgomery<u32>>(num)
} else {
_find_factor::<Montgomery<u64>>(num)
}
}
pub fn factor(num: u64) -> Factors {
let mut factors = Factors::one(); let mut factors = Factors::one();
if num < 2 { if n < 2 {
return factors; return factors;
} }
let mut n = num; let n_zeros = n.trailing_zeros();
let n_zeros = num.trailing_zeros();
if n_zeros > 0 { if n_zeros > 0 {
factors.add(2, n_zeros as Exponent); factors.add(2, n_zeros as Exponent);
n >>= n_zeros; n >>= n_zeros;
} }
debug_assert_eq!(num, n * factors.product());
if n == 1 { if n == 1 {
return factors; return factors;
} }
table::factor(&mut n, &mut factors); let (factors, n) = table::factor(n, factors);
debug_assert_eq!(num, n * factors.product());
if n == 1 { if n < (1 << 32) {
return factors; _factor::<Montgomery<u32>>(n, factors)
} else {
_factor::<Montgomery<u64>>(n, factors)
} }
let mut dec = Decomposition::one();
dec.add(n, 1);
while !dec.is_one() {
// Check correctness invariant
debug_assert_eq!(num, factors.product() * dec.product());
let (factor, exp) = dec.pop().unwrap();
if let Some(divisor) = find_factor(factor) {
let mut gcd_queue = Decomposition::one();
let quotient = factor / divisor;
let mut trivial_gcd = quotient == divisor;
if trivial_gcd {
gcd_queue.add(divisor, exp + 1);
} else {
gcd_queue.add(divisor, exp);
gcd_queue.add(quotient, exp);
}
while !trivial_gcd {
debug_assert_eq!(factor, gcd_queue.product());
let mut tmp = Decomposition::one();
trivial_gcd = true;
for i in 0..gcd_queue.0.len() - 1 {
let (mut a, exp_a) = gcd_queue.0[i];
let (mut b, exp_b) = gcd_queue.0[i + 1];
if a == 1 {
continue;
}
let g = gcd(a, b);
if g != 1 {
trivial_gcd = false;
a /= g;
b /= g;
}
if a != 1 {
tmp.add(a, exp_a);
}
if g != 1 {
tmp.add(g, exp_a + exp_b);
}
if i + 1 != gcd_queue.0.len() - 1 {
gcd_queue.0[i + 1].0 = b;
} else if b != 1 {
tmp.add(b, exp_b);
}
}
gcd_queue = tmp;
}
debug_assert_eq!(factor, gcd_queue.product());
dec.0.extend(gcd_queue.0);
} else {
// factor is prime
factors.add(factor, exp);
}
}
factors
} }
#[cfg(test)] #[cfg(test)]

View file

@ -14,8 +14,7 @@ use crate::Factors;
include!(concat!(env!("OUT_DIR"), "/prime_table.rs")); include!(concat!(env!("OUT_DIR"), "/prime_table.rs"));
pub(crate) fn factor(n: &mut u64, factors: &mut Factors) { pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) {
let mut num = *n;
for &(prime, inv, ceil) in P_INVS_U64 { for &(prime, inv, ceil) in P_INVS_U64 {
if num == 1 { if num == 1 {
break; break;
@ -43,5 +42,5 @@ pub(crate) fn factor(n: &mut u64, factors: &mut Factors) {
} }
} }
*n = num; (factors, num)
} }

View file

@ -29,6 +29,7 @@ enum FilterMode {
struct Settings { struct Settings {
mode: FilterMode, mode: FilterMode,
verbose: bool, verbose: bool,
zero_terminated: bool,
} }
impl Default for Settings { impl Default for Settings {
@ -36,6 +37,7 @@ impl Default for Settings {
Settings { Settings {
mode: FilterMode::Lines(10), mode: FilterMode::Lines(10),
verbose: false, verbose: false,
zero_terminated: false,
} }
} }
} }
@ -69,6 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
) )
.optflag("q", "quiet", "never print headers giving file names") .optflag("q", "quiet", "never print headers giving file names")
.optflag("v", "verbose", "always print headers giving file names") .optflag("v", "verbose", "always print headers giving file names")
.optflag("z", "zero-terminated", "line delimiter is NUL, not newline")
.optflag("h", "help", "display this help and exit") .optflag("h", "help", "display this help and exit")
.optflag("V", "version", "output version information and exit") .optflag("V", "version", "output version information and exit")
.parse(new_args); .parse(new_args);
@ -113,6 +116,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let quiet = matches.opt_present("q"); let quiet = matches.opt_present("q");
let verbose = matches.opt_present("v"); let verbose = matches.opt_present("v");
settings.zero_terminated = matches.opt_present("z");
let files = matches.free; let files = matches.free;
// GNU implementation allows multiple declarations of "-q" and "-v" with the // GNU implementation allows multiple declarations of "-q" and "-v" with the
@ -145,6 +149,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
first_time = false; first_time = false;
let path = Path::new(file); let path = Path::new(file);
if path.is_dir() || !path.metadata().is_ok() {
eprintln!(
"cannot open '{}' for reading: No such file or directory",
&path.to_str().unwrap()
);
continue;
}
let reader = File::open(&path).unwrap(); let reader = File::open(&path).unwrap();
let mut buffer = BufReader::new(reader); let mut buffer = BufReader::new(reader);
if !head(&mut buffer, &settings) { if !head(&mut buffer, &settings) {
@ -203,8 +214,14 @@ fn head<T: Read>(reader: &mut BufReader<T>, settings: &Settings) -> bool {
} }
} }
FilterMode::Lines(count) => { FilterMode::Lines(count) => {
for line in reader.lines().take(count) { if settings.zero_terminated {
println!("{}", line.unwrap()); for line in reader.split(0).take(count) {
print!("{}\0", String::from_utf8(line.unwrap()).unwrap())
}
} else {
for line in reader.lines().take(count) {
println!("{}", line.unwrap());
}
} }
} }
FilterMode::NLines(count) => { FilterMode::NLines(count) => {

View file

@ -291,7 +291,7 @@ fn pretty(possible_pw: Option<Passwd>) {
} }
} }
#[cfg(any(target_os = "macos", target_os = "freebsd"))] #[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
fn pline(possible_uid: Option<uid_t>) { fn pline(possible_uid: Option<uid_t>) {
let uid = possible_uid.unwrap_or_else(getuid); let uid = possible_uid.unwrap_or_else(getuid);
let pw = Passwd::locate(uid).unwrap(); let pw = Passwd::locate(uid).unwrap();

View file

@ -426,18 +426,25 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
let mut all_successful = true; let mut all_successful = true;
for sourcepath in files.iter() { for sourcepath in files.iter() {
let targetpath = match sourcepath.as_os_str().to_str() { if !sourcepath.exists() {
Some(name) => target_dir.join(name), show_info!(
None => { "cannot stat '{}': No such file or directory",
show_error!( sourcepath.display()
"cannot stat '{}': No such file or directory", );
sourcepath.display()
);
all_successful = false; all_successful = false;
continue; continue;
} }
};
if sourcepath.is_dir() {
show_info!("omitting directory '{}'", sourcepath.display());
all_successful = false;
continue;
}
let mut targetpath = target_dir.clone().to_path_buf();
let filename = sourcepath.components().last().unwrap();
targetpath.push(filename);
if copy(sourcepath, &targetpath, b).is_err() { if copy(sourcepath, &targetpath, b).is_err() {
all_successful = false; all_successful = false;

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/ls.rs" path = "src/ls.rs"
[dependencies] [dependencies]
getopts = "0.2.18" clap = "2.33"
lazy_static = "1.0.1" lazy_static = "1.0.1"
number_prefix = "0.4" number_prefix = "0.4"
term_grid = "0.1.5" term_grid = "0.1.5"

File diff suppressed because it is too large Load diff

View file

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

View file

@ -16,15 +16,15 @@ path = "src/more.rs"
[dependencies] [dependencies]
getopts = "0.2.18" getopts = "0.2.18"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
[target.'cfg(target_os = "redox")'.dependencies] [target.'cfg(target_os = "redox")'.dependencies]
redox_termios = "0.1" redox_termios = "0.1"
redox_syscall = "0.1" redox_syscall = "0.1"
[target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies] [target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies]
nix = "0.8.1" nix = "<=0.13"
[[bin]] [[bin]]
name = "more" name = "more"

View file

@ -17,7 +17,7 @@ use std::io::{stdout, Read, Write};
#[cfg(all(unix, not(target_os = "fuchsia")))] #[cfg(all(unix, not(target_os = "fuchsia")))]
extern crate nix; extern crate nix;
#[cfg(all(unix, not(target_os = "fuchsia")))] #[cfg(all(unix, not(target_os = "fuchsia")))]
use nix::sys::termios; use nix::sys::termios::{self, LocalFlags, SetArg};
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
extern crate redox_termios; extern crate redox_termios;
@ -92,10 +92,10 @@ fn help(usage: &str) {
fn setup_term() -> termios::Termios { fn setup_term() -> termios::Termios {
let mut term = termios::tcgetattr(0).unwrap(); let mut term = termios::tcgetattr(0).unwrap();
// Unset canonical mode, so we get characters immediately // Unset canonical mode, so we get characters immediately
term.c_lflag.remove(termios::ICANON); term.local_flags.remove(LocalFlags::ICANON);
// Disable local echo // Disable local echo
term.c_lflag.remove(termios::ECHO); term.local_flags.remove(LocalFlags::ECHO);
termios::tcsetattr(0, termios::TCSADRAIN, &term).unwrap(); termios::tcsetattr(0, SetArg::TCSADRAIN, &term).unwrap();
term term
} }
@ -110,8 +110,8 @@ fn setup_term() -> redox_termios::Termios {
let mut term = redox_termios::Termios::default(); let mut term = redox_termios::Termios::default();
let fd = syscall::dup(0, b"termios").unwrap(); let fd = syscall::dup(0, b"termios").unwrap();
syscall::read(fd, &mut term).unwrap(); syscall::read(fd, &mut term).unwrap();
term.c_lflag &= !redox_termios::ICANON; term.local_flags &= !redox_termios::ICANON;
term.c_lflag &= !redox_termios::ECHO; term.local_flags &= !redox_termios::ECHO;
syscall::write(fd, &term).unwrap(); syscall::write(fd, &term).unwrap();
let _ = syscall::close(fd); let _ = syscall::close(fd);
term term
@ -119,9 +119,9 @@ fn setup_term() -> redox_termios::Termios {
#[cfg(all(unix, not(target_os = "fuchsia")))] #[cfg(all(unix, not(target_os = "fuchsia")))]
fn reset_term(term: &mut termios::Termios) { fn reset_term(term: &mut termios::Termios) {
term.c_lflag.insert(termios::ICANON); term.local_flags.insert(LocalFlags::ICANON);
term.c_lflag.insert(termios::ECHO); term.local_flags.insert(LocalFlags::ECHO);
termios::tcsetattr(0, termios::TCSADRAIN, &term).unwrap(); termios::tcsetattr(0, SetArg::TCSADRAIN, &term).unwrap();
} }
#[cfg(any(windows, target_os = "fuchsia"))] #[cfg(any(windows, target_os = "fuchsia"))]
@ -132,8 +132,8 @@ fn reset_term(_: &mut usize) {}
fn reset_term(term: &mut redox_termios::Termios) { fn reset_term(term: &mut redox_termios::Termios) {
let fd = syscall::dup(0, b"termios").unwrap(); let fd = syscall::dup(0, b"termios").unwrap();
syscall::read(fd, term).unwrap(); syscall::read(fd, term).unwrap();
term.c_lflag |= redox_termios::ICANON; term.local_flags |= redox_termios::ICANON;
term.c_lflag |= redox_termios::ECHO; term.local_flags |= redox_termios::ECHO;
syscall::write(fd, &term).unwrap(); syscall::write(fd, &term).unwrap();
let _ = syscall::close(fd); let _ = syscall::close(fd);
} }

View file

@ -380,7 +380,7 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behavior) -> io::Result<()> {
match b.overwrite { match b.overwrite {
OverwriteMode::NoClobber => return Ok(()), OverwriteMode::NoClobber => return Ok(()),
OverwriteMode::Interactive => { OverwriteMode::Interactive => {
print!("{}: overwrite {}? ", executable!(), to.display()); println!("{}: overwrite {}? ", executable!(), to.display());
if !read_yes() { if !read_yes() {
return Ok(()); return Ok(());
} }

View file

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

View file

@ -10,6 +10,7 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{App, AppSettings, Arg};
use libc::{c_char, dup2, execvp, signal}; use libc::{c_char, dup2, execvp, signal};
use libc::{SIGHUP, SIG_IGN}; use libc::{SIGHUP, SIG_IGN};
use std::env; use std::env;
@ -20,50 +21,42 @@ use std::os::unix::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive}; use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive};
static NAME: &str = "nohup";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Run COMMAND ignoring hangup signals.";
static LONG_HELP: &str = "
If standard input is terminal, it'll be replaced with /dev/null.
If standard output is terminal, it'll be appended to nohup.out instead,
or $HOME/nohup.out, if nohup.out open failed.
If standard error is terminal, it'll be redirected to stdout.
";
static NOHUP_OUT: &str = "nohup.out";
// exit codes that match the GNU implementation
static EXIT_CANCELED: i32 = 125;
static EXIT_CANNOT_INVOKE: i32 = 126;
static EXIT_ENOENT: i32 = 127;
static POSIX_NOHUP_FAILURE: i32 = 127;
#[cfg(target_os = "macos")] mod options {
extern "C" { pub const CMD: &str = "cmd";
fn _vprocmgr_detach_from_console(flags: u32) -> *const libc::c_int;
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
unsafe fn _vprocmgr_detach_from_console(_: u32) -> *const libc::c_int {
std::ptr::null()
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let usage = get_usage();
let mut opts = getopts::Options::new(); let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::CMD)
.hidden(true)
.required(true)
.multiple(true),
)
.setting(AppSettings::TrailingVarArg)
.get_matches_from(args);
opts.optflag("h", "help", "Show help and exit");
opts.optflag("V", "version", "Show version and exit");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
show_error!("{}", f);
show_usage(&opts);
return 1;
}
};
if matches.opt_present("V") {
println!("{} {}", NAME, VERSION);
return 0;
}
if matches.opt_present("h") {
show_usage(&opts);
return 0;
}
if matches.free.is_empty() {
show_error!("Missing operand: COMMAND");
println!("Try `{} --help` for more information.", NAME);
return 1;
}
replace_fds(); replace_fds();
unsafe { signal(SIGHUP, SIG_IGN) }; unsafe { signal(SIGHUP, SIG_IGN) };
@ -73,13 +66,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}; };
let cstrs: Vec<CString> = matches let cstrs: Vec<CString> = matches
.free .values_of(options::CMD)
.iter() .unwrap()
.map(|x| CString::new(x.as_bytes()).unwrap()) .map(|x| CString::new(x.as_bytes()).unwrap())
.collect(); .collect();
let mut args: Vec<*const c_char> = cstrs.iter().map(|s| s.as_ptr()).collect(); let mut args: Vec<*const c_char> = cstrs.iter().map(|s| s.as_ptr()).collect();
args.push(std::ptr::null()); args.push(std::ptr::null());
unsafe { execvp(args[0], args.as_mut_ptr()) }
let ret = unsafe { execvp(args[0], args.as_mut_ptr()) };
match ret {
libc::ENOENT => EXIT_ENOENT,
_ => EXIT_CANNOT_INVOKE,
}
} }
fn replace_fds() { fn replace_fds() {
@ -108,23 +106,32 @@ fn replace_fds() {
} }
fn find_stdout() -> File { fn find_stdout() -> File {
let internal_failure_code = match std::env::var("POSIXLY_CORRECT") {
Ok(_) => POSIX_NOHUP_FAILURE,
Err(_) => EXIT_CANCELED,
};
match OpenOptions::new() match OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
.append(true) .append(true)
.open(Path::new("nohup.out")) .open(Path::new(NOHUP_OUT))
{ {
Ok(t) => { Ok(t) => {
show_warning!("Output is redirected to: nohup.out"); show_info!("ignoring input and appending output to '{}'", NOHUP_OUT);
t t
} }
Err(e) => { Err(e1) => {
let home = match env::var("HOME") { let home = match env::var("HOME") {
Err(_) => crash!(2, "Cannot replace STDOUT: {}", e), Err(_) => {
show_info!("failed to open '{}': {}", NOHUP_OUT, e1);
exit!(internal_failure_code)
}
Ok(h) => h, Ok(h) => h,
}; };
let mut homeout = PathBuf::from(home); let mut homeout = PathBuf::from(home);
homeout.push("nohup.out"); homeout.push(NOHUP_OUT);
let homeout_str = homeout.to_str().unwrap();
match OpenOptions::new() match OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
@ -132,30 +139,29 @@ fn find_stdout() -> File {
.open(&homeout) .open(&homeout)
{ {
Ok(t) => { Ok(t) => {
show_warning!("Output is redirected to: {:?}", homeout); show_info!("ignoring input and appending output to '{}'", homeout_str);
t t
} }
Err(e) => crash!(2, "Cannot replace STDOUT: {}", e), Err(e2) => {
show_info!("failed to open '{}': {}", NOHUP_OUT, e1);
show_info!("failed to open '{}': {}", homeout_str, e2);
exit!(internal_failure_code)
}
} }
} }
} }
} }
fn show_usage(opts: &getopts::Options) { fn get_usage() -> String {
let msg = format!( format!("{0} COMMAND [ARG]...\n {0} FLAG", executable!())
"{0} {1} }
Usage: #[cfg(target_vendor = "apple")]
{0} COMMAND [ARG]... extern "C" {
{0} OPTION fn _vprocmgr_detach_from_console(flags: u32) -> *const libc::c_int;
}
Run COMMAND ignoring hangup signals.
If standard input is terminal, it'll be replaced with /dev/null. #[cfg(any(target_os = "linux", target_os = "freebsd"))]
If standard output is terminal, it'll be appended to nohup.out instead, unsafe fn _vprocmgr_detach_from_console(_: u32) -> *const libc::c_int {
or $HOME/nohup.out, if nohup.out open failed. std::ptr::null()
If standard error is terminal, it'll be redirected to stdout.",
NAME, VERSION
);
print!("{}", opts.usage(&msg));
} }

View file

@ -15,7 +15,7 @@ use std::env;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub const _SC_NPROCESSORS_CONF: libc::c_int = 83; pub const _SC_NPROCESSORS_CONF: libc::c_int = 83;
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
pub const _SC_NPROCESSORS_CONF: libc::c_int = libc::_SC_NPROCESSORS_CONF; pub const _SC_NPROCESSORS_CONF: libc::c_int = libc::_SC_NPROCESSORS_CONF;
#[cfg(target_os = "freebsd")] #[cfg(target_os = "freebsd")]
pub const _SC_NPROCESSORS_CONF: libc::c_int = 57; pub const _SC_NPROCESSORS_CONF: libc::c_int = 57;
@ -89,7 +89,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
#[cfg(any( #[cfg(any(
target_os = "linux", target_os = "linux",
target_os = "macos", target_vendor = "apple",
target_os = "freebsd", target_os = "freebsd",
target_os = "netbsd" target_os = "netbsd"
))] ))]
@ -109,7 +109,7 @@ fn num_cpus_all() -> usize {
// Other platforms (e.g., windows), num_cpus::get() directly. // Other platforms (e.g., windows), num_cpus::get() directly.
#[cfg(not(any( #[cfg(not(any(
target_os = "linux", target_os = "linux",
target_os = "macos", target_vendor = "apple",
target_os = "freebsd", target_os = "freebsd",
target_os = "netbsd" target_os = "netbsd"
)))] )))]

View file

@ -16,7 +16,7 @@ path = "src/od.rs"
[dependencies] [dependencies]
byteorder = "1.3.2" byteorder = "1.3.2"
getopts = "0.2.18" clap = "2.33"
half = "1.6" half = "1.6"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }

View file

@ -41,15 +41,18 @@ use crate::parse_nrofbytes::parse_number_of_bytes;
use crate::partialreader::*; use crate::partialreader::*;
use crate::peekreader::*; use crate::peekreader::*;
use crate::prn_char::format_ascii_dump; use crate::prn_char::format_ascii_dump;
use clap::{self, AppSettings, Arg, ArgMatches};
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes
static ABOUT: &str = "dump files in octal and other formats";
static USAGE: &str = r#"Usage: static USAGE: &str = r#"
od [OPTION]... [--] [FILENAME]... od [OPTION]... [--] [FILENAME]...
od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]] od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]]
od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]] od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]"#;
static LONG_HELP: &str = r#"
Displays data in various human-readable formats. If multiple formats are Displays data in various human-readable formats. If multiple formats are
specified, the output will contain all formats in the order they appear on the specified, the output will contain all formats in the order they appear on the
command line. Each format will be printed on a new line. Only the line command line. Each format will be printed on a new line. Only the line
@ -88,85 +91,18 @@ Any type specification can have a "z" suffix, which will add a ASCII dump at
If an error occurred, a diagnostic message will be printed to stderr, and the If an error occurred, a diagnostic message will be printed to stderr, and the
exitcode will be non-zero."#; exitcode will be non-zero."#;
fn create_getopts_options() -> getopts::Options { pub(crate) mod options {
let mut opts = getopts::Options::new(); pub const ADDRESS_RADIX: &str = "address-radix";
pub const SKIP_BYTES: &str = "skip-bytes";
opts.optopt( pub const READ_BYTES: &str = "read-bytes";
"A", pub const ENDIAN: &str = "endian";
"address-radix", pub const STRINGS: &str = "strings";
"Select the base in which file offsets are printed.", pub const FORMAT: &str = "format";
"RADIX", pub const OUTPUT_DUPLICATES: &str = "output-duplicates";
); pub const TRADITIONAL: &str = "traditional";
opts.optopt( pub const WIDTH: &str = "width";
"j", pub const VERSION: &str = "version";
"skip-bytes", pub const FILENAME: &str = "FILENAME";
"Skip bytes input bytes before formatting and writing.",
"BYTES",
);
opts.optopt(
"N",
"read-bytes",
"limit dump to BYTES input bytes",
"BYTES",
);
opts.optopt(
"",
"endian",
"byte order to use for multi-byte formats",
"big|little",
);
opts.optopt(
"S",
"strings",
"output strings of at least BYTES graphic chars. 3 is assumed when \
BYTES is not specified.",
"BYTES",
);
opts.optflagmulti("a", "", "named characters, ignoring high-order bit");
opts.optflagmulti("b", "", "octal bytes");
opts.optflagmulti("c", "", "ASCII characters or backslash escapes");
opts.optflagmulti("d", "", "unsigned decimal 2-byte units");
opts.optflagmulti("D", "", "unsigned decimal 4-byte units");
opts.optflagmulti("o", "", "octal 2-byte units");
opts.optflagmulti("I", "", "decimal 8-byte units");
opts.optflagmulti("L", "", "decimal 8-byte units");
opts.optflagmulti("i", "", "decimal 4-byte units");
opts.optflagmulti("l", "", "decimal 8-byte units");
opts.optflagmulti("x", "", "hexadecimal 2-byte units");
opts.optflagmulti("h", "", "hexadecimal 2-byte units");
opts.optflagmulti("O", "", "octal 4-byte units");
opts.optflagmulti("s", "", "decimal 2-byte units");
opts.optflagmulti("X", "", "hexadecimal 4-byte units");
opts.optflagmulti("H", "", "hexadecimal 4-byte units");
opts.optflagmulti("e", "", "floating point double precision (64-bit) units");
opts.optflagmulti("f", "", "floating point single precision (32-bit) units");
opts.optflagmulti("F", "", "floating point double precision (64-bit) units");
opts.optmulti("t", "format", "select output format or formats", "TYPE");
opts.optflag(
"v",
"output-duplicates",
"do not use * to mark line suppression",
);
opts.optflagopt(
"w",
"width",
"output BYTES bytes per output line. 32 is implied when BYTES is not \
specified.",
"BYTES",
);
opts.optflag("", "help", "display this help and exit.");
opts.optflag("", "version", "output version information and exit.");
opts.optflag(
"",
"traditional",
"compatibility mode with one input, offset and label.",
);
opts
} }
struct OdOptions { struct OdOptions {
@ -182,8 +118,8 @@ struct OdOptions {
} }
impl OdOptions { impl OdOptions {
fn new(matches: getopts::Matches, args: Vec<String>) -> Result<OdOptions, String> { fn new<'a>(matches: ArgMatches<'a>, args: Vec<String>) -> Result<OdOptions, String> {
let byte_order = match matches.opt_str("endian").as_ref().map(String::as_ref) { let byte_order = match matches.value_of(options::ENDIAN) {
None => ByteOrder::Native, None => ByteOrder::Native,
Some("little") => ByteOrder::Little, Some("little") => ByteOrder::Little,
Some("big") => ByteOrder::Big, Some("big") => ByteOrder::Big,
@ -192,7 +128,7 @@ impl OdOptions {
} }
}; };
let mut skip_bytes = match matches.opt_default("skip-bytes", "0") { let mut skip_bytes = match matches.value_of(options::SKIP_BYTES) {
None => 0, None => 0,
Some(s) => match parse_number_of_bytes(&s) { Some(s) => match parse_number_of_bytes(&s) {
Ok(i) => i, Ok(i) => i,
@ -223,8 +159,9 @@ impl OdOptions {
} }
}; };
let mut line_bytes = match matches.opt_default("w", "32") { let mut line_bytes = match matches.value_of(options::WIDTH) {
None => 16, None => 16,
Some(_) if matches.occurrences_of(options::WIDTH) == 0 => 16,
Some(s) => s.parse::<usize>().unwrap_or(0), Some(s) => s.parse::<usize>().unwrap_or(0),
}; };
let min_bytes = formats.iter().fold(1, |max, next| { let min_bytes = formats.iter().fold(1, |max, next| {
@ -235,9 +172,9 @@ impl OdOptions {
line_bytes = min_bytes; line_bytes = min_bytes;
} }
let output_duplicates = matches.opt_present("v"); let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES);
let read_bytes = match matches.opt_str("read-bytes") { let read_bytes = match matches.value_of(options::READ_BYTES) {
None => None, None => None,
Some(s) => match parse_number_of_bytes(&s) { Some(s) => match parse_number_of_bytes(&s) {
Ok(i) => Some(i), Ok(i) => Some(i),
@ -247,10 +184,10 @@ impl OdOptions {
}, },
}; };
let radix = match matches.opt_str("A") { let radix = match matches.value_of(options::ADDRESS_RADIX) {
None => Radix::Octal, None => Radix::Octal,
Some(s) => { Some(s) => {
let st = s.into_bytes(); let st = s.as_bytes();
if st.len() != 1 { if st.len() != 1 {
return Err("Radix must be one of [d, o, n, x]".to_string()); return Err("Radix must be one of [d, o, n, x]".to_string());
} else { } else {
@ -286,26 +223,244 @@ impl OdOptions {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args.collect_str();
let opts = create_getopts_options(); let clap_opts = clap::App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(USAGE)
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::ADDRESS_RADIX)
.short("A")
.long(options::ADDRESS_RADIX)
.help("Select the base in which file offsets are printed.")
.value_name("RADIX"),
)
.arg(
Arg::with_name(options::SKIP_BYTES)
.short("j")
.long(options::SKIP_BYTES)
.help("Skip bytes input bytes before formatting and writing.")
.value_name("BYTES"),
)
.arg(
Arg::with_name(options::READ_BYTES)
.short("N")
.long(options::READ_BYTES)
.help("limit dump to BYTES input bytes")
.value_name("BYTES"),
)
.arg(
Arg::with_name(options::ENDIAN)
.long(options::ENDIAN)
.help("byte order to use for multi-byte formats")
.possible_values(&["big", "little"])
.value_name("big|little"),
)
.arg(
Arg::with_name(options::STRINGS)
.short("S")
.long(options::STRINGS)
.help(
"output strings of at least BYTES graphic chars. 3 is assumed when \
BYTES is not specified.",
)
.default_value("3")
.value_name("BYTES"),
)
.arg(
Arg::with_name("a")
.short("a")
.help("named characters, ignoring high-order bit")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("b")
.short("b")
.help("octal bytes")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("c")
.short("c")
.help("ASCII characters or backslash escapes")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("d")
.short("d")
.help("unsigned decimal 2-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("D")
.short("D")
.help("unsigned decimal 4-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("o")
.short("o")
.help("octal 2-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("I")
.short("I")
.help("decimal 8-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("L")
.short("L")
.help("decimal 8-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("i")
.short("i")
.help("decimal 4-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("l")
.short("l")
.help("decimal 8-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("x")
.short("x")
.help("hexadecimal 2-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("h")
.short("h")
.help("hexadecimal 2-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("O")
.short("O")
.help("octal 4-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("s")
.short("s")
.help("decimal 2-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("X")
.short("X")
.help("hexadecimal 4-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("H")
.short("H")
.help("hexadecimal 4-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("e")
.short("e")
.help("floating point double precision (64-bit) units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("f")
.short("f")
.help("floating point double precision (32-bit) units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("F")
.short("F")
.help("floating point double precision (64-bit) units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name(options::FORMAT)
.short("t")
.long(options::FORMAT)
.help("select output format or formats")
.multiple(true)
.value_name("TYPE"),
)
.arg(
Arg::with_name(options::OUTPUT_DUPLICATES)
.short("v")
.long(options::OUTPUT_DUPLICATES)
.help("do not use * to mark line suppression")
.takes_value(false)
.possible_values(&["big", "little"]),
)
.arg(
Arg::with_name(options::WIDTH)
.short("w")
.long(options::WIDTH)
.help(
"output BYTES bytes per output line. 32 is implied when BYTES is not \
specified.",
)
.default_value("32")
.value_name("BYTES"),
)
.arg(
Arg::with_name(options::VERSION)
.long(options::VERSION)
.help("output version information and exit.")
.takes_value(false),
)
.arg(
Arg::with_name(options::TRADITIONAL)
.long(options::TRADITIONAL)
.help("compatibility mode with one input, offset and label.")
.takes_value(false),
)
.arg(
Arg::with_name(options::FILENAME)
.hidden(true)
.multiple(true),
)
.settings(&[
AppSettings::TrailingVarArg,
AppSettings::DontDelimitTrailingValues,
AppSettings::DisableVersion,
AppSettings::DeriveDisplayOrder,
]);
let matches = match opts.parse(&args[1..]) { let clap_matches = clap_opts
Ok(m) => m, .clone() // Clone to reuse clap_otps to print help
Err(f) => { .get_matches_from(args.clone());
show_usage_error!("{}", f);
return 1;
}
};
if matches.opt_present("help") { if clap_matches.is_present(options::VERSION) {
println!("{}", opts.usage(&USAGE));
return 0;
}
if matches.opt_present("version") {
println!("{} {}", executable!(), VERSION); println!("{} {}", executable!(), VERSION);
return 0; return 0;
} }
let od_options = match OdOptions::new(matches, args) { let od_options = match OdOptions::new(clap_matches, args) {
Err(s) => { Err(s) => {
show_usage_error!("{}", s); show_usage_error!("{}", s);
return 1; return 1;

View file

@ -1,20 +1,24 @@
use getopts::Matches; use super::options;
use clap::ArgMatches;
/// Abstraction for getopts /// Abstraction for getopts
pub trait CommandLineOpts { pub trait CommandLineOpts {
/// returns all command line parameters which do not belong to an option. /// returns all command line parameters which do not belong to an option.
fn inputs(&self) -> Vec<String>; fn inputs(&self) -> Vec<&str>;
/// tests if any of the specified options is present. /// tests if any of the specified options is present.
fn opts_present(&self, _: &[&str]) -> bool; fn opts_present(&self, _: &[&str]) -> bool;
} }
/// Implementation for `getopts` /// Implementation for `getopts`
impl CommandLineOpts for Matches { impl<'a> CommandLineOpts for ArgMatches<'a> {
fn inputs(&self) -> Vec<String> { fn inputs(&self) -> Vec<&str> {
self.free.clone() self.values_of(options::FILENAME)
.map(|values| values.collect())
.unwrap_or_default()
} }
fn opts_present(&self, opts: &[&str]) -> bool { fn opts_present(&self, opts: &[&str]) -> bool {
self.opts_present(&opts.iter().map(|s| (*s).to_string()).collect::<Vec<_>>()) opts.iter().any(|opt| self.is_present(opt))
} }
} }
@ -39,7 +43,7 @@ pub enum CommandLineInputs {
/// '-' is used as filename if stdin is meant. This is also returned if /// '-' is used as filename if stdin is meant. This is also returned if
/// there is no input, as stdin is the default input. /// there is no input, as stdin is the default input.
pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs, String> { pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs, String> {
let mut input_strings: Vec<String> = matches.inputs(); let mut input_strings = matches.inputs();
if matches.opts_present(&["traditional"]) { if matches.opts_present(&["traditional"]) {
return parse_inputs_traditional(input_strings); return parse_inputs_traditional(input_strings);
@ -59,7 +63,7 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs,
} }
if input_strings.len() == 2 { if input_strings.len() == 2 {
return Ok(CommandLineInputs::FileAndOffset(( return Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone(), input_strings[0].clone().to_owned(),
n, n,
None, None,
))); )));
@ -69,23 +73,27 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs,
} }
if input_strings.is_empty() { if input_strings.is_empty() {
input_strings.push("-".to_string()); input_strings.push("-");
} }
Ok(CommandLineInputs::FileNames(input_strings)) Ok(CommandLineInputs::FileNames(
input_strings.iter().map(|s| s.to_string()).collect(),
))
} }
/// interprets inputs when --traditional is on the command line /// interprets inputs when --traditional is on the command line
/// ///
/// normally returns CommandLineInputs::FileAndOffset, but if no offset is found, /// normally returns CommandLineInputs::FileAndOffset, but if no offset is found,
/// it returns CommandLineInputs::FileNames (also to differentiate from the offset == 0) /// it returns CommandLineInputs::FileNames (also to differentiate from the offset == 0)
pub fn parse_inputs_traditional(input_strings: Vec<String>) -> Result<CommandLineInputs, String> { pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineInputs, String> {
match input_strings.len() { match input_strings.len() {
0 => Ok(CommandLineInputs::FileNames(vec!["-".to_string()])), 0 => Ok(CommandLineInputs::FileNames(vec!["-".to_string()])),
1 => { 1 => {
let offset0 = parse_offset_operand(&input_strings[0]); let offset0 = parse_offset_operand(&input_strings[0]);
Ok(match offset0 { Ok(match offset0 {
Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)), Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)),
_ => CommandLineInputs::FileNames(input_strings), _ => CommandLineInputs::FileNames(
input_strings.iter().map(|s| s.to_string()).collect(),
),
}) })
} }
2 => { 2 => {
@ -98,7 +106,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<String>) -> Result<CommandLin
Some(m), Some(m),
))), ))),
(_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( (_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone(), input_strings[0].clone().to_owned(),
m, m,
None, None,
))), ))),
@ -110,7 +118,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<String>) -> Result<CommandLin
let label = parse_offset_operand(&input_strings[2]); let label = parse_offset_operand(&input_strings[2]);
match (offset, label) { match (offset, label) {
(Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone(), input_strings[0].clone().to_owned(),
n, n,
Some(m), Some(m),
))), ))),
@ -178,8 +186,8 @@ mod tests {
} }
impl<'a> CommandLineOpts for MockOptions<'a> { impl<'a> CommandLineOpts for MockOptions<'a> {
fn inputs(&self) -> Vec<String> { fn inputs(&self) -> Vec<&str> {
self.inputs.clone() self.inputs.iter().map(|s| s.as_str()).collect()
} }
fn opts_present(&self, opts: &[&str]) -> bool { fn opts_present(&self, opts: &[&str]) -> bool {
for expected in opts.iter() { for expected in opts.iter() {

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/pathchk.rs" path = "src/pathchk.rs"
[dependencies] [dependencies]
getopts = "0.2.18" clap = "2.33"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -12,7 +12,7 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use getopts::Options; use clap::{App, Arg};
use std::fs; use std::fs;
use std::io::{ErrorKind, Write}; use std::io::{ErrorKind, Write};
@ -22,107 +22,94 @@ enum Mode {
Basic, // check basic compatibility with POSIX Basic, // check basic compatibility with POSIX
Extra, // check for leading dashes and empty names Extra, // check for leading dashes and empty names
Both, // a combination of `Basic` and `Extra` Both, // a combination of `Basic` and `Extra`
Help, // show help
Version, // show version information
} }
static NAME: &str = "pathchk"; static NAME: &str = "pathchk";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Check whether file names are valid or portable";
mod options {
pub const POSIX: &str = "posix";
pub const POSIX_SPECIAL: &str = "posix-special";
pub const PORTABILITY: &str = "portability";
pub const PATH: &str = "path";
}
// a few global constants as used in the GNU implementation // a few global constants as used in the GNU implementation
const POSIX_PATH_MAX: usize = 256; const POSIX_PATH_MAX: usize = 256;
const POSIX_NAME_MAX: usize = 14; const POSIX_NAME_MAX: usize = 14;
pub fn uumain(args: impl uucore::Args) -> i32 { fn get_usage() -> String {
let args = args.collect_str(); format!("{0} [OPTION]... NAME...", executable!())
}
// add options pub fn uumain(args: impl uucore::Args) -> i32 {
let mut opts = Options::new(); let usage = get_usage();
opts.optflag("p", "posix", "check for (most) POSIX systems");
opts.optflag( let matches = App::new(executable!())
"P", .version(VERSION)
"posix-special", .about(ABOUT)
"check for empty names and leading \"-\"", .usage(&usage[..])
); .arg(
opts.optflag( Arg::with_name(options::POSIX)
"", .short("p")
"portability", .help("check for most POSIX systems"),
"check for all POSIX systems (equivalent to -p -P)", )
); .arg(
opts.optflag("h", "help", "display this help text and exit"); Arg::with_name(options::POSIX_SPECIAL)
opts.optflag("V", "version", "output version information and exit"); .short("P")
let matches = match opts.parse(&args[1..]) { .help(r#"check for empty names and leading "-""#),
Ok(m) => m, )
Err(e) => crash!(1, "{}", e), .arg(
}; Arg::with_name(options::PORTABILITY)
.long(options::PORTABILITY)
.help("check for all POSIX systems (equivalent to -p -P)"),
)
.arg(Arg::with_name(options::PATH).hidden(true).multiple(true))
.get_matches_from(args);
// set working mode // set working mode
let mode = if matches.opt_present("version") { let is_posix = matches.values_of(options::POSIX).is_some();
Mode::Version let is_posix_special = matches.values_of(options::POSIX_SPECIAL).is_some();
} else if matches.opt_present("help") { let is_portability = matches.values_of(options::PORTABILITY).is_some();
Mode::Help
} else if (matches.opt_present("posix") && matches.opt_present("posix-special")) let mode = if (is_posix && is_posix_special) || is_portability {
|| matches.opt_present("portability")
{
Mode::Both Mode::Both
} else if matches.opt_present("posix") { } else if is_posix {
Mode::Basic Mode::Basic
} else if matches.opt_present("posix-special") { } else if is_posix_special {
Mode::Extra Mode::Extra
} else { } else {
Mode::Default Mode::Default
}; };
// take necessary actions // take necessary actions
match mode { let paths = matches.values_of(options::PATH);
Mode::Help => { let mut res = if paths.is_none() {
help(opts); show_error!("missing operand\nTry {} --help for more information", NAME);
0 false
} } else {
Mode::Version => { true
version(); };
0
} if res {
_ => { // free strings are path operands
let mut res = if matches.free.is_empty() { // FIXME: TCS, seems inefficient and overly verbose (?)
show_error!("missing operand\nTry {} --help for more information", NAME); for p in paths.unwrap() {
false let mut path = Vec::new();
} else { for path_segment in p.split('/') {
true path.push(path_segment.to_string());
};
// free strings are path operands
// FIXME: TCS, seems inefficient and overly verbose (?)
for p in matches.free {
let mut path = Vec::new();
for path_segment in p.split('/') {
path.push(path_segment.to_string());
}
res &= check_path(&mode, &path);
}
// determine error code
if res {
0
} else {
1
} }
res &= check_path(&mode, &path);
} }
} }
}
// print help // determine error code
fn help(opts: Options) { if res {
let msg = format!( 0
"Usage: {} [OPTION]... NAME...\n\n\ } else {
Diagnose invalid or unportable file names.", 1
NAME }
);
print!("{}", opts.usage(&msg));
}
// print version information
fn version() {
println!("{} {}", NAME, VERSION);
} }
// check a path, given as a slice of it's components and an operating mode // check a path, given as a slice of it's components and an operating mode

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/ptx.rs" path = "src/ptx.rs"
[dependencies] [dependencies]
clap = "2.33"
aho-corasick = "0.7.3" aho-corasick = "0.7.3"
getopts = "0.2.18" getopts = "0.2.18"
libc = "0.2.42" libc = "0.2.42"

View file

@ -10,7 +10,7 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use getopts::{Matches, Options}; use clap::{App, Arg};
use regex::Regex; use regex::Regex;
use std::cmp; use std::cmp;
use std::collections::{BTreeSet, HashMap, HashSet}; use std::collections::{BTreeSet, HashMap, HashSet};
@ -20,6 +20,12 @@ use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
static NAME: &str = "ptx"; static NAME: &str = "ptx";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static BRIEF: &str = "Usage: ptx [OPTION]... [INPUT]... (without -G) or: \
ptx -G [OPTION]... [INPUT [OUTPUT]] \n Output a permuted index, \
including context, of the words in the input files. \n\n Mandatory \
arguments to long options are mandatory for short options too.\n
With no FILE, or when FILE is -, read standard input. \
Default is '-F /'.";
#[derive(Debug)] #[derive(Debug)]
enum OutFormat { enum OutFormat {
@ -61,8 +67,11 @@ impl Default for Config {
} }
} }
fn read_word_filter_file(matches: &Matches, option: &str) -> HashSet<String> { fn read_word_filter_file(matches: &clap::ArgMatches, option: &str) -> HashSet<String> {
let filename = matches.opt_str(option).expect("parsing options failed!"); let filename = matches
.value_of(option)
.expect("parsing options failed!")
.to_string();
let reader = BufReader::new(crash_if_err!(1, File::open(filename))); let reader = BufReader::new(crash_if_err!(1, File::open(filename)));
let mut words: HashSet<String> = HashSet::new(); let mut words: HashSet<String> = HashSet::new();
for word in reader.lines() { for word in reader.lines() {
@ -81,23 +90,29 @@ struct WordFilter {
} }
impl WordFilter { impl WordFilter {
fn new(matches: &Matches, config: &Config) -> WordFilter { fn new(matches: &clap::ArgMatches, config: &Config) -> WordFilter {
let (o, oset): (bool, HashSet<String>) = if matches.opt_present("o") { let (o, oset): (bool, HashSet<String>) = if matches.is_present(options::ONLY_FILE) {
(true, read_word_filter_file(matches, "o")) (true, read_word_filter_file(matches, options::ONLY_FILE))
} else { } else {
(false, HashSet::new()) (false, HashSet::new())
}; };
let (i, iset): (bool, HashSet<String>) = if matches.opt_present("i") { let (i, iset): (bool, HashSet<String>) = if matches.is_present(options::IGNORE_FILE) {
(true, read_word_filter_file(matches, "i")) (true, read_word_filter_file(matches, options::IGNORE_FILE))
} else { } else {
(false, HashSet::new()) (false, HashSet::new())
}; };
if matches.opt_present("b") { if matches.is_present(options::BREAK_FILE) {
crash!(1, "-b not implemented yet"); crash!(1, "-b not implemented yet");
} }
// Ignore empty string regex from cmd-line-args // Ignore empty string regex from cmd-line-args
let arg_reg: Option<String> = if matches.opt_present("W") { let arg_reg: Option<String> = if matches.is_present(options::WORD_REGEXP) {
matches.opt_str("W").filter(|reg| !reg.is_empty()) match matches.value_of(options::WORD_REGEXP) {
Some(v) => match v.is_empty() {
true => None,
false => Some(v.to_string()),
},
None => None,
}
} else { } else {
None None
}; };
@ -131,55 +146,50 @@ struct WordRef {
filename: String, filename: String,
} }
fn print_version() { fn get_config(matches: &clap::ArgMatches) -> Config {
println!("{} {}", NAME, VERSION);
}
fn print_usage(opts: &Options) {
let brief = "Usage: ptx [OPTION]... [INPUT]... (without -G) or: \
ptx -G [OPTION]... [INPUT [OUTPUT]] \n Output a permuted index, \
including context, of the words in the input files. \n\n Mandatory \
arguments to long options are mandatory for short options too.";
let explanation = "With no FILE, or when FILE is -, read standard input. \
Default is '-F /'.";
println!("{}\n{}", opts.usage(&brief), explanation);
}
fn get_config(matches: &Matches) -> Config {
let mut config: Config = Default::default(); let mut config: Config = Default::default();
let err_msg = "parsing options failed"; let err_msg = "parsing options failed";
if matches.opt_present("G") { if matches.is_present(options::TRADITIONAL) {
config.gnu_ext = false; config.gnu_ext = false;
config.format = OutFormat::Roff; config.format = OutFormat::Roff;
config.context_regex = "[^ \t\n]+".to_owned(); config.context_regex = "[^ \t\n]+".to_owned();
} else { } else {
crash!(1, "GNU extensions not implemented yet"); crash!(1, "GNU extensions not implemented yet");
} }
if matches.opt_present("S") { if matches.is_present(options::SENTENCE_REGEXP) {
crash!(1, "-S not implemented yet"); crash!(1, "-S not implemented yet");
} }
config.auto_ref = matches.opt_present("A"); config.auto_ref = matches.is_present(options::AUTO_REFERENCE);
config.input_ref = matches.opt_present("r"); config.input_ref = matches.is_present(options::REFERENCES);
config.right_ref &= matches.opt_present("R"); config.right_ref &= matches.is_present(options::RIGHT_SIDE_REFS);
config.ignore_case = matches.opt_present("f"); config.ignore_case = matches.is_present(options::IGNORE_CASE);
if matches.opt_present("M") { if matches.is_present(options::MACRO_NAME) {
config.macro_name = matches.opt_str("M").expect(err_msg); config.macro_name = matches
.value_of(options::MACRO_NAME)
.expect(err_msg)
.to_string();
} }
if matches.opt_present("F") { if matches.is_present(options::IGNORE_CASE) {
config.trunc_str = matches.opt_str("F").expect(err_msg); config.trunc_str = matches
.value_of(options::IGNORE_CASE)
.expect(err_msg)
.to_string();
} }
if matches.opt_present("w") { if matches.is_present(options::WIDTH) {
let width_str = matches.opt_str("w").expect(err_msg); let width_str = matches.value_of(options::WIDTH).expect(err_msg).to_string();
config.line_width = crash_if_err!(1, usize::from_str_radix(&width_str, 10)); config.line_width = crash_if_err!(1, usize::from_str_radix(&width_str, 10));
} }
if matches.opt_present("g") { if matches.is_present(options::GAP_SIZE) {
let gap_str = matches.opt_str("g").expect(err_msg); let gap_str = matches
.value_of(options::GAP_SIZE)
.expect(err_msg)
.to_string();
config.gap_size = crash_if_err!(1, usize::from_str_radix(&gap_str, 10)); config.gap_size = crash_if_err!(1, usize::from_str_radix(&gap_str, 10));
} }
if matches.opt_present("O") { if matches.is_present(options::FORMAT_ROFF) {
config.format = OutFormat::Roff; config.format = OutFormat::Roff;
} }
if matches.opt_present("T") { if matches.is_present(options::FORMAT_TEX) {
config.format = OutFormat::Tex; config.format = OutFormat::Tex;
} }
config config
@ -494,102 +504,167 @@ fn write_traditional_output(
} }
} }
mod options {
pub static FILE: &str = "file";
pub static AUTO_REFERENCE: &str = "auto-reference";
pub static TRADITIONAL: &str = "traditional";
pub static FLAG_TRUNCATION: &str = "flag-truncation";
pub static MACRO_NAME: &str = "macro-name";
pub static FORMAT_ROFF: &str = "format=roff";
pub static RIGHT_SIDE_REFS: &str = "right-side-refs";
pub static SENTENCE_REGEXP: &str = "sentence-regexp";
pub static FORMAT_TEX: &str = "format=tex";
pub static WORD_REGEXP: &str = "word-regexp";
pub static BREAK_FILE: &str = "break-file";
pub static IGNORE_CASE: &str = "ignore-case";
pub static GAP_SIZE: &str = "gap-size";
pub static IGNORE_FILE: &str = "ignore-file";
pub static ONLY_FILE: &str = "only-file";
pub static REFERENCES: &str = "references";
pub static WIDTH: &str = "width";
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args.collect_str();
let mut opts = Options::new(); // let mut opts = Options::new();
opts.optflag( let matches = App::new(executable!())
"A", .name(NAME)
"auto-reference", .version(VERSION)
"output automatically generated references", .usage(BRIEF)
); .arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
opts.optflag("G", "traditional", "behave more like System V 'ptx'"); .arg(
opts.optopt( Arg::with_name(options::AUTO_REFERENCE)
"F", .short("A")
"flag-truncation", .long(options::AUTO_REFERENCE)
"use STRING for flagging line truncations", .help("output automatically generated references")
"STRING", .takes_value(false),
); )
opts.optopt( .arg(
"M", Arg::with_name(options::TRADITIONAL)
"macro-name", .short("G")
"macro name to use instead of 'xx'", .long(options::TRADITIONAL)
"STRING", .help("behave more like System V 'ptx'"),
); )
opts.optflag("O", "format=roff", "generate output as roff directives"); .arg(
opts.optflag( Arg::with_name(options::FLAG_TRUNCATION)
"R", .short("F")
"right-side-refs", .long(options::FLAG_TRUNCATION)
"put references at right, not counted in -w", .help("use STRING for flagging line truncations")
); .value_name("STRING")
opts.optopt( .takes_value(true),
"S", )
"sentence-regexp", .arg(
"for end of lines or end of sentences", Arg::with_name(options::MACRO_NAME)
"REGEXP", .short("M")
); .long(options::MACRO_NAME)
opts.optflag("T", "format=tex", "generate output as TeX directives"); .help("macro name to use instead of 'xx'")
opts.optopt( .value_name("STRING")
"W", .takes_value(true),
"word-regexp", )
"use REGEXP to match each keyword", .arg(
"REGEXP", Arg::with_name(options::FORMAT_ROFF)
); .short("O")
opts.optopt( .long(options::FORMAT_ROFF)
"b", .help("generate output as roff directives"),
"break-file", )
"word break characters in this FILE", .arg(
"FILE", Arg::with_name(options::RIGHT_SIDE_REFS)
); .short("R")
opts.optflag( .long(options::RIGHT_SIDE_REFS)
"f", .help("put references at right, not counted in -w")
"ignore-case", .takes_value(false),
"fold lower case to upper case for sorting", )
); .arg(
opts.optopt( Arg::with_name(options::SENTENCE_REGEXP)
"g", .short("S")
"gap-size", .long(options::SENTENCE_REGEXP)
"gap size in columns between output fields", .help("for end of lines or end of sentences")
"NUMBER", .value_name("REGEXP")
); .takes_value(true),
opts.optopt( )
"i", .arg(
"ignore-file", Arg::with_name(options::FORMAT_TEX)
"read ignore word list from FILE", .short("T")
"FILE", .long(options::FORMAT_TEX)
); .help("generate output as TeX directives"),
opts.optopt( )
"o", .arg(
"only-file", Arg::with_name(options::WORD_REGEXP)
"read only word list from this FILE", .short("W")
"FILE", .long(options::WORD_REGEXP)
); .help("use REGEXP to match each keyword")
opts.optflag("r", "references", "first field of each line is a reference"); .value_name("REGEXP")
opts.optopt( .takes_value(true),
"w", )
"width", .arg(
"output width in columns, reference excluded", Arg::with_name(options::BREAK_FILE)
"NUMBER", .short("b")
); .long(options::BREAK_FILE)
opts.optflag("", "help", "display this help and exit"); .help("word break characters in this FILE")
opts.optflag("", "version", "output version information and exit"); .value_name("FILE")
.takes_value(true),
)
.arg(
Arg::with_name(options::IGNORE_CASE)
.short("f")
.long(options::IGNORE_CASE)
.help("fold lower case to upper case for sorting")
.takes_value(false),
)
.arg(
Arg::with_name(options::GAP_SIZE)
.short("g")
.long(options::GAP_SIZE)
.help("gap size in columns between output fields")
.value_name("NUMBER")
.takes_value(true),
)
.arg(
Arg::with_name(options::IGNORE_FILE)
.short("i")
.long(options::IGNORE_FILE)
.help("read ignore word list from FILE")
.value_name("FILE")
.takes_value(true),
)
.arg(
Arg::with_name(options::ONLY_FILE)
.short("o")
.long(options::ONLY_FILE)
.help("read only word list from this FILE")
.value_name("FILE")
.takes_value(true),
)
.arg(
Arg::with_name(options::REFERENCES)
.short("r")
.long(options::REFERENCES)
.help("first field of each line is a reference")
.value_name("FILE")
.takes_value(false),
)
.arg(
Arg::with_name(options::WIDTH)
.short("w")
.long(options::WIDTH)
.help("output width in columns, reference excluded")
.value_name("NUMBER")
.takes_value(true),
)
.get_matches_from(args);
let matches = return_if_err!(1, opts.parse(&args[1..])); let input_files: Vec<String> = match &matches.values_of(options::FILE) {
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
None => vec!["-".to_string()],
};
if matches.opt_present("help") {
print_usage(&opts);
return 0;
}
if matches.opt_present("version") {
print_version();
return 0;
}
let config = get_config(&matches); let config = get_config(&matches);
let word_filter = WordFilter::new(&matches, &config); let word_filter = WordFilter::new(&matches, &config);
let file_map = read_input(&matches.free, &config); let file_map = read_input(&input_files, &config);
let word_set = create_word_set(&config, &word_filter, &file_map); let word_set = create_word_set(&config, &word_filter, &file_map);
let output_file = if !config.gnu_ext && matches.free.len() == 2 { let output_file = if !config.gnu_ext && matches.args.len() == 2 {
matches.free[1].clone() matches.value_of(options::FILE).unwrap_or("-").to_string()
} else { } else {
"-".to_owned() "-".to_owned()
}; };

View file

@ -149,7 +149,7 @@ use std::path::Path;
#[cfg(any( #[cfg(any(
target_os = "linux", target_os = "linux",
target_os = "macos", target_vendor = "apple",
target_os = "android", target_os = "android",
target_os = "freebsd" target_os = "freebsd"
))] ))]
@ -165,7 +165,7 @@ use uucore::libc::statvfs as Sstatfs;
#[cfg(any( #[cfg(any(
target_os = "linux", target_os = "linux",
target_os = "macos", target_vendor = "apple",
target_os = "android", target_os = "android",
target_os = "freebsd" target_os = "freebsd"
))] ))]
@ -211,11 +211,11 @@ impl FsMeta for Sstatfs {
fn free_fnodes(&self) -> u64 { fn free_fnodes(&self) -> u64 {
self.f_ffree as u64 self.f_ffree as u64
} }
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] #[cfg(any(target_os = "linux", target_vendor = "apple", target_os = "freebsd"))]
fn fs_type(&self) -> i64 { fn fs_type(&self) -> i64 {
self.f_type as i64 self.f_type as i64
} }
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))] #[cfg(not(any(target_os = "linux", target_vendor = "apple", target_os = "freebsd")))]
fn fs_type(&self) -> i64 { fn fs_type(&self) -> i64 {
// FIXME: statvfs doesn't have an equivalent, so we need to do something else // FIXME: statvfs doesn't have an equivalent, so we need to do something else
unimplemented!() unimplemented!()
@ -225,12 +225,12 @@ impl FsMeta for Sstatfs {
fn iosize(&self) -> u64 { fn iosize(&self) -> u64 {
self.f_frsize as u64 self.f_frsize as u64
} }
#[cfg(any(target_os = "macos", target_os = "freebsd"))] #[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
fn iosize(&self) -> u64 { fn iosize(&self) -> u64 {
self.f_iosize as u64 self.f_iosize as u64
} }
// XXX: dunno if this is right // XXX: dunno if this is right
#[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "linux")))] #[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))]
fn iosize(&self) -> u64 { fn iosize(&self) -> u64 {
self.f_bsize as u64 self.f_bsize as u64
} }
@ -241,13 +241,13 @@ impl FsMeta for Sstatfs {
// //
// Solaris, Irix and POSIX have a system call statvfs(2) that returns a // Solaris, Irix and POSIX have a system call statvfs(2) that returns a
// struct statvfs, containing an unsigned long f_fsid // struct statvfs, containing an unsigned long f_fsid
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "linux"))] #[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux"))]
fn fsid(&self) -> u64 { fn fsid(&self) -> u64 {
let f_fsid: &[u32; 2] = let f_fsid: &[u32; 2] =
unsafe { &*(&self.f_fsid as *const uucore::libc::fsid_t as *const [u32; 2]) }; unsafe { &*(&self.f_fsid as *const uucore::libc::fsid_t as *const [u32; 2]) };
(u64::from(f_fsid[0])) << 32 | u64::from(f_fsid[1]) (u64::from(f_fsid[0])) << 32 | u64::from(f_fsid[1])
} }
#[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "linux")))] #[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))]
fn fsid(&self) -> u64 { fn fsid(&self) -> u64 {
self.f_fsid as u64 self.f_fsid as u64
} }
@ -256,7 +256,7 @@ impl FsMeta for Sstatfs {
fn namelen(&self) -> u64 { fn namelen(&self) -> u64 {
self.f_namelen as u64 self.f_namelen as u64
} }
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
fn namelen(&self) -> u64 { fn namelen(&self) -> u64 {
1024 1024
} }
@ -265,7 +265,7 @@ impl FsMeta for Sstatfs {
self.f_namemax as u64 self.f_namemax as u64
} }
// XXX: should everything just use statvfs? // XXX: should everything just use statvfs?
#[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "linux")))] #[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))]
fn namelen(&self) -> u64 { fn namelen(&self) -> u64 {
self.f_namemax as u64 self.f_namemax as u64
} }

View file

@ -4,12 +4,12 @@ use std::env;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows")))] #[cfg(not(any(target_vendor = "apple", target_os = "windows")))]
mod platform { mod platform {
pub const DYLIB_EXT: &str = ".so"; pub const DYLIB_EXT: &str = ".so";
} }
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(any(target_vendor = "apple"))]
mod platform { mod platform {
pub const DYLIB_EXT: &str = ".dylib"; pub const DYLIB_EXT: &str = ".dylib";
} }

View file

@ -57,7 +57,7 @@ fn preload_strings() -> (&'static str, &'static str) {
("LD_PRELOAD", "so") ("LD_PRELOAD", "so")
} }
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
fn preload_strings() -> (&'static str, &'static str) { fn preload_strings() -> (&'static str, &'static str) {
("DYLD_LIBRARY_PATH", "dylib") ("DYLD_LIBRARY_PATH", "dylib")
} }
@ -67,7 +67,7 @@ fn preload_strings() -> (&'static str, &'static str) {
target_os = "freebsd", target_os = "freebsd",
target_os = "netbsd", target_os = "netbsd",
target_os = "dragonflybsd", target_os = "dragonflybsd",
target_os = "macos" target_vendor = "apple"
)))] )))]
fn preload_strings() -> (&'static str, &'static str) { fn preload_strings() -> (&'static str, &'static str) {
crash!(1, "Command not supported for this operating system!") crash!(1, "Command not supported for this operating system!")

View file

@ -80,6 +80,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("c") .short("c")
.long(options::BYTES) .long(options::BYTES)
.takes_value(true) .takes_value(true)
.allow_hyphen_values(true)
.help("Number of bytes to print"), .help("Number of bytes to print"),
) )
.arg( .arg(
@ -93,6 +94,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("n") .short("n")
.long(options::LINES) .long(options::LINES)
.takes_value(true) .takes_value(true)
.allow_hyphen_values(true)
.help("Number of lines to print"), .help("Number of lines to print"),
) )
.arg( .arg(
@ -343,9 +345,9 @@ pub fn parse_size(mut size_slice: &str) -> Result<u64, ParseSizeErr> {
// sole B is not a valid suffix // sole B is not a valid suffix
Err(ParseSizeErr::parse_failure(size_slice)) Err(ParseSizeErr::parse_failure(size_slice))
} else { } else {
let value: Option<u64> = size_slice.parse().ok(); let value: Option<i64> = size_slice.parse().ok();
value value
.map(|v| Ok(multiplier * v)) .map(|v| Ok((multiplier as i64 * v.abs()) as u64))
.unwrap_or_else(|| Err(ParseSizeErr::parse_failure(size_slice))) .unwrap_or_else(|| Err(ParseSizeErr::parse_failure(size_slice)))
} }
} }

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/tee.rs" path = "src/tee.rs"
[dependencies] [dependencies]
getopts = "0.2.18" clap = "2.33.3"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["libc"] } uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["libc"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -5,6 +5,10 @@
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -12,80 +16,61 @@ use std::path::{Path, PathBuf};
#[cfg(unix)] #[cfg(unix)]
use uucore::libc; use uucore::libc;
static NAME: &str = "tee";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Copy standard input to each FILE, and also to standard output.";
pub fn uumain(args: impl uucore::Args) -> i32 { mod options {
let args = args.collect_str(); pub const APPEND: &str = "append";
pub const IGNORE_INTERRUPTS: &str = "ignore-interrupts";
match options(&args).and_then(exec) { pub const FILE: &str = "file";
Ok(_) => 0,
Err(_) => 1,
}
} }
#[allow(dead_code)] #[allow(dead_code)]
struct Options { struct Options {
program: String,
append: bool, append: bool,
ignore_interrupts: bool, ignore_interrupts: bool,
print_and_exit: Option<String>,
files: Vec<String>, files: Vec<String>,
} }
fn options(args: &[String]) -> Result<Options> { fn get_usage() -> String {
let mut opts = getopts::Options::new(); format!("{0} [OPTION]... [FILE]...", executable!())
opts.optflag("a", "append", "append to the given FILEs, do not overwrite");
opts.optflag(
"i",
"ignore-interrupts",
"ignore interrupt signals (ignored on non-Unix platforms)",
);
opts.optflag("h", "help", "display this help and exit");
opts.optflag("V", "version", "output version information and exit");
opts.parse(&args[1..])
.map_err(|e| Error::new(ErrorKind::Other, format!("{}", e)))
.map(|m| {
let version = format!("{} {}", NAME, VERSION);
let arguments = "[OPTION]... [FILE]...";
let brief = "Copy standard input to each FILE, and also to standard output.";
let comment = "If a FILE is -, it refers to a file named - .";
let help = format!(
"{}\n\nUsage:\n {} {}\n\n{}\n{}",
version,
NAME,
arguments,
opts.usage(brief),
comment
);
let names: Vec<String> = m.free.clone().into_iter().collect();
let to_print = if m.opt_present("help") {
Some(help)
} else if m.opt_present("version") {
Some(version)
} else {
None
};
Options {
program: NAME.to_owned(),
append: m.opt_present("append"),
ignore_interrupts: m.opt_present("ignore-interrupts"),
print_and_exit: to_print,
files: names,
}
})
.map_err(|message| warn(format!("{}", message).as_ref()))
} }
fn exec(options: Options) -> Result<()> { pub fn uumain(args: impl uucore::Args) -> i32 {
match options.print_and_exit { let usage = get_usage();
Some(text) => {
println!("{}", text); let matches = App::new(executable!())
Ok(()) .version(VERSION)
} .about(ABOUT)
None => tee(options), .usage(&usage[..])
.after_help("If a FILE is -, it refers to a file named - .")
.arg(
Arg::with_name(options::APPEND)
.long(options::APPEND)
.short("a")
.help("append to the given FILEs, do not overwrite"),
)
.arg(
Arg::with_name(options::IGNORE_INTERRUPTS)
.long(options::IGNORE_INTERRUPTS)
.short("i")
.help("ignore interrupt signals (ignored on non-Unix platforms)"),
)
.arg(Arg::with_name(options::FILE).multiple(true))
.get_matches_from(args);
let options = Options {
append: matches.is_present(options::APPEND),
ignore_interrupts: matches.is_present(options::IGNORE_INTERRUPTS),
files: matches
.values_of(options::FILE)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default(),
};
match tee(options) {
Ok(_) => 0,
Err(_) => 1,
} }
} }
@ -173,7 +158,7 @@ impl Write for NamedWriter {
match self.inner.write(buf) { match self.inner.write(buf) {
Err(f) => { Err(f) => {
self.inner = Box::new(sink()) as Box<dyn Write>; self.inner = Box::new(sink()) as Box<dyn Write>;
warn(format!("{}: {}", self.path.display(), f.to_string()).as_ref()); show_warning!("{}: {}", self.path.display(), f.to_string());
Err(f) Err(f)
} }
okay => okay, okay => okay,
@ -184,7 +169,7 @@ impl Write for NamedWriter {
match self.inner.flush() { match self.inner.flush() {
Err(f) => { Err(f) => {
self.inner = Box::new(sink()) as Box<dyn Write>; self.inner = Box::new(sink()) as Box<dyn Write>;
warn(format!("{}: {}", self.path.display(), f.to_string()).as_ref()); show_warning!("{}: {}", self.path.display(), f.to_string());
Err(f) Err(f)
} }
okay => okay, okay => okay,
@ -200,15 +185,10 @@ impl Read for NamedReader {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
match self.inner.read(buf) { match self.inner.read(buf) {
Err(f) => { Err(f) => {
warn(format!("{}: {}", Path::new("stdin").display(), f.to_string()).as_ref()); show_warning!("{}: {}", Path::new("stdin").display(), f.to_string());
Err(f) Err(f)
} }
okay => okay, okay => okay,
} }
} }
} }
fn warn(message: &str) -> Error {
eprintln!("{}: {}", NAME, message);
Error::new(ErrorKind::Other, format!("{}: {}", NAME, message))
}

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/tsort.rs" path = "src/tsort.rs"
[dependencies] [dependencies]
getopts = "0.2.18" clap= "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -9,49 +9,35 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{App, Arg};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fs::File; use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read}; use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path; use std::path::Path;
static NAME: &str = "tsort";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "Topological sort the strings in FILE.
Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline).
If FILE is not passed in, stdin is used instead.";
static USAGE: &str = "tsort [OPTIONS] FILE";
mod options {
pub const FILE: &str = "file";
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args.collect_str();
let mut opts = getopts::Options::new(); let matches = App::new(executable!())
.version(VERSION)
.usage(USAGE)
.about(SUMMARY)
.arg(Arg::with_name(options::FILE).hidden(true))
.get_matches_from(args);
opts.optflag("h", "help", "display this help and exit"); let input = match matches.value_of(options::FILE) {
opts.optflag("V", "version", "output version information and exit"); Some(v) => v,
None => "-",
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => crash!(1, "{}", f),
};
if matches.opt_present("h") {
println!("{} {}", NAME, VERSION);
println!();
println!("Usage:");
println!(" {} [OPTIONS] FILE", NAME);
println!();
println!("{}", opts.usage("Topological sort the strings in FILE. Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). If FILE is not passed in, stdin is used instead."));
return 0;
}
if matches.opt_present("V") {
println!("{} {}", NAME, VERSION);
return 0;
}
let files = matches.free.clone();
let input = if files.len() > 1 {
crash!(1, "{}, extra operand '{}'", NAME, matches.free[1]);
} else if files.is_empty() {
"-".to_owned()
} else {
files[0].clone()
}; };
let mut stdin_buf; let mut stdin_buf;

View file

@ -39,7 +39,7 @@ const HOST_OS: &str = "Windows NT";
const HOST_OS: &str = "FreeBSD"; const HOST_OS: &str = "FreeBSD";
#[cfg(target_os = "openbsd")] #[cfg(target_os = "openbsd")]
const HOST_OS: &str = "OpenBSD"; const HOST_OS: &str = "OpenBSD";
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
const HOST_OS: &str = "Darwin"; const HOST_OS: &str = "Darwin";
#[cfg(target_os = "fuchsia")] #[cfg(target_os = "fuchsia")]
const HOST_OS: &str = "Fuchsia"; const HOST_OS: &str = "Fuchsia";

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/unexpand.rs" path = "src/unexpand.rs"
[dependencies] [dependencies]
getopts = "0.2.18" clap = "2.33"
unicode-width = "0.1.5" unicode-width = "0.1.5"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -11,7 +11,7 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{App, Arg};
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdout, Write}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdout, Write};
use std::str::from_utf8; use std::str::from_utf8;
@ -19,6 +19,9 @@ use unicode_width::UnicodeWidthChar;
static NAME: &str = "unexpand"; static NAME: &str = "unexpand";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static USAGE: &str = "unexpand [OPTION]... [FILE]...";
static SUMMARY: &str = "Convert blanks in each FILE to tabs, writing to standard output.\n
With no FILE, or when FILE is -, read standard input.";
const DEFAULT_TABSTOP: usize = 8; const DEFAULT_TABSTOP: usize = 8;
@ -46,6 +49,14 @@ fn tabstops_parse(s: String) -> Vec<usize> {
nums nums
} }
mod options {
pub const FILE: &str = "file";
pub const ALL: &str = "all";
pub const FIRST_ONLY: &str = "first-only";
pub const TABS: &str = "tabs";
pub const NO_UTF8: &str = "no-utf8";
}
struct Options { struct Options {
files: Vec<String>, files: Vec<String>,
tabstops: Vec<usize>, tabstops: Vec<usize>,
@ -54,20 +65,19 @@ struct Options {
} }
impl Options { impl Options {
fn new(matches: getopts::Matches) -> Options { fn new(matches: clap::ArgMatches) -> Options {
let tabstops = match matches.opt_str("t") { let tabstops = match matches.value_of(options::TABS) {
None => vec![DEFAULT_TABSTOP], None => vec![DEFAULT_TABSTOP],
Some(s) => tabstops_parse(s), Some(s) => tabstops_parse(s.to_string()),
}; };
let aflag = (matches.opt_present("all") || matches.opt_present("tabs")) let aflag = (matches.is_present(options::ALL) || matches.is_present(options::TABS))
&& !matches.opt_present("first-only"); && !matches.is_present(options::FIRST_ONLY);
let uflag = !matches.opt_present("U"); let uflag = !matches.is_present(options::NO_UTF8);
let files = if matches.free.is_empty() { let files = match matches.value_of(options::FILE) {
vec!["-".to_owned()] Some(v) => vec![v.to_string()],
} else { None => vec!["-".to_owned()],
matches.free
}; };
Options { Options {
@ -82,60 +92,39 @@ impl Options {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args.collect_str();
let mut opts = getopts::Options::new(); let matches = App::new(executable!())
.name(NAME)
opts.optflag( .version(VERSION)
"a", .usage(USAGE)
"all", .about(SUMMARY)
"convert all blanks, instead of just initial blanks", .arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
); .arg(
opts.optflag( Arg::with_name(options::ALL)
"", .short("a")
"first-only", .long(options::ALL)
"convert only leading sequences of blanks (overrides -a)", .help("convert all blanks, instead of just initial blanks")
); .takes_value(false),
opts.optopt( )
"t", .arg(
"tabs", Arg::with_name(options::FIRST_ONLY)
"have tabs N characters apart instead of 8 (enables -a)", .long(options::FIRST_ONLY)
"N", .help("convert only leading sequences of blanks (overrides -a)")
); .takes_value(false),
opts.optopt( )
"t", .arg(
"tabs", Arg::with_name(options::TABS)
"use comma separated LIST of tab positions (enables -a)", .short("t")
"LIST", .long(options::TABS)
); .long_help("use comma separated LIST of tab positions or have tabs N characters apart instead of 8 (enables -a)")
opts.optflag( .takes_value(true)
"U", )
"no-utf8", .arg(
"interpret input file as 8-bit ASCII rather than UTF-8", Arg::with_name(options::NO_UTF8)
); .short("U")
opts.optflag("h", "help", "display this help and exit"); .long(options::NO_UTF8)
opts.optflag("V", "version", "output version information and exit"); .takes_value(false)
.help("interpret input file as 8-bit ASCII rather than UTF-8"))
let matches = match opts.parse(&args[1..]) { .get_matches_from(args);
Ok(m) => m,
Err(f) => crash!(1, "{}", f),
};
if matches.opt_present("help") {
println!("{} {}\n", NAME, VERSION);
println!("Usage: {} [OPTION]... [FILE]...\n", NAME);
println!(
"{}",
opts.usage(
"Convert blanks in each FILE to tabs, writing to standard output.\n\
With no FILE, or when FILE is -, read standard input."
)
);
return 0;
}
if matches.opt_present("V") {
println!("{} {}", NAME, VERSION);
return 0;
}
unexpand(Options::new(matches)); unexpand(Options::new(matches));

View file

@ -60,12 +60,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
"count", "count",
"all login names and number of users logged on", "all login names and number of users logged on",
); );
#[cfg(any( #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))]
target_os = "macos",
target_os = "ios",
target_os = "linux",
target_os = "android"
))]
opts.optflag("r", "runlevel", "print current runlevel"); opts.optflag("r", "runlevel", "print current runlevel");
opts.optflag("s", "short", "print only name, line, and time (default)"); opts.optflag("s", "short", "print only name, line, and time (default)");
opts.optflag("t", "time", "print last system clock change"); opts.optflag("t", "time", "print last system clock change");
@ -305,12 +300,7 @@ impl Who {
#[allow(unused_assignments)] #[allow(unused_assignments)]
let mut res = false; let mut res = false;
#[cfg(any( #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))]
target_os = "macos",
target_os = "ios",
target_os = "linux",
target_os = "android"
))]
{ {
res = record == utmpx::RUN_LVL; res = record == utmpx::RUN_LVL;
} }

View file

@ -34,7 +34,7 @@
//! assert!(entries::Group::locate(root_group).is_ok()); //! assert!(entries::Group::locate(root_group).is_ok());
//! ``` //! ```
#[cfg(any(target_os = "freebsd", target_os = "macos"))] #[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
use libc::time_t; use libc::time_t;
use libc::{c_char, c_int, gid_t, uid_t}; use libc::{c_char, c_int, gid_t, uid_t};
use libc::{getgrgid, getgrnam, getgroups, getpwnam, getpwuid, group, passwd}; use libc::{getgrgid, getgrnam, getgroups, getpwnam, getpwuid, group, passwd};
@ -119,19 +119,19 @@ impl Passwd {
} }
/// AKA passwd.pw_class /// AKA passwd.pw_class
#[cfg(any(target_os = "freebsd", target_os = "macos"))] #[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
pub fn user_access_class(&self) -> Cow<str> { pub fn user_access_class(&self) -> Cow<str> {
cstr2cow!(self.inner.pw_class) cstr2cow!(self.inner.pw_class)
} }
/// AKA passwd.pw_change /// AKA passwd.pw_change
#[cfg(any(target_os = "freebsd", target_os = "macos"))] #[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
pub fn passwd_change_time(&self) -> time_t { pub fn passwd_change_time(&self) -> time_t {
self.inner.pw_change self.inner.pw_change
} }
/// AKA passwd.pw_expire /// AKA passwd.pw_expire
#[cfg(any(target_os = "freebsd", target_os = "macos"))] #[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
pub fn expiration(&self) -> time_t { pub fn expiration(&self) -> time_t {
self.inner.pw_expire self.inner.pw_expire
} }

View file

@ -197,7 +197,7 @@ No Name Default Action Description
*/ */
#[cfg(any(target_os = "macos", target_os = "freebsd"))] #[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
pub static ALL_SIGNALS: [Signal<'static>; 31] = [ pub static ALL_SIGNALS: [Signal<'static>; 31] = [
Signal { Signal {
name: "HUP", name: "HUP",

View file

@ -47,7 +47,7 @@ use libc::utmpx;
pub use libc::endutxent; pub use libc::endutxent;
pub use libc::getutxent; pub use libc::getutxent;
pub use libc::setutxent; pub use libc::setutxent;
#[cfg(any(target_os = "macos", target_os = "linux"))] #[cfg(any(target_vendor = "apple", target_os = "linux"))]
pub use libc::utmpxname; pub use libc::utmpxname;
#[cfg(target_os = "freebsd")] #[cfg(target_os = "freebsd")]
pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int { pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int {
@ -85,7 +85,7 @@ mod ut {
pub use libc::USER_PROCESS; pub use libc::USER_PROCESS;
} }
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
mod ut { mod ut {
pub static DEFAULT_FILE: &str = "/var/run/utmpx"; pub static DEFAULT_FILE: &str = "/var/run/utmpx";

View file

@ -5,6 +5,9 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
/// Deduce the name of the binary from the current source code filename.
///
/// e.g.: `src/uu/cp/src/cp.rs` -> `cp`
#[macro_export] #[macro_export]
macro_rules! executable( macro_rules! executable(
() => ({ () => ({
@ -18,6 +21,7 @@ macro_rules! executable(
}) })
); );
/// Show an error to stderr in a silimar style to GNU coreutils.
#[macro_export] #[macro_export]
macro_rules! show_error( macro_rules! show_error(
($($args:tt)+) => ({ ($($args:tt)+) => ({
@ -26,6 +30,7 @@ macro_rules! show_error(
}) })
); );
/// Show a warning to stderr in a silimar style to GNU coreutils.
#[macro_export] #[macro_export]
macro_rules! show_warning( macro_rules! show_warning(
($($args:tt)+) => ({ ($($args:tt)+) => ({
@ -34,6 +39,7 @@ macro_rules! show_warning(
}) })
); );
/// Show an info message to stderr in a silimar style to GNU coreutils.
#[macro_export] #[macro_export]
macro_rules! show_info( macro_rules! show_info(
($($args:tt)+) => ({ ($($args:tt)+) => ({
@ -42,6 +48,7 @@ macro_rules! show_info(
}) })
); );
/// Show a bad inocation help message in a similar style to GNU coreutils.
#[macro_export] #[macro_export]
macro_rules! show_usage_error( macro_rules! show_usage_error(
($($args:tt)+) => ({ ($($args:tt)+) => ({
@ -51,6 +58,7 @@ macro_rules! show_usage_error(
}) })
); );
/// Display the provided error message, then `exit()` with the provided exit code
#[macro_export] #[macro_export]
macro_rules! crash( macro_rules! crash(
($exit_code:expr, $($args:tt)+) => ({ ($exit_code:expr, $($args:tt)+) => ({
@ -59,6 +67,7 @@ macro_rules! crash(
}) })
); );
/// Calls `exit()` with the provided exit code.
#[macro_export] #[macro_export]
macro_rules! exit( macro_rules! exit(
($exit_code:expr) => ({ ($exit_code:expr) => ({
@ -66,6 +75,8 @@ macro_rules! exit(
}) })
); );
/// Unwraps the Result. Instead of panicking, it exists the program with the
/// provided exit code.
#[macro_export] #[macro_export]
macro_rules! crash_if_err( macro_rules! crash_if_err(
($exit_code:expr, $exp:expr) => ( ($exit_code:expr, $exp:expr) => (
@ -76,6 +87,9 @@ macro_rules! crash_if_err(
) )
); );
/// Unwraps the Result. Instead of panicking, it shows the error and then
/// returns from the function with the provided exit code.
/// Assumes the current function returns an i32 value.
#[macro_export] #[macro_export]
macro_rules! return_if_err( macro_rules! return_if_err(
($exit_code:expr, $exp:expr) => ( ($exit_code:expr, $exp:expr) => (
@ -109,6 +123,8 @@ macro_rules! safe_writeln(
) )
); );
/// Unwraps the Result. Instead of panicking, it exists the program with exit
/// code 1.
#[macro_export] #[macro_export]
macro_rules! safe_unwrap( macro_rules! safe_unwrap(
($exp:expr) => ( ($exp:expr) => (

View file

@ -10,16 +10,16 @@ fn test_output_multi_files_print_all_chars() {
.succeeds() .succeeds()
.stdout_only( .stdout_only(
" 1\tabcde$\n 2\tfghij$\n 3\tklmno$\n 4\tpqrst$\n \ " 1\tabcde$\n 2\tfghij$\n 3\tklmno$\n 4\tpqrst$\n \
5\tuvwxyz$\n 6\t^@^A^B^C^D^E^F^G^H^I$\n \ 5\tuvwxyz$\n 6\t^@^A^B^C^D^E^F^G^H^I$\n \
7\t^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\\^]^^^_ \ 7\t^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\\^]^^^_ \
!\"#$%&\'()*+,-./0123456789:;\ !\"#$%&\'()*+,-./0123456789:;\
<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~^?M-^@M-^AM-^\ <=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~^?M-^@M-^AM-^\
BM-^CM-^DM-^EM-^FM-^GM-^HM-^IM-^JM-^KM-^LM-^MM-^NM-^OM-^PM-^QM-^RM-^SM-^TM-^UM-^V\ BM-^CM-^DM-^EM-^FM-^GM-^HM-^IM-^JM-^KM-^LM-^MM-^NM-^OM-^PM-^QM-^RM-^SM-^TM-^UM-^V\
M-^WM-^XM-^YM-^ZM-^[M-^\\M-^]M-^^M-^_M- \ M-^WM-^XM-^YM-^ZM-^[M-^\\M-^]M-^^M-^_M- \
M-!M-\"M-#M-$M-%M-&M-\'M-(M-)M-*M-+M-,M--M-.M-/M-0M-1M-2M-3M-4M-5M-6M-7M-8M-9M-:\ M-!M-\"M-#M-$M-%M-&M-\'M-(M-)M-*M-+M-,M--M-.M-/M-0M-1M-2M-3M-4M-5M-6M-7M-8M-9M-:\
M-;M-<M-=M->M-?M-@M-AM-BM-CM-DM-EM-FM-GM-HM-IM-JM-KM-LM-MM-NM-OM-PM-QM-RM-SM-TM-U\ M-;M-<M-=M->M-?M-@M-AM-BM-CM-DM-EM-FM-GM-HM-IM-JM-KM-LM-MM-NM-OM-PM-QM-RM-SM-TM-U\
M-VM-WM-XM-YM-ZM-[M-\\M-]M-^M-_M-`M-aM-bM-cM-dM-eM-fM-gM-hM-iM-jM-kM-lM-mM-nM-oM-\ M-VM-WM-XM-YM-ZM-[M-\\M-]M-^M-_M-`M-aM-bM-cM-dM-eM-fM-gM-hM-iM-jM-kM-lM-mM-nM-oM-\
pM-qM-rM-sM-tM-uM-vM-wM-xM-yM-zM-{M-|M-}M-~M-^?", pM-qM-rM-sM-tM-uM-vM-wM-xM-yM-zM-{M-|M-}M-~M-^?",
); );
} }
@ -30,7 +30,7 @@ fn test_numbered_lines_no_trailing_newline() {
.succeeds() .succeeds()
.stdout_only( .stdout_only(
" 1\ttext without a trailing newlineabcde\n 2\tfghij\n \ " 1\ttext without a trailing newlineabcde\n 2\tfghij\n \
3\tklmno\n 4\tpqrst\n 5\tuvwxyz\n", 3\tklmno\n 4\tpqrst\n 5\tuvwxyz\n",
); );
} }

View file

@ -115,7 +115,7 @@ fn test_reference() {
} }
#[test] #[test]
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
fn test_reference() { fn test_reference() {
new_ucmd!() new_ucmd!()
.arg("-v") .arg("-v")

View file

@ -1 +1,98 @@
// ToDO: add tests use crate::common::util::*;
#[test]
fn test_missing_operand() {
let result = new_ucmd!().run();
assert_eq!(
true,
result
.stderr
.starts_with("error: The following required arguments were not provided")
);
assert_eq!(true, result.stderr.contains("<newroot>"));
}
#[test]
fn test_enter_chroot_fails() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("jail");
let result = ucmd.arg("jail").run();
assert_eq!(
true,
result.stderr.starts_with(
"chroot: error: cannot chroot to jail: Operation not permitted (os error 1)"
)
)
}
#[test]
fn test_no_such_directory() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch(&at.plus_as_string("a"));
ucmd.arg("a")
.fails()
.stderr_is("chroot: error: cannot change root directory to `a`: no such directory");
}
#[test]
fn test_invalid_user_spec() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
let result = ucmd.arg("a").arg("--userspec=ARABA:").run();
assert_eq!(
true,
result.stderr.starts_with("chroot: error: invalid userspec")
);
}
#[test]
fn test_preference_of_userspec() {
let scene = TestScenario::new(util_name!());
let result = scene.cmd("whoami").run();
if is_ci() && result.stderr.contains("No such user/group") {
// In the CI, some server are failing to return whoami.
// As seems to be a configuration issue, ignoring it
return;
}
println!("result.stdout {}", result.stdout);
println!("result.stderr = {}", result.stderr);
let username = result.stdout.trim_end();
let ts = TestScenario::new("id");
let result = ts.cmd("id").arg("-g").arg("-n").run();
println!("result.stdout {}", result.stdout);
println!("result.stderr = {}", result.stderr);
if is_ci() && result.stderr.contains("cannot find name for user ID") {
// In the CI, some server are failing to return id.
// As seems to be a configuration issue, ignoring it
return;
}
let group_name = result.stdout.trim_end();
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
let result = ucmd
.arg("a")
.arg("--user")
.arg("fake")
.arg("-G")
.arg("ABC,DEF")
.arg(format!("--userspec={}:{}", username, group_name))
.run();
println!("result.stdout {}", result.stdout);
println!("result.stderr = {}", result.stderr);
}

View file

@ -31,6 +31,12 @@ static TEST_COPY_FROM_FOLDER: &str = "hello_dir_with_file/";
static TEST_COPY_FROM_FOLDER_FILE: &str = "hello_dir_with_file/hello_world.txt"; static TEST_COPY_FROM_FOLDER_FILE: &str = "hello_dir_with_file/hello_world.txt";
static TEST_COPY_TO_FOLDER_NEW: &str = "hello_dir_new"; static TEST_COPY_TO_FOLDER_NEW: &str = "hello_dir_new";
static TEST_COPY_TO_FOLDER_NEW_FILE: &str = "hello_dir_new/hello_world.txt"; static TEST_COPY_TO_FOLDER_NEW_FILE: &str = "hello_dir_new/hello_world.txt";
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
static TEST_MOUNT_COPY_FROM_FOLDER: &str = "dir_with_mount";
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
static TEST_MOUNT_MOUNTPOINT: &str = "mount";
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
static TEST_MOUNT_OTHER_FILESYSTEM_FILE: &str = "mount/DO_NOT_copy_me.txt";
#[test] #[test]
fn test_cp_cp() { fn test_cp_cp() {
@ -1001,3 +1007,70 @@ fn test_cp_target_file_dev_null() {
assert!(at.file_exists(file2)); assert!(at.file_exists(file2));
} }
#[test]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
fn test_cp_one_file_system() {
use crate::common::util::AtPath;
use walkdir::WalkDir;
let scene = TestScenario::new(util_name!());
// Test must be run as root (or with `sudo -E`)
if scene.cmd("whoami").run().stdout != "root\n" {
return;
}
let at = scene.fixtures.clone();
let at_src = AtPath::new(&at.plus(TEST_MOUNT_COPY_FROM_FOLDER));
let at_dst = AtPath::new(&at.plus(TEST_COPY_TO_FOLDER_NEW));
// Prepare the mount
at_src.mkdir(TEST_MOUNT_MOUNTPOINT);
let mountpoint_path = &at_src.plus_as_string(TEST_MOUNT_MOUNTPOINT);
let _r = scene
.cmd("mount")
.arg("-t")
.arg("tmpfs")
.arg("-o")
.arg("size=640k") // ought to be enough
.arg("tmpfs")
.arg(mountpoint_path)
.run();
assert!(_r.code == Some(0), _r.stderr);
at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE);
// Begin testing -x flag
let result = scene
.ucmd()
.arg("-rx")
.arg(TEST_MOUNT_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW)
.run();
// Ditch the mount before the asserts
let _r = scene.cmd("umount").arg(mountpoint_path).run();
assert!(_r.code == Some(0), _r.stderr);
assert!(result.success);
assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE));
// Check if the other files were copied from the source folder hirerarchy
for entry in WalkDir::new(at_src.as_string()) {
let entry = entry.unwrap();
let relative_src = entry
.path()
.strip_prefix(at_src.as_string())
.unwrap()
.to_str()
.unwrap();
let ft = entry.file_type();
match (ft.is_dir(), ft.is_file(), ft.is_symlink()) {
(true, _, _) => assert!(at_dst.dir_exists(relative_src)),
(_, true, _) => assert!(at_dst.file_exists(relative_src)),
(_, _, _) => panic!(),
}
}
}

View file

@ -2,6 +2,8 @@ extern crate regex;
use self::regex::Regex; use self::regex::Regex;
use crate::common::util::*; use crate::common::util::*;
#[cfg(all(unix, not(target_os = "macos")))]
use rust_users::*;
#[test] #[test]
fn test_date_email() { fn test_date_email() {
@ -131,3 +133,92 @@ fn test_date_format_full_day() {
let re = Regex::new(r"\S+ \d{4}-\d{2}-\d{2}").unwrap(); let re = Regex::new(r"\S+ \d{4}-\d{2}-\d{2}").unwrap();
assert!(re.is_match(&result.stdout.trim())); assert!(re.is_match(&result.stdout.trim()));
} }
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn test_date_set_valid() {
if get_effective_uid() == 0 {
let (_, mut ucmd) = at_and_ucmd!();
let result = ucmd
.arg("--set")
.arg("2020-03-12 13:30:00+08:00")
.succeeds();
result.no_stdout().no_stderr();
}
}
#[test]
#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
fn test_date_set_invalid() {
let (_, mut ucmd) = at_and_ucmd!();
let result = ucmd.arg("--set").arg("123abcd").fails();
let result = result.no_stdout();
assert!(result.stderr.starts_with("date: invalid date "));
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn test_date_set_permissions_error() {
if !(get_effective_uid() == 0 || is_wsl()) {
let (_, mut ucmd) = at_and_ucmd!();
let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails();
let result = result.no_stdout();
assert!(result.stderr.starts_with("date: cannot set date: "));
}
}
#[test]
#[cfg(target_os = "macos")]
fn test_date_set_mac_unavailable() {
let (_, mut ucmd) = at_and_ucmd!();
let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails();
let result = result.no_stdout();
assert!(result
.stderr
.starts_with("date: setting the date is not supported by macOS"));
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
/// TODO: expected to fail currently; change to succeeds() when required.
fn test_date_set_valid_2() {
if get_effective_uid() == 0 {
let (_, mut ucmd) = at_and_ucmd!();
let result = ucmd
.arg("--set")
.arg("Sat 20 Mar 2021 14:53:01 AWST")
.fails();
let result = result.no_stdout();
assert!(result.stderr.starts_with("date: invalid date "));
}
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
/// TODO: expected to fail currently; change to succeeds() when required.
fn test_date_set_valid_3() {
if get_effective_uid() == 0 {
let (_, mut ucmd) = at_and_ucmd!();
let result = ucmd
.arg("--set")
.arg("Sat 20 Mar 2021 14:53:01") // Local timezone
.fails();
let result = result.no_stdout();
assert!(result.stderr.starts_with("date: invalid date "));
}
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
/// TODO: expected to fail currently; change to succeeds() when required.
fn test_date_set_valid_4() {
if get_effective_uid() == 0 {
let (_, mut ucmd) = at_and_ucmd!();
let result = ucmd
.arg("--set")
.arg("2020-03-11 21:45:00") // Local timezone
.fails();
let result = result.no_stdout();
assert!(result.stderr.starts_with("date: invalid date "));
}
}

View file

@ -12,7 +12,7 @@ fn test_du_basics() {
assert!(result.success); assert!(result.success);
assert_eq!(result.stderr, ""); assert_eq!(result.stderr, "");
} }
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
fn _du_basics(s: String) { fn _du_basics(s: String) {
let answer = "32\t./subdir let answer = "32\t./subdir
8\t./subdir/deeper 8\t./subdir/deeper
@ -21,7 +21,7 @@ fn _du_basics(s: String) {
"; ";
assert_eq!(s, answer); assert_eq!(s, answer);
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_vendor = "apple"))]
fn _du_basics(s: String) { fn _du_basics(s: String) {
let answer = "28\t./subdir let answer = "28\t./subdir
8\t./subdir/deeper 8\t./subdir/deeper
@ -41,11 +41,11 @@ fn test_du_basics_subdir() {
_du_basics_subdir(result.stdout); _du_basics_subdir(result.stdout);
} }
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
fn _du_basics_subdir(s: String) { fn _du_basics_subdir(s: String) {
assert_eq!(s, "4\tsubdir/deeper\n"); assert_eq!(s, "4\tsubdir/deeper\n");
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_vendor = "apple"))]
fn _du_basics_subdir(s: String) { fn _du_basics_subdir(s: String) {
// MS-WSL linux has altered expected output // MS-WSL linux has altered expected output
if !is_wsl() { if !is_wsl() {
@ -80,12 +80,12 @@ fn test_du_soft_link() {
_du_soft_link(result.stdout); _du_soft_link(result.stdout);
} }
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
fn _du_soft_link(s: String) { fn _du_soft_link(s: String) {
// 'macos' host variants may have `du` output variation for soft links // 'macos' host variants may have `du` output variation for soft links
assert!((s == "12\tsubdir/links\n") || (s == "16\tsubdir/links\n")); assert!((s == "12\tsubdir/links\n") || (s == "16\tsubdir/links\n"));
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_vendor = "apple"))]
fn _du_soft_link(s: String) { fn _du_soft_link(s: String) {
// MS-WSL linux has altered expected output // MS-WSL linux has altered expected output
if !is_wsl() { if !is_wsl() {
@ -109,11 +109,11 @@ fn test_du_hard_link() {
_du_hard_link(result.stdout); _du_hard_link(result.stdout);
} }
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
fn _du_hard_link(s: String) { fn _du_hard_link(s: String) {
assert_eq!(s, "12\tsubdir/links\n") assert_eq!(s, "12\tsubdir/links\n")
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_vendor = "apple"))]
fn _du_hard_link(s: String) { fn _du_hard_link(s: String) {
// MS-WSL linux has altered expected output // MS-WSL linux has altered expected output
if !is_wsl() { if !is_wsl() {
@ -133,11 +133,11 @@ fn test_du_d_flag() {
_du_d_flag(result.stdout); _du_d_flag(result.stdout);
} }
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
fn _du_d_flag(s: String) { fn _du_d_flag(s: String) {
assert_eq!(s, "16\t./subdir\n20\t./\n"); assert_eq!(s, "16\t./subdir\n20\t./\n");
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_vendor = "apple"))]
fn _du_d_flag(s: String) { fn _du_d_flag(s: String) {
// MS-WSL linux has altered expected output // MS-WSL linux has altered expected output
if !is_wsl() { if !is_wsl() {

View file

@ -173,3 +173,58 @@ fn test_disable_escapes() {
.succeeds() .succeeds()
.stdout_only(format!("{}\n", input_str)); .stdout_only(format!("{}\n", input_str));
} }
#[test]
fn test_hyphen_value() {
new_ucmd!().arg("-abc").succeeds().stdout_is("-abc\n");
}
#[test]
fn test_multiple_hyphen_values() {
new_ucmd!()
.args(&["-abc", "-def", "-edf"])
.succeeds()
.stdout_is("-abc -def -edf\n");
}
#[test]
fn test_hyphen_values_inside_string() {
new_ucmd!()
.arg("'\"\n'CXXFLAGS=-g -O2'\n\"'")
.succeeds()
.stdout
.contains("CXXFLAGS");
}
#[test]
fn test_hyphen_values_at_start() {
let result = new_ucmd!()
.arg("-E")
.arg("-test")
.arg("araba")
.arg("-merci")
.run();
assert!(result.success);
assert_eq!(false, result.stdout.contains("-E"));
assert_eq!(result.stdout, "-test araba -merci\n");
}
#[test]
fn test_hyphen_values_between() {
let result = new_ucmd!().arg("test").arg("-E").arg("araba").run();
assert!(result.success);
assert_eq!(result.stdout, "test -E araba\n");
let result = new_ucmd!()
.arg("dumdum ")
.arg("dum dum dum")
.arg("-e")
.arg("dum")
.run();
assert!(result.success);
assert_eq!(result.stdout, "dumdum dum dum dum -e dum\n");
assert_eq!(true, result.stdout.contains("-e"));
}

View file

@ -86,6 +86,14 @@ fn test_verbose() {
.stdout_is_fixture("lorem_ipsum_verbose.expected"); .stdout_is_fixture("lorem_ipsum_verbose.expected");
} }
#[test]
fn test_zero_terminated() {
new_ucmd!()
.args(&["-z", "zero_terminated.txt"])
.run()
.stdout_is_fixture("zero_terminated.expected");
}
#[test] #[test]
#[ignore] #[ignore]
fn test_spams_newline() { fn test_spams_newline() {
@ -159,3 +167,15 @@ fn test_bug_in_negative_zero_lines() {
//GNU Head returns "a\nb\n" //GNU Head returns "a\nb\n"
.stdout_is(""); .stdout_is("");
} }
#[test]
fn test_no_such_file_or_directory() {
let result = new_ucmd!().arg("no_such_file.toml").run();
assert_eq!(
true,
result
.stderr
.contains("cannot open 'no_such_file.toml' for reading: No such file or directory")
)
}

View file

@ -11,7 +11,7 @@ fn test_hostname() {
} }
// FixME: fails for "MacOS" // FixME: fails for "MacOS"
#[cfg(not(target_os = "macos"))] #[cfg(not(target_vendor = "apple"))]
#[test] #[test]
fn test_hostname_ip() { fn test_hostname_ip() {
let result = new_ucmd!().arg("-i").run(); let result = new_ucmd!().arg("-i").run();

View file

@ -17,9 +17,9 @@ fn test_install_help() {
#[test] #[test]
fn test_install_basic() { fn test_install_basic() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_install_target_dir_dir_a"; let dir = "target_dir";
let file1 = "test_install_target_dir_file_a1"; let file1 = "source_file1";
let file2 = "test_install_target_dir_file_a2"; let file2 = "source_file2";
at.touch(file1); at.touch(file1);
at.touch(file2); at.touch(file2);
@ -34,7 +34,7 @@ fn test_install_basic() {
#[test] #[test]
fn test_install_twice_dir() { fn test_install_twice_dir() {
let dir = "test_install_target_dir_dir_a"; let dir = "dir";
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
scene.ucmd().arg("-d").arg(dir).succeeds(); scene.ucmd().arg("-d").arg(dir).succeeds();
@ -47,9 +47,9 @@ fn test_install_twice_dir() {
#[test] #[test]
fn test_install_failing_not_dir() { fn test_install_failing_not_dir() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "file1";
let file2 = "test_install_target_dir_file_a2"; let file2 = "file2";
let file3 = "test_install_target_dir_file_a3"; let file3 = "file3";
at.touch(file1); at.touch(file1);
at.touch(file2); at.touch(file2);
@ -66,8 +66,8 @@ fn test_install_failing_not_dir() {
#[test] #[test]
fn test_install_unimplemented_arg() { fn test_install_unimplemented_arg() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_install_target_dir_dir_b"; let dir = "target_dir";
let file = "test_install_target_dir_file_b"; let file = "source_file";
let context_arg = "--context"; let context_arg = "--context";
at.touch(file); at.touch(file);
@ -86,9 +86,9 @@ fn test_install_unimplemented_arg() {
#[test] #[test]
fn test_install_component_directories() { fn test_install_component_directories() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let component1 = "test_install_target_dir_component_c1"; let component1 = "component1";
let component2 = "test_install_target_dir_component_c2"; let component2 = "component2";
let component3 = "test_install_target_dir_component_c3"; let component3 = "component3";
let directories_arg = "-d"; let directories_arg = "-d";
ucmd.args(&[directories_arg, component1, component2, component3]) ucmd.args(&[directories_arg, component1, component2, component3])
@ -104,10 +104,10 @@ fn test_install_component_directories() {
fn test_install_mode_numeric() { fn test_install_mode_numeric() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
let dir = "test_install_target_dir_dir_e"; let dir = "dir1";
let dir2 = "test_install_target_dir_dir_e2"; let dir2 = "dir2";
let file = "test_install_target_dir_file_e"; let file = "file";
let mode_arg = "--mode=333"; let mode_arg = "--mode=333";
at.touch(file); at.touch(file);
@ -145,8 +145,8 @@ fn test_install_mode_numeric() {
#[test] #[test]
fn test_install_mode_symbolic() { fn test_install_mode_symbolic() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_install_target_dir_dir_f"; let dir = "target_dir";
let file = "test_install_target_dir_file_f"; let file = "source_file";
let mode_arg = "--mode=o+wx"; let mode_arg = "--mode=o+wx";
at.touch(file); at.touch(file);
@ -163,8 +163,8 @@ fn test_install_mode_symbolic() {
#[test] #[test]
fn test_install_mode_failing() { fn test_install_mode_failing() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_install_target_dir_dir_g"; let dir = "target_dir";
let file = "test_install_target_dir_file_g"; let file = "source_file";
let mode_arg = "--mode=999"; let mode_arg = "--mode=999";
at.touch(file); at.touch(file);
@ -185,7 +185,7 @@ fn test_install_mode_failing() {
#[test] #[test]
fn test_install_mode_directories() { fn test_install_mode_directories() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let component = "test_install_target_dir_component_h"; let component = "component";
let directories_arg = "-d"; let directories_arg = "-d";
let mode_arg = "--mode=333"; let mode_arg = "--mode=333";
@ -203,8 +203,8 @@ fn test_install_mode_directories() {
#[test] #[test]
fn test_install_target_file() { fn test_install_target_file() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_file_file_i1"; let file1 = "source_file";
let file2 = "test_install_target_file_file_i2"; let file2 = "target_file";
at.touch(file1); at.touch(file1);
at.touch(file2); at.touch(file2);
@ -217,8 +217,8 @@ fn test_install_target_file() {
#[test] #[test]
fn test_install_target_new_file() { fn test_install_target_new_file() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file = "test_install_target_new_filer_file_j"; let file = "file";
let dir = "test_install_target_new_file_dir_j"; let dir = "target_dir";
at.touch(file); at.touch(file);
at.mkdir(dir); at.mkdir(dir);
@ -234,8 +234,8 @@ fn test_install_target_new_file() {
#[test] #[test]
fn test_install_target_new_file_with_group() { fn test_install_target_new_file_with_group() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file = "test_install_target_new_filer_file_j"; let file = "file";
let dir = "test_install_target_new_file_dir_j"; let dir = "target_dir";
let gid = get_effective_gid(); let gid = get_effective_gid();
at.touch(file); at.touch(file);
@ -264,8 +264,8 @@ fn test_install_target_new_file_with_group() {
#[test] #[test]
fn test_install_target_new_file_with_owner() { fn test_install_target_new_file_with_owner() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file = "test_install_target_new_filer_file_j"; let file = "file";
let dir = "test_install_target_new_file_dir_j"; let dir = "target_dir";
let uid = get_effective_uid(); let uid = get_effective_uid();
at.touch(file); at.touch(file);
@ -294,9 +294,9 @@ fn test_install_target_new_file_with_owner() {
#[test] #[test]
fn test_install_target_new_file_failing_nonexistent_parent() { fn test_install_target_new_file_failing_nonexistent_parent() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_new_file_failing_file_k1"; let file1 = "source_file";
let file2 = "test_install_target_new_file_failing_file_k2"; let file2 = "target_file";
let dir = "test_install_target_new_file_failing_dir_k"; let dir = "target_dir";
at.touch(file1); at.touch(file1);
@ -312,8 +312,8 @@ fn test_install_target_new_file_failing_nonexistent_parent() {
#[test] #[test]
fn test_install_preserve_timestamps() { fn test_install_preserve_timestamps() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "source_file";
let file2 = "test_install_target_dir_file_a2"; let file2 = "target_file";
at.touch(file1); at.touch(file1);
ucmd.arg(file1).arg(file2).arg("-p").succeeds().no_stderr(); ucmd.arg(file1).arg(file2).arg("-p").succeeds().no_stderr();
@ -338,8 +338,8 @@ fn test_install_preserve_timestamps() {
#[test] #[test]
fn test_install_copy_file() { fn test_install_copy_file() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "source_file";
let file2 = "test_install_target_dir_file_a2"; let file2 = "target_file";
at.touch(file1); at.touch(file1);
ucmd.arg(file1).arg(file2).succeeds().no_stderr(); ucmd.arg(file1).arg(file2).succeeds().no_stderr();
@ -353,8 +353,57 @@ fn test_install_copy_file() {
fn test_install_target_file_dev_null() { fn test_install_target_file_dev_null() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "/dev/null"; let file1 = "/dev/null";
let file2 = "test_install_target_file_file_i2"; let file2 = "target_file";
ucmd.arg(file1).arg(file2).succeeds().no_stderr(); ucmd.arg(file1).arg(file2).succeeds().no_stderr();
assert!(at.file_exists(file2)); assert!(at.file_exists(file2));
} }
#[test]
fn test_install_nested_paths_copy_file() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "source_file";
let dir1 = "source_dir";
let dir2 = "target_dir";
at.mkdir(dir1);
at.mkdir(dir2);
at.touch(&format!("{}/{}", dir1, file1));
ucmd.arg(format!("{}/{}", dir1, file1))
.arg(dir2)
.succeeds()
.no_stderr();
assert!(at.file_exists(&format!("{}/{}", dir2, file1)));
}
#[test]
fn test_install_failing_omitting_directory() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "source_file";
let dir1 = "source_dir";
let dir2 = "target_dir";
at.mkdir(dir1);
at.mkdir(dir2);
at.touch(file1);
let r = ucmd.arg(dir1).arg(file1).arg(dir2).run();
assert!(r.code == Some(1));
assert!(r.stderr.contains("omitting directory"));
}
#[test]
fn test_install_failing_no_such_file() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "source_file";
let file2 = "inexistent_file";
let dir1 = "target_dir";
at.mkdir(dir1);
at.touch(file1);
let r = ucmd.arg(file1).arg(file2).arg(dir1).run();
assert!(r.code == Some(1));
assert!(r.stderr.contains("No such file or directory"));
}

View file

@ -57,6 +57,200 @@ fn test_ls_a() {
assert!(!result.stdout.contains("..")); assert!(!result.stdout.contains(".."));
} }
#[test]
fn test_ls_width() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-width-1"));
at.touch(&at.plus_as_string("test-width-2"));
at.touch(&at.plus_as_string("test-width-3"));
at.touch(&at.plus_as_string("test-width-4"));
for option in &["-w 100", "-w=100", "--width=100", "--width 100"] {
let result = scene
.ucmd()
.args(&option.split(" ").collect::<Vec<_>>())
.run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert_eq!(
result.stdout,
"test-width-1 test-width-2 test-width-3 test-width-4\n",
)
}
for option in &["-w 50", "-w=50", "--width=50", "--width 50"] {
let result = scene
.ucmd()
.args(&option.split(" ").collect::<Vec<_>>())
.run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert_eq!(
result.stdout,
"test-width-1 test-width-3\ntest-width-2 test-width-4\n",
)
}
for option in &[
"-w 25",
"-w=25",
"--width=25",
"--width 25",
"-w 0",
"-w=0",
"--width=0",
"--width 0",
] {
let result = scene
.ucmd()
.args(&option.split(" ").collect::<Vec<_>>())
.run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert_eq!(
result.stdout,
"test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n",
)
}
}
#[test]
fn test_ls_columns() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-columns-1"));
at.touch(&at.plus_as_string("test-columns-2"));
at.touch(&at.plus_as_string("test-columns-3"));
at.touch(&at.plus_as_string("test-columns-4"));
// Columns is the default
let result = scene.ucmd().run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
#[cfg(not(windows))]
assert_eq!(
result.stdout,
"test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n"
);
#[cfg(windows)]
assert_eq!(
result.stdout,
"test-columns-1 test-columns-2 test-columns-3 test-columns-4\n"
);
for option in &["-C", "--format=columns"] {
let result = scene.ucmd().arg(option).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
#[cfg(not(windows))]
assert_eq!(
result.stdout,
"test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n"
);
#[cfg(windows)]
assert_eq!(
result.stdout,
"test-columns-1 test-columns-2 test-columns-3 test-columns-4\n"
);
}
for option in &["-C", "--format=columns"] {
let result = scene.ucmd().arg("-w=40").arg(option).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
assert_eq!(
result.stdout,
"test-columns-1 test-columns-3\ntest-columns-2 test-columns-4\n"
);
}
}
#[test]
fn test_ls_across() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-across-1"));
at.touch(&at.plus_as_string("test-across-2"));
at.touch(&at.plus_as_string("test-across-3"));
at.touch(&at.plus_as_string("test-across-4"));
for option in &["-x", "--format=across"] {
let result = scene.ucmd().arg(option).succeeds();
// Because the test terminal has width 0, this is the same output as
// the columns option.
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
if cfg!(unix) {
assert_eq!(
result.stdout,
"test-across-1\ntest-across-2\ntest-across-3\ntest-across-4\n"
);
} else {
assert_eq!(
result.stdout,
"test-across-1 test-across-2 test-across-3 test-across-4\n"
);
}
}
for option in &["-x", "--format=across"] {
let result = scene.ucmd().arg("-w=30").arg(option).run();
// Because the test terminal has width 0, this is the same output as
// the columns option.
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert_eq!(
result.stdout,
"test-across-1 test-across-2\ntest-across-3 test-across-4\n"
);
}
}
#[test]
fn test_ls_commas() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-commas-1"));
at.touch(&at.plus_as_string("test-commas-2"));
at.touch(&at.plus_as_string("test-commas-3"));
at.touch(&at.plus_as_string("test-commas-4"));
for option in &["-m", "--format=commas"] {
let result = scene.ucmd().arg(option).succeeds();
if cfg!(unix) {
assert_eq!(
result.stdout,
"test-commas-1,\ntest-commas-2,\ntest-commas-3,\ntest-commas-4\n"
);
} else {
assert_eq!(
result.stdout,
"test-commas-1, test-commas-2, test-commas-3, test-commas-4\n"
);
}
}
for option in &["-m", "--format=commas"] {
let result = scene.ucmd().arg("-w=30").arg(option).succeeds();
assert_eq!(
result.stdout,
"test-commas-1, test-commas-2,\ntest-commas-3, test-commas-4\n"
);
}
for option in &["-m", "--format=commas"] {
let result = scene.ucmd().arg("-w=45").arg(option).succeeds();
assert_eq!(
result.stdout,
"test-commas-1, test-commas-2, test-commas-3,\ntest-commas-4\n"
);
}
}
#[test] #[test]
fn test_ls_long() { fn test_ls_long() {
#[cfg(not(windows))] #[cfg(not(windows))]
@ -71,16 +265,20 @@ fn test_ls_long() {
} }
} }
let (at, mut ucmd) = at_and_ucmd!(); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-long")); at.touch(&at.plus_as_string("test-long"));
let result = ucmd.arg("-l").arg("test-long").succeeds();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
#[cfg(not(windows))]
assert!(result.stdout.contains("-rw-rw-r--"));
#[cfg(windows)] for arg in &["-l", "--long", "--format=long", "--format=verbose"] {
assert!(result.stdout.contains("---------- 1 somebody somegroup")); let result = scene.ucmd().arg(arg).arg("test-long").succeeds();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
#[cfg(not(windows))]
assert!(result.stdout.contains("-rw-rw-r--"));
#[cfg(windows)]
assert!(result.stdout.contains("---------- 1 somebody somegroup"));
}
#[cfg(not(windows))] #[cfg(not(windows))]
{ {
@ -90,6 +288,126 @@ fn test_ls_long() {
} }
} }
#[test]
fn test_ls_long_formats() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-long-formats"));
// Regex for three names, so all of author, group and owner
let re_three = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){3}0").unwrap();
// Regex for two names, either:
// - group and owner
// - author and owner
// - author and group
let re_two = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){2}0").unwrap();
// Regex for one name: author, group or owner
let re_one = Regex::new(r"[xrw-]{9} \d [-0-9_a-z]+ 0").unwrap();
// Regex for no names
let re_zero = Regex::new(r"[xrw-]{9} \d 0").unwrap();
let result = scene
.ucmd()
.arg("-l")
.arg("--author")
.arg("test-long-formats")
.run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(re_three.is_match(&result.stdout));
let result = scene
.ucmd()
.arg("-l1")
.arg("--author")
.arg("test-long-formats")
.run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(re_three.is_match(&result.stdout));
for arg in &[
"-l", // only group and owner
"-g --author", // only author and group
"-o --author", // only author and owner
"-lG --author", // only author and owner
"-l --no-group --author", // only author and owner
] {
let result = scene
.ucmd()
.args(&arg.split(" ").collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(re_two.is_match(&result.stdout));
}
for arg in &[
"-g", // only group
"-gl", // only group
"-o", // only owner
"-ol", // only owner
"-oG", // only owner
"-lG", // only owner
"-l --no-group", // only owner
"-gG --author", // only author
] {
let result = scene
.ucmd()
.args(&arg.split(" ").collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(re_one.is_match(&result.stdout));
}
for arg in &[
"-og",
"-ogl",
"-lgo",
"-gG",
"-g --no-group",
"-og --no-group",
"-og --format=long",
"-ogCl",
"-og --format=vertical -l",
"-og1",
"-og1l",
] {
let result = scene
.ucmd()
.args(&arg.split(" ").collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(re_zero.is_match(&result.stdout));
}
}
#[test]
fn test_ls_oneline() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-oneline-1"));
at.touch(&at.plus_as_string("test-oneline-2"));
// Bit of a weird situation: in the tests oneline and columns have the same output,
// except on Windows.
for option in &["-1", "--format=single-column"] {
let result = scene.ucmd().arg(option).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
assert_eq!(result.stdout, "test-oneline-1\ntest-oneline-2\n");
}
}
#[test] #[test]
fn test_ls_deref() { fn test_ls_deref() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
@ -166,27 +484,55 @@ fn test_ls_order_size() {
} }
#[test] #[test]
fn test_ls_order_creation() { fn test_ls_long_ctime() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("test-long-ctime-1");
let result = scene.ucmd().arg("-lc").succeeds();
// Should show the time on Unix, but question marks on windows.
#[cfg(unix)]
assert!(result.stdout.contains(":"));
#[cfg(not(unix))]
assert!(result.stdout.contains("???"));
}
#[test]
fn test_ls_order_time() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
at.touch("test-1"); at.touch("test-1");
at.append("test-1", "1"); at.append("test-1", "1");
sleep(Duration::from_millis(500)); sleep(Duration::from_millis(100));
at.touch("test-2"); at.touch("test-2");
at.append("test-2", "22"); at.append("test-2", "22");
sleep(Duration::from_millis(500));
sleep(Duration::from_millis(100));
at.touch("test-3"); at.touch("test-3");
at.append("test-3", "333"); at.append("test-3", "333");
sleep(Duration::from_millis(500)); sleep(Duration::from_millis(100));
at.touch("test-4"); at.touch("test-4");
at.append("test-4", "4444"); at.append("test-4", "4444");
sleep(Duration::from_millis(100));
// Read test-3, only changing access time
at.read("test-3");
// Set permissions of test-2, only changing ctime
std::fs::set_permissions(
at.plus_as_string("test-2"),
at.metadata("test-2").permissions(),
)
.unwrap();
let result = scene.ucmd().arg("-al").run(); let result = scene.ucmd().arg("-al").run();
println!("stderr = {:?}", result.stderr); println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout); println!("stdout = {:?}", result.stdout);
assert!(result.success); assert!(result.success);
// ctime was changed at write, so the order is 4 3 2 1
let result = scene.ucmd().arg("-t").run(); let result = scene.ucmd().arg("-t").run();
println!("stderr = {:?}", result.stderr); println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout); println!("stdout = {:?}", result.stdout);
@ -196,7 +542,7 @@ fn test_ls_order_creation() {
#[cfg(windows)] #[cfg(windows)]
assert_eq!(result.stdout, "test-4 test-3 test-2 test-1\n"); assert_eq!(result.stdout, "test-4 test-3 test-2 test-1\n");
let result = scene.ucmd().arg("-t").arg("-r").run(); let result = scene.ucmd().arg("-tr").run();
println!("stderr = {:?}", result.stderr); println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout); println!("stdout = {:?}", result.stdout);
assert!(result.success); assert!(result.success);
@ -204,6 +550,41 @@ fn test_ls_order_creation() {
assert_eq!(result.stdout, "test-1\ntest-2\ntest-3\ntest-4\n"); assert_eq!(result.stdout, "test-1\ntest-2\ntest-3\ntest-4\n");
#[cfg(windows)] #[cfg(windows)]
assert_eq!(result.stdout, "test-1 test-2 test-3 test-4\n"); assert_eq!(result.stdout, "test-1 test-2 test-3 test-4\n");
// 3 was accessed last in the read
// So the order should be 2 3 4 1
let result = scene.ucmd().arg("-tu").run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap();
let file4_access = at.open("test-4").metadata().unwrap().accessed().unwrap();
if file3_access > file4_access {
if cfg!(not(windows)) {
assert_eq!(result.stdout, "test-3\ntest-4\ntest-2\ntest-1\n");
} else {
assert_eq!(result.stdout, "test-3 test-4 test-2 test-1\n");
}
} else {
// Access time does not seem to be set on Windows and some other
// systems so the order is 4 3 2 1
if cfg!(not(windows)) {
assert_eq!(result.stdout, "test-4\ntest-3\ntest-2\ntest-1\n");
} else {
assert_eq!(result.stdout, "test-4 test-3 test-2 test-1\n");
}
}
// test-2 had the last ctime change when the permissions were set
// So the order should be 2 4 3 1
#[cfg(unix)]
{
let result = scene.ucmd().arg("-tc").run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
assert_eq!(result.stdout, "test-2\ntest-4\ntest-3\ntest-1\n");
}
} }
#[test] #[test]
@ -270,45 +651,197 @@ fn test_ls_recursive() {
assert!(result.stdout.contains("a\\b:\nb")); assert!(result.stdout.contains("a\\b:\nb"));
} }
#[cfg(unix)]
#[test] #[test]
fn test_ls_ls_color() { fn test_ls_ls_color() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
at.mkdir("a"); at.mkdir("a");
at.mkdir("a/nested_dir");
at.mkdir("z"); at.mkdir("z");
at.touch(&at.plus_as_string("a/a")); at.touch(&at.plus_as_string("a/nested_file"));
scene.ucmd().arg("--color").succeeds(); at.touch("test-color");
scene.ucmd().arg("--color=always").succeeds();
scene.ucmd().arg("--color=never").succeeds(); let a_with_colors = "\x1b[01;34ma\x1b[0m";
scene.ucmd().arg("--color").arg("a").succeeds(); let z_with_colors = "\x1b[01;34mz\x1b[0m";
scene.ucmd().arg("--color=always").arg("a/a").succeeds(); let nested_dir_with_colors = "\x1b[01;34mnested_dir\x1b[0m";
scene.ucmd().arg("--color=never").arg("z").succeeds();
// Color is disabled by default
let result = scene.ucmd().succeeds();
assert!(!result.stdout.contains(a_with_colors));
assert!(!result.stdout.contains(z_with_colors));
// Color should be enabled
let result = scene.ucmd().arg("--color").succeeds();
assert!(result.stdout.contains(a_with_colors));
assert!(result.stdout.contains(z_with_colors));
// Color should be enabled
let result = scene.ucmd().arg("--color=always").succeeds();
assert!(result.stdout.contains(a_with_colors));
assert!(result.stdout.contains(z_with_colors));
// Color should be disabled
let result = scene.ucmd().arg("--color=never").succeeds();
assert!(!result.stdout.contains(a_with_colors));
assert!(!result.stdout.contains(z_with_colors));
// Nested dir should be shown and colored
let result = scene.ucmd().arg("--color").arg("a").succeeds();
assert!(result.stdout.contains(nested_dir_with_colors));
// Color has no effect
let result = scene
.ucmd()
.arg("--color=always")
.arg("a/nested_file")
.succeeds();
assert!(result.stdout.contains("a/nested_file\n"));
// No output
let result = scene.ucmd().arg("--color=never").arg("z").succeeds();
assert_eq!(result.stdout, "");
} }
#[cfg(not(any(target_os = "macos", target_os = "windows")))] // Truncate not available on mac or win #[cfg(unix)]
#[test] #[test]
fn test_ls_human() { fn test_ls_inode() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let file = "test_human"; let at = &scene.fixtures;
let result = scene.cmd("truncate").arg("-s").arg("+1000").arg(file).run();
let file = "test_inode";
at.touch(file);
let re_short = Regex::new(r" *(\d+) test_inode").unwrap();
let re_long = Regex::new(r" *(\d+) [xrw-]{10} \d .+ test_inode").unwrap();
let result = scene.ucmd().arg("test_inode").arg("-i").run();
println!("stderr = {:?}", result.stderr); println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout); println!("stdout = {:?}", result.stdout);
let result = scene.ucmd().arg("-hl").arg(file).run(); assert!(re_short.is_match(&result.stdout));
let inode_short = re_short
.captures(&result.stdout)
.unwrap()
.get(1)
.unwrap()
.as_str();
let result = scene.ucmd().arg("test_inode").run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(!re_short.is_match(&result.stdout));
assert!(!result.stdout.contains(inode_short));
let result = scene.ucmd().arg("-li").arg("test_inode").run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(re_long.is_match(&result.stdout));
let inode_long = re_long
.captures(&result.stdout)
.unwrap()
.get(1)
.unwrap()
.as_str();
let result = scene.ucmd().arg("-l").arg("test_inode").run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(!re_long.is_match(&result.stdout));
assert!(!result.stdout.contains(inode_long));
assert_eq!(inode_short, inode_long)
}
#[cfg(not(any(target_vendor = "apple", target_os = "windows")))] // Truncate not available on mac or win
#[test]
fn test_ls_human_si() {
let scene = TestScenario::new(util_name!());
let file1 = "test_human-1";
let result = scene
.cmd("truncate")
.arg("-s")
.arg("+1000")
.arg(file1)
.run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
let result = scene.ucmd().arg("-hl").arg(file1).run();
println!("stderr = {:?}", result.stderr); println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout); println!("stdout = {:?}", result.stdout);
assert!(result.success); assert!(result.success);
assert!(result.stdout.contains("1.00K")); assert!(result.stdout.contains(" 1000 "));
let result = scene.ucmd().arg("-l").arg("--si").arg(file1).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
assert!(result.stdout.contains(" 1.0k "));
scene scene
.cmd("truncate") .cmd("truncate")
.arg("-s") .arg("-s")
.arg("+1000k") .arg("+1000k")
.arg(file) .arg(file1)
.run(); .run();
let result = scene.ucmd().arg("-hl").arg(file).run();
let result = scene.ucmd().arg("-hl").arg(file1).run();
println!("stderr = {:?}", result.stderr); println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout); println!("stdout = {:?}", result.stdout);
assert!(result.success); assert!(result.success);
assert!(result.stdout.contains("1.02M")); assert!(result.stdout.contains(" 1001K "));
let result = scene.ucmd().arg("-l").arg("--si").arg(file1).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
assert!(result.stdout.contains(" 1.1M "));
let file2 = "test-human-2";
let result = scene
.cmd("truncate")
.arg("-s")
.arg("+12300k")
.arg(file2)
.run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
let result = scene.ucmd().arg("-hl").arg(file2).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
// GNU rounds up, so we must too.
assert!(result.stdout.contains(" 13M "));
let result = scene.ucmd().arg("-l").arg("--si").arg(file2).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
// GNU rounds up, so we must too.
assert!(result.stdout.contains(" 13M "));
let file3 = "test-human-3";
let result = scene
.cmd("truncate")
.arg("-s")
.arg("+9999")
.arg(file3)
.run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
let result = scene.ucmd().arg("-hl").arg(file3).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
assert!(result.stdout.contains(" 9.8K "));
let result = scene.ucmd().arg("-l").arg("--si").arg(file3).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
assert!(result.stdout.contains(" 10k "));
} }
#[cfg(windows)] #[cfg(windows)]
@ -336,3 +869,81 @@ fn test_ls_hidden_windows() {
assert!(result.success); assert!(result.success);
assert!(result.stdout.contains(file)); assert!(result.stdout.contains(file));
} }
#[test]
fn test_ls_version_sort() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
for filename in &[
"a2",
"b1",
"b20",
"a1.4",
"a1.40",
"b3",
"b11",
"b20b",
"b20a",
"a100",
"a1.13",
"aa",
"a1",
"aaa",
"a1.00000040",
"abab",
"ab",
"a01.40",
"a001.001",
"a01.0000001",
"a01.001",
"a001.01",
] {
at.touch(filename);
}
let mut expected = vec![
"a1",
"a001.001",
"a001.01",
"a01.0000001",
"a01.001",
"a1.4",
"a1.13",
"a01.40",
"a1.00000040",
"a1.40",
"a2",
"a100",
"aa",
"aaa",
"ab",
"abab",
"b1",
"b3",
"b11",
"b20",
"b20a",
"b20b",
"", // because of '\n' at the end of the output
];
let result = scene.ucmd().arg("-1v").run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert_eq!(result.stdout.split('\n').collect::<Vec<_>>(), expected);
let result = scene.ucmd().arg("-1").arg("--sort=version").run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert_eq!(result.stdout.split('\n').collect::<Vec<_>>(), expected);
let result = scene.ucmd().arg("-a1v").run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
expected.insert(0, "..");
expected.insert(0, ".");
assert_eq!(result.stdout.split('\n').collect::<Vec<_>>(), expected,)
}

View file

@ -15,7 +15,9 @@ fn test_negative_adjustment() {
// correctly. // correctly.
let res = new_ucmd!().args(&["-n", "-1", "true"]).run(); let res = new_ucmd!().args(&["-n", "-1", "true"]).run();
assert!(res.stderr.starts_with("nice: warning: setpriority: Permission denied")); assert!(res
.stderr
.starts_with("nice: warning: setpriority: Permission denied"));
} }
#[test] #[test]

View file

@ -23,8 +23,8 @@ fn test_padding_without_overflow() {
.run() .run()
.stdout_is( .stdout_is(
"000001xL1\n001001xL2\n002001xL3\n003001xL4\n004001xL5\n005001xL6\n006001xL7\n0070\ "000001xL1\n001001xL2\n002001xL3\n003001xL4\n004001xL5\n005001xL6\n006001xL7\n0070\
01xL8\n008001xL9\n009001xL10\n010001xL11\n011001xL12\n012001xL13\n013001xL14\n014\ 01xL8\n008001xL9\n009001xL10\n010001xL11\n011001xL12\n012001xL13\n013001xL14\n014\
001xL15\n", 001xL15\n",
); );
} }
@ -35,7 +35,7 @@ fn test_padding_with_overflow() {
.run() .run()
.stdout_is( .stdout_is(
"0001xL1\n1001xL2\n2001xL3\n3001xL4\n4001xL5\n5001xL6\n6001xL7\n7001xL8\n8001xL9\n\ "0001xL1\n1001xL2\n2001xL3\n3001xL4\n4001xL5\n5001xL6\n6001xL7\n7001xL8\n8001xL9\n\
9001xL10\n10001xL11\n11001xL12\n12001xL13\n13001xL14\n14001xL15\n", 9001xL10\n10001xL11\n11001xL12\n12001xL13\n13001xL14\n14001xL15\n",
); );
} }
@ -45,15 +45,15 @@ fn test_sections_and_styles() {
( (
"section.txt", "section.txt",
"\nHEADER1\nHEADER2\n\n1 |BODY1\n2 \ "\nHEADER1\nHEADER2\n\n1 |BODY1\n2 \
|BODY2\n\nFOOTER1\nFOOTER2\n\nNEXTHEADER1\nNEXTHEADER2\n\n1 \ |BODY2\n\nFOOTER1\nFOOTER2\n\nNEXTHEADER1\nNEXTHEADER2\n\n1 \
|NEXTBODY1\n2 |NEXTBODY2\n\nNEXTFOOTER1\nNEXTFOOTER2\n", |NEXTBODY1\n2 |NEXTBODY2\n\nNEXTFOOTER1\nNEXTFOOTER2\n",
), ),
( (
"joinblanklines.txt", "joinblanklines.txt",
"1 |Nonempty\n2 |Nonempty\n3 |Followed by 10x empty\n\n\n\n\n4 \ "1 |Nonempty\n2 |Nonempty\n3 |Followed by 10x empty\n\n\n\n\n4 \
|\n\n\n\n\n5 |\n6 |Followed by 5x empty\n\n\n\n\n7 |\n8 \ |\n\n\n\n\n5 |\n6 |Followed by 5x empty\n\n\n\n\n7 |\n8 \
|Followed by 4x empty\n\n\n\n\n9 |Nonempty\n10 |Nonempty\n11 \ |Followed by 4x empty\n\n\n\n\n9 |Nonempty\n10 |Nonempty\n11 \
|Nonempty.\n", |Nonempty.\n",
), ),
] { ] {
new_ucmd!() new_ucmd!()

View file

@ -1 +1,19 @@
// ToDO: add tests use crate::common::util::*;
use std::thread::sleep;
// General observation: nohup.out will not be created in tests run by cargo test
// because stdin/stdout is not attached to a TTY.
// All that can be tested is the side-effects.
#[test]
#[cfg(any(target_os = "linux", target_os = "freebsd", target_vendor = "apple"))]
fn test_nohup_multiple_args_and_flags() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.args(&["touch", "-t", "1006161200", "file1", "file2"])
.succeeds();
sleep(std::time::Duration::from_millis(10));
assert!(at.file_exists("file1"));
assert!(at.file_exists("file2"));
}

View file

@ -9,7 +9,7 @@ fn test_default_mode() {
// fail on long inputs // fail on long inputs
new_ucmd!() new_ucmd!()
.args(&[repeat_str("test", 20000)]) .args(&["test".repeat(20000)])
.fails() .fails()
.no_stdout(); .no_stdout();
} }

View file

@ -177,7 +177,7 @@ fn test_rm_directory_without_flag() {
let dir = "test_rm_directory_without_flag_dir"; let dir = "test_rm_directory_without_flag_dir";
at.mkdir(dir); at.mkdir(dir);
let result = ucmd.arg(dir).fails(); let result = ucmd.arg(dir).fails();
println!("{}", result.stderr); println!("{}", result.stderr);
assert!(result assert!(result

View file

@ -40,7 +40,7 @@ fn test_rmdir_nonempty_directory_no_parents() {
ucmd.arg(dir).fails().stderr_is( ucmd.arg(dir).fails().stderr_is(
"rmdir: error: failed to remove 'test_rmdir_nonempty_no_parents': Directory not \ "rmdir: error: failed to remove 'test_rmdir_nonempty_no_parents': Directory not \
empty\n", empty\n",
); );
assert!(at.dir_exists(dir)); assert!(at.dir_exists(dir));
@ -60,9 +60,9 @@ fn test_rmdir_nonempty_directory_with_parents() {
ucmd.arg("-p").arg(dir).fails().stderr_is( ucmd.arg("-p").arg(dir).fails().stderr_is(
"rmdir: error: failed to remove 'test_rmdir_nonempty/with/parents': Directory not \ "rmdir: error: failed to remove 'test_rmdir_nonempty/with/parents': Directory not \
empty\nrmdir: error: failed to remove 'test_rmdir_nonempty/with': Directory not \ empty\nrmdir: error: failed to remove 'test_rmdir_nonempty/with': Directory not \
empty\nrmdir: error: failed to remove 'test_rmdir_nonempty': Directory not \ empty\nrmdir: error: failed to remove 'test_rmdir_nonempty': Directory not \
empty\n", empty\n",
); );
assert!(at.dir_exists(dir)); assert!(at.dir_exists(dir));

View file

@ -329,3 +329,17 @@ fn test_multiple_input_quiet_flag_overrides_verbose_flag_for_suppressing_headers
.run() .run()
.stdout_is_fixture("foobar_multiple_quiet.expected"); .stdout_is_fixture("foobar_multiple_quiet.expected");
} }
#[test]
fn test_negative_indexing() {
let positive_lines_index = new_ucmd!().arg("-n").arg("5").arg(FOOBAR_TXT).run();
let negative_lines_index = new_ucmd!().arg("-n").arg("-5").arg(FOOBAR_TXT).run();
let positive_bytes_index = new_ucmd!().arg("-c").arg("20").arg(FOOBAR_TXT).run();
let negative_bytes_index = new_ucmd!().arg("-c").arg("-20").arg(FOOBAR_TXT).run();
assert_eq!(positive_lines_index.stdout, negative_lines_index.stdout);
assert_eq!(positive_bytes_index.stdout, negative_bytes_index.stdout);
}

View file

@ -15,3 +15,36 @@ fn test_sort_self_loop() {
.succeeds() .succeeds()
.stdout_only("first\nsecond\n"); .stdout_only("first\nsecond\n");
} }
#[test]
fn test_no_such_file() {
let result = new_ucmd!().arg("invalid_file_txt").run();
assert_eq!(true, result.stderr.contains("No such file or directory"));
}
#[test]
fn test_version_flag() {
let version_short = new_ucmd!().arg("-V").run();
let version_long = new_ucmd!().arg("--version").run();
assert_eq!(version_short.stdout, version_long.stdout);
}
#[test]
fn test_help_flag() {
let help_short = new_ucmd!().arg("-h").run();
let help_long = new_ucmd!().arg("--help").run();
assert_eq!(help_short.stdout, help_long.stdout);
}
#[test]
fn test_multiple_arguments() {
let result = new_ucmd!()
.arg("call_graph.txt")
.arg("invalid_file.txt")
.run();
assert_eq!(true, result.stderr.contains("error: Found argument 'invalid_file.txt' which wasn't expected, or isn't valid in this context"))
}

View file

@ -136,3 +136,22 @@ fn unexpand_spaces_after_fields() {
.run() .run()
.stdout_is("\t\tA B C D\t\t A\t\n"); .stdout_is("\t\tA B C D\t\t A\t\n");
} }
#[test]
fn unexpand_read_from_file() {
new_ucmd!()
.arg("with_spaces.txt")
.arg("-t4")
.run()
.success();
}
#[test]
fn unexpand_read_from_two_file() {
new_ucmd!()
.arg("with_spaces.txt")
.arg("with_spaces.txt")
.arg("-t4")
.run()
.success();
}

View file

@ -23,7 +23,7 @@ fn test_unlink_multiple_files() {
ucmd.arg(file_a).arg(file_b).fails().stderr_is( ucmd.arg(file_a).arg(file_b).fails().stderr_is(
"unlink: error: extra operand: 'test_unlink_multiple_file_b'\nTry 'unlink --help' \ "unlink: error: extra operand: 'test_unlink_multiple_file_b'\nTry 'unlink --help' \
for more information.\n", for more information.\n",
); );
} }
@ -36,7 +36,7 @@ fn test_unlink_directory() {
ucmd.arg(dir).fails().stderr_is( ucmd.arg(dir).fails().stderr_is(
"unlink: error: cannot unlink 'test_unlink_empty_directory': Not a regular file \ "unlink: error: cannot unlink 'test_unlink_empty_directory': Not a regular file \
or symlink\n", or symlink\n",
); );
} }
@ -46,6 +46,6 @@ fn test_unlink_nonexistent() {
new_ucmd!().arg(file).fails().stderr_is( new_ucmd!().arg(file).fails().stderr_is(
"unlink: error: Cannot stat 'test_unlink_nonexistent': No such file or directory \ "unlink: error: Cannot stat 'test_unlink_nonexistent': No such file or directory \
(os error 2)\n", (os error 2)\n",
); );
} }

View file

@ -81,6 +81,6 @@ fn test_multiple_default() {
.run() .run()
.stdout_is( .stdout_is(
" 13 109 772 lorem_ipsum.txt\n 18 204 1115 moby_dick.txt\n 5 57 302 \ " 13 109 772 lorem_ipsum.txt\n 18 204 1115 moby_dick.txt\n 5 57 302 \
alice_in_wonderland.txt\n 36 370 2189 total\n", alice_in_wonderland.txt\n 36 370 2189 total\n",
); );
} }

View file

@ -1,3 +1,6 @@
/// Assertion helper macro for [`CmdResult`] types
///
/// [`CmdResult`]: crate::tests::common::util::CmdResult
#[macro_export] #[macro_export]
macro_rules! assert_empty_stderr( macro_rules! assert_empty_stderr(
($cond:expr) => ( ($cond:expr) => (
@ -7,6 +10,9 @@ macro_rules! assert_empty_stderr(
); );
); );
/// Assertion helper macro for [`CmdResult`] types
///
/// [`CmdResult`]: crate::tests::common::util::CmdResult
#[macro_export] #[macro_export]
macro_rules! assert_empty_stdout( macro_rules! assert_empty_stdout(
($cond:expr) => ( ($cond:expr) => (
@ -16,6 +22,9 @@ macro_rules! assert_empty_stdout(
); );
); );
/// Assertion helper macro for [`CmdResult`] types
///
/// [`CmdResult`]: crate::tests::common::util::CmdResult
#[macro_export] #[macro_export]
macro_rules! assert_no_error( macro_rules! assert_no_error(
($cond:expr) => ( ($cond:expr) => (
@ -26,6 +35,7 @@ macro_rules! assert_no_error(
); );
); );
/// Platform-independent helper for constructing a PathBuf from individual elements
#[macro_export] #[macro_export]
macro_rules! path_concat { macro_rules! path_concat {
($e:expr, ..$n:expr) => {{ ($e:expr, ..$n:expr) => {{
@ -47,6 +57,9 @@ macro_rules! path_concat {
}}; }};
} }
/// Deduce the name of the test binary from the test filename.
///
/// e.g.: `tests/by-util/test_cat.rs` -> `cat`
#[macro_export] #[macro_export]
macro_rules! util_name { macro_rules! util_name {
() => { () => {
@ -54,6 +67,16 @@ macro_rules! util_name {
}; };
} }
/// Convenience macro for acquiring a [`UCommand`] builder.
///
/// Returns the following:
/// - a [`UCommand`] builder for invoking the binary to be tested
///
/// This macro is intended for quick, single-call tests. For more complex tests
/// that require multiple invocations of the tested binary, see [`TestScenario`]
///
/// [`UCommand`]: crate::tests::common::util::UCommand
/// [`TestScenario]: crate::tests::common::util::TestScenario
#[macro_export] #[macro_export]
macro_rules! new_ucmd { macro_rules! new_ucmd {
() => { () => {
@ -61,6 +84,18 @@ macro_rules! new_ucmd {
}; };
} }
/// Convenience macro for acquiring a [`UCommand`] builder and a test path.
///
/// Returns a tuple containing the following:
/// - an [`AsPath`] that points to a unique temporary test directory
/// - a [`UCommand`] builder for invoking the binary to be tested
///
/// This macro is intended for quick, single-call tests. For more complex tests
/// that require multiple invocations of the tested binary, see [`TestScenario`]
///
/// [`UCommand`]: crate::tests::common::util::UCommand
/// [`AsPath`]: crate::tests::common::util::AsPath
/// [`TestScenario]: crate::tests::common::util::TestScenario
#[macro_export] #[macro_export]
macro_rules! at_and_ucmd { macro_rules! at_and_ucmd {
() => {{ () => {{

View file

@ -1,3 +1,5 @@
#![allow(dead_code)]
use std::env; use std::env;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs::{self, File, OpenOptions}; use std::fs::{self, File, OpenOptions};
@ -27,7 +29,7 @@ static ALREADY_RUN: &str = " you have already run this UCommand, if you want to
testing();"; testing();";
static MULTIPLE_STDIN_MEANINGLESS: &str = "Ucommand is designed around a typical use case of: provide args and input stream -> spawn process -> block until completion -> return output streams. For verifying that a particular section of the input stream is what causes a particular behavior, use the Command type directly."; static MULTIPLE_STDIN_MEANINGLESS: &str = "Ucommand is designed around a typical use case of: provide args and input stream -> spawn process -> block until completion -> return output streams. For verifying that a particular section of the input stream is what causes a particular behavior, use the Command type directly.";
/// Test if the program are running under CI /// Test if the program is running under CI
pub fn is_ci() -> bool { pub fn is_ci() -> bool {
std::env::var("CI") std::env::var("CI")
.unwrap_or(String::from("false")) .unwrap_or(String::from("false"))
@ -55,14 +57,6 @@ fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: &Option<Rc<TempDir>>, file_rel_p
AtPath::new(tmpdir_path).read(file_rel_path.as_ref().to_str().unwrap()) AtPath::new(tmpdir_path).read(file_rel_path.as_ref().to_str().unwrap())
} }
pub fn repeat_str(s: &str, n: u32) -> String {
let mut repeated = String::new();
for _ in 0..n {
repeated.push_str(s);
}
repeated
}
/// A command result is the outputs of a command (streams and status code) /// A command result is the outputs of a command (streams and status code)
/// within a struct which has convenience assertion functions about those outputs /// within a struct which has convenience assertion functions about those outputs
#[derive(Debug)] #[derive(Debug)]
@ -384,8 +378,10 @@ impl AtPath {
/// An environment for running a single uutils test case, serves three functions: /// An environment for running a single uutils test case, serves three functions:
/// 1. centralizes logic for locating the uutils binary and calling the utility /// 1. centralizes logic for locating the uutils binary and calling the utility
/// 2. provides a temporary directory for the test case /// 2. provides a unique temporary directory for the test case
/// 3. copies over fixtures for the utility to the temporary directory /// 3. copies over fixtures for the utility to the temporary directory
///
/// Fixtures can be found under `tests/fixtures/$util_name/`
pub struct TestScenario { pub struct TestScenario {
bin_path: PathBuf, bin_path: PathBuf,
util_name: String, util_name: String,
@ -420,12 +416,16 @@ impl TestScenario {
ts ts
} }
/// Returns builder for invoking the target uutils binary. Paths given are
/// treated relative to the environment's unique temporary test directory.
pub fn ucmd(&self) -> UCommand { pub fn ucmd(&self) -> UCommand {
let mut cmd = self.cmd(&self.bin_path); let mut cmd = self.cmd(&self.bin_path);
cmd.arg(&self.util_name); cmd.arg(&self.util_name);
cmd cmd
} }
/// Returns builder for invoking any system command. Paths given are treated
/// relative to the environment's unique temporary test directory.
pub fn cmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand { pub fn cmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
UCommand::new_from_tmp(bin, self.tmpd.clone(), true) UCommand::new_from_tmp(bin, self.tmpd.clone(), true)
} }
@ -495,6 +495,8 @@ impl UCommand {
ucmd ucmd
} }
/// Add a parameter to the invocation. Path arguments are treated relative
/// to the test environment directory.
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> Box<&mut UCommand> { pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> Box<&mut UCommand> {
if self.has_run { if self.has_run {
panic!(ALREADY_RUN); panic!(ALREADY_RUN);
@ -505,6 +507,8 @@ impl UCommand {
Box::new(self) Box::new(self)
} }
/// Add multiple parameters to the invocation. Path arguments are treated relative
/// to the test environment directory.
pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> Box<&mut UCommand> { pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> Box<&mut UCommand> {
if self.has_run { if self.has_run {
panic!(MULTIPLE_STDIN_MEANINGLESS); panic!(MULTIPLE_STDIN_MEANINGLESS);

View file

View file

Binary file not shown.

BIN
tests/fixtures/head/zero_terminated.txt vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,2 @@
abc d e f g \t\t A