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
run: |
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
make PROFILE=release
BUILDDIR="$PWD/target/release/"
cp "${BUILDDIR}/install" "${BUILDDIR}/ginstall" # The GNU tests rename this script before running, to avoid confusion with the make target
# Create *sum binaries
for sum in b2sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum
do
sum_path="${BUILDDIR}/${sum}"
test -f "${sum_path}" || cp "${BUILDDIR}/hashsum" "${sum_path}"
done
test -f "${BUILDDIR}/[" || cp "${BUILDDIR}/test" "${BUILDDIR}/["
popd
GNULIB_SRCDIR="$PWD/gnulib"
pushd gnu/
# Any binaries that aren't built become `false` so their tests fail
for binary in $(./build-aux/gen-lists-of-programs.sh --list-progs)
do
bin_path="${BUILDDIR}/${binary}"
test -f "${bin_path}" || { echo "'${binary}' was not built with uutils, using the 'false' program"; cp "${BUILDDIR}/false" "${bin_path}"; }
done
./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR"
./configure --quiet --disable-gcc-warnings
#Add timeout to to protect against hangs
sed -i 's|"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver
# Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils
sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile
sed -i 's| tr | /usr/bin/tr |' tests/init.sh
make
# Generate the factor tests, so they can be fixed
for i in $(seq -w 1 36)
for i in {00..36}
do
make tests/factor/t${i}.sh
done
grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||'
sed -i -e 's|^seq |/usr/bin/seq |' 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
sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh
# Remove tests checking for --version & --help
# Not really interesting for us and logs are too big
sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \
-e '/tests\/misc\/help-version.sh/ D' \
-e '/tests\/misc\/help-version-getopt.sh/ D' \
Makefile
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}"
- name: Run GNU tests
@ -83,17 +111,22 @@ jobs:
GNULIB_DIR="${PWD}/gnulib"
pushd gnu
unbuffer timeout -sKILL 3600 make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || :
timeout -sKILL 2h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make
- name: Extract tests info
shell: bash
run: |
TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-)
FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
XPASS=$( grep "# XPASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
ERROR=$( grep "# ERROR:" gnu/tests/test-suite.log|cut -d' ' -f2-)
echo "::warning ::GNU testsuite = $TOTAL / $PASS / $FAIL / $ERROR"
if test -f gnu/tests/test-suite.log
then
TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-)
FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
XPASS=$( grep "# XPASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
ERROR=$( grep "# ERROR:" gnu/tests/test-suite.log|cut -d' ' -f2-)
echo "::warning ::GNU testsuite = $TOTAL / $PASS / $FAIL / $ERROR"
else
echo "::error ::Failed to get summary of test results"
fi
- uses: actions/upload-artifact@v2
with:

144
Cargo.lock generated
View file

@ -59,11 +59,6 @@ name = "bit-vec"
version = "0.6.3"
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]]
name = "bitflags"
version = "1.2.1"
@ -95,7 +90,7 @@ dependencies = [
"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)",
"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]]
@ -193,7 +188,7 @@ dependencies = [
"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)",
"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)",
"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)",
@ -300,6 +295,7 @@ dependencies = [
"uu_whoami 0.0.4",
"uu_yes 0.0.4",
"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)",
]
@ -404,13 +400,13 @@ dependencies = [
"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)",
"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)",
"serde 1.0.124 (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.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_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)",
"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]]
@ -472,7 +468,7 @@ dependencies = [
"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)",
"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]]
@ -517,7 +513,7 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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]]
@ -657,10 +653,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "js-sys"
version = "0.3.48"
version = "0.3.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
@ -726,17 +722,6 @@ dependencies = [
"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]]
name = "nix"
version = "0.13.1"
@ -854,8 +839,8 @@ dependencies = [
"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-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)",
"web-sys 0.3.48 (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.49 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1035,7 +1020,7 @@ dependencies = [
[[package]]
name = "regex"
version = "1.4.4"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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]]
name = "serde"
version = "1.0.124"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -1124,12 +1109,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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]]
name = "serde_derive"
version = "1.0.124"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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 = [
"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)",
"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]]
@ -1301,7 +1286,7 @@ name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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)",
]
@ -1397,7 +1382,7 @@ version = "0.0.4"
dependencies = [
"uucore 0.0.7",
"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]]
@ -1407,7 +1392,7 @@ dependencies = [
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7",
"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]]
@ -1418,14 +1403,14 @@ dependencies = [
"glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7",
"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]]
name = "uu_chroot"
version = "0.0.4"
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_procs 0.0.5",
]
@ -1460,7 +1445,7 @@ dependencies = [
"quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7",
"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)",
"xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1471,7 +1456,7 @@ version = "0.0.4"
dependencies = [
"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)",
"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)",
"uucore 0.0.7",
"uucore_procs 0.0.5",
@ -1491,8 +1476,10 @@ version = "0.0.4"
dependencies = [
"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)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7",
"uucore_procs 0.0.5",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1538,6 +1525,7 @@ dependencies = [
name = "uu_echo"
version = "0.0.4"
dependencies = [
"clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7",
"uucore_procs 0.0.5",
]
@ -1557,7 +1545,7 @@ dependencies = [
name = "uu_expand"
version = "0.0.4"
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)",
"uucore 0.0.7",
"uucore_procs 0.0.5",
@ -1634,7 +1622,7 @@ dependencies = [
"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)",
"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)",
"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)",
@ -1745,7 +1733,7 @@ name = "uu_ls"
version = "0.0.4"
dependencies = [
"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)",
"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)",
@ -1802,7 +1790,7 @@ name = "uu_more"
version = "0.0.4"
dependencies = [
"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_termios 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7",
@ -1838,7 +1826,7 @@ dependencies = [
"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)",
"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)",
"uucore 0.0.7",
"uucore_procs 0.0.5",
@ -1848,7 +1836,7 @@ dependencies = [
name = "uu_nohup"
version = "0.0.4"
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)",
"uucore 0.0.7",
"uucore_procs 0.0.5",
@ -1879,7 +1867,7 @@ name = "uu_od"
version = "0.0.4"
dependencies = [
"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)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7",
@ -1939,7 +1927,7 @@ dependencies = [
"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)",
"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)",
"uucore 0.0.7",
"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)",
"uucore 0.0.7",
"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]]
@ -2140,7 +2128,7 @@ dependencies = [
name = "uu_tee"
version = "0.0.4"
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)",
"uucore 0.0.7",
"uucore_procs 0.0.5",
@ -2209,7 +2197,7 @@ dependencies = [
name = "uu_tsort"
version = "0.0.4"
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_procs 0.0.5",
]
@ -2238,7 +2226,7 @@ dependencies = [
name = "uu_unexpand"
version = "0.0.4"
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)",
"uucore 0.0.7",
"uucore_procs 0.0.5",
@ -2357,7 +2345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "walkdir"
version = "2.3.1"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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]]
name = "wasm-bindgen"
version = "0.2.71"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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]]
name = "wasm-bindgen-backend"
version = "0.2.71"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"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)",
"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]]
name = "wasm-bindgen-macro"
version = "0.2.71"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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]]
name = "wasm-bindgen-macro-support"
version = "0.2.71"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"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-shared 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.72 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.71"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "web-sys"
version = "0.3.48"
version = "0.3.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"js-sys 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.71 (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.72 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2490,7 +2478,6 @@ dependencies = [
"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-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 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"
@ -2549,7 +2536,7 @@ dependencies = [
"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 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 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"
@ -2561,7 +2548,6 @@ dependencies = [
"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 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 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"
@ -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.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 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-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"
@ -2610,9 +2596,9 @@ dependencies = [
"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-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_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 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"
@ -2640,14 +2626,14 @@ dependencies = [
"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 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 wasm-bindgen 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7"
"checksum wasm-bindgen-backend 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8"
"checksum wasm-bindgen-macro 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b"
"checksum wasm-bindgen-macro-support 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e"
"checksum wasm-bindgen-shared 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "7d6f8ec44822dd71f5f221a5847fb34acd9060535c1211b70a05844c0f6383b1"
"checksum web-sys 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)" = "ec600b26223b2948cedfde2a0aa6756dcf1fef616f43d7b3097aaf53a6c4d92b"
"checksum wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe"
"checksum wasm-bindgen-backend 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3"
"checksum wasm-bindgen-macro 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b"
"checksum wasm-bindgen-macro-support 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d"
"checksum wasm-bindgen-shared 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa"
"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 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"

View file

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

View file

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

View file

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

View file

@ -10,60 +10,81 @@
#[macro_use]
extern crate uucore;
use uucore::entries;
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
use clap::{App, Arg};
use std::ffi::CString;
use std::io::Error;
use std::path::Path;
use std::process::Command;
use uucore::entries;
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
static VERSION: &str = env!("CARGO_PKG_VERSION");
static NAME: &str = "chroot";
static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT.";
static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]";
static SUMMARY: &str = "Run COMMAND with root directory set to NEWROOT.";
static LONG_HELP: &str = "
If COMMAND is not specified, it defaults to '$(SHELL) -i'.
If $(SHELL) is not set, /bin/sh is used.
";
mod options {
pub const NEWROOT: &str = "newroot";
pub const USER: &str = "user";
pub const GROUP: &str = "group";
pub const GROUPS: &str = "groups";
pub const USERSPEC: &str = "userspec";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optopt(
"u",
"user",
"User (ID or name) to switch before running the program",
"USER",
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(SYNTAX)
.arg(Arg::with_name(options::NEWROOT).hidden(true).required(true))
.arg(
Arg::with_name(options::USER)
.short("u")
.long(options::USER)
.help("User (ID or name) to switch before running the program")
.value_name("USER"),
)
.optopt("g", "group", "Group (ID or name) to switch to", "GROUP")
.optopt(
"G",
"groups",
"Comma-separated list of groups to switch to",
"GROUP1,GROUP2...",
.arg(
Arg::with_name(options::GROUP)
.short("g")
.long(options::GROUP)
.help("Group (ID or name) to switch to")
.value_name("GROUP"),
)
.optopt(
"",
"userspec",
"Colon-separated user and group to switch to. \
.arg(
Arg::with_name(options::GROUPS)
.short("G")
.long(options::GROUPS)
.help("Comma-separated list of groups to switch to")
.value_name("GROUP1,GROUP2..."),
)
.arg(
Arg::with_name(options::USERSPEC)
.long(options::USERSPEC)
.help(
"Colon-separated user and group to switch to. \
Same as -u USER -g GROUP. \
Userspec has higher preference than -u and/or -g",
"USER:GROUP",
)
.value_name("USER:GROUP"),
)
.parse(args);
if matches.free.is_empty() {
println!("Missing operand: NEWROOT");
println!("Try `{} --help` for more information.", NAME);
return 1;
}
.get_matches_from(args);
let default_shell: &'static str = "/bin/sh";
let default_option: &'static str = "-i";
let user_shell = std::env::var("SHELL");
let newroot = Path::new(&matches.free[0][..]);
let newroot: &Path = match matches.value_of(options::NEWROOT) {
Some(v) => Path::new(v),
None => crash!(
1,
"Missing operand: NEWROOT\nTry '{} --help' for more information.",
NAME
),
};
if !newroot.is_dir() {
crash!(
1,
@ -72,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
);
}
let command: Vec<&str> = match matches.free.len() {
let command: Vec<&str> = match matches.args.len() {
1 => {
let shell: &str = match user_shell {
Err(_) => default_shell,
@ -80,7 +101,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
};
vec![shell, default_option]
}
_ => matches.free[1..].iter().map(|x| &x[..]).collect(),
_ => {
let mut vector: Vec<&str> = Vec::new();
for (&k, v) in matches.args.iter() {
vector.push(k.clone());
vector.push(&v.vals[0].to_str().unwrap());
}
vector
}
};
set_context(&newroot, &matches);
@ -97,30 +125,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
}
fn set_context(root: &Path, options: &getopts::Matches) {
let userspec_str = options.opt_str("userspec");
let user_str = options.opt_str("user").unwrap_or_default();
let group_str = options.opt_str("group").unwrap_or_default();
let groups_str = options.opt_str("groups").unwrap_or_default();
fn set_context(root: &Path, options: &clap::ArgMatches) {
let userspec_str = options.value_of(options::USERSPEC);
let user_str = options.value_of(options::USER).unwrap_or_default();
let group_str = options.value_of(options::GROUP).unwrap_or_default();
let groups_str = options.value_of(options::GROUPS).unwrap_or_default();
let userspec = match userspec_str {
Some(ref u) => {
let s: Vec<&str> = u.split(':').collect();
if s.len() != 2 {
if s.len() != 2 || s.iter().any(|&spec| spec == "") {
crash!(1, "invalid userspec: `{}`", u)
};
s
}
None => Vec::new(),
};
let user = if userspec.is_empty() {
&user_str[..]
let (user, group) = if userspec.is_empty() {
(&user_str[..], &group_str[..])
} else {
&userspec[0][..]
};
let group = if userspec.is_empty() {
&group_str[..]
} else {
&userspec[1][..]
(&userspec[0][..], &userspec[1][..])
};
enter_chroot(root);
@ -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 {
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::path::Path;
include!(concat!(env!("OUT_DIR"), "/crc_table.rs"));
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
const CRC_TABLE_LEN: usize = 256;
const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table();
static SYNTAX: &str = "[OPTIONS] [FILE]...";
static SUMMARY: &str = "Print CRC and size for each file";
static LONG_HELP: &str = "";
const SYNTAX: &str = "[OPTIONS] [FILE]...";
const SUMMARY: &str = "Print CRC and size for each file";
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]
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),
}
}
//Ok((0 as u32,0 as usize))
}
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.
macro_rules! prompt_yes(
($($args:tt)+) => ({
@ -431,6 +431,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
.short("d")
.help("same as --no-dereference --preserve=links"))
.arg(Arg::with_name(OPT_ONE_FILE_SYSTEM)
.short("x")
.long(OPT_ONE_FILE_SYSTEM)
.help("stay on this file system"))
// TODO: implement the following args
.arg(Arg::with_name(OPT_COPY_CONTENTS)
@ -442,10 +446,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(true)
.value_name("WHEN")
.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)
.long(OPT_CONTEXT)
.takes_value(true)
@ -563,6 +563,7 @@ impl Options {
let not_implemented_opts = vec![
OPT_COPY_CONTENTS,
OPT_SPARSE,
#[cfg(not(any(windows, unix)))]
OPT_ONE_FILE_SYSTEM,
OPT_CONTEXT,
#[cfg(windows)]
@ -937,7 +938,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
#[cfg(any(windows, target_os = "redox"))]
let mut hard_links: Vec<(String, u64)> = vec![];
for path in WalkDir::new(root) {
for path in WalkDir::new(root).same_file_system(options.one_file_system) {
let p = or_continue!(path);
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
let path = if (options.no_dereference || options.dereference) && is_symlink {

View file

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

View file

@ -12,13 +12,20 @@
#[macro_use]
extern crate uucore;
use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
#[cfg(windows)]
use chrono::{Datelike, Timelike};
use clap::{App, Arg};
use chrono::offset::Utc;
use chrono::{DateTime, FixedOffset, Local, Offset};
#[cfg(all(unix, not(target_os = "macos")))]
use libc::{clock_settime, timespec, CLOCK_REALTIME};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
#[cfg(windows)]
use winapi::{
shared::minwindef::WORD,
um::{minwinbase::SYSTEMTIME, sysinfoapi::SetSystemTime},
};
// Options
const DATE: &str = "date";
@ -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.
Example: 2006-08-14 02:34:56-06:00";
#[cfg(not(target_os = "macos"))]
static OPT_SET_HELP_STRING: &str = "set time described by STRING";
#[cfg(target_os = "macos")]
static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)";
/// Settings for this program, parsed from the command line
struct Settings {
utc: bool,
@ -186,7 +198,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("s")
.long(OPT_SET)
.takes_value(true)
.help("set time described by STRING"),
.help(OPT_SET_HELP_STRING),
)
.arg(
Arg::with_name(OPT_UNIVERSAL)
@ -222,18 +234,31 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
DateSource::Now
};
let set_to = match matches.value_of(OPT_SET).map(parse_date) {
None => None,
Some(Err((input, _err))) => {
eprintln!("date: invalid date '{}'", input);
return 1;
}
Some(Ok(date)) => Some(date),
};
let settings = Settings {
utc: matches.is_present(OPT_UNIVERSAL),
format,
date_source,
// TODO: Handle this option:
set_to: None,
set_to,
};
if let Some(_time) = settings.set_to {
unimplemented!();
// Probably need to use this syscall:
// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html
if let Some(date) = settings.set_to {
// All set time functions expect UTC datetimes.
let date: DateTime<Utc> = if settings.utc {
date.with_timezone(&Utc)
} else {
date.into()
};
return set_system_datetime(date);
} else {
// Declare a file here because it needs to outlive the `dates` iterator.
let file: File;
@ -247,15 +272,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
now.with_timezone(now.offset())
};
/// Parse a `String` into a `DateTime`.
/// If it fails, return a tuple of the `String` along with its `ParseError`.
fn parse_date(
s: String,
) -> Result<DateTime<FixedOffset>, (String, chrono::format::ParseError)> {
// TODO: The GNU date command can parse a wide variety of inputs.
s.parse().map_err(|e| (s, e))
}
// Iterate over all dates - whether it's a single date or a file.
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
DateSource::Custom(ref input) => {
@ -314,3 +330,76 @@ fn make_format_string(settings: &Settings) -> &str {
Format::Default => "%c",
}
}
/// Parse a `String` into a `DateTime`.
/// If it fails, return a tuple of the `String` along with its `ParseError`.
fn parse_date<S: AsRef<str> + Clone>(
s: S,
) -> Result<DateTime<FixedOffset>, (String, chrono::format::ParseError)> {
// TODO: The GNU date command can parse a wide variety of inputs.
s.as_ref().parse().map_err(|e| (s.as_ref().into(), e))
}
#[cfg(not(any(unix, windows)))]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
unimplemented!("setting date not implemented (unsupported target)");
}
#[cfg(target_os = "macos")]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
eprintln!("date: setting the date is not supported by macOS");
return 1;
}
#[cfg(all(unix, not(target_os = "macos")))]
/// System call to set date (unix).
/// See here for more:
/// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html
/// https://linux.die.net/man/3/clock_settime
/// https://www.gnu.org/software/libc/manual/html_node/Time-Types.html
fn set_system_datetime(date: DateTime<Utc>) -> i32 {
let timespec = timespec {
tv_sec: date.timestamp() as _,
tv_nsec: date.timestamp_subsec_nanos() as _,
};
let result = unsafe { clock_settime(CLOCK_REALTIME, &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)]
use std::mem;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use libc::c_int;
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
use libc::statfs;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::ffi::CStr;
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "windows"))]
#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "windows"))]
use std::ptr;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::slice;
#[cfg(target_os = "freebsd")]
@ -137,7 +137,7 @@ struct MountInfo {
#[cfg(all(
target_os = "freebsd",
not(all(target_os = "macos", target_arch = "x86_64"))
not(all(target_vendor = "apple", target_arch = "x86_64"))
))]
#[repr(C)]
#[derive(Copy, Clone)]
@ -209,20 +209,20 @@ fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!())
}
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
extern "C" {
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
#[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
#[link_name = "getmntinfo$INODE64"]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
#[cfg(any(
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;
}
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
impl From<statfs> for MountInfo {
fn from(statfs: statfs) -> Self {
let mut info = MountInfo {
@ -585,7 +585,7 @@ fn read_fs_list() -> Vec<MountInfo> {
})
.collect::<Vec<_>>()
}
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
{
let mut mptr: *mut statfs = ptr::null_mut();
let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) };

View file

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

View file

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

View file

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

View file

@ -9,7 +9,7 @@ use smallvec::SmallVec;
use std::cell::RefCell;
use std::fmt;
use crate::numeric::{gcd, Arithmetic, Montgomery};
use crate::numeric::{Arithmetic, Montgomery};
use crate::{miller_rabin, rho, table};
type Exponent = u8;
@ -29,20 +29,15 @@ impl Decomposition {
fn add(&mut self, factor: u64, exp: Exponent) {
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))
}
fn is_one(&self) -> bool {
self.0.is_empty()
}
fn pop(&mut self) -> Option<(u64, Exponent)> {
self.0.pop()
if let Some((_, e)) = self.0.iter_mut().find(|(f, _)| *f == factor) {
*e += exp;
} else {
self.0.push((factor, exp))
}
}
#[cfg(test)]
fn product(&self) -> u64 {
self.0
.iter()
@ -86,11 +81,11 @@ impl Factors {
self.0.borrow_mut().add(prime, exp)
}
#[cfg(test)]
pub fn push(&mut self, prime: u64) {
self.add(prime, 1)
}
#[cfg(test)]
fn product(&self) -> u64 {
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::*;
// 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);
match miller_rabin::test::<A>(n) {
Prime => None,
Composite(d) => Some(d),
Pseudoprime => Some(rho::find_divisor::<A>(n)),
}
let divisor = match miller_rabin::test::<A>(n) {
Prime => {
let mut r = f;
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> {
if num < (1 << 32) {
_find_factor::<Montgomery<u32>>(num)
} else {
_find_factor::<Montgomery<u64>>(num)
}
}
pub fn factor(num: u64) -> Factors {
pub fn factor(mut n: u64) -> Factors {
let mut factors = Factors::one();
if num < 2 {
if n < 2 {
return factors;
}
let mut n = num;
let n_zeros = num.trailing_zeros();
let n_zeros = n.trailing_zeros();
if n_zeros > 0 {
factors.add(2, n_zeros as Exponent);
n >>= n_zeros;
}
debug_assert_eq!(num, n * factors.product());
if n == 1 {
return factors;
}
table::factor(&mut n, &mut factors);
debug_assert_eq!(num, n * factors.product());
let (factors, n) = table::factor(n, factors);
if n == 1 {
return factors;
if n < (1 << 32) {
_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)]

View file

@ -14,8 +14,7 @@ use crate::Factors;
include!(concat!(env!("OUT_DIR"), "/prime_table.rs"));
pub(crate) fn factor(n: &mut u64, factors: &mut Factors) {
let mut num = *n;
pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) {
for &(prime, inv, ceil) in P_INVS_U64 {
if num == 1 {
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 {
mode: FilterMode,
verbose: bool,
zero_terminated: bool,
}
impl Default for Settings {
@ -36,6 +37,7 @@ impl Default for Settings {
Settings {
mode: FilterMode::Lines(10),
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("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("V", "version", "output version information and exit")
.parse(new_args);
@ -113,6 +116,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let quiet = matches.opt_present("q");
let verbose = matches.opt_present("v");
settings.zero_terminated = matches.opt_present("z");
let files = matches.free;
// 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;
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 mut buffer = BufReader::new(reader);
if !head(&mut buffer, &settings) {
@ -203,8 +214,14 @@ fn head<T: Read>(reader: &mut BufReader<T>, settings: &Settings) -> bool {
}
}
FilterMode::Lines(count) => {
for line in reader.lines().take(count) {
println!("{}", line.unwrap());
if settings.zero_terminated {
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) => {

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>) {
let uid = possible_uid.unwrap_or_else(getuid);
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;
for sourcepath in files.iter() {
let targetpath = match sourcepath.as_os_str().to_str() {
Some(name) => target_dir.join(name),
None => {
show_error!(
"cannot stat '{}': No such file or directory",
sourcepath.display()
);
if !sourcepath.exists() {
show_info!(
"cannot stat '{}': No such file or directory",
sourcepath.display()
);
all_successful = false;
continue;
}
};
all_successful = false;
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() {
all_successful = false;

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/ls.rs"
[dependencies]
getopts = "0.2.18"
clap = "2.33"
lazy_static = "1.0.1"
number_prefix = "0.4"
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]
getopts = "0.2.18"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" }
uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
[target.'cfg(target_os = "redox")'.dependencies]
redox_termios = "0.1"
redox_syscall = "0.1"
[target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies]
nix = "0.8.1"
nix = "<=0.13"
[[bin]]
name = "more"

View file

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

View file

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

View file

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

View file

@ -10,6 +10,7 @@
#[macro_use]
extern crate uucore;
use clap::{App, AppSettings, Arg};
use libc::{c_char, dup2, execvp, signal};
use libc::{SIGHUP, SIG_IGN};
use std::env;
@ -20,50 +21,42 @@ use std::os::unix::prelude::*;
use std::path::{Path, PathBuf};
use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive};
static NAME: &str = "nohup";
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")]
extern "C" {
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()
mod options {
pub const CMD: &str = "cmd";
}
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();
unsafe { signal(SIGHUP, SIG_IGN) };
@ -73,13 +66,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
};
let cstrs: Vec<CString> = matches
.free
.iter()
.values_of(options::CMD)
.unwrap()
.map(|x| CString::new(x.as_bytes()).unwrap())
.collect();
let mut args: Vec<*const c_char> = cstrs.iter().map(|s| s.as_ptr()).collect();
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() {
@ -108,23 +106,32 @@ fn replace_fds() {
}
fn find_stdout() -> File {
let internal_failure_code = match std::env::var("POSIXLY_CORRECT") {
Ok(_) => POSIX_NOHUP_FAILURE,
Err(_) => EXIT_CANCELED,
};
match OpenOptions::new()
.write(true)
.create(true)
.append(true)
.open(Path::new("nohup.out"))
.open(Path::new(NOHUP_OUT))
{
Ok(t) => {
show_warning!("Output is redirected to: nohup.out");
show_info!("ignoring input and appending output to '{}'", NOHUP_OUT);
t
}
Err(e) => {
Err(e1) => {
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,
};
let mut homeout = PathBuf::from(home);
homeout.push("nohup.out");
homeout.push(NOHUP_OUT);
let homeout_str = homeout.to_str().unwrap();
match OpenOptions::new()
.write(true)
.create(true)
@ -132,30 +139,29 @@ fn find_stdout() -> File {
.open(&homeout)
{
Ok(t) => {
show_warning!("Output is redirected to: {:?}", homeout);
show_info!("ignoring input and appending output to '{}'", homeout_str);
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) {
let msg = format!(
"{0} {1}
Usage:
{0} COMMAND [ARG]...
{0} OPTION
Run COMMAND ignoring hangup signals.
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.",
NAME, VERSION
);
print!("{}", opts.usage(&msg));
fn get_usage() -> String {
format!("{0} COMMAND [ARG]...\n {0} FLAG", executable!())
}
#[cfg(target_vendor = "apple")]
extern "C" {
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()
}

View file

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

View file

@ -16,7 +16,7 @@ path = "src/od.rs"
[dependencies]
byteorder = "1.3.2"
getopts = "0.2.18"
clap = "2.33"
half = "1.6"
libc = "0.2.42"
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::peekreader::*;
use crate::prn_char::format_ascii_dump;
use clap::{self, AppSettings, Arg, ArgMatches};
static VERSION: &str = env!("CARGO_PKG_VERSION");
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 [-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
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
@ -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
exitcode will be non-zero."#;
fn create_getopts_options() -> getopts::Options {
let mut opts = getopts::Options::new();
opts.optopt(
"A",
"address-radix",
"Select the base in which file offsets are printed.",
"RADIX",
);
opts.optopt(
"j",
"skip-bytes",
"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
pub(crate) mod options {
pub const ADDRESS_RADIX: &str = "address-radix";
pub const SKIP_BYTES: &str = "skip-bytes";
pub const READ_BYTES: &str = "read-bytes";
pub const ENDIAN: &str = "endian";
pub const STRINGS: &str = "strings";
pub const FORMAT: &str = "format";
pub const OUTPUT_DUPLICATES: &str = "output-duplicates";
pub const TRADITIONAL: &str = "traditional";
pub const WIDTH: &str = "width";
pub const VERSION: &str = "version";
pub const FILENAME: &str = "FILENAME";
}
struct OdOptions {
@ -182,8 +118,8 @@ struct OdOptions {
}
impl OdOptions {
fn new(matches: getopts::Matches, args: Vec<String>) -> Result<OdOptions, String> {
let byte_order = match matches.opt_str("endian").as_ref().map(String::as_ref) {
fn new<'a>(matches: ArgMatches<'a>, args: Vec<String>) -> Result<OdOptions, String> {
let byte_order = match matches.value_of(options::ENDIAN) {
None => ByteOrder::Native,
Some("little") => ByteOrder::Little,
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,
Some(s) => match parse_number_of_bytes(&s) {
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,
Some(_) if matches.occurrences_of(options::WIDTH) == 0 => 16,
Some(s) => s.parse::<usize>().unwrap_or(0),
};
let min_bytes = formats.iter().fold(1, |max, next| {
@ -235,9 +172,9 @@ impl OdOptions {
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,
Some(s) => match parse_number_of_bytes(&s) {
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,
Some(s) => {
let st = s.into_bytes();
let st = s.as_bytes();
if st.len() != 1 {
return Err("Radix must be one of [d, o, n, x]".to_string());
} else {
@ -286,26 +223,244 @@ impl OdOptions {
pub fn uumain(args: impl uucore::Args) -> i32 {
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..]) {
Ok(m) => m,
Err(f) => {
show_usage_error!("{}", f);
return 1;
}
};
let clap_matches = clap_opts
.clone() // Clone to reuse clap_otps to print help
.get_matches_from(args.clone());
if matches.opt_present("help") {
println!("{}", opts.usage(&USAGE));
return 0;
}
if matches.opt_present("version") {
if clap_matches.is_present(options::VERSION) {
println!("{} {}", executable!(), VERSION);
return 0;
}
let od_options = match OdOptions::new(matches, args) {
let od_options = match OdOptions::new(clap_matches, args) {
Err(s) => {
show_usage_error!("{}", s);
return 1;

View file

@ -1,20 +1,24 @@
use getopts::Matches;
use super::options;
use clap::ArgMatches;
/// Abstraction for getopts
pub trait CommandLineOpts {
/// 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.
fn opts_present(&self, _: &[&str]) -> bool;
}
/// Implementation for `getopts`
impl CommandLineOpts for Matches {
fn inputs(&self) -> Vec<String> {
self.free.clone()
impl<'a> CommandLineOpts for ArgMatches<'a> {
fn inputs(&self) -> Vec<&str> {
self.values_of(options::FILENAME)
.map(|values| values.collect())
.unwrap_or_default()
}
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
/// there is no input, as stdin is the default input.
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"]) {
return parse_inputs_traditional(input_strings);
@ -59,7 +63,7 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs,
}
if input_strings.len() == 2 {
return Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone(),
input_strings[0].clone().to_owned(),
n,
None,
)));
@ -69,23 +73,27 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs,
}
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
///
/// normally returns CommandLineInputs::FileAndOffset, but if no offset is found,
/// 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() {
0 => Ok(CommandLineInputs::FileNames(vec!["-".to_string()])),
1 => {
let offset0 = parse_offset_operand(&input_strings[0]);
Ok(match offset0 {
Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)),
_ => CommandLineInputs::FileNames(input_strings),
_ => CommandLineInputs::FileNames(
input_strings.iter().map(|s| s.to_string()).collect(),
),
})
}
2 => {
@ -98,7 +106,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<String>) -> Result<CommandLin
Some(m),
))),
(_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone(),
input_strings[0].clone().to_owned(),
m,
None,
))),
@ -110,7 +118,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<String>) -> Result<CommandLin
let label = parse_offset_operand(&input_strings[2]);
match (offset, label) {
(Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone(),
input_strings[0].clone().to_owned(),
n,
Some(m),
))),
@ -178,8 +186,8 @@ mod tests {
}
impl<'a> CommandLineOpts for MockOptions<'a> {
fn inputs(&self) -> Vec<String> {
self.inputs.clone()
fn inputs(&self) -> Vec<&str> {
self.inputs.iter().map(|s| s.as_str()).collect()
}
fn opts_present(&self, opts: &[&str]) -> bool {
for expected in opts.iter() {

View file

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

View file

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

View file

@ -10,7 +10,7 @@
#[macro_use]
extern crate uucore;
use getopts::{Matches, Options};
use clap::{App, Arg};
use regex::Regex;
use std::cmp;
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 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)]
enum OutFormat {
@ -61,8 +67,11 @@ impl Default for Config {
}
}
fn read_word_filter_file(matches: &Matches, option: &str) -> HashSet<String> {
let filename = matches.opt_str(option).expect("parsing options failed!");
fn read_word_filter_file(matches: &clap::ArgMatches, option: &str) -> HashSet<String> {
let filename = matches
.value_of(option)
.expect("parsing options failed!")
.to_string();
let reader = BufReader::new(crash_if_err!(1, File::open(filename)));
let mut words: HashSet<String> = HashSet::new();
for word in reader.lines() {
@ -81,23 +90,29 @@ struct WordFilter {
}
impl WordFilter {
fn new(matches: &Matches, config: &Config) -> WordFilter {
let (o, oset): (bool, HashSet<String>) = if matches.opt_present("o") {
(true, read_word_filter_file(matches, "o"))
fn new(matches: &clap::ArgMatches, config: &Config) -> WordFilter {
let (o, oset): (bool, HashSet<String>) = if matches.is_present(options::ONLY_FILE) {
(true, read_word_filter_file(matches, options::ONLY_FILE))
} else {
(false, HashSet::new())
};
let (i, iset): (bool, HashSet<String>) = if matches.opt_present("i") {
(true, read_word_filter_file(matches, "i"))
let (i, iset): (bool, HashSet<String>) = if matches.is_present(options::IGNORE_FILE) {
(true, read_word_filter_file(matches, options::IGNORE_FILE))
} else {
(false, HashSet::new())
};
if matches.opt_present("b") {
if matches.is_present(options::BREAK_FILE) {
crash!(1, "-b not implemented yet");
}
// Ignore empty string regex from cmd-line-args
let arg_reg: Option<String> = if matches.opt_present("W") {
matches.opt_str("W").filter(|reg| !reg.is_empty())
let arg_reg: Option<String> = if matches.is_present(options::WORD_REGEXP) {
match matches.value_of(options::WORD_REGEXP) {
Some(v) => match v.is_empty() {
true => None,
false => Some(v.to_string()),
},
None => None,
}
} else {
None
};
@ -131,55 +146,50 @@ struct WordRef {
filename: String,
}
fn print_version() {
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 {
fn get_config(matches: &clap::ArgMatches) -> Config {
let mut config: Config = Default::default();
let err_msg = "parsing options failed";
if matches.opt_present("G") {
if matches.is_present(options::TRADITIONAL) {
config.gnu_ext = false;
config.format = OutFormat::Roff;
config.context_regex = "[^ \t\n]+".to_owned();
} else {
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");
}
config.auto_ref = matches.opt_present("A");
config.input_ref = matches.opt_present("r");
config.right_ref &= matches.opt_present("R");
config.ignore_case = matches.opt_present("f");
if matches.opt_present("M") {
config.macro_name = matches.opt_str("M").expect(err_msg);
config.auto_ref = matches.is_present(options::AUTO_REFERENCE);
config.input_ref = matches.is_present(options::REFERENCES);
config.right_ref &= matches.is_present(options::RIGHT_SIDE_REFS);
config.ignore_case = matches.is_present(options::IGNORE_CASE);
if matches.is_present(options::MACRO_NAME) {
config.macro_name = matches
.value_of(options::MACRO_NAME)
.expect(err_msg)
.to_string();
}
if matches.opt_present("F") {
config.trunc_str = matches.opt_str("F").expect(err_msg);
if matches.is_present(options::IGNORE_CASE) {
config.trunc_str = matches
.value_of(options::IGNORE_CASE)
.expect(err_msg)
.to_string();
}
if matches.opt_present("w") {
let width_str = matches.opt_str("w").expect(err_msg);
if matches.is_present(options::WIDTH) {
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));
}
if matches.opt_present("g") {
let gap_str = matches.opt_str("g").expect(err_msg);
if matches.is_present(options::GAP_SIZE) {
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));
}
if matches.opt_present("O") {
if matches.is_present(options::FORMAT_ROFF) {
config.format = OutFormat::Roff;
}
if matches.opt_present("T") {
if matches.is_present(options::FORMAT_TEX) {
config.format = OutFormat::Tex;
}
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 {
let args = args.collect_str();
let mut opts = Options::new();
opts.optflag(
"A",
"auto-reference",
"output automatically generated references",
);
opts.optflag("G", "traditional", "behave more like System V 'ptx'");
opts.optopt(
"F",
"flag-truncation",
"use STRING for flagging line truncations",
"STRING",
);
opts.optopt(
"M",
"macro-name",
"macro name to use instead of 'xx'",
"STRING",
);
opts.optflag("O", "format=roff", "generate output as roff directives");
opts.optflag(
"R",
"right-side-refs",
"put references at right, not counted in -w",
);
opts.optopt(
"S",
"sentence-regexp",
"for end of lines or end of sentences",
"REGEXP",
);
opts.optflag("T", "format=tex", "generate output as TeX directives");
opts.optopt(
"W",
"word-regexp",
"use REGEXP to match each keyword",
"REGEXP",
);
opts.optopt(
"b",
"break-file",
"word break characters in this FILE",
"FILE",
);
opts.optflag(
"f",
"ignore-case",
"fold lower case to upper case for sorting",
);
opts.optopt(
"g",
"gap-size",
"gap size in columns between output fields",
"NUMBER",
);
opts.optopt(
"i",
"ignore-file",
"read ignore word list from FILE",
"FILE",
);
opts.optopt(
"o",
"only-file",
"read only word list from this FILE",
"FILE",
);
opts.optflag("r", "references", "first field of each line is a reference");
opts.optopt(
"w",
"width",
"output width in columns, reference excluded",
"NUMBER",
);
opts.optflag("", "help", "display this help and exit");
opts.optflag("", "version", "output version information and exit");
// let mut opts = Options::new();
let matches = App::new(executable!())
.name(NAME)
.version(VERSION)
.usage(BRIEF)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.arg(
Arg::with_name(options::AUTO_REFERENCE)
.short("A")
.long(options::AUTO_REFERENCE)
.help("output automatically generated references")
.takes_value(false),
)
.arg(
Arg::with_name(options::TRADITIONAL)
.short("G")
.long(options::TRADITIONAL)
.help("behave more like System V 'ptx'"),
)
.arg(
Arg::with_name(options::FLAG_TRUNCATION)
.short("F")
.long(options::FLAG_TRUNCATION)
.help("use STRING for flagging line truncations")
.value_name("STRING")
.takes_value(true),
)
.arg(
Arg::with_name(options::MACRO_NAME)
.short("M")
.long(options::MACRO_NAME)
.help("macro name to use instead of 'xx'")
.value_name("STRING")
.takes_value(true),
)
.arg(
Arg::with_name(options::FORMAT_ROFF)
.short("O")
.long(options::FORMAT_ROFF)
.help("generate output as roff directives"),
)
.arg(
Arg::with_name(options::RIGHT_SIDE_REFS)
.short("R")
.long(options::RIGHT_SIDE_REFS)
.help("put references at right, not counted in -w")
.takes_value(false),
)
.arg(
Arg::with_name(options::SENTENCE_REGEXP)
.short("S")
.long(options::SENTENCE_REGEXP)
.help("for end of lines or end of sentences")
.value_name("REGEXP")
.takes_value(true),
)
.arg(
Arg::with_name(options::FORMAT_TEX)
.short("T")
.long(options::FORMAT_TEX)
.help("generate output as TeX directives"),
)
.arg(
Arg::with_name(options::WORD_REGEXP)
.short("W")
.long(options::WORD_REGEXP)
.help("use REGEXP to match each keyword")
.value_name("REGEXP")
.takes_value(true),
)
.arg(
Arg::with_name(options::BREAK_FILE)
.short("b")
.long(options::BREAK_FILE)
.help("word break characters in this FILE")
.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 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 output_file = if !config.gnu_ext && matches.free.len() == 2 {
matches.free[1].clone()
let output_file = if !config.gnu_ext && matches.args.len() == 2 {
matches.value_of(options::FILE).unwrap_or("-").to_string()
} else {
"-".to_owned()
};

View file

@ -149,7 +149,7 @@ use std::path::Path;
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_vendor = "apple",
target_os = "android",
target_os = "freebsd"
))]
@ -165,7 +165,7 @@ use uucore::libc::statvfs as Sstatfs;
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_vendor = "apple",
target_os = "android",
target_os = "freebsd"
))]
@ -211,11 +211,11 @@ impl FsMeta for Sstatfs {
fn free_fnodes(&self) -> 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 {
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 {
// FIXME: statvfs doesn't have an equivalent, so we need to do something else
unimplemented!()
@ -225,12 +225,12 @@ impl FsMeta for Sstatfs {
fn iosize(&self) -> 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 {
self.f_iosize as u64
}
// 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 {
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
// 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 {
let f_fsid: &[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])
}
#[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 {
self.f_fsid as u64
}
@ -256,7 +256,7 @@ impl FsMeta for Sstatfs {
fn namelen(&self) -> u64 {
self.f_namelen as u64
}
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
fn namelen(&self) -> u64 {
1024
}
@ -265,7 +265,7 @@ impl FsMeta for Sstatfs {
self.f_namemax as u64
}
// 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 {
self.f_namemax as u64
}

View file

@ -4,12 +4,12 @@ use std::env;
use std::fs;
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 {
pub const DYLIB_EXT: &str = ".so";
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg(any(target_vendor = "apple"))]
mod platform {
pub const DYLIB_EXT: &str = ".dylib";
}

View file

@ -57,7 +57,7 @@ fn preload_strings() -> (&'static str, &'static str) {
("LD_PRELOAD", "so")
}
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
fn preload_strings() -> (&'static str, &'static str) {
("DYLD_LIBRARY_PATH", "dylib")
}
@ -67,7 +67,7 @@ fn preload_strings() -> (&'static str, &'static str) {
target_os = "freebsd",
target_os = "netbsd",
target_os = "dragonflybsd",
target_os = "macos"
target_vendor = "apple"
)))]
fn preload_strings() -> (&'static str, &'static str) {
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")
.long(options::BYTES)
.takes_value(true)
.allow_hyphen_values(true)
.help("Number of bytes to print"),
)
.arg(
@ -93,6 +94,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("n")
.long(options::LINES)
.takes_value(true)
.allow_hyphen_values(true)
.help("Number of lines to print"),
)
.arg(
@ -343,9 +345,9 @@ pub fn parse_size(mut size_slice: &str) -> Result<u64, ParseSizeErr> {
// sole B is not a valid suffix
Err(ParseSizeErr::parse_failure(size_slice))
} else {
let value: Option<u64> = size_slice.parse().ok();
let value: Option<i64> = size_slice.parse().ok();
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)))
}
}

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/tee.rs"
[dependencies]
getopts = "0.2.18"
clap = "2.33.3"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["libc"] }
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
// * file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs::OpenOptions;
use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write};
use std::path::{Path, PathBuf};
@ -12,80 +16,61 @@ use std::path::{Path, PathBuf};
#[cfg(unix)]
use uucore::libc;
static NAME: &str = "tee";
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 {
let args = args.collect_str();
match options(&args).and_then(exec) {
Ok(_) => 0,
Err(_) => 1,
}
mod options {
pub const APPEND: &str = "append";
pub const IGNORE_INTERRUPTS: &str = "ignore-interrupts";
pub const FILE: &str = "file";
}
#[allow(dead_code)]
struct Options {
program: String,
append: bool,
ignore_interrupts: bool,
print_and_exit: Option<String>,
files: Vec<String>,
}
fn options(args: &[String]) -> Result<Options> {
let mut opts = getopts::Options::new();
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 get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!())
}
fn exec(options: Options) -> Result<()> {
match options.print_and_exit {
Some(text) => {
println!("{}", text);
Ok(())
}
None => tee(options),
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.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) {
Err(f) => {
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)
}
okay => okay,
@ -184,7 +169,7 @@ impl Write for NamedWriter {
match self.inner.flush() {
Err(f) => {
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)
}
okay => okay,
@ -200,15 +185,10 @@ impl Read for NamedReader {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
match self.inner.read(buf) {
Err(f) => {
warn(format!("{}: {}", Path::new("stdin").display(), f.to_string()).as_ref());
show_warning!("{}: {}", Path::new("stdin").display(), f.to_string());
Err(f)
}
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"
[dependencies]
getopts = "0.2.18"
clap= "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -9,49 +9,35 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path;
static NAME: &str = "tsort";
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 {
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");
opts.optflag("V", "version", "output version information and exit");
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 input = match matches.value_of(options::FILE) {
Some(v) => v,
None => "-",
};
let mut stdin_buf;

View file

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

View file

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

View file

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

View file

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

View file

@ -34,7 +34,7 @@
//! 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::{c_char, c_int, gid_t, uid_t};
use libc::{getgrgid, getgrnam, getgroups, getpwnam, getpwuid, group, passwd};
@ -119,19 +119,19 @@ impl Passwd {
}
/// 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> {
cstr2cow!(self.inner.pw_class)
}
/// 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 {
self.inner.pw_change
}
/// 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 {
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] = [
Signal {
name: "HUP",

View file

@ -47,7 +47,7 @@ use libc::utmpx;
pub use libc::endutxent;
pub use libc::getutxent;
pub use libc::setutxent;
#[cfg(any(target_os = "macos", target_os = "linux"))]
#[cfg(any(target_vendor = "apple", target_os = "linux"))]
pub use libc::utmpxname;
#[cfg(target_os = "freebsd")]
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;
}
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
mod ut {
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
// 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_rules! executable(
() => ({
@ -18,6 +21,7 @@ macro_rules! executable(
})
);
/// Show an error to stderr in a silimar style to GNU coreutils.
#[macro_export]
macro_rules! show_error(
($($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_rules! show_warning(
($($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_rules! show_info(
($($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_rules! show_usage_error(
($($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_rules! crash(
($exit_code:expr, $($args:tt)+) => ({
@ -59,6 +67,7 @@ macro_rules! crash(
})
);
/// Calls `exit()` with the provided exit code.
#[macro_export]
macro_rules! exit(
($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_rules! crash_if_err(
($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_rules! return_if_err(
($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_rules! safe_unwrap(
($exp:expr) => (

View file

@ -10,16 +10,16 @@ fn test_output_multi_files_print_all_chars() {
.succeeds()
.stdout_only(
" 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 \
7\t^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\\^]^^^_ \
!\"#$%&\'()*+,-./0123456789:;\
<=>?@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\
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-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-\
pM-qM-rM-sM-tM-uM-vM-wM-xM-yM-zM-{M-|M-}M-~M-^?",
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^[^\\^]^^^_ \
!\"#$%&\'()*+,-./0123456789:;\
<=>?@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\
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-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-\
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()
.stdout_only(
" 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]
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
fn test_reference() {
new_ucmd!()
.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_TO_FOLDER_NEW: &str = "hello_dir_new";
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]
fn test_cp_cp() {
@ -1001,3 +1007,70 @@ fn test_cp_target_file_dev_null() {
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 crate::common::util::*;
#[cfg(all(unix, not(target_os = "macos")))]
use rust_users::*;
#[test]
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();
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_eq!(result.stderr, "");
}
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
fn _du_basics(s: String) {
let answer = "32\t./subdir
8\t./subdir/deeper
@ -21,7 +21,7 @@ fn _du_basics(s: String) {
";
assert_eq!(s, answer);
}
#[cfg(not(target_os = "macos"))]
#[cfg(not(target_vendor = "apple"))]
fn _du_basics(s: String) {
let answer = "28\t./subdir
8\t./subdir/deeper
@ -41,11 +41,11 @@ fn test_du_basics_subdir() {
_du_basics_subdir(result.stdout);
}
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
fn _du_basics_subdir(s: String) {
assert_eq!(s, "4\tsubdir/deeper\n");
}
#[cfg(not(target_os = "macos"))]
#[cfg(not(target_vendor = "apple"))]
fn _du_basics_subdir(s: String) {
// MS-WSL linux has altered expected output
if !is_wsl() {
@ -80,12 +80,12 @@ fn test_du_soft_link() {
_du_soft_link(result.stdout);
}
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
fn _du_soft_link(s: String) {
// 'macos' host variants may have `du` output variation for soft links
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) {
// MS-WSL linux has altered expected output
if !is_wsl() {
@ -109,11 +109,11 @@ fn test_du_hard_link() {
_du_hard_link(result.stdout);
}
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
fn _du_hard_link(s: String) {
assert_eq!(s, "12\tsubdir/links\n")
}
#[cfg(not(target_os = "macos"))]
#[cfg(not(target_vendor = "apple"))]
fn _du_hard_link(s: String) {
// MS-WSL linux has altered expected output
if !is_wsl() {
@ -133,11 +133,11 @@ fn test_du_d_flag() {
_du_d_flag(result.stdout);
}
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
fn _du_d_flag(s: String) {
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) {
// MS-WSL linux has altered expected output
if !is_wsl() {

View file

@ -173,3 +173,58 @@ fn test_disable_escapes() {
.succeeds()
.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");
}
#[test]
fn test_zero_terminated() {
new_ucmd!()
.args(&["-z", "zero_terminated.txt"])
.run()
.stdout_is_fixture("zero_terminated.expected");
}
#[test]
#[ignore]
fn test_spams_newline() {
@ -159,3 +167,15 @@ fn test_bug_in_negative_zero_lines() {
//GNU Head returns "a\nb\n"
.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"
#[cfg(not(target_os = "macos"))]
#[cfg(not(target_vendor = "apple"))]
#[test]
fn test_hostname_ip() {
let result = new_ucmd!().arg("-i").run();

View file

@ -17,9 +17,9 @@ fn test_install_help() {
#[test]
fn test_install_basic() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_install_target_dir_dir_a";
let file1 = "test_install_target_dir_file_a1";
let file2 = "test_install_target_dir_file_a2";
let dir = "target_dir";
let file1 = "source_file1";
let file2 = "source_file2";
at.touch(file1);
at.touch(file2);
@ -34,7 +34,7 @@ fn test_install_basic() {
#[test]
fn test_install_twice_dir() {
let dir = "test_install_target_dir_dir_a";
let dir = "dir";
let scene = TestScenario::new(util_name!());
scene.ucmd().arg("-d").arg(dir).succeeds();
@ -47,9 +47,9 @@ fn test_install_twice_dir() {
#[test]
fn test_install_failing_not_dir() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1";
let file2 = "test_install_target_dir_file_a2";
let file3 = "test_install_target_dir_file_a3";
let file1 = "file1";
let file2 = "file2";
let file3 = "file3";
at.touch(file1);
at.touch(file2);
@ -66,8 +66,8 @@ fn test_install_failing_not_dir() {
#[test]
fn test_install_unimplemented_arg() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_install_target_dir_dir_b";
let file = "test_install_target_dir_file_b";
let dir = "target_dir";
let file = "source_file";
let context_arg = "--context";
at.touch(file);
@ -86,9 +86,9 @@ fn test_install_unimplemented_arg() {
#[test]
fn test_install_component_directories() {
let (at, mut ucmd) = at_and_ucmd!();
let component1 = "test_install_target_dir_component_c1";
let component2 = "test_install_target_dir_component_c2";
let component3 = "test_install_target_dir_component_c3";
let component1 = "component1";
let component2 = "component2";
let component3 = "component3";
let directories_arg = "-d";
ucmd.args(&[directories_arg, component1, component2, component3])
@ -104,10 +104,10 @@ fn test_install_component_directories() {
fn test_install_mode_numeric() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let dir = "test_install_target_dir_dir_e";
let dir2 = "test_install_target_dir_dir_e2";
let dir = "dir1";
let dir2 = "dir2";
let file = "test_install_target_dir_file_e";
let file = "file";
let mode_arg = "--mode=333";
at.touch(file);
@ -145,8 +145,8 @@ fn test_install_mode_numeric() {
#[test]
fn test_install_mode_symbolic() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_install_target_dir_dir_f";
let file = "test_install_target_dir_file_f";
let dir = "target_dir";
let file = "source_file";
let mode_arg = "--mode=o+wx";
at.touch(file);
@ -163,8 +163,8 @@ fn test_install_mode_symbolic() {
#[test]
fn test_install_mode_failing() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_install_target_dir_dir_g";
let file = "test_install_target_dir_file_g";
let dir = "target_dir";
let file = "source_file";
let mode_arg = "--mode=999";
at.touch(file);
@ -185,7 +185,7 @@ fn test_install_mode_failing() {
#[test]
fn test_install_mode_directories() {
let (at, mut ucmd) = at_and_ucmd!();
let component = "test_install_target_dir_component_h";
let component = "component";
let directories_arg = "-d";
let mode_arg = "--mode=333";
@ -203,8 +203,8 @@ fn test_install_mode_directories() {
#[test]
fn test_install_target_file() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_file_file_i1";
let file2 = "test_install_target_file_file_i2";
let file1 = "source_file";
let file2 = "target_file";
at.touch(file1);
at.touch(file2);
@ -217,8 +217,8 @@ fn test_install_target_file() {
#[test]
fn test_install_target_new_file() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_install_target_new_filer_file_j";
let dir = "test_install_target_new_file_dir_j";
let file = "file";
let dir = "target_dir";
at.touch(file);
at.mkdir(dir);
@ -234,8 +234,8 @@ fn test_install_target_new_file() {
#[test]
fn test_install_target_new_file_with_group() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_install_target_new_filer_file_j";
let dir = "test_install_target_new_file_dir_j";
let file = "file";
let dir = "target_dir";
let gid = get_effective_gid();
at.touch(file);
@ -264,8 +264,8 @@ fn test_install_target_new_file_with_group() {
#[test]
fn test_install_target_new_file_with_owner() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_install_target_new_filer_file_j";
let dir = "test_install_target_new_file_dir_j";
let file = "file";
let dir = "target_dir";
let uid = get_effective_uid();
at.touch(file);
@ -294,9 +294,9 @@ fn test_install_target_new_file_with_owner() {
#[test]
fn test_install_target_new_file_failing_nonexistent_parent() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_new_file_failing_file_k1";
let file2 = "test_install_target_new_file_failing_file_k2";
let dir = "test_install_target_new_file_failing_dir_k";
let file1 = "source_file";
let file2 = "target_file";
let dir = "target_dir";
at.touch(file1);
@ -312,8 +312,8 @@ fn test_install_target_new_file_failing_nonexistent_parent() {
#[test]
fn test_install_preserve_timestamps() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1";
let file2 = "test_install_target_dir_file_a2";
let file1 = "source_file";
let file2 = "target_file";
at.touch(file1);
ucmd.arg(file1).arg(file2).arg("-p").succeeds().no_stderr();
@ -338,8 +338,8 @@ fn test_install_preserve_timestamps() {
#[test]
fn test_install_copy_file() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1";
let file2 = "test_install_target_dir_file_a2";
let file1 = "source_file";
let file2 = "target_file";
at.touch(file1);
ucmd.arg(file1).arg(file2).succeeds().no_stderr();
@ -353,8 +353,57 @@ fn test_install_copy_file() {
fn test_install_target_file_dev_null() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "/dev/null";
let file2 = "test_install_target_file_file_i2";
let file2 = "target_file";
ucmd.arg(file1).arg(file2).succeeds().no_stderr();
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(".."));
}
#[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]
fn test_ls_long() {
#[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"));
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)]
assert!(result.stdout.contains("---------- 1 somebody somegroup"));
for arg in &["-l", "--long", "--format=long", "--format=verbose"] {
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))]
{
@ -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]
fn test_ls_deref() {
let scene = TestScenario::new(util_name!());
@ -166,27 +484,55 @@ fn test_ls_order_size() {
}
#[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 at = &scene.fixtures;
at.touch("test-1");
at.append("test-1", "1");
sleep(Duration::from_millis(500));
sleep(Duration::from_millis(100));
at.touch("test-2");
at.append("test-2", "22");
sleep(Duration::from_millis(500));
sleep(Duration::from_millis(100));
at.touch("test-3");
at.append("test-3", "333");
sleep(Duration::from_millis(500));
sleep(Duration::from_millis(100));
at.touch("test-4");
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();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
// ctime was changed at write, so the order is 4 3 2 1
let result = scene.ucmd().arg("-t").run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
@ -196,7 +542,7 @@ fn test_ls_order_creation() {
#[cfg(windows)]
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!("stdout = {:?}", result.stdout);
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");
#[cfg(windows)]
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]
@ -270,45 +651,197 @@ fn test_ls_recursive() {
assert!(result.stdout.contains("a\\b:\nb"));
}
#[cfg(unix)]
#[test]
fn test_ls_ls_color() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("a");
at.mkdir("a/nested_dir");
at.mkdir("z");
at.touch(&at.plus_as_string("a/a"));
scene.ucmd().arg("--color").succeeds();
scene.ucmd().arg("--color=always").succeeds();
scene.ucmd().arg("--color=never").succeeds();
scene.ucmd().arg("--color").arg("a").succeeds();
scene.ucmd().arg("--color=always").arg("a/a").succeeds();
scene.ucmd().arg("--color=never").arg("z").succeeds();
at.touch(&at.plus_as_string("a/nested_file"));
at.touch("test-color");
let a_with_colors = "\x1b[01;34ma\x1b[0m";
let z_with_colors = "\x1b[01;34mz\x1b[0m";
let nested_dir_with_colors = "\x1b[01;34mnested_dir\x1b[0m";
// 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]
fn test_ls_human() {
fn test_ls_inode() {
let scene = TestScenario::new(util_name!());
let file = "test_human";
let result = scene.cmd("truncate").arg("-s").arg("+1000").arg(file).run();
let at = &scene.fixtures;
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!("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!("stdout = {:?}", result.stdout);
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
.cmd("truncate")
.arg("-s")
.arg("+1000k")
.arg(file)
.arg(file1)
.run();
let result = scene.ucmd().arg("-hl").arg(file).run();
let result = scene.ucmd().arg("-hl").arg(file1).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
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)]
@ -336,3 +869,81 @@ fn test_ls_hidden_windows() {
assert!(result.success);
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.
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]

View file

@ -23,8 +23,8 @@ fn test_padding_without_overflow() {
.run()
.stdout_is(
"000001xL1\n001001xL2\n002001xL3\n003001xL4\n004001xL5\n005001xL6\n006001xL7\n0070\
01xL8\n008001xL9\n009001xL10\n010001xL11\n011001xL12\n012001xL13\n013001xL14\n014\
001xL15\n",
01xL8\n008001xL9\n009001xL10\n010001xL11\n011001xL12\n012001xL13\n013001xL14\n014\
001xL15\n",
);
}
@ -35,7 +35,7 @@ fn test_padding_with_overflow() {
.run()
.stdout_is(
"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",
"\nHEADER1\nHEADER2\n\n1 |BODY1\n2 \
|BODY2\n\nFOOTER1\nFOOTER2\n\nNEXTHEADER1\nNEXTHEADER2\n\n1 \
|NEXTBODY1\n2 |NEXTBODY2\n\nNEXTFOOTER1\nNEXTFOOTER2\n",
|BODY2\n\nFOOTER1\nFOOTER2\n\nNEXTHEADER1\nNEXTHEADER2\n\n1 \
|NEXTBODY1\n2 |NEXTBODY2\n\nNEXTFOOTER1\nNEXTFOOTER2\n",
),
(
"joinblanklines.txt",
"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 \
|Followed by 4x empty\n\n\n\n\n9 |Nonempty\n10 |Nonempty\n11 \
|Nonempty.\n",
|\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 \
|Nonempty.\n",
),
] {
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
new_ucmd!()
.args(&[repeat_str("test", 20000)])
.args(&["test".repeat(20000)])
.fails()
.no_stdout();
}

View file

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

View file

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

View file

@ -329,3 +329,17 @@ fn test_multiple_input_quiet_flag_overrides_verbose_flag_for_suppressing_headers
.run()
.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()
.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()
.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(
"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(
"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(
"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()
.stdout_is(
" 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_rules! assert_empty_stderr(
($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_rules! assert_empty_stdout(
($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_rules! assert_no_error(
($cond:expr) => (
@ -26,6 +35,7 @@ macro_rules! assert_no_error(
);
);
/// Platform-independent helper for constructing a PathBuf from individual elements
#[macro_export]
macro_rules! path_concat {
($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_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_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_rules! at_and_ucmd {
() => {{

View file

@ -1,3 +1,5 @@
#![allow(dead_code)]
use std::env;
use std::ffi::OsStr;
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();";
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 {
std::env::var("CI")
.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())
}
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)
/// within a struct which has convenience assertion functions about those outputs
#[derive(Debug)]
@ -384,8 +378,10 @@ impl AtPath {
/// An environment for running a single uutils test case, serves three functions:
/// 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
///
/// Fixtures can be found under `tests/fixtures/$util_name/`
pub struct TestScenario {
bin_path: PathBuf,
util_name: String,
@ -420,12 +416,16 @@ impl TestScenario {
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 {
let mut cmd = self.cmd(&self.bin_path);
cmd.arg(&self.util_name);
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 {
UCommand::new_from_tmp(bin, self.tmpd.clone(), true)
}
@ -495,6 +495,8 @@ impl UCommand {
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> {
if self.has_run {
panic!(ALREADY_RUN);
@ -505,6 +507,8 @@ impl UCommand {
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> {
if self.has_run {
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