diff --git a/.cirrus.yml b/.cirrus.yml index 3a34a933a..5d16dce92 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -11,4 +11,4 @@ task: - cargo build test_script: - . $HOME/.cargo/env - - cargo test + - cargo test -p uucore -p coreutils diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..e0988d0bd --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,18 @@ +# Number of days of inactivity before an issue/PR becomes stale +daysUntilStale: 365 +# Number of days of inactivity before a stale issue/PR is closed +daysUntilClose: 365 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - "Good first bug" +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index f1ddf9be1..cc0972bf9 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -150,7 +150,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --features "feat_os_unix" + args: --features "feat_os_unix" -p uucore -p coreutils env: RUSTFLAGS: '-Awarnings' @@ -181,6 +181,28 @@ jobs: cd tmp/busybox-*/testsuite S=$(bindir=$bindir ./runtest) && printf "%s\n" "$S" || { printf "%s\n" "$S" | grep "FAIL:" | sed -e "s/FAIL: /::warning ::Test failure:/g" ; } + makefile_build: + name: Test the build target of the Makefile + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest } + steps: + - uses: actions/checkout@v1 + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + profile: minimal # minimal component installation (ie, no documentation) + - name: "Run make build" + shell: bash + run: | + sudo apt-get -y update ; sudo apt-get -y install python3-sphinx; + make build + build: name: Build runs-on: ${{ matrix.job.os }} @@ -514,6 +536,17 @@ jobs: CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" echo set-output name=UTILITY_LIST::${UTILITY_LIST} echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS} + - name: Test uucore + uses: actions-rs/cargo@v1 + with: + command: test + args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast -p uucore + env: + CARGO_INCREMENTAL: '0' + RUSTC_WRAPPER: '' + RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort' + RUSTDOCFLAGS: '-Cpanic=abort' + # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }} - name: Test uses: actions-rs/cargo@v1 with: diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 9eb5da2b9..35efccbe5 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -84,8 +84,8 @@ jobs: 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|timeout \([[:digit:]]\)| /usr/bin/timeout \1|' tests/tail-2/inotify-rotate.sh tests/tail-2/inotify-dir-recreate.sh tests/tail-2/inotify-rotate-resources.sh tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.sh tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh tests/tail-2/pid.sh tests/dd/stats.sh tests/tail-2/follow-name.sh tests/misc/shuf.sh # Don't break the function called 'grep_timeout' + sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh tests/chgrp/basic.sh tests/misc/shuf.sh sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..66d2a5f5a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +# https://pre-commit.com +repos: + - repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: cargo-check + - id: clippy + - id: fmt diff --git a/.travis.yml b/.travis.yml index 27525b5f2..389ba44b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ install: script: - cargo build $CARGO_ARGS --features "$FEATURES" - - if [ ! $REDOX ]; then cargo test $CARGO_ARGS --features "$FEATURES" --no-fail-fast; fi + - if [ ! $REDOX ]; then cargo test $CARGO_ARGS -p uucore -p coreutils --features "$FEATURES" --no-fail-fast; fi - if [ -n "$TEST_INSTALL" ]; then mkdir installdir_test; DESTDIR=installdir_test make install; [ `ls installdir_test/usr/local/bin | wc -l` -gt 0 ]; fi addons: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..6c50b811d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +sylvestre@debian.org. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/Cargo.lock b/Cargo.lock index 4f61fb1b0..c59af5c06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,1886 +4,2150 @@ name = "advapi32-sys" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8", + "winapi-build", ] [[package]] name = "aho-corasick" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" dependencies = [ - "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.4", ] [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9", ] [[package]] name = "arrayvec" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" dependencies = [ - "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", + "winapi 0.3.9", ] [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bit-set" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" dependencies = [ - "bit-vec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "blake2-rfc" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" dependencies = [ - "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec", + "constant_time_eq", ] [[package]] name = "block-buffer" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814" dependencies = [ - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools", + "generic-array", ] [[package]] name = "bstr" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" 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.125 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", + "memchr 2.3.4", + "regex-automata", + "serde", ] [[package]] name = "bumpalo" version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" [[package]] name = "byte-tools" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cast" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" dependencies = [ - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version", ] [[package]] name = "cc" version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" dependencies = [ - "num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer", + "num-traits", + "time", ] [[package]] name = "clap" version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", ] [[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", ] [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "conv" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" dependencies = [ - "custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "custom_derive", ] [[package]] name = "coreutils" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "filetime 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "unindent 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uu_arch 0.0.4", - "uu_base32 0.0.4", - "uu_base64 0.0.4", - "uu_basename 0.0.4", - "uu_cat 0.0.4", - "uu_chgrp 0.0.4", - "uu_chmod 0.0.4", - "uu_chown 0.0.4", - "uu_chroot 0.0.4", - "uu_cksum 0.0.4", - "uu_comm 0.0.4", - "uu_cp 0.0.4", - "uu_csplit 0.0.4", - "uu_cut 0.0.4", - "uu_date 0.0.4", - "uu_df 0.0.4", - "uu_dircolors 0.0.4", - "uu_dirname 0.0.4", - "uu_du 0.0.4", - "uu_echo 0.0.4", - "uu_env 0.0.4", - "uu_expand 0.0.4", - "uu_expr 0.0.4", - "uu_factor 0.0.4", - "uu_false 0.0.4", - "uu_fmt 0.0.4", - "uu_fold 0.0.4", - "uu_groups 0.0.4", - "uu_hashsum 0.0.4", - "uu_head 0.0.4", - "uu_hostid 0.0.4", - "uu_hostname 0.0.4", - "uu_id 0.0.4", - "uu_install 0.0.4", - "uu_join 0.0.4", - "uu_kill 0.0.4", - "uu_link 0.0.4", - "uu_ln 0.0.4", - "uu_logname 0.0.4", - "uu_ls 0.0.4", - "uu_mkdir 0.0.4", - "uu_mkfifo 0.0.4", - "uu_mknod 0.0.4", - "uu_mktemp 0.0.4", - "uu_more 0.0.4", - "uu_mv 0.0.4", - "uu_nice 0.0.4", - "uu_nl 0.0.4", - "uu_nohup 0.0.4", - "uu_nproc 0.0.4", - "uu_numfmt 0.0.4", - "uu_od 0.0.4", - "uu_paste 0.0.4", - "uu_pathchk 0.0.4", - "uu_pinky 0.0.4", - "uu_pr 0.0.4", - "uu_printenv 0.0.4", - "uu_printf 0.0.4", - "uu_ptx 0.0.4", - "uu_pwd 0.0.4", - "uu_readlink 0.0.4", - "uu_realpath 0.0.4", - "uu_relpath 0.0.4", - "uu_rm 0.0.4", - "uu_rmdir 0.0.4", - "uu_seq 0.0.4", - "uu_shred 0.0.4", - "uu_shuf 0.0.4", - "uu_sleep 0.0.4", - "uu_sort 0.0.4", - "uu_split 0.0.4", - "uu_stat 0.0.4", - "uu_stdbuf 0.0.4", - "uu_sum 0.0.4", - "uu_sync 0.0.4", - "uu_tac 0.0.4", - "uu_tail 0.0.4", - "uu_tee 0.0.4", - "uu_test 0.0.4", - "uu_timeout 0.0.4", - "uu_touch 0.0.4", - "uu_tr 0.0.4", - "uu_true 0.0.4", - "uu_truncate 0.0.4", - "uu_tsort 0.0.4", - "uu_tty 0.0.4", - "uu_uname 0.0.4", - "uu_unexpand 0.0.4", - "uu_uniq 0.0.4", - "uu_unlink 0.0.4", - "uu_uptime 0.0.4", - "uu_users 0.0.4", - "uu_wc 0.0.4", - "uu_who 0.0.4", - "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)", + "conv", + "filetime", + "glob 0.3.0", + "lazy_static", + "libc", + "nix 0.20.0", + "rand 0.7.3", + "regex", + "sha1", + "tempdir", + "tempfile", + "textwrap", + "time", + "unindent", + "unix_socket", + "users", + "uu_arch", + "uu_base32", + "uu_base64", + "uu_basename", + "uu_cat", + "uu_chgrp", + "uu_chmod", + "uu_chown", + "uu_chroot", + "uu_cksum", + "uu_comm", + "uu_cp", + "uu_csplit", + "uu_cut", + "uu_date", + "uu_df", + "uu_dircolors", + "uu_dirname", + "uu_du", + "uu_echo", + "uu_env", + "uu_expand", + "uu_expr", + "uu_factor", + "uu_false", + "uu_fmt", + "uu_fold", + "uu_groups", + "uu_hashsum", + "uu_head", + "uu_hostid", + "uu_hostname", + "uu_id", + "uu_install", + "uu_join", + "uu_kill", + "uu_link", + "uu_ln", + "uu_logname", + "uu_ls", + "uu_mkdir", + "uu_mkfifo", + "uu_mknod", + "uu_mktemp", + "uu_more", + "uu_mv", + "uu_nice", + "uu_nl", + "uu_nohup", + "uu_nproc", + "uu_numfmt", + "uu_od", + "uu_paste", + "uu_pathchk", + "uu_pinky", + "uu_pr", + "uu_printenv", + "uu_printf", + "uu_ptx", + "uu_pwd", + "uu_readlink", + "uu_realpath", + "uu_relpath", + "uu_rm", + "uu_rmdir", + "uu_seq", + "uu_shred", + "uu_shuf", + "uu_sleep", + "uu_sort", + "uu_split", + "uu_stat", + "uu_stdbuf", + "uu_sum", + "uu_sync", + "uu_tac", + "uu_tail", + "uu_tee", + "uu_test", + "uu_timeout", + "uu_touch", + "uu_tr", + "uu_true", + "uu_truncate", + "uu_tsort", + "uu_tty", + "uu_uname", + "uu_unexpand", + "uu_uniq", + "uu_unlink", + "uu_uptime", + "uu_users", + "uu_wc", + "uu_who", + "uu_whoami", + "uu_yes", + "uucore", + "walkdir", ] [[package]] name = "cpp" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4875a08600be48dcc9cb6ee07f104a3e0752e95184dede6a30044d6480bf50e8" dependencies = [ - "cpp_macros 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_macros", ] [[package]] name = "cpp_build" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c47531e7e09532ad4827098729794f5e1a5b1c2ccbb5e295498d2e7ab451c445" dependencies = [ - "cc 1.0.67 (registry+https://github.com/rust-lang/crates.io-index)", - "cpp_common 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cpp_synmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "cpp_common 0.4.0", + "cpp_syn", + "cpp_synmap", + "cpp_synom", + "lazy_static", ] [[package]] name = "cpp_common" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e39149a7943affa02f5b6e347ca2840a129cc78d5883ee229f0f1c4027d628" dependencies = [ - "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_syn", + "cpp_synom", + "lazy_static", + "quote 0.3.15", ] [[package]] name = "cpp_common" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df78ad28e5fe814285016779fb3d3b874520c799a847e6190bf2b834cc4ff283" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", + "proc-macro2", + "syn", ] [[package]] name = "cpp_macros" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f93a21e618c10abc84ebb63ffa5952e1f7a4568b8141d542d5ef860e4a8fc25" dependencies = [ - "aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "cpp_common 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "if_rust_version 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick", + "byteorder", + "cpp_common 0.5.6", + "if_rust_version", + "lazy_static", + "proc-macro2", + "quote 1.0.9", + "syn", ] [[package]] name = "cpp_syn" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8cd649bf5b3804d92fe12a60c7698f5a538a6033ed8a668bf5241d4d4f1644e" dependencies = [ - "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synom", + "quote 0.3.15", + "unicode-xid 0.0.4", ] [[package]] name = "cpp_synmap" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897e4f9cdbe2874edd3ffe53718ee5d8b89e2a970057b2c93d3214104f2e90b6" dependencies = [ - "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_syn", + "cpp_synom", + "memchr 1.0.2", ] [[package]] name = "cpp_synom" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc8da5694233b646150c785118f77835ad0a49680c7f312a10ef30957c67b6d" dependencies = [ - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4", ] [[package]] name = "criterion" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23" dependencies = [ - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "csv 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "oorandom 11.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "plotters 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 1.5.0 (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.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.2 (registry+https://github.com/rust-lang/crates.io-index)", + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools 0.10.0", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", ] [[package]] name = "criterion-plot" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" dependencies = [ - "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cast", + "itertools 0.9.0", ] [[package]] name = "crossbeam-channel" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-epoch 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "cfg-if 1.0.0", + "lazy_static", ] [[package]] name = "csv" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ - "bstr 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", - "csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)", + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", ] [[package]] name = "csv-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ - "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.4", ] [[package]] name = "custom_derive" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" [[package]] name = "data-encoding" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" [[package]] name = "digest" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a" dependencies = [ - "generic-array 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array", ] [[package]] name = "dunce" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2641c4a7c0c4101df53ea572bffdc561c146f6c2eb09e4df02bc4811e3feeb4" [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "env_logger" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ - "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "regex", ] [[package]] name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "file_diff" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5" [[package]] name = "filetime" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.5", + "winapi 0.3.9", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fs_extra" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "generic-array" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2297fb0e3ea512e380da24b52dca3924028f59df5e3a17a18f81d8349ca7ebe" dependencies = [ - "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "typenum 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop", + "typenum", ] [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width", ] [[package]] name = "getrandom" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "libc", + "wasi", ] [[package]] name = "glob" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" [[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "globset" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] [[package]] name = "half" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" + +[[package]] +name = "heck" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "hermit-abi" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "hex" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" [[package]] name = "hostname" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "match_cfg 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "match_cfg", + "winapi 0.3.9", ] [[package]] name = "if_rust_version" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec" [[package]] name = "ioctl-sys" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" [[package]] name = "itertools" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" dependencies = [ - "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "either", ] [[package]] name = "itertools" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" dependencies = [ - "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "either", ] [[package]] name = "itertools" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" dependencies = [ - "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "either", ] [[package]] name = "itoa" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "js-sys" -version = "0.3.49" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" dependencies = [ - "wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen", ] [[package]] name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8", + "winapi-build", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", ] [[package]] name = "match_cfg" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "maybe-uninit" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "md5" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" [[package]] name = "memchr" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "memoffset" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] name = "nix" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.67 (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)", + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", + "void", +] + +[[package]] +name = "nix" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", ] [[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "num-integer" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "num-traits", ] [[package]] name = "num-traits" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ - "hermit-abi 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", ] [[package]] name = "number_prefix" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "numtoa" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "onig" version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8518fcb2b1b8c2f45f0ad499df4fda6087fc3475ca69a185c173b8315d2fb383" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "lazy_static", + "libc", + "onig_sys", ] [[package]] name = "onig_sys" version = "69.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0" dependencies = [ - "cc 1.0.67 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "pkg-config", ] [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "paste" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" dependencies = [ - "paste-impl 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.19 (registry+https://github.com/rust-lang/crates.io-index)", + "paste-impl", + "proc-macro-hack", ] [[package]] name = "paste-impl" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" dependencies = [ - "proc-macro-hack 0.5.19 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack", ] [[package]] name = "pkg-config" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "platform-info" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ea9cd21d89bffb387b6c7363d23bead0807be9de676c671b474dd29e7436d3" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "winapi 0.3.9", ] [[package]] name = "plotters" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca0ae5f169d0917a7c7f5a9c1a3d3d9598f18f529dd2b8373ed988efea307a" 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.72 (registry+https://github.com/rust-lang/crates.io-index)", - "web-sys 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590" [[package]] name = "plotters-svg" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211" dependencies = [ - "plotters-backend 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "plotters-backend", ] [[package]] name = "ppv-lite86" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "proc-macro-hack" version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.1", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quickcheck" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" dependencies = [ - "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger", + "log", + "rand 0.7.3", + "rand_core 0.5.1", ] [[package]] name = "quote" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" [[package]] name = "quote" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", ] [[package]] name = "rand" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "winapi 0.3.9", ] [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", + "libc", + "rand_chacha", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "ppv-lite86 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2", ] [[package]] name = "rand_core" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1", ] [[package]] name = "rand_pcg" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1", ] [[package]] name = "rayon" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon-core 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", ] [[package]] name = "rayon-core" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ - "crossbeam-channel 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", ] [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", ] [[package]] name = "redox_termios" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" dependencies = [ - "redox_syscall 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.2.5", ] [[package]] name = "regex" version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" dependencies = [ - "aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick", + "memchr 2.3.4", + "regex-syntax", ] [[package]] name = "regex-automata" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" dependencies = [ - "byteorder 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", ] [[package]] name = "regex-syntax" version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9", ] +[[package]] +name = "retain_mut" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53552c6c49e1e13f1a203ef0080ab3bbef0beb570a528993e83df057a9d9bba1" + [[package]] name = "rust-ini" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver", ] [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util", ] [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver-parser", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" [[package]] name = "serde_cbor" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" dependencies = [ - "half 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)", + "half", + "serde", ] [[package]] name = "serde_derive" version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 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)", + "proc-macro2", + "quote 1.0.9", + "syn", ] [[package]] name = "serde_json" version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 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.125 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa", + "ryu", + "serde", ] [[package]] name = "sha1" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" [[package]] name = "sha2" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d963c78ce367df26d7ea8b8cc655c651b42e8a1e584e869c1e17dae3ccb116a" dependencies = [ - "block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer", + "byte-tools", + "digest", + "fake-simd", + "generic-array", ] [[package]] name = "sha3" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26405905b6a56a94c60109cfda62610507ac14a65be531f5767dec5c5a8dd6a0" dependencies = [ - "block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer", + "byte-tools", + "digest", + "generic-array", ] [[package]] name = "smallvec" version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" dependencies = [ - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit", ] [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strum" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c" + +[[package]] +name = "strum_macros" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" +dependencies = [ + "heck", + "proc-macro2", + "quote 1.0.9", + "syn", +] [[package]] name = "syn" -version = "1.0.64" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" 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)", - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.9", + "unicode-xid 0.2.1", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", ] [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "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)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", + "libc", + "rand 0.7.3", + "redox_syscall 0.1.57", + "remove_dir_all", + "winapi 0.3.9", ] [[package]] name = "term_grid" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230d3e804faaed5a39b08319efb797783df2fd9671b39b7596490cb486d702cf" dependencies = [ - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width", ] [[package]] name = "term_size" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "winapi 0.3.9", ] [[package]] name = "termion" version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_termios 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "numtoa", + "redox_syscall 0.2.5", + "redox_termios", ] [[package]] name = "termsize" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e86d824a8e90f342ad3ef4bd51ef7119a9b681b0cc9f8ee7b2852f02ccd2517" dependencies = [ - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "atty", + "kernel32-sys", + "libc", + "termion", + "winapi 0.2.8", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "term_size", + "unicode-width", ] [[package]] name = "thiserror" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" dependencies = [ - "thiserror-impl 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" 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)", + "proc-macro2", + "quote 1.0.9", + "syn", ] [[package]] name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "redox_syscall 0.1.57", + "winapi 0.3.9", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ - "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", + "serde_json", ] [[package]] name = "typenum" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" [[package]] name = "unicode-width" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "unindent" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" [[package]] name = "unix_socket" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" dependencies = [ - "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)", + "cfg-if 0.1.10", + "libc", ] [[package]] name = "users" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "log", ] [[package]] name = "uu_arch" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "platform-info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "platform-info", + "uucore", + "uucore_procs", ] [[package]] name = "uu_base32" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "uucore 0.0.7", - "uucore_procs 0.0.5", + "uucore", + "uucore_procs", ] [[package]] name = "uu_base64" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "uucore 0.0.7", - "uucore_procs 0.0.5", + "uucore", + "uucore_procs", ] [[package]] name = "uu_basename" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "uucore 0.0.7", - "uucore_procs 0.0.5", + "uucore", + "uucore_procs", ] [[package]] name = "uu_cat" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "quick-error", + "unix_socket", + "uucore", + "uucore_procs", ] [[package]] name = "uu_chgrp" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "uucore 0.0.7", - "uucore_procs 0.0.5", - "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore", + "uucore_procs", + "walkdir", ] [[package]] name = "uu_chmod" -version = "0.0.4" +version = "0.0.6" 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.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap", + "libc", + "uucore", + "uucore_procs", + "walkdir", ] [[package]] name = "uu_chown" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", - "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap", + "glob 0.3.0", + "uucore", + "uucore_procs", + "walkdir", ] [[package]] name = "uu_chroot" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_cksum" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_comm" -version = "0.0.4" +version = "0.0.6" 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)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_cp" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "filetime 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "ioctl-sys 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", - "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)", + "clap", + "filetime", + "ioctl-sys", + "libc", + "quick-error", + "uucore", + "uucore_procs", + "walkdir", + "winapi 0.3.9", + "xattr", ] [[package]] name = "uu_csplit" -version = "0.0.4" +version = "0.0.6" 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.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", + "clap", + "glob 0.2.11", + "regex", + "thiserror", + "uucore", + "uucore_procs", ] [[package]] name = "uu_cut" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_date" -version = "0.0.4" +version = "0.0.6" 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)", + "chrono", + "clap", + "libc", + "uucore", + "uucore_procs", + "winapi 0.3.9", ] [[package]] name = "uu_df" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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)", - "number_prefix 0.4.0 (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)", + "clap", + "libc", + "number_prefix", + "uucore", + "uucore_procs", + "winapi 0.3.9", ] [[package]] name = "uu_dircolors" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "glob 0.3.0", + "uucore", + "uucore_procs", ] [[package]] name = "uu_dirname" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_du" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "chrono", + "uucore", + "uucore_procs", + "winapi 0.3.9", ] [[package]] name = "uu_echo" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_env" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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)", - "rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "libc", + "rust-ini", + "uucore", + "uucore_procs", ] [[package]] name = "uu_expand" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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", + "clap", + "unicode-width", + "uucore", + "uucore_procs", ] [[package]] name = "uu_expr" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "onig 4.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "libc", + "onig", + "uucore", + "uucore_procs", ] [[package]] name = "uu_factor" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "criterion 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "paste 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", - "quickcheck 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "criterion", + "num-traits", + "paste", + "quickcheck", + "rand 0.7.3", + "rand_chacha", + "smallvec", + "uucore", + "uucore_procs", ] [[package]] name = "uu_false" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "uucore 0.0.7", - "uucore_procs 0.0.5", + "uucore", + "uucore_procs", ] [[package]] name = "uu_fmt" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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)", - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "libc", + "unicode-width", + "uucore", + "uucore_procs", ] [[package]] name = "uu_fold" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_groups" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_hashsum" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "md5 0.3.8 (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)", - "sha3 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "blake2-rfc", + "clap", + "digest", + "hex", + "libc", + "md5", + "regex", + "regex-syntax", + "sha1", + "sha2", + "sha3", + "uucore", + "uucore_procs", ] [[package]] name = "uu_head" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_hostid" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_hostname" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "hostname 0.3.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", - "uucore_procs 0.0.5", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "clap", + "hostname", + "libc", + "uucore", + "uucore_procs", + "winapi 0.3.9", ] [[package]] name = "uu_id" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_install" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "filetime 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "file_diff", + "filetime", + "libc", + "time", + "uucore", + "uucore_procs", ] [[package]] name = "uu_join" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_kill" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_link" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_ln" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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", + "clap", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_logname" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_ls" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "atty 0.2.14 (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)", - "termsize 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (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", + "atty", + "clap", + "globset", + "lazy_static", + "number_prefix", + "term_grid", + "termsize", + "time", + "unicode-width", + "uucore", + "uucore_procs", ] [[package]] name = "uu_mkdir" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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", + "clap", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_mkfifo" -version = "0.0.4" +version = "0.0.6" 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)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_mknod" -version = "0.0.4" +version = "0.0.6" 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)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "getopts", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_mktemp" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "rand 0.5.6", + "tempfile", + "uucore", + "uucore_procs", ] [[package]] name = "uu_more" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "getopts 0.2.21 (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", - "uucore_procs 0.0.5", + "clap", + "nix 0.13.1", + "redox_syscall 0.1.57", + "redox_termios", + "uucore", + "uucore_procs", ] [[package]] name = "uu_mv" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fs_extra 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "fs_extra", + "uucore", + "uucore_procs", ] [[package]] name = "uu_nice" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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)", - "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "libc", + "nix 0.13.1", + "uucore", + "uucore_procs", ] [[package]] name = "uu_nl" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.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", + "aho-corasick", + "clap", + "libc", + "memchr 2.3.4", + "regex", + "regex-syntax", + "uucore", + "uucore_procs", ] [[package]] name = "uu_nohup" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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", + "clap", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_nproc" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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)", - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "libc", + "num_cpus", + "uucore", + "uucore_procs", ] [[package]] name = "uu_numfmt" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_od" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "byteorder 1.4.3 (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", - "uucore_procs 0.0.5", + "byteorder", + "clap", + "half", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_paste" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_pathchk" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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", + "clap", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_pinky" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "uucore 0.0.7", - "uucore_procs 0.0.5", + "uucore", + "uucore_procs", ] [[package]] @@ -1902,741 +2166,596 @@ dependencies = [ [[package]] name = "uu_printenv" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_printf" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "itertools 0.8.2", + "uucore", + "uucore_procs", ] [[package]] name = "uu_ptx" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.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", + "aho-corasick", + "clap", + "libc", + "memchr 2.3.4", + "regex", + "regex-syntax", + "uucore", + "uucore_procs", ] [[package]] name = "uu_pwd" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_readlink" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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", + "clap", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_realpath" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_relpath" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_rm" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", - "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap", + "remove_dir_all", + "uucore", + "uucore_procs", + "walkdir", ] [[package]] name = "uu_rmdir" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_seq" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_shred" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "filetime 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)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "filetime", + "libc", + "rand 0.5.6", + "time", + "uucore", + "uucore_procs", ] [[package]] name = "uu_shuf" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "rand 0.5.6", + "uucore", + "uucore_procs", ] [[package]] name = "uu_sleep" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_sort" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "fnv", + "itertools 0.8.2", + "rand 0.7.3", + "rayon", + "semver", + "uucore", + "uucore_procs", ] [[package]] name = "uu_split" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_stat" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "time", + "uucore", + "uucore_procs", ] [[package]] name = "uu_stdbuf" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uu_stdbuf_libstdbuf 0.0.4", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "getopts", + "tempfile", + "uu_stdbuf_libstdbuf", + "uucore", + "uucore_procs", ] [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "cpp 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "cpp_build 0.4.0 (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", + "cpp", + "cpp_build", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_sum" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_sync" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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)", + "clap", + "libc", + "uucore", + "uucore_procs", + "winapi 0.3.9", ] [[package]] name = "uu_tac" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_tail" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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)", - "redox_syscall 0.1.57 (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)", + "clap", + "libc", + "redox_syscall 0.1.57", + "uucore", + "uucore_procs", + "winapi 0.3.9", ] [[package]] name = "uu_tee" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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", + "clap", + "libc", + "retain_mut", + "uucore", + "uucore_procs", ] [[package]] name = "uu_test" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "libc", + "redox_syscall 0.1.57", + "uucore", + "uucore_procs", ] [[package]] name = "uu_timeout" -version = "0.0.4" +version = "0.0.6" 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)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "getopts", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_touch" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "filetime 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "filetime", + "time", + "uucore", + "uucore_procs", ] [[package]] name = "uu_tr" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "bit-set", + "clap", + "fnv", + "uucore", + "uucore_procs", ] [[package]] name = "uu_true" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "uucore 0.0.7", - "uucore_procs 0.0.5", + "uucore", + "uucore_procs", ] [[package]] name = "uu_truncate" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_tsort" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_tty" -version = "0.0.4" +version = "0.0.6" 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)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_uname" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "platform-info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "platform-info", + "uucore", + "uucore_procs", ] [[package]] name = "uu_unexpand" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "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", + "clap", + "unicode-width", + "uucore", + "uucore_procs", ] [[package]] name = "uu_uniq" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "strum", + "strum_macros", + "uucore", + "uucore_procs", ] [[package]] name = "uu_unlink" -version = "0.0.4" +version = "0.0.6" 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)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "getopts", + "libc", + "uucore", + "uucore_procs", ] [[package]] name = "uu_uptime" -version = "0.0.4" +version = "0.0.6" 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)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "chrono", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_users" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uu_wc" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "libc", + "nix 0.20.0", + "thiserror", + "uucore", + "uucore_procs", ] [[package]] name = "uu_who" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "uucore 0.0.7", - "uucore_procs 0.0.5", + "uucore", + "uucore_procs", ] [[package]] name = "uu_whoami" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "advapi32-sys 0.2.0 (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", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "advapi32-sys", + "clap", + "uucore", + "uucore_procs", + "winapi 0.3.9", ] [[package]] name = "uu_yes" -version = "0.0.4" +version = "0.0.6" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "uucore 0.0.7", - "uucore_procs 0.0.5", + "clap", + "uucore", + "uucore_procs", ] [[package]] name = "uucore" -version = "0.0.7" +version = "0.0.8" dependencies = [ - "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "dunce 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", - "platform-info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "wild 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "data-encoding", + "dunce", + "getopts", + "lazy_static", + "libc", + "nix 0.13.1", + "platform-info", + "termion", + "thiserror", + "time", + "wild", ] [[package]] name = "uucore_procs" version = "0.0.5" 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)", + "proc-macro2", + "quote 1.0.9", + "syn", ] [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "walkdir" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ - "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "same-file", + "winapi 0.3.9", + "winapi-util", ] [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.72" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.72" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" dependencies = [ - "bumpalo 3.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote 1.0.9", + "syn", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.72" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" dependencies = [ - "quote 1.0.9 (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)", + "quote 1.0.9", + "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.72" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" 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.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)", + "proc-macro2", + "quote 1.0.9", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.72" +version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" [[package]] name = "web-sys" -version = "0.3.49" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" dependencies = [ - "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)", + "js-sys", + "wasm-bindgen", ] [[package]] name = "wild" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020" dependencies = [ - "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.3.0", ] [[package]] name = "winapi" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "xattr" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" dependencies = [ - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] - -[metadata] -"checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" -"checksum aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" -"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -"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 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" -"checksum bstr 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" -"checksum bumpalo 3.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" -"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" -"checksum byteorder 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" -"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" -"checksum cc 1.0.67 (registry+https://github.com/rust-lang/crates.io-index)" = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -"checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" -"checksum clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)" = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -"checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" -"checksum cpp 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4875a08600be48dcc9cb6ee07f104a3e0752e95184dede6a30044d6480bf50e8" -"checksum cpp_build 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c47531e7e09532ad4827098729794f5e1a5b1c2ccbb5e295498d2e7ab451c445" -"checksum cpp_common 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "79e39149a7943affa02f5b6e347ca2840a129cc78d5883ee229f0f1c4027d628" -"checksum cpp_common 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "df78ad28e5fe814285016779fb3d3b874520c799a847e6190bf2b834cc4ff283" -"checksum cpp_macros 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4f93a21e618c10abc84ebb63ffa5952e1f7a4568b8141d542d5ef860e4a8fc25" -"checksum cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8cd649bf5b3804d92fe12a60c7698f5a538a6033ed8a668bf5241d4d4f1644e" -"checksum cpp_synmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "897e4f9cdbe2874edd3ffe53718ee5d8b89e2a970057b2c93d3214104f2e90b6" -"checksum cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fc8da5694233b646150c785118f77835ad0a49680c7f312a10ef30957c67b6d" -"checksum criterion 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23" -"checksum criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" -"checksum crossbeam-channel 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" -"checksum crossbeam-deque 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" -"checksum crossbeam-epoch 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" -"checksum crossbeam-utils 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" -"checksum csv 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -"checksum csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -"checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" -"checksum data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" -"checksum digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a" -"checksum dunce 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b2641c4a7c0c4101df53ea572bffdc561c146f6c2eb09e4df02bc4811e3feeb4" -"checksum either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -"checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum filetime 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" -"checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -"checksum fs_extra 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum generic-array 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2297fb0e3ea512e380da24b52dca3924028f59df5e3a17a18f81d8349ca7ebe" -"checksum getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -"checksum getrandom 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -"checksum half 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" -"checksum hermit-abi 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" -"checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" -"checksum hostname 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -"checksum if_rust_version 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec" -"checksum ioctl-sys 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" -"checksum itertools 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" -"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.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" -"checksum log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -"checksum match_cfg 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" -"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -"checksum md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" -"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" -"checksum memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" -"checksum memoffset 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cc14fc54a812b4472b4113facc3e44d099fbc0ea2ce0551fa5c703f8edfbfd38" -"checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" -"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" -"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -"checksum number_prefix 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" -"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" -"checksum onig 4.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8518fcb2b1b8c2f45f0ad499df4fda6087fc3475ca69a185c173b8315d2fb383" -"checksum onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0" -"checksum oorandom 11.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" -"checksum paste 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -"checksum paste-impl 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -"checksum pkg-config 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" -"checksum platform-info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "16ea9cd21d89bffb387b6c7363d23bead0807be9de676c671b474dd29e7436d3" -"checksum plotters 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "45ca0ae5f169d0917a7c7f5a9c1a3d3d9598f18f529dd2b8373ed988efea307a" -"checksum plotters-backend 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590" -"checksum plotters-svg 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211" -"checksum ppv-lite86 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" -"checksum proc-macro-hack 0.5.19 (registry+https://github.com/rust-lang/crates.io-index)" = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" -"checksum proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" -"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -"checksum quickcheck 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" -"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" -"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -"checksum rayon 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" -"checksum rayon-core 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" -"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.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" -"checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" -"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -"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.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.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" -"checksum sha3 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26405905b6a56a94c60109cfda62610507ac14a65be531f5767dec5c5a8dd6a0" -"checksum smallvec 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" -"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "230d3e804faaed5a39b08319efb797783df2fd9671b39b7596490cb486d702cf" -"checksum term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -"checksum termion 1.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" -"checksum termsize 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "5e86d824a8e90f342ad3ef4bd51ef7119a9b681b0cc9f8ee7b2852f02ccd2517" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thiserror 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" -"checksum thiserror-impl 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" -"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum tinytemplate 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -"checksum typenum 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" -"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" -"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -"checksum unindent 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" -"checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" -"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.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.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" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" diff --git a/Cargo.toml b/Cargo.toml index 1bd559763..498366181 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "coreutils" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" @@ -43,6 +43,7 @@ feat_common_core = [ "df", "dircolors", "dirname", + "du", "echo", "env", "expand", @@ -150,7 +151,6 @@ feat_require_unix = [ "chmod", "chown", "chroot", - "du", "groups", "hostid", "id", @@ -227,105 +227,105 @@ test = [ "uu_test" ] [dependencies] lazy_static = { version="1.3" } textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review -uucore = { version=">=0.0.7", package="uucore", path="src/uucore" } +uucore = { version=">=0.0.8", package="uucore", path="src/uucore" } # * uutils -uu_test = { optional=true, version="0.0.4", package="uu_test", path="src/uu/test" } +uu_test = { optional=true, version="0.0.6", package="uu_test", path="src/uu/test" } # -arch = { optional=true, version="0.0.4", package="uu_arch", path="src/uu/arch" } -base32 = { optional=true, version="0.0.4", package="uu_base32", path="src/uu/base32" } -base64 = { optional=true, version="0.0.4", package="uu_base64", path="src/uu/base64" } -basename = { optional=true, version="0.0.4", package="uu_basename", path="src/uu/basename" } -cat = { optional=true, version="0.0.4", package="uu_cat", path="src/uu/cat" } -chgrp = { optional=true, version="0.0.4", package="uu_chgrp", path="src/uu/chgrp" } -chmod = { optional=true, version="0.0.4", package="uu_chmod", path="src/uu/chmod" } -chown = { optional=true, version="0.0.4", package="uu_chown", path="src/uu/chown" } -chroot = { optional=true, version="0.0.4", package="uu_chroot", path="src/uu/chroot" } -cksum = { optional=true, version="0.0.4", package="uu_cksum", path="src/uu/cksum" } -comm = { optional=true, version="0.0.4", package="uu_comm", path="src/uu/comm" } -cp = { optional=true, version="0.0.4", package="uu_cp", path="src/uu/cp" } -csplit = { optional=true, version="0.0.4", package="uu_csplit", path="src/uu/csplit" } -cut = { optional=true, version="0.0.4", package="uu_cut", path="src/uu/cut" } -date = { optional=true, version="0.0.4", package="uu_date", path="src/uu/date" } -df = { optional=true, version="0.0.4", package="uu_df", path="src/uu/df" } -dircolors= { optional=true, version="0.0.4", package="uu_dircolors", path="src/uu/dircolors" } -dirname = { optional=true, version="0.0.4", package="uu_dirname", path="src/uu/dirname" } -du = { optional=true, version="0.0.4", package="uu_du", path="src/uu/du" } -echo = { optional=true, version="0.0.4", package="uu_echo", path="src/uu/echo" } -env = { optional=true, version="0.0.4", package="uu_env", path="src/uu/env" } -expand = { optional=true, version="0.0.4", package="uu_expand", path="src/uu/expand" } -expr = { optional=true, version="0.0.4", package="uu_expr", path="src/uu/expr" } -factor = { optional=true, version="0.0.4", package="uu_factor", path="src/uu/factor" } -false = { optional=true, version="0.0.4", package="uu_false", path="src/uu/false" } -fmt = { optional=true, version="0.0.4", package="uu_fmt", path="src/uu/fmt" } -fold = { optional=true, version="0.0.4", package="uu_fold", path="src/uu/fold" } -groups = { optional=true, version="0.0.4", package="uu_groups", path="src/uu/groups" } -hashsum = { optional=true, version="0.0.4", package="uu_hashsum", path="src/uu/hashsum" } -head = { optional=true, version="0.0.4", package="uu_head", path="src/uu/head" } -hostid = { optional=true, version="0.0.4", package="uu_hostid", path="src/uu/hostid" } -hostname = { optional=true, version="0.0.4", package="uu_hostname", path="src/uu/hostname" } -id = { optional=true, version="0.0.4", package="uu_id", path="src/uu/id" } -install = { optional=true, version="0.0.4", package="uu_install", path="src/uu/install" } -join = { optional=true, version="0.0.4", package="uu_join", path="src/uu/join" } -kill = { optional=true, version="0.0.4", package="uu_kill", path="src/uu/kill" } -link = { optional=true, version="0.0.4", package="uu_link", path="src/uu/link" } -ln = { optional=true, version="0.0.4", package="uu_ln", path="src/uu/ln" } -ls = { optional=true, version="0.0.4", package="uu_ls", path="src/uu/ls" } -logname = { optional=true, version="0.0.4", package="uu_logname", path="src/uu/logname" } -mkdir = { optional=true, version="0.0.4", package="uu_mkdir", path="src/uu/mkdir" } -mkfifo = { optional=true, version="0.0.4", package="uu_mkfifo", path="src/uu/mkfifo" } -mknod = { optional=true, version="0.0.4", package="uu_mknod", path="src/uu/mknod" } -mktemp = { optional=true, version="0.0.4", package="uu_mktemp", path="src/uu/mktemp" } -more = { optional=true, version="0.0.4", package="uu_more", path="src/uu/more" } -mv = { optional=true, version="0.0.4", package="uu_mv", path="src/uu/mv" } -nice = { optional=true, version="0.0.4", package="uu_nice", path="src/uu/nice" } -nl = { optional=true, version="0.0.4", package="uu_nl", path="src/uu/nl" } -nohup = { optional=true, version="0.0.4", package="uu_nohup", path="src/uu/nohup" } -nproc = { optional=true, version="0.0.4", package="uu_nproc", path="src/uu/nproc" } -numfmt = { optional=true, version="0.0.4", package="uu_numfmt", path="src/uu/numfmt" } -od = { optional=true, version="0.0.4", package="uu_od", path="src/uu/od" } -paste = { optional=true, version="0.0.4", package="uu_paste", path="src/uu/paste" } -pathchk = { optional=true, version="0.0.4", package="uu_pathchk", path="src/uu/pathchk" } -pinky = { optional=true, version="0.0.4", package="uu_pinky", path="src/uu/pinky" } -pr = { optional=true, version="0.0.4", package="uu_pr", path="src/uu/pr" } -printenv = { optional=true, version="0.0.4", package="uu_printenv", path="src/uu/printenv" } -printf = { optional=true, version="0.0.4", package="uu_printf", path="src/uu/printf" } -ptx = { optional=true, version="0.0.4", package="uu_ptx", path="src/uu/ptx" } -pwd = { optional=true, version="0.0.4", package="uu_pwd", path="src/uu/pwd" } -readlink = { optional=true, version="0.0.4", package="uu_readlink", path="src/uu/readlink" } -realpath = { optional=true, version="0.0.4", package="uu_realpath", path="src/uu/realpath" } -relpath = { optional=true, version="0.0.4", package="uu_relpath", path="src/uu/relpath" } -rm = { optional=true, version="0.0.4", package="uu_rm", path="src/uu/rm" } -rmdir = { optional=true, version="0.0.4", package="uu_rmdir", path="src/uu/rmdir" } -seq = { optional=true, version="0.0.4", package="uu_seq", path="src/uu/seq" } -shred = { optional=true, version="0.0.4", package="uu_shred", path="src/uu/shred" } -shuf = { optional=true, version="0.0.4", package="uu_shuf", path="src/uu/shuf" } -sleep = { optional=true, version="0.0.4", package="uu_sleep", path="src/uu/sleep" } -sort = { optional=true, version="0.0.4", package="uu_sort", path="src/uu/sort" } -split = { optional=true, version="0.0.4", package="uu_split", path="src/uu/split" } -stat = { optional=true, version="0.0.4", package="uu_stat", path="src/uu/stat" } -stdbuf = { optional=true, version="0.0.4", package="uu_stdbuf", path="src/uu/stdbuf" } -sum = { optional=true, version="0.0.4", package="uu_sum", path="src/uu/sum" } -sync = { optional=true, version="0.0.4", package="uu_sync", path="src/uu/sync" } -tac = { optional=true, version="0.0.4", package="uu_tac", path="src/uu/tac" } -tail = { optional=true, version="0.0.4", package="uu_tail", path="src/uu/tail" } -tee = { optional=true, version="0.0.4", package="uu_tee", path="src/uu/tee" } -timeout = { optional=true, version="0.0.4", package="uu_timeout", path="src/uu/timeout" } -touch = { optional=true, version="0.0.4", package="uu_touch", path="src/uu/touch" } -tr = { optional=true, version="0.0.4", package="uu_tr", path="src/uu/tr" } -true = { optional=true, version="0.0.4", package="uu_true", path="src/uu/true" } -truncate = { optional=true, version="0.0.4", package="uu_truncate", path="src/uu/truncate" } -tsort = { optional=true, version="0.0.4", package="uu_tsort", path="src/uu/tsort" } -tty = { optional=true, version="0.0.4", package="uu_tty", path="src/uu/tty" } -uname = { optional=true, version="0.0.4", package="uu_uname", path="src/uu/uname" } -unexpand = { optional=true, version="0.0.4", package="uu_unexpand", path="src/uu/unexpand" } -uniq = { optional=true, version="0.0.4", package="uu_uniq", path="src/uu/uniq" } -unlink = { optional=true, version="0.0.4", package="uu_unlink", path="src/uu/unlink" } -uptime = { optional=true, version="0.0.4", package="uu_uptime", path="src/uu/uptime" } -users = { optional=true, version="0.0.4", package="uu_users", path="src/uu/users" } -wc = { optional=true, version="0.0.4", package="uu_wc", path="src/uu/wc" } -who = { optional=true, version="0.0.4", package="uu_who", path="src/uu/who" } -whoami = { optional=true, version="0.0.4", package="uu_whoami", path="src/uu/whoami" } -yes = { optional=true, version="0.0.4", package="uu_yes", path="src/uu/yes" } +arch = { optional=true, version="0.0.6", package="uu_arch", path="src/uu/arch" } +base32 = { optional=true, version="0.0.6", package="uu_base32", path="src/uu/base32" } +base64 = { optional=true, version="0.0.6", package="uu_base64", path="src/uu/base64" } +basename = { optional=true, version="0.0.6", package="uu_basename", path="src/uu/basename" } +cat = { optional=true, version="0.0.6", package="uu_cat", path="src/uu/cat" } +chgrp = { optional=true, version="0.0.6", package="uu_chgrp", path="src/uu/chgrp" } +chmod = { optional=true, version="0.0.6", package="uu_chmod", path="src/uu/chmod" } +chown = { optional=true, version="0.0.6", package="uu_chown", path="src/uu/chown" } +chroot = { optional=true, version="0.0.6", package="uu_chroot", path="src/uu/chroot" } +cksum = { optional=true, version="0.0.6", package="uu_cksum", path="src/uu/cksum" } +comm = { optional=true, version="0.0.6", package="uu_comm", path="src/uu/comm" } +cp = { optional=true, version="0.0.6", package="uu_cp", path="src/uu/cp" } +csplit = { optional=true, version="0.0.6", package="uu_csplit", path="src/uu/csplit" } +cut = { optional=true, version="0.0.6", package="uu_cut", path="src/uu/cut" } +date = { optional=true, version="0.0.6", package="uu_date", path="src/uu/date" } +df = { optional=true, version="0.0.6", package="uu_df", path="src/uu/df" } +dircolors= { optional=true, version="0.0.6", package="uu_dircolors", path="src/uu/dircolors" } +dirname = { optional=true, version="0.0.6", package="uu_dirname", path="src/uu/dirname" } +du = { optional=true, version="0.0.6", package="uu_du", path="src/uu/du" } +echo = { optional=true, version="0.0.6", package="uu_echo", path="src/uu/echo" } +env = { optional=true, version="0.0.6", package="uu_env", path="src/uu/env" } +expand = { optional=true, version="0.0.6", package="uu_expand", path="src/uu/expand" } +expr = { optional=true, version="0.0.6", package="uu_expr", path="src/uu/expr" } +factor = { optional=true, version="0.0.6", package="uu_factor", path="src/uu/factor" } +false = { optional=true, version="0.0.6", package="uu_false", path="src/uu/false" } +fmt = { optional=true, version="0.0.6", package="uu_fmt", path="src/uu/fmt" } +fold = { optional=true, version="0.0.6", package="uu_fold", path="src/uu/fold" } +groups = { optional=true, version="0.0.6", package="uu_groups", path="src/uu/groups" } +hashsum = { optional=true, version="0.0.6", package="uu_hashsum", path="src/uu/hashsum" } +head = { optional=true, version="0.0.6", package="uu_head", path="src/uu/head" } +hostid = { optional=true, version="0.0.6", package="uu_hostid", path="src/uu/hostid" } +hostname = { optional=true, version="0.0.6", package="uu_hostname", path="src/uu/hostname" } +id = { optional=true, version="0.0.6", package="uu_id", path="src/uu/id" } +install = { optional=true, version="0.0.6", package="uu_install", path="src/uu/install" } +join = { optional=true, version="0.0.6", package="uu_join", path="src/uu/join" } +kill = { optional=true, version="0.0.6", package="uu_kill", path="src/uu/kill" } +link = { optional=true, version="0.0.6", package="uu_link", path="src/uu/link" } +ln = { optional=true, version="0.0.6", package="uu_ln", path="src/uu/ln" } +ls = { optional=true, version="0.0.6", package="uu_ls", path="src/uu/ls" } +logname = { optional=true, version="0.0.6", package="uu_logname", path="src/uu/logname" } +mkdir = { optional=true, version="0.0.6", package="uu_mkdir", path="src/uu/mkdir" } +mkfifo = { optional=true, version="0.0.6", package="uu_mkfifo", path="src/uu/mkfifo" } +mknod = { optional=true, version="0.0.6", package="uu_mknod", path="src/uu/mknod" } +mktemp = { optional=true, version="0.0.6", package="uu_mktemp", path="src/uu/mktemp" } +more = { optional=true, version="0.0.6", package="uu_more", path="src/uu/more" } +mv = { optional=true, version="0.0.6", package="uu_mv", path="src/uu/mv" } +nice = { optional=true, version="0.0.6", package="uu_nice", path="src/uu/nice" } +nl = { optional=true, version="0.0.6", package="uu_nl", path="src/uu/nl" } +nohup = { optional=true, version="0.0.6", package="uu_nohup", path="src/uu/nohup" } +nproc = { optional=true, version="0.0.6", package="uu_nproc", path="src/uu/nproc" } +numfmt = { optional=true, version="0.0.6", package="uu_numfmt", path="src/uu/numfmt" } +od = { optional=true, version="0.0.6", package="uu_od", path="src/uu/od" } +paste = { optional=true, version="0.0.6", package="uu_paste", path="src/uu/paste" } +pathchk = { optional=true, version="0.0.6", package="uu_pathchk", path="src/uu/pathchk" } +pinky = { optional=true, version="0.0.6", package="uu_pinky", path="src/uu/pinky" } +pr = { optional=true, version="0.0.6", package="uu_pr", path="src/uu/pr" } +printenv = { optional=true, version="0.0.6", package="uu_printenv", path="src/uu/printenv" } +printf = { optional=true, version="0.0.6", package="uu_printf", path="src/uu/printf" } +ptx = { optional=true, version="0.0.6", package="uu_ptx", path="src/uu/ptx" } +pwd = { optional=true, version="0.0.6", package="uu_pwd", path="src/uu/pwd" } +readlink = { optional=true, version="0.0.6", package="uu_readlink", path="src/uu/readlink" } +realpath = { optional=true, version="0.0.6", package="uu_realpath", path="src/uu/realpath" } +relpath = { optional=true, version="0.0.6", package="uu_relpath", path="src/uu/relpath" } +rm = { optional=true, version="0.0.6", package="uu_rm", path="src/uu/rm" } +rmdir = { optional=true, version="0.0.6", package="uu_rmdir", path="src/uu/rmdir" } +seq = { optional=true, version="0.0.6", package="uu_seq", path="src/uu/seq" } +shred = { optional=true, version="0.0.6", package="uu_shred", path="src/uu/shred" } +shuf = { optional=true, version="0.0.6", package="uu_shuf", path="src/uu/shuf" } +sleep = { optional=true, version="0.0.6", package="uu_sleep", path="src/uu/sleep" } +sort = { optional=true, version="0.0.6", package="uu_sort", path="src/uu/sort" } +split = { optional=true, version="0.0.6", package="uu_split", path="src/uu/split" } +stat = { optional=true, version="0.0.6", package="uu_stat", path="src/uu/stat" } +stdbuf = { optional=true, version="0.0.6", package="uu_stdbuf", path="src/uu/stdbuf" } +sum = { optional=true, version="0.0.6", package="uu_sum", path="src/uu/sum" } +sync = { optional=true, version="0.0.6", package="uu_sync", path="src/uu/sync" } +tac = { optional=true, version="0.0.6", package="uu_tac", path="src/uu/tac" } +tail = { optional=true, version="0.0.6", package="uu_tail", path="src/uu/tail" } +tee = { optional=true, version="0.0.6", package="uu_tee", path="src/uu/tee" } +timeout = { optional=true, version="0.0.6", package="uu_timeout", path="src/uu/timeout" } +touch = { optional=true, version="0.0.6", package="uu_touch", path="src/uu/touch" } +tr = { optional=true, version="0.0.6", package="uu_tr", path="src/uu/tr" } +true = { optional=true, version="0.0.6", package="uu_true", path="src/uu/true" } +truncate = { optional=true, version="0.0.6", package="uu_truncate", path="src/uu/truncate" } +tsort = { optional=true, version="0.0.6", package="uu_tsort", path="src/uu/tsort" } +tty = { optional=true, version="0.0.6", package="uu_tty", path="src/uu/tty" } +uname = { optional=true, version="0.0.6", package="uu_uname", path="src/uu/uname" } +unexpand = { optional=true, version="0.0.6", package="uu_unexpand", path="src/uu/unexpand" } +uniq = { optional=true, version="0.0.6", package="uu_uniq", path="src/uu/uniq" } +unlink = { optional=true, version="0.0.6", package="uu_unlink", path="src/uu/unlink" } +uptime = { optional=true, version="0.0.6", package="uu_uptime", path="src/uu/uptime" } +users = { optional=true, version="0.0.6", package="uu_users", path="src/uu/users" } +wc = { optional=true, version="0.0.6", package="uu_wc", path="src/uu/wc" } +who = { optional=true, version="0.0.6", package="uu_who", path="src/uu/who" } +whoami = { optional=true, version="0.0.6", package="uu_whoami", path="src/uu/whoami" } +yes = { optional=true, version="0.0.6", package="uu_yes", path="src/uu/yes" } # # * pinned transitive dependencies # Not needed for now. Keep as examples: @@ -337,6 +337,7 @@ conv = "0.3" filetime = "0.2" glob = "0.3.0" libc = "0.2" +nix = "0.20.0" rand = "0.7" regex = "1.0" sha1 = { version="0.6", features=["std"] } @@ -345,13 +346,15 @@ sha1 = { version="0.6", features=["std"] } tempfile = "= 3.1.0" time = "0.1" unindent = "0.1" -uucore = { version=">=0.0.7", package="uucore", path="src/uucore", features=["entries"] } +uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] } walkdir = "2.2" +tempdir = "0.3" [target.'cfg(unix)'.dev-dependencies] rust-users = { version="0.10", package="users" } unix_socket = "0.5.0" + [[bin]] name = "coreutils" path = "src/bin/coreutils.rs" diff --git a/DEVELOPER_INSTRUCTIONS.md b/DEVELOPER_INSTRUCTIONS.md new file mode 100644 index 000000000..c3b20dd46 --- /dev/null +++ b/DEVELOPER_INSTRUCTIONS.md @@ -0,0 +1,38 @@ +Code Coverage Report Generation +--------------------------------- + +Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov). + +### Using Nightly Rust + +To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-cc) coverage report + +```bash +$ export CARGO_INCREMENTAL=0 +$ export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" +$ export RUSTDOCFLAGS="-Cpanic=abort" +$ cargo build # e.g., --features feat_os_unix +$ cargo test # e.g., --features feat_os_unix test_pathchk +$ grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/ +$ # open target/debug/coverage/index.html in browser +``` + +if changes are not reflected in the report then run `cargo clean` and run the above commands. + +### Using Stable Rust + +If you are using stable version of Rust that doesn't enable code coverage instrumentation by default +then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above. + + +pre-commit hooks +---------------- + +A configuration for `pre-commit` is provided in the repository. It allows automatically checking every git commit you make to ensure it compiles, and passes `clippy` and `rustfmt` without warnings. + +To use the provided hook: + +1. [Install `pre-commit`](https://pre-commit.com/#install) +2. Run `pre-commit install` while in the repository directory + +Your git commits will then automatically be checked. If a check fails, an error message will explain why, and your commit will be canceled. You can then make the suggested changes, and run `git commit ...` again. diff --git a/GNUmakefile b/GNUmakefile index c4c9cbe8b..409a527cd 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -30,7 +30,7 @@ ifneq ($(.SHELLSTATUS),0) override INSTALLDIR_MAN=$(DESTDIR)$(PREFIX)$(MANDIR) endif -#prefix to apply to uutils binary and all tool binaries +#prefix to apply to coreutils binary and all tool binaries PROG_PREFIX ?= # This won't support any directory with spaces in its name, but you can just @@ -237,7 +237,7 @@ EXES := \ INSTALLEES := ${EXES} ifeq (${MULTICALL}, y) -INSTALLEES := ${INSTALLEES} uutils +INSTALLEES := ${INSTALLEES} coreutils endif all: build @@ -250,13 +250,13 @@ ifneq (${MULTICALL}, y) ${CARGO} build ${CARGOFLAGS} ${PROFILE_CMD} $(foreach pkg,$(EXES),-p uu_$(pkg)) endif -build-uutils: +build-coreutils: ${CARGO} build ${CARGOFLAGS} --features "${EXES}" ${PROFILE_CMD} --no-default-features build-manpages: cd $(DOCSDIR) && $(MAKE) man -build: build-uutils build-pkgs build-manpages +build: build-coreutils build-pkgs build-manpages $(foreach test,$(filter-out $(SKIP_UTILS),$(PROGS)),$(eval $(call TEST_BUSYBOX,$(test)))) @@ -275,7 +275,7 @@ $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config cp $< $@ # Test under the busybox testsuite -$(BUILDDIR)/busybox: busybox-src build-uutils $(BUILDDIR)/.config +$(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config cp $(BUILDDIR)/coreutils $(BUILDDIR)/busybox; \ chmod +x $@; @@ -298,10 +298,10 @@ install: build mkdir -p $(INSTALLDIR_BIN) mkdir -p $(INSTALLDIR_MAN) ifeq (${MULTICALL}, y) - $(INSTALL) $(BUILDDIR)/uutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)uutils - cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out uutils, $(INSTALLEES)), \ - ln -fs $(PROG_PREFIX)uutils $(PROG_PREFIX)$(prog) &&) : - cat $(DOCSDIR)/_build/man/uutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)uutils.1.gz + $(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils + cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out coreutils, $(INSTALLEES)), \ + ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)$(prog) &&) : + cat $(DOCSDIR)/_build/man/coreutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)coreutils.1.gz else $(foreach prog, $(INSTALLEES), \ $(INSTALL) $(BUILDDIR)/$(prog) $(INSTALLDIR_BIN)/$(PROG_PREFIX)$(prog);) @@ -311,10 +311,10 @@ endif uninstall: ifeq (${MULTICALL}, y) - rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)uutils) + rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)coreutils) endif - rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)uutils.1.gz) + rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz) rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(INSTALLDIR_MAN)/$(PROG_PREFIX),$(addsuffix .1.gz,$(PROGS))) -.PHONY: all build build-uutils build-pkgs build-docs test distclean clean busytest install uninstall +.PHONY: all build build-coreutils build-pkgs build-docs test distclean clean busytest install uninstall diff --git a/README.md b/README.md index f292b7184..f63c94987 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -uutils coreutils -================ +# uutils coreutils [![Crates.io](https://img.shields.io/crates/v/coreutils.svg)](https://crates.io/crates/coreutils) [![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ) @@ -9,16 +8,18 @@ uutils coreutils [![Build Status](https://api.travis-ci.org/uutils/coreutils.svg?branch=master)](https://travis-ci.org/uutils/coreutils) [![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master) -[![codecov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils) +[![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils) ----------------------------------------------- + + + uutils is an attempt at writing universal (as in cross-platform) CLI utilities in [Rust](http://www.rust-lang.org). This repository is intended to aggregate GNU coreutils rewrites. -Why? ----- +## Why? Many GNU, Linux and other utilities are useful, and obviously [some](http://gnuwin32.sourceforge.net) [effort](http://unxutils.sourceforge.net) @@ -29,260 +30,300 @@ have other issues. Rust provides a good, platform-agnostic way of writing systems utilities that are easy to compile anywhere, and this is as good a way as any to try and learn it. -Requirements ------------- +## Requirements * Rust (`cargo`, `rustc`) * GNU Make (required to build documentation) * [Sphinx](http://www.sphinx-doc.org/) (for documentation) * gzip (for installing documentation) -### Rust Version ### +### Rust Version uutils follows Rust's release channels and is tested against stable, beta and nightly. The current oldest supported version of the Rust compiler is `1.40.0`. On both Windows and Redox, only the nightly version is tested currently. -Build Instructions ------------------- +## Build Instructions -There are currently two methods to build uutils: GNU Make and Cargo. However, -while there may be two methods, both systems are required to build on Unix -(only Cargo is required on Windows). +There are currently two methods to build the uutils binaries: either Cargo +or GNU Make. + +> Building the full package, including all documentation, requires both Cargo +> and Gnu Make on a Unix platform. + +For either method, we first need to fetch the repository: -First, for both methods, we need to fetch the repository: ```bash $ git clone https://github.com/uutils/coreutils $ cd coreutils ``` -### Cargo ### +### Cargo Building uutils using Cargo is easy because the process is the same as for every other Rust program: + ```bash -# to keep debug information, compile without --release $ cargo build --release ``` -Because the above command attempts to build utilities that only work on -Unix-like platforms at the moment, to build on Windows, you must do the -following: +This command builds the most portable common core set of uutils into a multicall +(BusyBox-type) binary, named 'coreutils', on most Rust-supported platforms. + +Additional platform-specific uutils are often available. Building these +expanded sets of uutils for a platform (on that platform) is as simple as +specifying it as a feature: + ```bash -# to keep debug information, compile without --release -$ cargo build --release --no-default-features --features windows +$ cargo build --release --features macos +# or ... +$ cargo build --release --features windows +# or ... +$ cargo build --release --features unix ``` If you don't want to build every utility available on your platform into the -multicall binary (the Busybox-esque binary), you can also specify which ones -you want to build manually. For example: +final binary, you can also specify which ones you want to build manually. +For example: + ```bash $ cargo build --features "base32 cat echo rm" --no-default-features ``` -If you don't even want to build the multicall binary and would prefer to just -build the utilities as individual binaries, that is possible too. For example: +If you don't want to build the multicall binary and would prefer to build +the utilities as individual binaries, that is also possible. Each utility +is contained in it's own package within the main repository, named +"uu_UTILNAME". To build individual utilities, use cargo to build just the +specific packages (using the `--package` [aka `-p`] option). For example: + ```bash $ cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm ``` -### GNU Make ### +### GNU Make Building using `make` is a simple process as well. To simply build all available utilities: + ```bash $ make ``` To build all but a few of the available utilities: + ```bash $ make SKIP_UTILS='UTILITY_1 UTILITY_2' ``` To build only a few of the available utilities: + ```bash $ make UTILS='UTILITY_1 UTILITY_2' ``` -Installation Instructions -------------------------- +## Installation Instructions -### Cargo ### +### Cargo Likewise, installing can simply be done using: + ```bash $ cargo install --path . ``` This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`). -### GNU Make ### +### GNU Make To install all available utilities: + ```bash $ make install ``` To install using `sudo` switch `-E` must be used: + ```bash $ sudo -E make install ``` To install all but a few of the available utilities: + ```bash $ make SKIP_UTILS='UTILITY_1 UTILITY_2' install ``` To install only a few of the available utilities: + ```bash $ make UTILS='UTILITY_1 UTILITY_2' install ``` To install every program with a prefix (e.g. uu-echo uu-cat): + ```bash $ make PROG_PREFIX=PREFIX_GOES_HERE install ``` To install the multicall binary: + ```bash $ make MULTICALL=y install ``` Set install parent directory (default value is /usr/local): + ```bash # DESTDIR is also supported $ make PREFIX=/my/path install ``` -### NixOS ### +### NixOS The [standard package set](https://nixos.org/nixpkgs/manual/) of [NixOS](https://nixos.org/) provides this package out of the box since 18.03: -``` -nix-env -iA nixos.uutils-coreutils +```shell +$ nix-env -iA nixos.uutils-coreutils ``` -Uninstallation Instructions ---------------------------- +## Un-installation Instructions -Uninstallation differs depending on how you have installed uutils. If you used +Un-installation differs depending on how you have installed uutils. If you used Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use Make to uninstall. -### Cargo ### +### Cargo To uninstall uutils: + ```bash $ cargo uninstall uutils ``` -### GNU Make ### +### GNU Make To uninstall all utilities: + ```bash $ make uninstall ``` To uninstall every program with a set prefix: + ```bash $ make PROG_PREFIX=PREFIX_GOES_HERE uninstall ``` To uninstall the multicall binary: + ```bash $ make MULTICALL=y uninstall ``` To uninstall from a custom parent directory: + ```bash # DESTDIR is also supported $ make PREFIX=/my/path uninstall ``` -Test Instructions ------------------ +## Test Instructions Testing can be done using either Cargo or `make`. -### Cargo ### +### Cargo Just like with building, we follow the standard procedure for testing using Cargo: + ```bash $ cargo test ``` By default, `cargo test` only runs the common programs. To run also platform specific tests, run: + ```bash $ cargo test --features unix ``` If you would prefer to test a select few utilities: + ```bash $ cargo test --features "chmod mv tail" --no-default-features ``` +If you also want to test the core utilities: + +```bash +$ cargo test -p uucore -p coreutils +``` + To debug: + ```bash $ gdb --args target/debug/coreutils ls (gdb) b ls.rs:79 (gdb) run ``` -### GNU Make ### +### GNU Make To simply test all available utilities: + ```bash $ make test ``` To test all but a few of the available utilities: + ```bash $ make SKIP_UTILS='UTILITY_1 UTILITY_2' test ``` To test only a few of the available utilities: + ```bash $ make UTILS='UTILITY_1 UTILITY_2' test ``` To include tests for unimplemented behavior: + ```bash $ make UTILS='UTILITY_1 UTILITY_2' SPEC=y test ``` -Run Busybox Tests ------------------ +## Run Busybox Tests This testing functionality is only available on *nix operating systems and requires `make`. To run busybox's tests for all utilities for which busybox has tests + ```bash $ make busytest ``` To run busybox's tests for a few of the available utilities + ```bash $ make UTILS='UTILITY_1 UTILITY_2' busytest ``` To pass an argument like "-v" to the busybox test runtime + ```bash $ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest ``` -Contribute ----------- +## Contribute To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). -Utilities ---------- +## Utilities | Done | Semi-Done | To Do | |-----------|-----------|--------| @@ -372,8 +413,34 @@ Utilities | whoami | | | | yes | | | -License -------- +## Targets that compile + +This is an auto-generated table showing which binaries compile for each target-triple. Note that this **does not** indicate that they are fully implemented, or that the tests pass. + +|######OS######|###ARCH####|arch|base32|base64|basename|cat|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|df|dircolors|dirname|du|echo|env|expand|expr|factor|false|fmt|fold|groups|hashsum|head|hostid|hostname|id|install|join|kill|link|ln|logname|ls|mkdir|mkfifo|mknod|mktemp|more|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|printenv|printf|ptx|pwd|readlink|realpath|relpath|rm|rmdir|seq|shred|shuf|sleep|sort|split|stat|stdbuf|sum|sync|tac|tail|tee|test|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|uptime|users|wc|who|whoami|yes| +|--------------|-----------|----|------|------|--------|---|-----|-----|-----|------|-----|----|--|------|---|----|--|---------|-------|--|----|---|------|----|------|-----|---|----|------|-------|----|------|--------|--|-------|----|----|----|--|-------|--|-----|------|-----|------|----|--|----|--|-----|-----|------|--|-----|-------|-----|--------|------|---|---|--------|--------|-------|--|-----|---|-----|----|-----|----|-----|----|------|---|----|---|----|---|----|-------|-----|--|----|--------|-----|---|-----|--------|----|------|------|-----|--|---|------|---| +|linux-gnu|aarch64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| +|linux-gnu|i686|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| +|linux-gnu|powerpc64|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| +|linux-gnu|riscv64gc| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +|linux-gnu|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| +|windows-msvc|aarch64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y| |y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| | |y| +|windows-gnu|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y| +|windows-msvc|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y| +|windows-gnu|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y| +|windows-msvc|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y| +|apple MacOS|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| +|freebsd|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| +|netbsd|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y| +|android|aarch64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y| +|android|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y| +|solaris|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +|wasi|wasm32| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +|redox|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +|fuchsia|aarch64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +|fuchsia|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + +## License uutils is licensed under the MIT License - see the `LICENSE` file for details diff --git a/build.rs b/build.rs index 625a3ccc8..ae38177b0 100644 --- a/build.rs +++ b/build.rs @@ -44,10 +44,10 @@ pub fn main() { mf.write_all( "type UtilityMap = HashMap<&'static str, fn(T) -> i32>;\n\ - \n\ - fn util_map() -> UtilityMap {\n\ - \tlet mut map = UtilityMap::new();\n\ - " + \n\ + fn util_map() -> UtilityMap {\n\ + \tlet mut map = UtilityMap::new();\n\ + " .as_bytes(), ) .unwrap(); @@ -97,21 +97,21 @@ pub fn main() { mf.write_all( format!( "\ - \tmap.insert(\"{krate}\", {krate}::uumain);\n\ - \t\tmap.insert(\"md5sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha1sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha224sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha256sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha384sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha512sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha3sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha3-224sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha3-256sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha3-384sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"sha3-512sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"shake128sum\", {krate}::uumain);\n\ - \t\tmap.insert(\"shake256sum\", {krate}::uumain);\n\ - ", + \tmap.insert(\"{krate}\", {krate}::uumain);\n\ + \t\tmap.insert(\"md5sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"sha1sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"sha224sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"sha256sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"sha384sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"sha512sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"sha3sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"sha3-224sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"sha3-256sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"sha3-384sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"sha3-512sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"shake128sum\", {krate}::uumain);\n\ + \t\tmap.insert(\"shake256sum\", {krate}::uumain);\n\ + ", krate = krate ) .as_bytes(), diff --git a/docs/compiles_table.csv b/docs/compiles_table.csv new file mode 100644 index 000000000..6da110132 --- /dev/null +++ b/docs/compiles_table.csv @@ -0,0 +1,21 @@ +target,arch,base32,base64,basename,cat,chgrp,chmod,chown,chroot,cksum,comm,cp,csplit,cut,date,df,dircolors,dirname,du,echo,env,expand,expr,factor,false,fmt,fold,groups,hashsum,head,hostid,hostname,id,install,join,kill,link,ln,logname,ls,mkdir,mkfifo,mknod,mktemp,more,mv,nice,nl,nohup,nproc,numfmt,od,paste,pathchk,pinky,printenv,printf,ptx,pwd,readlink,realpath,relpath,rm,rmdir,seq,shred,shuf,sleep,sort,split,stat,stdbuf,sum,sync,tac,tail,tee,test,timeout,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,wc,who,whoami,yes +aarch64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +i686-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +powerpc64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +riscv64gc-unknown-linux-gnu,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +x86_64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +aarch64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,101,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,101,0 +i686-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0 +i686-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0 +x86_64-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0 +x86_64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0 +x86_64-apple-darwin,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +x86_64-unknown-freebsd,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +x86_64-unknown-netbsd,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0 +aarch64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0 +x86_64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0 +x86_64-sun-solaris,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +wasm32-wasi,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +x86_64-unknown-redox,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +aarch64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +x86_64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 diff --git a/docs/compiles_table.py b/docs/compiles_table.py new file mode 100644 index 000000000..0cbfdf0e9 --- /dev/null +++ b/docs/compiles_table.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +import multiprocessing +import subprocess +import argparse +import csv +import sys +from collections import defaultdict +from pathlib import Path + +# third party dependencies +from tqdm import tqdm + +BINS_PATH=Path("../src/uu") +CACHE_PATH=Path("compiles_table.csv") +TARGETS = [ + # Linux - GNU + "aarch64-unknown-linux-gnu", + "i686-unknown-linux-gnu", + "powerpc64-unknown-linux-gnu", + "riscv64gc-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + # Windows + "aarch64-pc-windows-msvc", + "i686-pc-windows-gnu", + "i686-pc-windows-msvc", + "x86_64-pc-windows-gnu", + "x86_64-pc-windows-msvc", + # Apple + "x86_64-apple-darwin", + "aarch64-apple-ios", + "x86_64-apple-ios", + # BSDs + "x86_64-unknown-freebsd", + "x86_64-unknown-netbsd", + # Android + "aarch64-linux-android", + "x86_64-linux-android", + # Solaris + "x86_64-sun-solaris", + # WASM + "wasm32-wasi", + # Redox + "x86_64-unknown-redox", + # Fuchsia + "aarch64-fuchsia", + "x86_64-fuchsia", +] + +class Target(str): + def __new__(cls, content): + obj = super().__new__(cls, content) + obj.arch, obj.platfrom, obj.os = Target.parse(content) + return obj + + @staticmethod + def parse(s): + elem = s.split("-") + if len(elem) == 2: + arch, platform, os = elem[0], "n/a", elem[1] + else: + arch, platform, os = elem[0], elem[1], "-".join(elem[2:]) + if os == "ios": + os = "apple IOS" + if os == "darwin": + os = "apple MacOS" + return (arch, platform, os) + + @staticmethod + def get_heading(): + return ["OS", "ARCH"] + + def get_row_heading(self): + return [self.os, self.arch] + + def requires_nightly(self): + return "redox" in self + + # Perform the 'it-compiles' check + def check(self, binary): + if self.requires_nightly(): + args = [ "cargo", "+nightly", "check", + "-p", f"uu_{binary}", "--bin", binary, + f"--target={self}"] + else: + args = ["cargo", "check", + "-p", f"uu_{binary}", "--bin", binary, + f"--target={self}"] + + res = subprocess.run(args, capture_output=True) + return res.returncode + + # Validate that the dependencies for running this target are met + def is_installed(self): + # check IOS sdk is installed, raise exception otherwise + if "ios" in self: + res = subprocess.run(["which", "xcrun"], capture_output=True) + if len(res.stdout) == 0: + raise Exception("Error: IOS sdk does not seem to be installed. Please do that manually") + if not self.requires_nightly(): + # check std toolchains are installed + toolchains = subprocess.run(["rustup", "target", "list"], capture_output=True) + toolchains = toolchains.stdout.decode('utf-8').split("\n") + if "installed" not in next(filter(lambda x: self in x, toolchains)): + raise Exception(f"Error: the {t} target is not installed. Please do that manually") + else: + # check nightly toolchains are installed + toolchains = subprocess.run(["rustup", "+nightly", "target", "list"], capture_output=True) + toolchains = toolchains.stdout.decode('utf-8').split("\n") + if "installed" not in next(filter(lambda x: self in x, toolchains)): + raise Exception(f"Error: the {t} nightly target is not installed. Please do that manually") + return True + +def install_targets(): + cmd = ["rustup", "target", "add"] + TARGETS + print(" ".join(cmd)) + ret = subprocess.run(cmd) + assert(ret.returncode == 0) + +def get_all_bins(): + bins = map(lambda x: x.name, BINS_PATH.iterdir()) + return sorted(list(bins)) + +def get_targets(selection): + if "all" in selection: + return list(map(Target, TARGETS)) + else: + # preserve the same order as in TARGETS + return list(map(Target, filter(lambda x: x in selection, TARGETS))) + +def test_helper(tup): + bin, target = tup + retcode = target.check(bin) + return (target, bin, retcode) + +def test_all_targets(targets, bins): + pool = multiprocessing.Pool() + inputs = [(b, t) for b in bins for t in targets] + + outputs = list(tqdm(pool.imap(test_helper, inputs), total=len(inputs))) + + table = defaultdict(dict) + for (t, b, r) in outputs: + table[t][b] = r + return table + +def save_csv(file, table): + targets = get_targets(table.keys()) # preserve order in CSV + bins = list(list(table.values())[0].keys()) + with open(file, "w") as csvfile: + header = ["target"] + bins + writer = csv.DictWriter(csvfile, fieldnames=header) + writer.writeheader() + for t in targets: + d = {"target" : t} + d.update(table[t]) + writer.writerow(d) + +def load_csv(file): + table = {} + cols = [] + rows = [] + with open(file, "r") as csvfile: + reader = csv.DictReader(csvfile) + cols = list(filter(lambda x: x!="target", reader.fieldnames)) + for row in reader: + t = Target(row["target"]) + rows += [t] + del row["target"] + table[t] = dict([k, int(v)] for k, v in row.items()) + return (table, rows, cols) + +def merge_tables(old, new): + from copy import deepcopy + tmp = deepcopy(old) + tmp.update(deepcopy(new)) + return tmp + +def render_md(fd, table, headings: str, row_headings: Target): + def print_row(lst, lens=[]): + lens = lens + [0] * (len(lst) - len(lens)) + for e, l in zip(lst, lens): + fmt = '|{}' if l == 0 else '|{:>%s}' % len(header[0]) + fd.write(fmt.format(e)) + fd.write("|\n") + def cell_render(target, bin): + return "y" if table[target][bin] == 0 else " " + + # add some 'hard' padding to specific columns + lens = [ + max(map(lambda x: len(x.os), row_headings)) + 2, + max(map(lambda x: len(x.arch), row_headings)) + 2 + ] + header = Target.get_heading() + header[0] = ("{:#^%d}" % lens[0]).format(header[0]) + header[1] = ("{:#^%d}" % lens[1]).format(header[1]) + + header += headings + print_row(header) + lines = list(map(lambda x: '-'*len(x), header)) + print_row(lines) + + for t in row_headings: + row = list(map(lambda b: cell_render(t, b), headings)) + row = t.get_row_heading() + row + print_row(row) + +if __name__ == "__main__": + # create the top-level parser + parser = argparse.ArgumentParser(prog='compiles_table.py') + subparsers = parser.add_subparsers(help='sub-command to execute', required=True, dest="cmd") + # create the parser for the "check" command + parser_a = subparsers.add_parser('check', help='run cargo check on specified targets and update csv cache') + parser_a.add_argument("targets", metavar="TARGET", type=str, nargs='+', choices=["all"]+TARGETS, + help="target-triple to check, as shown by 'rustup target list'") + # create the parser for the "render" command + parser_b = subparsers.add_parser('render', help='print a markdown table to stdout') + parser_b.add_argument("--equidistant", action="store_true", + help="NOT IMPLEMENTED: render each column with an equal width (in plaintext)") + args = parser.parse_args() + + if args.cmd == "render": + table, targets, bins = load_csv(CACHE_PATH) + render_md(sys.stdout, table, bins, targets) + + if args.cmd == "check": + targets = get_targets(args.targets) + bins = get_all_bins() + + assert(all(map(Target.is_installed, targets))) + table = test_all_targets(targets, bins) + + prev_table, _, _ = load_csv(CACHE_PATH) + new_table = merge_tables(prev_table, table) + save_csv(CACHE_PATH, new_table) \ No newline at end of file diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index 4ca0fbda7..0b4359620 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_arch" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "arch ~ (uutils) display machine architecture" @@ -16,7 +16,7 @@ path = "src/arch.rs" [dependencies] platform-info = "0.1" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index 0abb718c8..a1d7ba17e 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base32" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" @@ -15,7 +15,7 @@ edition = "2018" path = "src/base32.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features = ["encoding"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index eef9b1ec3..841ab140c 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base64" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" @@ -15,7 +15,7 @@ edition = "2018" path = "src/base64.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features = ["encoding"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index 218d9eb08..92d0ca4cd 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basename" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" @@ -15,7 +15,7 @@ edition = "2018" path = "src/basename.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index 415477588..e44a874c1 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cat" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "cat ~ (uutils) concatenate and display input" @@ -15,8 +15,9 @@ edition = "2018" path = "src/cat.rs" [dependencies] +clap = "2.33" quick-error = "1.2.3" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(unix)'.dependencies] diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 0a35e243b..cf5a384a4 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -17,6 +17,7 @@ extern crate unix_socket; extern crate uucore; // last synced with: cat (GNU coreutils) 8.13 +use clap::{App, Arg}; use quick_error::ResultExt; use std::fs::{metadata, File}; use std::io::{self, stderr, stdin, stdout, BufWriter, Read, Write}; @@ -30,10 +31,11 @@ use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use unix_socket::UnixStream; +static NAME: &str = "cat"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); static SYNTAX: &str = "[OPTION]... [FILE]..."; static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output With no FILE, or when FILE is -, read standard input."; -static LONG_HELP: &str = ""; #[derive(PartialEq)] enum NumberingMode { @@ -124,50 +126,122 @@ enum InputType { type CatResult = Result; +mod options { + pub static FILE: &str = "file"; + pub static SHOW_ALL: &str = "show-all"; + pub static NUMBER_NONBLANK: &str = "number-nonblank"; + pub static SHOW_NONPRINTING_ENDS: &str = "e"; + pub static SHOW_ENDS: &str = "show-ends"; + pub static NUMBER: &str = "number"; + pub static SQUEEZE_BLANK: &str = "squeeze-blank"; + pub static SHOW_NONPRINTING_TABS: &str = "t"; + pub static SHOW_TABS: &str = "show-tabs"; + pub static SHOW_NONPRINTING: &str = "show-nonprinting"; +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optflag("A", "show-all", "equivalent to -vET") - .optflag( - "b", - "number-nonblank", - "number nonempty output lines, overrides -n", + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .usage(SYNTAX) + .about(SUMMARY) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .arg( + Arg::with_name(options::SHOW_ALL) + .short("A") + .long(options::SHOW_ALL) + .help("equivalent to -vET"), ) - .optflag("e", "", "equivalent to -vE") - .optflag("E", "show-ends", "display $ at end of each line") - .optflag("n", "number", "number all output lines") - .optflag("s", "squeeze-blank", "suppress repeated empty output lines") - .optflag("t", "", "equivalent to -vT") - .optflag("T", "show-tabs", "display TAB characters as ^I") - .optflag( - "v", - "show-nonprinting", - "use ^ and M- notation, except for LF (\\n) and TAB (\\t)", + .arg( + Arg::with_name(options::NUMBER_NONBLANK) + .short("b") + .long(options::NUMBER_NONBLANK) + .help("number nonempty output lines, overrides -n") + .overrides_with(options::NUMBER), ) - .parse(args); + .arg( + Arg::with_name(options::SHOW_NONPRINTING_ENDS) + .short("e") + .help("equivalent to -vE"), + ) + .arg( + Arg::with_name(options::SHOW_ENDS) + .short("E") + .long(options::SHOW_ENDS) + .help("display $ at end of each line"), + ) + .arg( + Arg::with_name(options::NUMBER) + .short("n") + .long(options::NUMBER) + .help("number all output lines"), + ) + .arg( + Arg::with_name(options::SQUEEZE_BLANK) + .short("s") + .long(options::SQUEEZE_BLANK) + .help("suppress repeated empty output lines"), + ) + .arg( + Arg::with_name(options::SHOW_NONPRINTING_TABS) + .short("t") + .long(options::SHOW_NONPRINTING_TABS) + .help("equivalent to -vT"), + ) + .arg( + Arg::with_name(options::SHOW_TABS) + .short("T") + .long(options::SHOW_TABS) + .help("display TAB characters at ^I"), + ) + .arg( + Arg::with_name(options::SHOW_NONPRINTING) + .short("v") + .long(options::SHOW_NONPRINTING) + .help("use ^ and M- notation, except for LF (\\n) and TAB (\\t)"), + ) + .get_matches_from(args); - let number_mode = if matches.opt_present("b") { + let number_mode = if matches.is_present(options::NUMBER_NONBLANK) { NumberingMode::NonEmpty - } else if matches.opt_present("n") { + } else if matches.is_present(options::NUMBER) { NumberingMode::All } else { NumberingMode::None }; - let show_nonprint = matches.opts_present(&[ - "A".to_owned(), - "e".to_owned(), - "t".to_owned(), - "v".to_owned(), - ]); - let show_ends = matches.opts_present(&["E".to_owned(), "A".to_owned(), "e".to_owned()]); - let show_tabs = matches.opts_present(&["A".to_owned(), "T".to_owned(), "t".to_owned()]); - let squeeze_blank = matches.opt_present("s"); - let mut files = matches.free; - if files.is_empty() { - files.push("-".to_owned()); - } + let show_nonprint = vec![ + options::SHOW_ALL.to_owned(), + options::SHOW_NONPRINTING_ENDS.to_owned(), + options::SHOW_NONPRINTING_TABS.to_owned(), + options::SHOW_NONPRINTING.to_owned(), + ] + .iter() + .any(|v| matches.is_present(v)); + + let show_ends = vec![ + options::SHOW_ENDS.to_owned(), + options::SHOW_ALL.to_owned(), + options::SHOW_NONPRINTING_ENDS.to_owned(), + ] + .iter() + .any(|v| matches.is_present(v)); + + let show_tabs = vec![ + options::SHOW_ALL.to_owned(), + options::SHOW_TABS.to_owned(), + options::SHOW_NONPRINTING_TABS.to_owned(), + ] + .iter() + .any(|v| matches.is_present(v)); + + let squeeze_blank = matches.is_present(options::SQUEEZE_BLANK); + let files: Vec = match matches.values_of(options::FILE) { + Some(v) => v.clone().map(|v| v.to_owned()).collect(), + None => vec!["-".to_owned()], + }; let can_write_fast = !(show_tabs || show_nonprint @@ -361,7 +435,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState } writer.write_all(options.end_of_line.as_bytes())?; if handle.is_interactive { - writer.flush().context(&file[..])?; + writer.flush().context(file)?; } } state.at_line_start = true; diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 60f9d6370..9424ad35e 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chgrp" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" @@ -15,7 +15,7 @@ edition = "2018" path = "src/chgrp.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } walkdir = "2.2" diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index 41b73d8a6..ac7030b62 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chmod" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "chmod ~ (uutils) change mode of FILE" @@ -15,8 +15,9 @@ edition = "2018" path = "src/chmod.rs" [dependencies] +clap = "2.33.3" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs", "mode"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } walkdir = "2.2" diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 61c56dd77..d9d8c8cf2 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -10,6 +10,7 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::fs; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::Path; @@ -18,115 +19,173 @@ use uucore::fs::display_permissions_unix; use uucore::mode; use walkdir::WalkDir; -const NAME: &str = "chmod"; -static SUMMARY: &str = "Change the mode of each FILE to MODE. +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Change the mode of each FILE to MODE. With --reference, change the mode of each FILE to that of RFILE."; -static LONG_HELP: &str = " - Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'. -"; + +mod options { + pub const CHANGES: &str = "changes"; + pub const QUIET: &str = "quiet"; // visible_alias("silent") + pub const VERBOSE: &str = "verbose"; + pub const NO_PRESERVE_ROOT: &str = "no-preserve-root"; + pub const PRESERVE_ROOT: &str = "preserve-root"; + pub const REFERENCE: &str = "RFILE"; + pub const RECURSIVE: &str = "recursive"; + pub const MODE: &str = "MODE"; + pub const FILE: &str = "FILE"; +} + +fn get_usage() -> String { + format!( + "{0} [OPTION]... MODE[,MODE]... FILE... +or: {0} [OPTION]... OCTAL-MODE FILE... +or: {0} [OPTION]... --reference=RFILE FILE...", + executable!() + ) +} + +fn get_long_usage() -> String { + String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.") +} pub fn uumain(args: impl uucore::Args) -> i32 { let mut args = args.collect_str(); - let syntax = format!( - "[OPTION]... MODE[,MODE]... FILE... - {0} [OPTION]... OCTAL-MODE FILE... - {0} [OPTION]... --reference=RFILE FILE...", - NAME - ); - let mut opts = app!(&syntax, SUMMARY, LONG_HELP); - opts.optflag( - "c", - "changes", - "like verbose but report only when a change is made", - ) - // TODO: support --silent (can be done using clap) - .optflag("f", "quiet", "suppress most error messages") - .optflag( - "v", - "verbose", - "output a diagnostic for every file processed", - ) - .optflag( - "", - "no-preserve-root", - "do not treat '/' specially (the default)", - ) - .optflag("", "preserve-root", "fail to operate recursively on '/'") - .optopt( - "", - "reference", - "use RFILE's mode instead of MODE values", - "RFILE", - ) - .optflag("R", "recursive", "change files and directories recursively"); + // Before we can parse 'args' with clap (and previously getopts), + // a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE"). + let mode_had_minus_prefix = strip_minus_from_mode(&mut args); - // sanitize input for - at beginning (e.g. chmod -x test_file). Remove - // the option and save it for later, after parsing is finished. - let negative_option = sanitize_input(&mut args); + let usage = get_usage(); + let after_help = get_long_usage(); - let mut matches = opts.parse(args); - if matches.free.is_empty() { - show_error!("missing an argument"); - show_error!("for help, try '{} --help'", NAME); - return 1; - } else { - let changes = matches.opt_present("changes"); - let quiet = matches.opt_present("quiet"); - let verbose = matches.opt_present("verbose"); - let preserve_root = matches.opt_present("preserve-root"); - let recursive = matches.opt_present("recursive"); - let fmode = matches - .opt_str("reference") + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .after_help(&after_help[..]) + .arg( + Arg::with_name(options::CHANGES) + .long(options::CHANGES) + .short("c") + .help("like verbose but report only when a change is made"), + ) + .arg( + Arg::with_name(options::QUIET) + .long(options::QUIET) + .visible_alias("silent") + .short("f") + .help("suppress most error messages"), + ) + .arg( + Arg::with_name(options::VERBOSE) + .long(options::VERBOSE) + .short("v") + .help("output a diagnostic for every file processed"), + ) + .arg( + Arg::with_name(options::NO_PRESERVE_ROOT) + .long(options::NO_PRESERVE_ROOT) + .help("do not treat '/' specially (the default)"), + ) + .arg( + Arg::with_name(options::PRESERVE_ROOT) + .long(options::PRESERVE_ROOT) + .help("fail to operate recursively on '/'"), + ) + .arg( + Arg::with_name(options::RECURSIVE) + .long(options::RECURSIVE) + .short("R") + .help("change files and directories recursively"), + ) + .arg( + Arg::with_name(options::REFERENCE) + .long("reference") + .takes_value(true) + .help("use RFILE's mode instead of MODE values"), + ) + .arg( + Arg::with_name(options::MODE) + .required_unless(options::REFERENCE) + .takes_value(true), + // It would be nice if clap could parse with delimeter, e.g. "g-x,u+x", + // however .multiple(true) cannot be used here because FILE already needs that. + // Only one positional argument with .multiple(true) set is allowed per command + ) + .arg( + Arg::with_name(options::FILE) + .required_unless(options::MODE) + .multiple(true), + ) + .get_matches_from(args); + + let changes = matches.is_present(options::CHANGES); + let quiet = matches.is_present(options::QUIET); + let verbose = matches.is_present(options::VERBOSE); + let preserve_root = matches.is_present(options::PRESERVE_ROOT); + let recursive = matches.is_present(options::RECURSIVE); + let fmode = + matches + .value_of(options::REFERENCE) .and_then(|ref fref| match fs::metadata(fref) { Ok(meta) => Some(meta.mode()), Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err), }); - let cmode = if fmode.is_none() { - // If there was a negative option, now it's a good time to - // use it. - if negative_option.is_some() { - negative_option - } else { - Some(matches.free.remove(0)) - } - } else { - None - }; - let chmoder = Chmoder { - changes, - quiet, - verbose, - preserve_root, - recursive, - fmode, - cmode, - }; - match chmoder.chmod(matches.free) { - Ok(()) => {} - Err(e) => return e, - } + let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required + let mut cmode = if mode_had_minus_prefix { + // clap parsing is finished, now put prefix back + Some(format!("-{}", modes)) + } else { + Some(modes.to_string()) + }; + let mut files: Vec = matches + .values_of(options::FILE) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + if fmode.is_some() { + // "--reference" and MODE are mutually exclusive + // if "--reference" was used MODE needs to be interpreted as another FILE + // it wasn't possible to implement this behavior directly with clap + files.push(cmode.unwrap()); + cmode = None; + } + + let chmoder = Chmoder { + changes, + quiet, + verbose, + preserve_root, + recursive, + fmode, + cmode, + }; + match chmoder.chmod(files) { + Ok(()) => {} + Err(e) => return e, } 0 } -fn sanitize_input(args: &mut Vec) -> Option { +// Iterate 'args' and delete the first occurrence +// of a prefix '-' if it's associated with MODE +// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE" +pub fn strip_minus_from_mode(args: &mut Vec) -> bool { for i in 0..args.len() { - let first = args[i].chars().next().unwrap(); - if first != '-' { - continue; - } - if let Some(second) = args[i].chars().nth(1) { - match second { - 'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => { - return Some(args.remove(i)); + if args[i].starts_with("-") { + if let Some(second) = args[i].chars().nth(1) { + match second { + 'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => { + // TODO: use strip_prefix() once minimum rust version reaches 1.45.0 + args[i] = args[i][1..args[i].len()].to_string(); + return true; + } + _ => {} } - _ => {} } } } - None + false } struct Chmoder { @@ -147,7 +206,17 @@ impl Chmoder { let filename = &filename[..]; let file = Path::new(filename); if !file.exists() { - show_error!("no such file or directory '{}'", filename); + if is_symlink(file) { + println!( + "failed to change mode of '{}' from 0000 (---------) to 0000 (---------)", + filename + ); + if !self.quiet { + show_error!("cannot operate on dangling symlink '{}'", filename); + } + } else { + show_error!("cannot access '{}': No such file or directory", filename); + } return Err(1); } if self.recursive && self.preserve_root && filename == "/" { @@ -181,18 +250,16 @@ impl Chmoder { let mut fperm = match fs::metadata(file) { Ok(meta) => meta.mode() & 0o7777, Err(err) => { - if !self.quiet { - if is_symlink(file) { - if self.verbose { - show_info!( - "neither symbolic link '{}' nor referent has been changed", - file.display() - ); - } - return Ok(()); - } else { - show_error!("{}: '{}'", err, file.display()); + if is_symlink(file) { + if self.verbose { + println!( + "neither symbolic link '{}' nor referent has been changed", + file.display() + ); } + return Ok(()); + } else { + show_error!("{}: '{}'", err, file.display()); } return Err(1); } @@ -232,11 +299,11 @@ impl Chmoder { fn change_file(&self, fperm: u32, mode: u32, file: &Path) -> Result<(), i32> { if fperm == mode { if self.verbose && !self.changes { - show_info!( - "mode of '{}' retained as {:o} ({})", + println!( + "mode of '{}' retained as {:04o} ({})", file.display(), fperm, - display_permissions_unix(fperm) + display_permissions_unix(fperm), ); } Ok(()) diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index f2d21ef88..74533af04 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chown" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" @@ -17,7 +17,7 @@ path = "src/chown.rs" [dependencies] clap = "2.33" glob = "0.3.0" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } walkdir = "2.2" diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index e967d4137..bf1e0ef59 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chroot" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" @@ -16,7 +16,7 @@ path = "src/chroot.rs" [dependencies] clap= "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 2c3bcbca4..44c5dfa02 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -65,8 +65,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .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", + Same as -u USER -g GROUP. \ + Userspec has higher preference than -u and/or -g", ) .value_name("USER:GROUP"), ) diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index ef3ec8b46..0332efbf8 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cksum" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" @@ -15,8 +15,9 @@ edition = "2018" path = "src/cksum.rs" [dependencies] +clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index e1c75fffc..b6436de87 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -10,6 +10,7 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::fs::File; use std::io::{self, stdin, BufReader, Read}; use std::path::Path; @@ -18,9 +19,10 @@ use std::path::Path; const CRC_TABLE_LEN: usize = 256; const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table(); +const VERSION: &str = env!("CARGO_PKG_VERSION"); +const NAME: &str = "cksum"; const SYNTAX: &str = "[OPTIONS] [FILE]..."; const SUMMARY: &str = "Print CRC and size for each file"; -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 @@ -138,7 +140,20 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> { let mut rd: Box = match fname { "-" => Box::new(stdin()), _ => { - file = File::open(&Path::new(fname))?; + let path = &Path::new(fname); + if path.is_dir() { + return Err(std::io::Error::new( + io::ErrorKind::InvalidInput, + "Is a directory", + )); + }; + if path.metadata().is_err() { + return Err(std::io::Error::new( + io::ErrorKind::NotFound, + "No such file or directory", + )); + }; + file = File::open(&path)?; Box::new(BufReader::new(file)) } }; @@ -160,10 +175,25 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> { } } -pub fn uumain(args: impl uucore::Args) -> i32 { - let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); +mod options { + pub static FILE: &str = "file"; +} - let files = matches.free; +pub fn uumain(args: impl uucore::Args) -> i32 { + let args = args.collect_str(); + + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .about(SUMMARY) + .usage(SYNTAX) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .get_matches_from(args); + + let files: Vec = match matches.values_of(options::FILE) { + Some(v) => v.clone().map(|v| v.to_owned()).collect(), + None => vec![], + }; if files.is_empty() { match cksum("-") { diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index ddfbb6a47..f02217790 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_comm" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "comm ~ (uutils) compare sorted inputs" @@ -15,9 +15,9 @@ edition = "2018" path = "src/comm.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 3fb1ef395..34b4330c9 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -15,21 +15,34 @@ use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; -static SYNTAX: &str = "[OPTIONS] FILE1 FILE2"; -static SUMMARY: &str = "Compare sorted files line by line"; +use clap::{App, Arg, ArgMatches}; + +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "compare two sorted files line by line"; static LONG_HELP: &str = ""; -fn mkdelim(col: usize, opts: &getopts::Matches) -> String { - let mut s = String::new(); - let delim = match opts.opt_str("output-delimiter") { - Some(d) => d, - None => "\t".to_owned(), - }; +mod options { + pub const COLUMN_1: &str = "1"; + pub const COLUMN_2: &str = "2"; + pub const COLUMN_3: &str = "3"; + pub const DELIMITER: &str = "output-delimiter"; + pub const DELIMITER_DEFAULT: &str = "\t"; + pub const FILE_1: &str = "FILE1"; + pub const FILE_2: &str = "FILE2"; +} - if col > 1 && !opts.opt_present("1") { +fn get_usage() -> String { + format!("{} [OPTION]... FILE1 FILE2", executable!()) +} + +fn mkdelim(col: usize, opts: &ArgMatches) -> String { + let mut s = String::new(); + let delim = opts.value_of(options::DELIMITER).unwrap(); + + if col > 1 && !opts.is_present(options::COLUMN_1) { s.push_str(delim.as_ref()); } - if col > 2 && !opts.opt_present("2") { + if col > 2 && !opts.is_present(options::COLUMN_2) { s.push_str(delim.as_ref()); } @@ -57,7 +70,7 @@ impl LineReader { } } -fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) { +fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) { let delim: Vec = (0..4).map(|col| mkdelim(col, opts)).collect(); let ra = &mut String::new(); @@ -80,7 +93,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) { match ord { Ordering::Less => { - if !opts.opt_present("1") { + if !opts.is_present(options::COLUMN_1) { ensure_nl(ra); print!("{}{}", delim[1], ra); } @@ -88,7 +101,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) { na = a.read_line(ra); } Ordering::Greater => { - if !opts.opt_present("2") { + if !opts.is_present(options::COLUMN_2) { ensure_nl(rb); print!("{}{}", delim[2], rb); } @@ -96,7 +109,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) { nb = b.read_line(rb); } Ordering::Equal => { - if !opts.opt_present("3") { + if !opts.is_present(options::COLUMN_3) { ensure_nl(ra); print!("{}{}", delim[3], ra); } @@ -120,21 +133,42 @@ fn open_file(name: &str) -> io::Result { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let usage = get_usage(); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optflag("1", "", "suppress column 1 (lines uniq to FILE1)") - .optflag("2", "", "suppress column 2 (lines uniq to FILE2)") - .optflag( - "3", - "", - "suppress column 3 (lines that appear in both files)", + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::COLUMN_1) + .short(options::COLUMN_1) + .help("suppress column 1 (lines unique to FILE1)"), ) - .optopt("", "output-delimiter", "separate columns with STR", "STR") - .parse(args); + .arg( + Arg::with_name(options::COLUMN_2) + .short(options::COLUMN_2) + .help("suppress column 2 (lines unique to FILE2)"), + ) + .arg( + Arg::with_name(options::COLUMN_3) + .short(options::COLUMN_3) + .help("suppress column 3 (lines that appear in both files)"), + ) + .arg( + Arg::with_name(options::DELIMITER) + .long(options::DELIMITER) + .help("separate columns with STR") + .value_name("STR") + .default_value(options::DELIMITER_DEFAULT) + .hide_default_value(true), + ) + .arg(Arg::with_name(options::FILE_1).required(true)) + .arg(Arg::with_name(options::FILE_2).required(true)) + .get_matches_from(args); - let mut f1 = open_file(matches.free[0].as_ref()).unwrap(); - let mut f2 = open_file(matches.free[1].as_ref()).unwrap(); + let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap(); + let mut f2 = open_file(matches.value_of(options::FILE_2).unwrap()).unwrap(); comm(&mut f1, &mut f2, &matches); diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index a0253433f..9d582adae 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cp" -version = "0.0.4" +version = "0.0.6" authors = [ "Jordy Dickinson ", "Joshua S. Miller ", @@ -23,7 +23,7 @@ clap = "2.33" filetime = "0.2" libc = "0.2.85" quick-error = "1.2.3" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } walkdir = "2.2" diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index 68c6e2322..7687991b0 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_csplit" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output" @@ -15,11 +15,11 @@ edition = "2018" path = "src/csplit.rs" [dependencies] -getopts = "0.2.17" +clap = "2.33" thiserror = "1.0" regex = "1.0.0" glob = "0.2.11" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "fs"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 7eb287522..ce11ba49a 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -2,7 +2,7 @@ #[macro_use] extern crate uucore; -use getopts::Matches; +use clap::{App, Arg, ArgMatches}; use regex::Regex; use std::cmp::Ordering; use std::io::{self, BufReader}; @@ -18,17 +18,25 @@ mod splitname; use crate::csplit_error::CsplitError; use crate::splitname::SplitName; -static SYNTAX: &str = "[OPTION]... FILE PATTERN..."; +static VERSION: &str = env!("CARGO_PKG_VERSION"); static SUMMARY: &str = "split a file into sections determined by context lines"; static LONG_HELP: &str = "Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output."; -static SUFFIX_FORMAT_OPT: &str = "suffix-format"; -static SUPPRESS_MATCHED_OPT: &str = "suppress-matched"; -static DIGITS_OPT: &str = "digits"; -static PREFIX_OPT: &str = "prefix"; -static KEEP_FILES_OPT: &str = "keep-files"; -static QUIET_OPT: &str = "quiet"; -static ELIDE_EMPTY_FILES_OPT: &str = "elide-empty-files"; +mod options { + pub const SUFFIX_FORMAT: &str = "suffix-format"; + pub const SUPPRESS_MATCHED: &str = "suppress-matched"; + pub const DIGITS: &str = "digits"; + pub const PREFIX: &str = "prefix"; + pub const KEEP_FILES: &str = "keep-files"; + pub const QUIET: &str = "quiet"; + pub const ELIDE_EMPTY_FILES: &str = "elide-empty-files"; + pub const FILE: &str = "file"; + pub const PATTERN: &str = "pattern"; +} + +fn get_usage() -> String { + format!("{0} [OPTION]... FILE PATTERN...", executable!()) +} /// Command line options for csplit. pub struct CsplitOptions { @@ -40,19 +48,19 @@ pub struct CsplitOptions { } impl CsplitOptions { - fn new(matches: &Matches) -> CsplitOptions { - let keep_files = matches.opt_present(KEEP_FILES_OPT); - let quiet = matches.opt_present(QUIET_OPT); - let elide_empty_files = matches.opt_present(ELIDE_EMPTY_FILES_OPT); - let suppress_matched = matches.opt_present(SUPPRESS_MATCHED_OPT); + fn new(matches: &ArgMatches) -> CsplitOptions { + let keep_files = matches.is_present(options::KEEP_FILES); + let quiet = matches.is_present(options::QUIET); + let elide_empty_files = matches.is_present(options::ELIDE_EMPTY_FILES); + let suppress_matched = matches.is_present(options::SUPPRESS_MATCHED); CsplitOptions { split_name: crash_if_err!( 1, SplitName::new( - matches.opt_str(PREFIX_OPT), - matches.opt_str(SUFFIX_FORMAT_OPT), - matches.opt_str(DIGITS_OPT) + matches.value_of(options::PREFIX).map(str::to_string), + matches.value_of(options::SUFFIX_FORMAT).map(str::to_string), + matches.value_of(options::DIGITS).map(str::to_string) ) ), keep_files, @@ -702,45 +710,78 @@ mod tests { } pub fn uumain(args: impl uucore::Args) -> i32 { + let usage = get_usage(); let args = args.collect_str(); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optopt( - "b", - SUFFIX_FORMAT_OPT, - "use sprintf FORMAT instead of %02d", - "FORMAT", + let matches = App::new(executable!()) + .version(VERSION) + .about(SUMMARY) + .usage(&usage[..]) + .arg( + Arg::with_name(options::SUFFIX_FORMAT) + .short("b") + .long(options::SUFFIX_FORMAT) + .value_name("FORMAT") + .help("use sprintf FORMAT instead of %02d"), ) - .optopt("f", PREFIX_OPT, "use PREFIX instead of 'xx'", "PREFIX") - .optflag("k", KEEP_FILES_OPT, "do not remove output files on errors") - .optflag( - "", - SUPPRESS_MATCHED_OPT, - "suppress the lines matching PATTERN", + .arg( + Arg::with_name(options::PREFIX) + .short("f") + .long(options::PREFIX) + .value_name("PREFIX") + .help("use PREFIX instead of 'xx'"), ) - .optopt( - "n", - DIGITS_OPT, - "use specified number of digits instead of 2", - "DIGITS", + .arg( + Arg::with_name(options::KEEP_FILES) + .short("k") + .long(options::KEEP_FILES) + .help("do not remove output files on errors"), ) - .optflag("s", QUIET_OPT, "do not print counts of output file sizes") - .optflag("z", ELIDE_EMPTY_FILES_OPT, "remove empty output files") - .parse(args); + .arg( + Arg::with_name(options::SUPPRESS_MATCHED) + .long(options::SUPPRESS_MATCHED) + .help("suppress the lines matching PATTERN"), + ) + .arg( + Arg::with_name(options::DIGITS) + .short("n") + .long(options::DIGITS) + .value_name("DIGITS") + .help("use specified number of digits instead of 2"), + ) + .arg( + Arg::with_name(options::QUIET) + .short("s") + .long(options::QUIET) + .visible_alias("silent") + .help("do not print counts of output file sizes"), + ) + .arg( + Arg::with_name(options::ELIDE_EMPTY_FILES) + .short("z") + .long(options::ELIDE_EMPTY_FILES) + .help("remove empty output files"), + ) + .arg(Arg::with_name(options::FILE).hidden(true).required(true)) + .arg( + Arg::with_name(options::PATTERN) + .hidden(true) + .multiple(true) + .required(true), + ) + .after_help(LONG_HELP) + .get_matches_from(args); - // check for mandatory arguments - if matches.free.is_empty() { - show_error!("missing operand"); - exit!(1); - } - if matches.free.len() == 1 { - show_error!("missing operand after '{}'", matches.free[0]); - exit!(1); - } - // get the patterns to split on - let patterns = return_if_err!(1, patterns::get_patterns(&matches.free[1..])); // get the file to split - let file_name: &str = &matches.free[0]; + let file_name = matches.value_of(options::FILE).unwrap(); + + // get the patterns to split on + let patterns: Vec = matches + .values_of(options::PATTERN) + .unwrap() + .map(str::to_string) + .collect(); + let patterns = return_if_err!(1, patterns::get_patterns(&patterns[..])); let options = CsplitOptions::new(&matches); if file_name == "-" { let stdin = io::stdin(); diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 71f7c3f6a..d892ddeb5 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cut" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" @@ -15,7 +15,8 @@ edition = "2018" path = "src/cut.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +clap = "2.33" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 95411e3fb..6b09b91d9 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -10,6 +10,7 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, Read, Stdout, Write}; use std::path::Path; @@ -20,6 +21,8 @@ use uucore::ranges::Range; mod buffer; mod searcher; +static NAME: &str = "cut"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); static SYNTAX: &str = "[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+"; static SUMMARY: &str = @@ -398,8 +401,13 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { } else { let path = Path::new(&filename[..]); - if !path.exists() { - show_error!("{}", msg_args_nonexistent_file!(filename)); + if path.is_dir() { + show_error!("{}: Is a directory", filename); + continue; + } + + if !path.metadata().is_ok() { + show_error!("{}: No such file or directory", filename); continue; } @@ -422,34 +430,123 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { exit_code } +mod options { + pub const BYTES: &str = "bytes"; + pub const CHARACTERS: &str = "characters"; + pub const DELIMITER: &str = "delimiter"; + pub const FIELDS: &str = "fields"; + pub const ZERO_TERMINATED: &str = "zero-terminated"; + pub const ONLY_DELIMITED: &str = "only-delimited"; + pub const OUTPUT_DELIMITER: &str = "output-delimiter"; + pub const COMPLEMENT: &str = "complement"; + pub const FILE: &str = "file"; +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optopt("b", "bytes", "filter byte columns from the input source", "sequence") - .optopt("c", "characters", "alias for character mode", "sequence") - .optopt("d", "delimiter", "specify the delimiter character that separates fields in the input source. Defaults to Tab.", "delimiter") - .optopt("f", "fields", "filter field columns from the input source", "sequence") - .optflag("n", "", "legacy option - has no effect.") - .optflag("", "complement", "invert the filter - instead of displaying only the filtered columns, display all but those columns") - .optflag("s", "only-delimited", "in field mode, only print lines which contain the delimiter") - .optflag("z", "zero-terminated", "instead of filtering columns based on line, filter columns based on \\0 (NULL character)") - .optopt("", "output-delimiter", "in field mode, replace the delimiter in output lines with this option's argument", "new delimiter") - .parse(args); - let complement = matches.opt_present("complement"); + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .usage(SYNTAX) + .about(SUMMARY) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::BYTES) + .short("b") + .long(options::BYTES) + .takes_value(true) + .help("filter byte columns from the input source") + .allow_hyphen_values(true) + .value_name("LIST") + .display_order(1), + ) + .arg( + Arg::with_name(options::CHARACTERS) + .short("c") + .long(options::CHARACTERS) + .help("alias for character mode") + .takes_value(true) + .allow_hyphen_values(true) + .value_name("LIST") + .display_order(2), + ) + .arg( + Arg::with_name(options::DELIMITER) + .short("d") + .long(options::DELIMITER) + .help("specify the delimiter character that separates fields in the input source. Defaults to Tab.") + .takes_value(true) + .value_name("DELIM") + .display_order(3), + ) + .arg( + Arg::with_name(options::FIELDS) + .short("f") + .long(options::FIELDS) + .help("filter field columns from the input source") + .takes_value(true) + .allow_hyphen_values(true) + .value_name("LIST") + .display_order(4), + ) + .arg( + Arg::with_name(options::COMPLEMENT) + .long(options::COMPLEMENT) + .help("invert the filter - instead of displaying only the filtered columns, display all but those columns") + .takes_value(false) + .display_order(5), + ) + .arg( + Arg::with_name(options::ONLY_DELIMITED) + .short("s") + .long(options::ONLY_DELIMITED) + .help("in field mode, only print lines which contain the delimiter") + .takes_value(false) + .display_order(6), + ) + .arg( + Arg::with_name(options::ZERO_TERMINATED) + .short("z") + .long(options::ZERO_TERMINATED) + .help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)") + .takes_value(false) + .display_order(8), + ) + .arg( + Arg::with_name(options::OUTPUT_DELIMITER) + .long(options::OUTPUT_DELIMITER) + .help("in field mode, replace the delimiter in output lines with this option's argument") + .takes_value(true) + .value_name("NEW_DELIM") + .display_order(7), + ) + .arg( + Arg::with_name(options::FILE) + .hidden(true) + .multiple(true) + ) + .get_matches_from(args); + + let complement = matches.is_present(options::COMPLEMENT); let mode_parse = match ( - matches.opt_str("bytes"), - matches.opt_str("characters"), - matches.opt_str("fields"), + matches.value_of(options::BYTES), + matches.value_of(options::CHARACTERS), + matches.value_of(options::FIELDS), ) { (Some(byte_ranges), None, None) => { list_to_ranges(&byte_ranges[..], complement).map(|ranges| { Mode::Bytes( ranges, Options { - out_delim: matches.opt_str("output-delimiter"), - zero_terminated: matches.opt_present("zero-terminated"), + out_delim: Some( + matches + .value_of(options::OUTPUT_DELIMITER) + .unwrap_or_default() + .to_owned(), + ), + zero_terminated: matches.is_present(options::ZERO_TERMINATED), }, ) }) @@ -459,29 +556,34 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Mode::Characters( ranges, Options { - out_delim: matches.opt_str("output-delimiter"), - zero_terminated: matches.opt_present("zero-terminated"), + out_delim: Some( + matches + .value_of(options::OUTPUT_DELIMITER) + .unwrap_or_default() + .to_owned(), + ), + zero_terminated: matches.is_present(options::ZERO_TERMINATED), }, ) }) } (None, None, Some(field_ranges)) => { list_to_ranges(&field_ranges[..], complement).and_then(|ranges| { - let out_delim = match matches.opt_str("output-delimiter") { + let out_delim = match matches.value_of(options::OUTPUT_DELIMITER) { Some(s) => { if s.is_empty() { Some("\0".to_owned()) } else { - Some(s) + Some(s.to_owned()) } } None => None, }; - let only_delimited = matches.opt_present("only-delimited"); - let zero_terminated = matches.opt_present("zero-terminated"); + let only_delimited = matches.is_present(options::ONLY_DELIMITED); + let zero_terminated = matches.is_present(options::ZERO_TERMINATED); - match matches.opt_str("delimiter") { + match matches.value_of(options::DELIMITER) { Some(delim) => { if delim.chars().count() > 1 { Err(msg_opt_invalid_should_be!( @@ -494,7 +596,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let delim = if delim.is_empty() { "\0".to_owned() } else { - delim + delim.to_owned() }; Ok(Mode::Fields( @@ -533,10 +635,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mode_parse = match mode_parse { Err(_) => mode_parse, Ok(mode) => match mode { - Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("delimiter") => Err( - msg_opt_only_usable_if!("printing a sequence of fields", "--delimiter", "-d"), - ), - Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("only-delimited") => { + Mode::Bytes(_, _) | Mode::Characters(_, _) + if matches.is_present(options::DELIMITER) => + { + Err(msg_opt_only_usable_if!( + "printing a sequence of fields", + "--delimiter", + "-d" + )) + } + Mode::Bytes(_, _) | Mode::Characters(_, _) + if matches.is_present(options::ONLY_DELIMITED) => + { Err(msg_opt_only_usable_if!( "printing a sequence of fields", "--only-delimited", @@ -547,8 +657,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }, }; + let files: Vec = matches + .values_of(options::FILE) + .unwrap_or_default() + .map(str::to_owned) + .collect(); + match mode_parse { - Ok(mode) => cut_files(matches.free, mode), + Ok(mode) => cut_files(files, mode), Err(err_msg) => { show_error!("{}", err_msg); 1 diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index c62cfe2b3..db6c077bd 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_date" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "date ~ (uutils) display or set the current time" @@ -17,7 +17,7 @@ path = "src/date.rs" [dependencies] chrono = "0.4.4" clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(unix)'.dependencies] diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index 24fce763b..4770cb557 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_df" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "df ~ (uutils) display file system information" @@ -18,7 +18,7 @@ path = "src/df.rs" clap = "2.33" libc = "0.2" number_prefix = "0.4" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(target_os = "windows")'.dependencies] diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index 667fc3b70..5e822820e 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dircolors" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "dircolors ~ (uutils) display commands to set LS_COLORS" @@ -16,7 +16,7 @@ path = "src/dircolors.rs" [dependencies] glob = "0.3.0" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index 9c565a3e6..0975f33bb 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dirname" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "dirname ~ (uutils) display parent directory of PATHNAME" @@ -15,8 +15,9 @@ edition = "2018" path = "src/dirname.rs" [dependencies] +clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 59f47ff01..1cf35d0c4 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -8,32 +8,57 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::path::Path; static NAME: &str = "dirname"; static SYNTAX: &str = "[OPTION] NAME..."; static SUMMARY: &str = "strip last component from file name"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); static LONG_HELP: &str = " Output each NAME with its last non-slash component and trailing slashes removed; if NAME contains no /'s, output '.' (meaning the current directory). "; +mod options { + pub const ZERO: &str = "zero"; + pub const DIR: &str = "dir"; +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optflag("z", "zero", "separate output with NUL rather than newline") - .parse(args); + let matches = App::new(executable!()) + .name(NAME) + .usage(SYNTAX) + .about(SUMMARY) + .after_help(LONG_HELP) + .version(VERSION) + .arg( + Arg::with_name(options::ZERO) + .short(options::ZERO) + .short("z") + .takes_value(false) + .help("separate output with NUL rather than newline"), + ) + .arg(Arg::with_name(options::DIR).hidden(true).multiple(true)) + .get_matches_from(args); - let separator = if matches.opt_present("zero") { + let separator = if matches.is_present(options::ZERO) { "\0" } else { "\n" }; - if !matches.free.is_empty() { - for path in &matches.free { + let dirnames: Vec = matches + .values_of(options::DIR) + .unwrap_or_default() + .map(str::to_owned) + .collect(); + + if !dirnames.is_empty() { + for path in dirnames.iter() { let p = Path::new(path); match p.parent() { Some(d) => { @@ -54,8 +79,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { print!("{}", separator); } } else { - println!("{0}: missing operand", NAME); - println!("Try '{0} --help' for more information.", NAME); + show_usage_error!("missing operand"); return 1; } diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 912eef17e..3ce9d8361 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_du" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "du ~ (uutils) display disk usage" @@ -15,9 +15,10 @@ edition = "2018" path = "src/du.rs" [dependencies] -time = "0.1.40" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +chrono = "0.4" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +winapi = { version="0.3", features=[] } [[bin]] name = "du" diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 4ed80f18b..615b66a4e 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -10,14 +10,31 @@ #[macro_use] extern crate uucore; +use chrono::prelude::DateTime; +use chrono::Local; use std::collections::HashSet; use std::env; use std::fs; use std::io::{stderr, Result, Write}; use std::iter; +#[cfg(not(windows))] use std::os::unix::fs::MetadataExt; +#[cfg(windows)] +use std::os::windows::fs::MetadataExt; +#[cfg(windows)] +use std::os::windows::io::AsRawHandle; use std::path::PathBuf; -use time::Timespec; +use std::time::{Duration, UNIX_EPOCH}; +#[cfg(windows)] +use winapi::shared::minwindef::{DWORD, LPVOID}; +#[cfg(windows)] +use winapi::um::fileapi::{FILE_ID_INFO, FILE_STANDARD_INFO}; +#[cfg(windows)] +use winapi::um::minwinbase::{FileIdInfo, FileStandardInfo}; +#[cfg(windows)] +use winapi::um::winbase::GetFileInformationByHandleEx; +#[cfg(windows)] +use winapi::um::winnt::{FILE_ID_128, ULONGLONG}; const NAME: &str = "du"; const SUMMARY: &str = "estimate file space usage"; @@ -43,12 +60,18 @@ struct Options { separate_dirs: bool, } +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +struct FileInfo { + file_id: u128, + dev_id: u64, +} + struct Stat { path: PathBuf, is_dir: bool, size: u64, blocks: u64, - inode: u64, + inode: Option, created: u64, accessed: u64, modified: u64, @@ -57,19 +80,114 @@ struct Stat { impl Stat { fn new(path: PathBuf) -> Result { let metadata = fs::symlink_metadata(&path)?; - Ok(Stat { + + #[cfg(not(windows))] + let file_info = FileInfo { + file_id: metadata.ino() as u128, + dev_id: metadata.dev(), + }; + #[cfg(not(windows))] + return Ok(Stat { path, is_dir: metadata.is_dir(), size: metadata.len(), blocks: metadata.blocks() as u64, - inode: metadata.ino() as u64, + inode: Some(file_info), created: metadata.mtime() as u64, accessed: metadata.atime() as u64, modified: metadata.mtime() as u64, + }); + + #[cfg(windows)] + let size_on_disk = get_size_on_disk(&path); + #[cfg(windows)] + let file_info = get_file_info(&path); + #[cfg(windows)] + Ok(Stat { + path, + is_dir: metadata.is_dir(), + size: metadata.len(), + blocks: size_on_disk / 1024 * 2, + inode: file_info, + created: windows_time_to_unix_time(metadata.creation_time()), + accessed: windows_time_to_unix_time(metadata.last_access_time()), + modified: windows_time_to_unix_time(metadata.last_write_time()), }) } } +#[cfg(windows)] +// https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.creation_time +// "The returned 64-bit value [...] which represents the number of 100-nanosecond intervals since January 1, 1601 (UTC)." +fn windows_time_to_unix_time(win_time: u64) -> u64 { + win_time / 10_000_000 - 11_644_473_600 +} + +#[cfg(windows)] +fn get_size_on_disk(path: &PathBuf) -> u64 { + let mut size_on_disk = 0; + + // bind file so it stays in scope until end of function + // if it goes out of scope the handle below becomes invalid + let file = match fs::File::open(path) { + Ok(file) => file, + Err(_) => return size_on_disk, // opening directories will fail + }; + + let handle = file.as_raw_handle(); + + unsafe { + let mut file_info: FILE_STANDARD_INFO = core::mem::zeroed(); + let file_info_ptr: *mut FILE_STANDARD_INFO = &mut file_info; + + let success = GetFileInformationByHandleEx( + handle, + FileStandardInfo, + file_info_ptr as LPVOID, + std::mem::size_of::() as DWORD, + ); + + if success != 0 { + size_on_disk = *file_info.AllocationSize.QuadPart() as u64; + } + } + + size_on_disk +} + +#[cfg(windows)] +fn get_file_info(path: &PathBuf) -> Option { + let mut result = None; + + let file = match fs::File::open(path) { + Ok(file) => file, + Err(_) => return result, + }; + + let handle = file.as_raw_handle(); + + unsafe { + let mut file_info: FILE_ID_INFO = core::mem::zeroed(); + let file_info_ptr: *mut FILE_ID_INFO = &mut file_info; + + let success = GetFileInformationByHandleEx( + handle, + FileIdInfo, + file_info_ptr as LPVOID, + std::mem::size_of::() as DWORD, + ); + + if success != 0 { + result = Some(FileInfo { + file_id: std::mem::transmute::(file_info.FileId), + dev_id: std::mem::transmute::(file_info.VolumeSerialNumber), + }); + } + } + + result +} + fn unit_string_to_number(s: &str) -> Option { let mut offset = 0; let mut s_chars = s.chars().rev(); @@ -137,7 +255,7 @@ fn du( mut my_stat: Stat, options: &Options, depth: usize, - inodes: &mut HashSet, + inodes: &mut HashSet, ) -> Box> { let mut stats = vec![]; let mut futures = vec![]; @@ -164,10 +282,13 @@ fn du( if this_stat.is_dir { futures.push(du(this_stat, options, depth + 1, inodes)); } else { - if inodes.contains(&this_stat.inode) { - continue; + if this_stat.inode.is_some() { + let inode = this_stat.inode.unwrap(); + if inodes.contains(&inode) { + continue; + } + inodes.insert(inode); } - inodes.insert(this_stat.inode); my_stat.size += this_stat.size; my_stat.blocks += this_stat.blocks; if options.all { @@ -200,6 +321,9 @@ fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String { return format!("{:.1}{}", (size as f64) / (limit as f64), unit); } } + if size == 0 { + return format!("0"); + } format!("{}B", size) } @@ -418,7 +542,7 @@ Try '{} --help' for more information.", let path = PathBuf::from(&path_str); match Stat::new(path) { Ok(stat) => { - let mut inodes: HashSet = HashSet::new(); + let mut inodes: HashSet = HashSet::new(); let iter = du(stat, &options, 0, &mut inodes); let (_, len) = iter.size_hint(); @@ -433,8 +557,8 @@ Try '{} --help' for more information.", }; if matches.opt_present("time") { let tm = { - let (secs, nsecs) = { - let time = match matches.opt_str("time") { + let secs = { + match matches.opt_str("time") { Some(s) => match &s[..] { "accessed" => stat.accessed, "created" => stat.created, @@ -451,13 +575,12 @@ Try '{} --help' for more information.", } }, None => stat.modified, - }; - ((time / 1000) as i64, (time % 1000 * 1_000_000) as i32) + } }; - time::at(Timespec::new(secs, nsecs)) + DateTime::::from(UNIX_EPOCH + Duration::from_secs(secs)) }; if !summarize || index == len - 1 { - let time_str = tm.strftime(time_format_str).unwrap(); + let time_str = tm.format(time_format_str).to_string(); print!( "{}\t{}\t{}{}", convert_size(size), diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index 7b831fcb8..15f189030 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_echo" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "echo ~ (uutils) display TEXT" @@ -16,7 +16,7 @@ path = "src/echo.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index c991f5d3f..4d38d7748 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -14,10 +14,10 @@ use std::io::{self, Write}; use std::iter::Peekable; use std::str::Chars; -static NAME: &str = "echo"; -static USAGE: &str = "[OPTIONS]... [STRING]..."; -static SUMMARY: &str = "display a line of text"; -static AFTER_HELP: &str = r#" +const NAME: &str = "echo"; +const SUMMARY: &str = "display a line of text"; +const USAGE: &str = "[OPTIONS]... [STRING]..."; +const AFTER_HELP: &str = r#" Echo the STRING(s) to standard output. If -e is in effect, the following sequences are recognized: @@ -37,10 +37,10 @@ static AFTER_HELP: &str = r#" "#; 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"; + pub const STRING: &str = "STRING"; + pub const NO_NEWLINE: &str = "no_newline"; + pub const ENABLE_BACKSLASH_ESCAPE: &str = "enable_backslash_escape"; + pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape"; } fn parse_code( @@ -113,8 +113,6 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); - let matches = App::new(executable!()) .name(NAME) // TrailingVarArg specifies the final positional argument is a VarArg @@ -123,9 +121,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .setting(clap::AppSettings::TrailingVarArg) .setting(clap::AppSettings::AllowLeadingHyphen) .version(crate_version!()) - .usage(USAGE) .about(SUMMARY) .after_help(AFTER_HELP) + .usage(USAGE) .arg( Arg::with_name(options::NO_NEWLINE) .short("n") @@ -149,7 +147,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) .arg( Arg::with_name(options::STRING) - .hidden(true) .multiple(true) .allow_hyphen_values(true), ) diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index 864ecd1b2..ef0017e02 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_env" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND" @@ -18,7 +18,7 @@ path = "src/env.rs" clap = "2.33" libc = "0.2.42" rust-ini = "0.13.0" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index b5c0343e7..4931cf53c 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expand" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "expand ~ (uutils) convert input tabs to spaces" @@ -17,7 +17,7 @@ path = "src/expand.rs" [dependencies] clap = "2.33" unicode-width = "0.1.5" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 1246ff873..c535df7ce 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expr" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "expr ~ (uutils) display the value of EXPRESSION" @@ -17,7 +17,7 @@ path = "src/expr.rs" [dependencies] libc = "0.2.42" onig = "~4.3.2" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index d1160c493..489c713be 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_factor" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "factor ~ (uutils) display the prime factors of each NUMBER" @@ -19,7 +19,7 @@ num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd" rand = { version="0.7", features=["small_rng"] } smallvec = { version="0.6.14, < 1.0" } -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [dev-dependencies] diff --git a/src/uu/factor/sieve.rs b/src/uu/factor/sieve.rs index c10321a7f..41893699b 100644 --- a/src/uu/factor/sieve.rs +++ b/src/uu/factor/sieve.rs @@ -7,7 +7,7 @@ // spell-checker:ignore (ToDO) filts, minidx, minkey paridx -use std::iter::{Chain, Cycle, Map}; +use std::iter::{Chain, Copied, Cycle}; use std::slice::Iter; /// A lazy Sieve of Eratosthenes. @@ -68,27 +68,17 @@ impl Sieve { #[allow(dead_code)] #[inline] pub fn primes() -> PrimeSieve { - #[allow(clippy::trivially_copy_pass_by_ref)] - fn deref(x: &u64) -> u64 { - *x - } - let deref = deref as fn(&u64) -> u64; - INIT_PRIMES.iter().map(deref).chain(Sieve::new()) + INIT_PRIMES.iter().copied().chain(Sieve::new()) } #[allow(dead_code)] #[inline] pub fn odd_primes() -> PrimeSieve { - #[allow(clippy::trivially_copy_pass_by_ref)] - fn deref(x: &u64) -> u64 { - *x - } - let deref = deref as fn(&u64) -> u64; - (&INIT_PRIMES[1..]).iter().map(deref).chain(Sieve::new()) + (&INIT_PRIMES[1..]).iter().copied().chain(Sieve::new()) } } -pub type PrimeSieve = Chain, fn(&u64) -> u64>, Sieve>; +pub type PrimeSieve = Chain>, Sieve>; /// An iterator that generates an infinite list of numbers that are /// not divisible by any of 2, 3, 5, or 7. diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index 413cbc990..d7cbcd13a 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_false" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "false ~ (uutils) do nothing and fail" @@ -15,7 +15,7 @@ edition = "2018" path = "src/false.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index e150b2962..24ee13b35 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fmt" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "fmt ~ (uutils) reformat each paragraph of input" @@ -18,7 +18,7 @@ path = "src/fmt.rs" clap = "2.33" libc = "0.2.42" unicode-width = "0.1.5" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/fmt/src/linebreak.rs b/src/uu/fmt/src/linebreak.rs index c41fd41bd..50cb6f77f 100644 --- a/src/uu/fmt/src/linebreak.rs +++ b/src/uu/fmt/src/linebreak.rs @@ -81,7 +81,7 @@ pub fn break_lines(para: &Paragraph, opts: &FmtOptions, ostream: &mut BufWriter< let mut break_args = BreakArgs { opts, init_len: p_init_len, - indent_str: &p_indent[..], + indent_str: p_indent, indent_len: p_indent_len, uniform, ostream, diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index 23dadc8eb..c5578384e 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fold" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "fold ~ (uutils) wrap each line of input" @@ -15,7 +15,8 @@ edition = "2018" path = "src/fold.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +clap = "2.33" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 05f0725ef..c35e996f2 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -10,46 +10,71 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; +const TAB_WIDTH: usize = 8; + +static NAME: &str = "fold"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); static SYNTAX: &str = "[OPTION]... [FILE]..."; static SUMMARY: &str = "Writes each file (or standard input if no files are given) to standard output whilst breaking long lines"; -static LONG_HELP: &str = ""; + +mod options { + pub const BYTES: &str = "bytes"; + pub const SPACES: &str = "spaces"; + pub const WIDTH: &str = "width"; + pub const FILE: &str = "file"; +} pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); let (args, obs_width) = handle_obsolete(&args[..]); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optflag( - "b", - "bytes", - "count using bytes rather than columns (meaning control characters \ - such as newline are not treated specially)", + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .usage(SYNTAX) + .about(SUMMARY) + .arg( + Arg::with_name(options::BYTES) + .long(options::BYTES) + .short("b") + .help( + "count using bytes rather than columns (meaning control characters \ + such as newline are not treated specially)", + ) + .takes_value(false), ) - .optflag( - "s", - "spaces", - "break lines at word boundaries rather than a hard cut-off", + .arg( + Arg::with_name(options::SPACES) + .long(options::SPACES) + .short("s") + .help("break lines at word boundaries rather than a hard cut-off") + .takes_value(false), ) - .optopt( - "w", - "width", - "set WIDTH as the maximum line width rather than 80", - "WIDTH", + .arg( + Arg::with_name(options::WIDTH) + .long(options::WIDTH) + .short("w") + .help("set WIDTH as the maximum line width rather than 80") + .value_name("WIDTH") + .allow_hyphen_values(true) + .takes_value(true), ) - .parse(args); + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .get_matches_from(args.clone()); - let bytes = matches.opt_present("b"); - let spaces = matches.opt_present("s"); - let poss_width = if matches.opt_present("w") { - matches.opt_str("w") - } else { - obs_width + let bytes = matches.is_present(options::BYTES); + let spaces = matches.is_present(options::SPACES); + let poss_width = match matches.value_of(options::WIDTH) { + Some(v) => Some(v.to_owned()), + None => obs_width, }; + let width = match poss_width { Some(inp_width) => match inp_width.parse::() { Ok(width) => width, @@ -57,11 +82,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }, None => 80, }; - let files = if matches.free.is_empty() { - vec!["-".to_owned()] - } else { - matches.free + + let files = match matches.values_of(options::FILE) { + Some(v) => v.map(|v| v.to_owned()).collect(), + None => vec!["-".to_owned()], }; + fold(files, bytes, spaces, width); 0 @@ -79,7 +105,6 @@ fn handle_obsolete(args: &[String]) -> (Vec, Option) { (args.to_vec(), None) } -#[inline] fn fold(filenames: Vec, bytes: bool, spaces: bool, width: usize) { for filename in &filenames { let filename: &str = &filename; @@ -92,120 +117,163 @@ fn fold(filenames: Vec, bytes: bool, spaces: bool, width: usize) { file_buf = safe_unwrap!(File::open(Path::new(filename))); &mut file_buf as &mut dyn Read }); - fold_file(buffer, bytes, spaces, width); - } -} -#[inline] -fn fold_file(file: BufReader, bytes: bool, spaces: bool, width: usize) { - for line_result in file.lines() { - let mut line = safe_unwrap!(line_result); if bytes { - let len = line.len(); - let mut i = 0; - while i < len { - let width = if len - i >= width { width } else { len - i }; - let slice = { - let slice = &line[i..i + width]; - if spaces && i + width < len { - match slice.rfind(char::is_whitespace) { - Some(m) => &slice[..=m], - None => slice, - } - } else { - slice - } - }; - print!("{}", slice); - i += slice.len(); - } + fold_file_bytewise(buffer, spaces, width); } else { - let mut len = line.chars().count(); - let newline = line.ends_with('\n'); - if newline { - if len == 1 { - println!(); - continue; - } - len -= 1; - line.truncate(len); - } - let mut output = String::new(); - let mut count = 0; - for (i, ch) in line.chars().enumerate() { - if count >= width { - let (val, ncount) = { - let slice = &output[..]; - let (out, val, ncount) = if spaces && i + 1 < len { - match rfind_whitespace(slice) { - Some(m) => { - let routput = &slice[m + 1..slice.chars().count()]; - let ncount = routput.chars().fold(0, |out, ch: char| { - out + match ch { - '\t' => 8, - '\x08' => { - if out > 0 { - !0 - } else { - 0 - } - } - '\r' => return 0, - _ => 1, - } - }); - (&slice[0..=m], routput, ncount) - } - None => (slice, "", 0), - } - } else { - (slice, "", 0) - }; - println!("{}", out); - (val.to_owned(), ncount) - }; - output = val; - count = ncount; - } - match ch { - '\t' => { - count += 8; - if count > width { - println!("{}", output); - output.truncate(0); - count = 8; - } - } - '\x08' => { - if count > 0 { - count -= 1; - let len = output.len() - 1; - output.truncate(len); - } - continue; - } - '\r' => { - output.truncate(0); - count = 0; - continue; - } - _ => count += 1, - }; - output.push(ch); - } - if count > 0 { - println!("{}", output); - } + fold_file(buffer, spaces, width); } } } -#[inline] -fn rfind_whitespace(slice: &str) -> Option { - for (i, ch) in slice.chars().rev().enumerate() { - if ch.is_whitespace() { - return Some(slice.chars().count() - (i + 1)); +/// Fold `file` to fit `width` (number of columns), counting all characters as +/// one column. +/// +/// This function handles folding for the `-b`/`--bytes` option, counting +/// tab, backspace, and carriage return as occupying one column, identically +/// to all other characters in the stream. +/// +/// If `spaces` is `true`, attempt to break lines at whitespace boundaries. +fn fold_file_bytewise(mut file: BufReader, spaces: bool, width: usize) { + let mut line = String::new(); + + loop { + if let Ok(0) = file.read_line(&mut line) { + break; } + + if line == "\n" { + println!(); + line.truncate(0); + continue; + } + + let len = line.len(); + let mut i = 0; + + while i < len { + let width = if len - i >= width { width } else { len - i }; + let slice = { + let slice = &line[i..i + width]; + if spaces && i + width < len { + match slice.rfind(|c: char| c.is_whitespace() && c != '\r') { + Some(m) => &slice[..=m], + None => slice, + } + } else { + slice + } + }; + + // Don't duplicate trailing newlines: if the slice is "\n", the + // previous iteration folded just before the end of the line and + // has already printed this newline. + if slice == "\n" { + break; + } + + i += slice.len(); + + let at_eol = i >= len; + + if at_eol { + print!("{}", slice); + } else { + println!("{}", slice); + } + } + + line.truncate(0); + } +} + +/// Fold `file` to fit `width` (number of columns). +/// +/// By default `fold` treats tab, backspace, and carriage return specially: +/// tab characters count as 8 columns, backspace decreases the +/// column count, and carriage return resets the column count to 0. +/// +/// If `spaces` is `true`, attempt to break lines at whitespace boundaries. +#[allow(unused_assignments)] +fn fold_file(mut file: BufReader, spaces: bool, width: usize) { + let mut line = String::new(); + let mut output = String::new(); + let mut col_count = 0; + let mut last_space = None; + + /// Print the output line, resetting the column and character counts. + /// + /// If `spaces` is `true`, print the output line up to the last + /// encountered whitespace character (inclusive) and set the remaining + /// characters as the start of the next line. + macro_rules! emit_output { + () => { + let consume = match last_space { + Some(i) => i + 1, + None => output.len(), + }; + + println!("{}", &output[..consume]); + output.replace_range(..consume, ""); + + // we know there are no tabs left in output, so each char counts + // as 1 column + col_count = output.len(); + + last_space = None; + }; + } + + loop { + if let Ok(0) = file.read_line(&mut line) { + break; + } + + for ch in line.chars() { + if ch == '\n' { + // make sure to _not_ split output at whitespace, since we + // know the entire output will fit + last_space = None; + emit_output!(); + break; + } + + if col_count >= width { + emit_output!(); + } + + match ch { + '\r' => col_count = 0, + '\t' => { + let next_tab_stop = col_count + TAB_WIDTH - col_count % TAB_WIDTH; + + if next_tab_stop > width && !output.is_empty() { + emit_output!(); + } + + col_count = next_tab_stop; + last_space = if spaces { Some(output.len()) } else { None }; + } + '\x08' => { + if col_count > 0 { + col_count -= 1; + } + } + _ if spaces && ch.is_whitespace() => { + last_space = Some(output.len()); + col_count += 1; + } + _ => col_count += 1, + }; + + output.push(ch); + } + + if !output.is_empty() { + print!("{}", output); + output.truncate(0); + } + + line.truncate(0); } - None } diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 455e2d224..1a56bc2ab 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_groups" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "groups ~ (uutils) display group memberships for USERNAME" @@ -15,7 +15,7 @@ edition = "2018" path = "src/groups.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } clap = "2.33" diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index c9bb4da9f..04a22cac7 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hashsum" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "hashsum ~ (uutils) display or check input digests" @@ -26,7 +26,7 @@ sha1 = "0.6.0" sha2 = "0.6.0" sha3 = "0.6.0" blake2-rfc = "0.2.18" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index adcce2726..3c383cb6f 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_head" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "head ~ (uutils) display the first lines of input" @@ -15,8 +15,8 @@ edition = "2018" path = "src/head.rs" [dependencies] -libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +clap = "2.33" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 0036dbba9..3500af544 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -1,240 +1,642 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) Alan Andrade -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. -// * -// * Synced with: https://raw.github.com/avsm/src/master/usr.bin/head/head.c +use clap::{App, Arg}; +use std::convert::TryFrom; +use std::ffi::OsString; +use std::io::{ErrorKind, Read, Seek, SeekFrom, Write}; +use uucore::{crash, executable, show_error}; -#[macro_use] -extern crate uucore; +const EXIT_FAILURE: i32 = 1; +const EXIT_SUCCESS: i32 = 0; +const BUF_SIZE: usize = 65536; -use std::collections::VecDeque; -use std::fs::File; -use std::io::{stdin, BufRead, BufReader, Read}; -use std::path::Path; -use std::str::from_utf8; +const VERSION: &str = env!("CARGO_PKG_VERSION"); +const ABOUT: &str = "\ + Print the first 10 lines of each FILE to standard output.\n\ + With more than one FILE, precede each with a header giving the file name.\n\ + \n\ + With no FILE, or when FILE is -, read standard input.\n\ + \n\ + Mandatory arguments to long flags are mandatory for short flags too.\ + "; +const USAGE: &str = "head [FLAG]... [FILE]..."; -static SYNTAX: &str = ""; -static SUMMARY: &str = ""; -static LONG_HELP: &str = ""; +mod options { + pub const BYTES_NAME: &str = "BYTES"; + pub const LINES_NAME: &str = "LINES"; + pub const QUIET_NAME: &str = "QUIET"; + pub const VERBOSE_NAME: &str = "VERBOSE"; + pub const ZERO_NAME: &str = "ZERO"; + pub const FILES_NAME: &str = "FILE"; +} +mod parse; +mod split; -enum FilterMode { - Bytes(usize), +fn app<'a>() -> App<'a, 'a> { + App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(USAGE) + .arg( + Arg::with_name(options::BYTES_NAME) + .short("c") + .long("bytes") + .value_name("[-]NUM") + .takes_value(true) + .help( + "\ + print the first NUM bytes of each file;\n\ + with the leading '-', print all but the last\n\ + NUM bytes of each file\ + ", + ) + .overrides_with_all(&[options::BYTES_NAME, options::LINES_NAME]) + .allow_hyphen_values(true), + ) + .arg( + Arg::with_name(options::LINES_NAME) + .short("n") + .long("lines") + .value_name("[-]NUM") + .takes_value(true) + .help( + "\ + print the first NUM lines instead of the first 10;\n\ + with the leading '-', print all but the last\n\ + NUM lines of each file\ + ", + ) + .overrides_with_all(&[options::LINES_NAME, options::BYTES_NAME]) + .allow_hyphen_values(true), + ) + .arg( + Arg::with_name(options::QUIET_NAME) + .short("q") + .long("--quiet") + .visible_alias("silent") + .help("never print headers giving file names") + .overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]), + ) + .arg( + Arg::with_name(options::VERBOSE_NAME) + .short("v") + .long("verbose") + .help("always print headers giving file names") + .overrides_with_all(&[options::QUIET_NAME, options::VERBOSE_NAME]), + ) + .arg( + Arg::with_name(options::ZERO_NAME) + .short("z") + .long("zero-terminated") + .help("line delimiter is NUL, not newline") + .overrides_with(options::ZERO_NAME), + ) + .arg(Arg::with_name(options::FILES_NAME).multiple(true)) +} +#[derive(PartialEq, Debug, Clone, Copy)] +enum Modes { Lines(usize), - NLines(usize), + Bytes(usize), } -struct Settings { - mode: FilterMode, - verbose: bool, - zero_terminated: bool, +fn parse_mode(src: &str, closure: F) -> Result<(Modes, bool), String> +where + F: FnOnce(usize) -> Modes, +{ + match parse::parse_num(src) { + Ok((n, last)) => Ok((closure(n), last)), + Err(reason) => match reason { + parse::ParseError::Syntax => Err(format!("'{}'", src)), + parse::ParseError::Overflow => { + Err(format!("'{}': Value too large for defined datatype", src)) + } + }, + } } -impl Default for Settings { - fn default() -> Settings { - Settings { - mode: FilterMode::Lines(10), - verbose: false, - zero_terminated: false, +fn arg_iterate<'a>( + mut args: impl uucore::Args + 'a, +) -> Result + 'a>, String> { + // argv[0] is always present + let first = args.next().unwrap(); + if let Some(second) = args.next() { + if let Some(s) = second.to_str() { + match parse::parse_obsolete(s) { + Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))), + Some(Err(e)) => match e { + parse::ParseError::Syntax => Err(format!("bad argument format: '{}'", s)), + parse::ParseError::Overflow => Err(format!( + "invalid argument: '{}' Value too large for defined datatype", + s + )), + }, + None => Ok(Box::new(vec![first, second].into_iter().chain(args))), + } + } else { + Err("bad argument encoding".to_owned()) } + } else { + Ok(Box::new(vec![first].into_iter())) + } +} + +#[derive(Debug, PartialEq)] +struct HeadOptions { + pub quiet: bool, + pub verbose: bool, + pub zeroed: bool, + pub all_but_last: bool, + pub mode: Modes, + pub files: Vec, +} + +impl HeadOptions { + pub fn new() -> HeadOptions { + HeadOptions { + quiet: false, + verbose: false, + zeroed: false, + all_but_last: false, + mode: Modes::Lines(10), + files: Vec::new(), + } + } + + ///Construct options from matches + pub fn get_from(args: impl uucore::Args) -> Result { + let matches = app().get_matches_from(arg_iterate(args)?); + + let mut options = HeadOptions::new(); + + options.quiet = matches.is_present(options::QUIET_NAME); + options.verbose = matches.is_present(options::VERBOSE_NAME); + options.zeroed = matches.is_present(options::ZERO_NAME); + + let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) { + match parse_mode(v, Modes::Bytes) { + Ok(v) => v, + Err(err) => { + return Err(format!("invalid number of bytes: {}", err)); + } + } + } else if let Some(v) = matches.value_of(options::LINES_NAME) { + match parse_mode(v, Modes::Lines) { + Ok(v) => v, + Err(err) => { + return Err(format!("invalid number of lines: {}", err)); + } + } + } else { + (Modes::Lines(10), false) + }; + + options.mode = mode_and_from_end.0; + options.all_but_last = mode_and_from_end.1; + + options.files = match matches.values_of(options::FILES_NAME) { + Some(v) => v.map(|s| s.to_owned()).collect(), + None => vec!["-".to_owned()], + }; + //println!("{:#?}", options); + Ok(options) + } +} +// to make clippy shut up +impl Default for HeadOptions { + fn default() -> Self { + Self::new() + } +} + +fn rbuf_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> { + if n == 0 { + return Ok(()); + } + let mut readbuf = [0u8; BUF_SIZE]; + let mut i = 0usize; + + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + + loop { + let read = loop { + match input.read(&mut readbuf) { + Ok(n) => break n, + Err(e) => match e.kind() { + ErrorKind::Interrupted => {} + _ => return Err(e), + }, + } + }; + if read == 0 { + // might be unexpected if + // we haven't read `n` bytes + // but this mirrors GNU's behavior + return Ok(()); + } + stdout.write_all(&readbuf[..read.min(n - i)])?; + i += read.min(n - i); + if i == n { + return Ok(()); + } + } +} + +fn rbuf_n_lines(input: &mut impl std::io::BufRead, n: usize, zero: bool) -> std::io::Result<()> { + if n == 0 { + return Ok(()); + } + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + let mut lines = 0usize; + split::walk_lines(input, zero, |e| match e { + split::Event::Data(dat) => { + stdout.write_all(dat)?; + Ok(true) + } + split::Event::Line => { + lines += 1; + if lines == n { + Ok(false) + } else { + Ok(true) + } + } + }) +} + +fn rbuf_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> { + if n == 0 { + //prints everything + return rbuf_n_bytes(input, std::usize::MAX); + } + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + + let mut ringbuf = vec![0u8; n]; + + // first we fill the ring buffer + if let Err(e) = input.read_exact(&mut ringbuf) { + if e.kind() == ErrorKind::UnexpectedEof { + return Ok(()); + } else { + return Err(e); + } + } + let mut buffer = [0u8; BUF_SIZE]; + loop { + let read = loop { + match input.read(&mut buffer) { + Ok(n) => break n, + Err(e) => match e.kind() { + ErrorKind::Interrupted => {} + _ => return Err(e), + }, + } + }; + if read == 0 { + return Ok(()); + } else if read >= n { + stdout.write_all(&ringbuf)?; + stdout.write_all(&buffer[..read - n])?; + for i in 0..n { + ringbuf[i] = buffer[read - n + i]; + } + } else { + stdout.write_all(&ringbuf[..read])?; + for i in 0..n - read { + ringbuf[i] = ringbuf[read + i]; + } + ringbuf[n - read..].copy_from_slice(&buffer[..read]); + } + } +} + +fn rbuf_but_last_n_lines( + input: &mut impl std::io::BufRead, + n: usize, + zero: bool, +) -> std::io::Result<()> { + if n == 0 { + //prints everything + return rbuf_n_bytes(input, std::usize::MAX); + } + let mut ringbuf = vec![Vec::new(); n]; + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + let mut line = Vec::new(); + let mut lines = 0usize; + split::walk_lines(input, zero, |e| match e { + split::Event::Data(dat) => { + line.extend_from_slice(dat); + Ok(true) + } + split::Event::Line => { + if lines < n { + ringbuf[lines] = std::mem::replace(&mut line, Vec::new()); + lines += 1; + } else { + stdout.write_all(&ringbuf[0])?; + ringbuf.rotate_left(1); + ringbuf[n - 1] = std::mem::replace(&mut line, Vec::new()); + } + Ok(true) + } + }) +} + +fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> { + assert!(options.all_but_last); + let size = input.seek(SeekFrom::End(0))?; + let size = usize::try_from(size).unwrap(); + match options.mode { + Modes::Bytes(n) => { + if n >= size { + return Ok(()); + } else { + input.seek(SeekFrom::Start(0))?; + rbuf_n_bytes( + &mut std::io::BufReader::with_capacity(BUF_SIZE, input), + size - n, + )?; + } + } + Modes::Lines(n) => { + let mut buffer = [0u8; BUF_SIZE]; + let buffer = &mut buffer[..BUF_SIZE.min(size)]; + let mut i = 0usize; + let mut lines = 0usize; + + let found = 'o: loop { + // the casts here are ok, `buffer.len()` should never be above a few k + input.seek(SeekFrom::Current( + -((buffer.len() as i64).min((size - i) as i64)), + ))?; + input.read_exact(buffer)?; + for byte in buffer.iter().rev() { + match byte { + b'\n' if !options.zeroed => { + lines += 1; + } + 0u8 if options.zeroed => { + lines += 1; + } + _ => {} + } + // if it were just `n`, + if lines == n + 1 { + break 'o i; + } + i += 1; + } + if size - i == 0 { + return Ok(()); + } + }; + input.seek(SeekFrom::Start(0))?; + rbuf_n_bytes( + &mut std::io::BufReader::with_capacity(BUF_SIZE, input), + size - found, + )?; + } + } + Ok(()) +} + +fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> { + if options.all_but_last { + head_backwards_file(input, options) + } else { + match options.mode { + Modes::Bytes(n) => { + rbuf_n_bytes(&mut std::io::BufReader::with_capacity(BUF_SIZE, input), n) + } + Modes::Lines(n) => rbuf_n_lines( + &mut std::io::BufReader::with_capacity(BUF_SIZE, input), + n, + options.zeroed, + ), + } + } +} + +fn uu_head(options: &HeadOptions) { + let mut first = true; + for fname in &options.files { + let res = match fname.as_str() { + "-" => { + if options.verbose { + if !first { + println!(); + } + println!("==> standard input <==") + } + let stdin = std::io::stdin(); + let mut stdin = stdin.lock(); + match options.mode { + Modes::Bytes(n) => { + if options.all_but_last { + rbuf_but_last_n_bytes(&mut stdin, n) + } else { + rbuf_n_bytes(&mut stdin, n) + } + } + Modes::Lines(n) => { + if options.all_but_last { + rbuf_but_last_n_lines(&mut stdin, n, options.zeroed) + } else { + rbuf_n_lines(&mut stdin, n, options.zeroed) + } + } + } + } + name => { + let mut file = match std::fs::File::open(name) { + Ok(f) => f, + Err(err) => match err.kind() { + ErrorKind::NotFound => { + crash!( + EXIT_FAILURE, + "head: cannot open '{}' for reading: No such file or directory", + name + ); + } + ErrorKind::PermissionDenied => { + crash!( + EXIT_FAILURE, + "head: cannot open '{}' for reading: Permission denied", + name + ); + } + _ => { + crash!( + EXIT_FAILURE, + "head: cannot open '{}' for reading: {}", + name, + err + ); + } + }, + }; + if (options.files.len() > 1 && !options.quiet) || options.verbose { + println!("==> {} <==", name) + } + head_file(&mut file, options) + } + }; + if res.is_err() { + if fname.as_str() == "-" { + crash!( + EXIT_FAILURE, + "head: error reading standard input: Input/output error" + ); + } else { + crash!( + EXIT_FAILURE, + "head: error reading {}: Input/output error", + fname + ); + } + } + first = false; } } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); - - let mut settings: Settings = Default::default(); - - // handle obsolete -number syntax - let new_args = match obsolete(&args[0..]) { - (args, Some(n)) => { - settings.mode = FilterMode::Lines(n); - args - } - (args, None) => args, - }; - - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optopt( - "c", - "bytes", - "Print the first K bytes. With the leading '-', print all but the last K bytes", - "[-]K", - ) - .optopt( - "n", - "lines", - "Print the first K lines. With the leading '-', print all but the last K lines", - "[-]K", - ) - .optflag("q", "quiet", "never print headers giving file names") - .optflag("v", "verbose", "always print headers giving file names") - .optflag("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); - - let use_bytes = matches.opt_present("c"); - // TODO: suffixes (e.g. b, kB, etc.) - match matches.opt_str("n") { - Some(n) => { - if use_bytes { - show_error!("cannot specify both --bytes and --lines."); - return 1; - } - - match n.parse::() { - Ok(m) => { - settings.mode = if m < 0 { - let m: usize = m.abs() as usize; - FilterMode::NLines(m) - } else { - let m: usize = m.abs() as usize; - FilterMode::Lines(m) - } - } - Err(e) => { - show_error!("invalid line count '{}': {}", n, e); - return 1; - } - } - } - None => { - if let Some(count) = matches.opt_str("c") { - match count.parse::() { - Ok(m) => settings.mode = FilterMode::Bytes(m), - Err(e) => { - show_error!("invalid byte count '{}': {}", count, e); - return 1; - } - } - } + let args = match HeadOptions::get_from(args) { + Ok(o) => o, + Err(s) => { + crash!(EXIT_FAILURE, "head: {}", s); } }; + uu_head(&args); - let quiet = matches.opt_present("q"); - let verbose = matches.opt_present("v"); - settings.zero_terminated = matches.opt_present("z"); - let files = matches.free; - - // GNU implementation allows multiple declarations of "-q" and "-v" with the - // last flag winning. This can't be simulated with the getopts cargo unless - // we manually parse the arguments. Given the declaration of both flags, - // verbose mode always wins. This is a potential future improvement. - if files.len() > 1 && !quiet && !verbose { - settings.verbose = true; - } - if quiet { - settings.verbose = false; - } - if verbose { - settings.verbose = true; - } - - if files.is_empty() { - let mut buffer = BufReader::new(stdin()); - head(&mut buffer, &settings); - } else { - let mut first_time = true; - - for file in &files { - if settings.verbose { - if !first_time { - println!(); - } - println!("==> {} <==", file); - } - first_time = false; - - let path = Path::new(file); - 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) { - break; - } - } - } - - 0 + EXIT_SUCCESS } -// It searches for an option in the form of -123123 -// -// In case is found, the options vector will get rid of that object so that -// getopts works correctly. -fn obsolete(options: &[String]) -> (Vec, Option) { - let mut options: Vec = options.to_vec(); - let mut a = 1; - let b = options.len(); +#[cfg(test)] +mod tests { + use std::ffi::OsString; - while a < b { - let previous = options[a - 1].clone(); - let current = options[a].clone(); - let current = current.as_bytes(); - - if previous != "-n" && current.len() > 1 && current[0] == b'-' { - let len = current.len(); - for pos in 1..len { - // Ensure that the argument is only made out of digits - if !(current[pos] as char).is_numeric() { - break; - } - - // If this is the last number - if pos == len - 1 { - options.remove(a); - let number: Option = - from_utf8(¤t[1..len]).unwrap().parse::().ok(); - return (options, Some(number.unwrap())); - } - } - } - - a += 1; + use super::*; + fn options(args: &str) -> Result { + let combined = "head ".to_owned() + args; + let args = combined.split_whitespace(); + HeadOptions::get_from(args.map(|s| OsString::from(s))) } + #[test] + fn test_args_modes() { + let args = options("-n -10M -vz").unwrap(); + assert!(args.zeroed); + assert!(args.verbose); + assert!(args.all_but_last); + assert_eq!(args.mode, Modes::Lines(10 * 1024 * 1024)); + } + #[test] + fn test_gnu_compatibility() { + let args = options("-n 1 -c 1 -n 5 -c kiB -vqvqv").unwrap(); + assert!(args.mode == Modes::Bytes(1024)); + assert!(args.verbose); + assert_eq!(options("-5").unwrap().mode, Modes::Lines(5)); + assert_eq!(options("-2b").unwrap().mode, Modes::Bytes(1024)); + assert_eq!(options("-5 -c 1").unwrap().mode, Modes::Bytes(1)); + } + #[test] + fn all_args_test() { + assert!(options("--silent").unwrap().quiet); + assert!(options("--quiet").unwrap().quiet); + assert!(options("-q").unwrap().quiet); + assert!(options("--verbose").unwrap().verbose); + assert!(options("-v").unwrap().verbose); + assert!(options("--zero-terminated").unwrap().zeroed); + assert!(options("-z").unwrap().zeroed); + assert_eq!(options("--lines 15").unwrap().mode, Modes::Lines(15)); + assert_eq!(options("-n 15").unwrap().mode, Modes::Lines(15)); + assert_eq!(options("--bytes 15").unwrap().mode, Modes::Bytes(15)); + assert_eq!(options("-c 15").unwrap().mode, Modes::Bytes(15)); + } + #[test] + fn test_options_errors() { + assert!(options("-n IsThisTheRealLife?").is_err()); + assert!(options("-c IsThisJustFantasy").is_err()); + } + #[test] + fn test_options_correct_defaults() { + let opts = HeadOptions::new(); + let opts2: HeadOptions = Default::default(); - (options, None) -} + assert_eq!(opts, opts2); -// TODO: handle errors on read -fn head(reader: &mut BufReader, settings: &Settings) -> bool { - match settings.mode { - FilterMode::Bytes(count) => { - for byte in reader.bytes().take(count) { - print!("{}", byte.unwrap() as char); - } - } - FilterMode::Lines(count) => { - 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) => { - let mut vector: VecDeque = VecDeque::new(); - - for line in reader.lines() { - vector.push_back(line.unwrap()); - if vector.len() <= count { - continue; - } - println!("{}", vector.pop_front().unwrap()); + assert!(opts.verbose == false); + assert!(opts.quiet == false); + assert!(opts.zeroed == false); + assert!(opts.all_but_last == false); + assert_eq!(opts.mode, Modes::Lines(10)); + assert!(opts.files.is_empty()); + } + #[test] + fn test_parse_mode() { + assert_eq!( + parse_mode("123", Modes::Lines), + Ok((Modes::Lines(123), false)) + ); + assert_eq!( + parse_mode("-456", Modes::Bytes), + Ok((Modes::Bytes(456), true)) + ); + assert!(parse_mode("Nonsensical Nonsense", Modes::Bytes).is_err()); + #[cfg(target_pointer_width = "64")] + assert!(parse_mode("1Y", Modes::Lines).is_err()); + #[cfg(target_pointer_width = "32")] + assert!(parse_mode("1T", Modes::Bytes).is_err()); + } + fn arg_outputs(src: &str) -> Result { + let split = src.split_whitespace().map(|x| OsString::from(x)); + match arg_iterate(split) { + Ok(args) => { + let vec = args + .map(|s| s.to_str().unwrap().to_owned()) + .collect::>(); + Ok(vec.join(" ")) } + Err(e) => Err(e), } } - true + #[test] + fn test_arg_iterate() { + // test that normal args remain unchanged + assert_eq!( + arg_outputs("head -n -5 -zv"), + Ok("head -n -5 -zv".to_owned()) + ); + // tests that nonsensical args are unchanged + assert_eq!( + arg_outputs("head -to_be_or_not_to_be,..."), + Ok("head -to_be_or_not_to_be,...".to_owned()) + ); + //test that the obsolete syntax is unrolled + assert_eq!( + arg_outputs("head -123qvqvqzc"), + Ok("head -q -z -c 123".to_owned()) + ); + //test that bad obsoletes are an error + assert!(arg_outputs("head -123FooBar").is_err()); + //test overflow + assert!(arg_outputs("head -100000000000000000000000000000000000000000").is_err()); + //test that empty args remain unchanged + assert_eq!(arg_outputs("head"), Ok("head".to_owned())); + } + #[test] + #[cfg(linux)] + fn test_arg_iterate_bad_encoding() { + let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") }; + // this arises from a conversion from OsString to &str + assert!( + arg_iterate(vec![OsString::from("head"), OsString::from(invalid)].into_iter()).is_err() + ); + } + #[test] + fn rbuf_early_exit() { + let mut empty = std::io::BufReader::new(std::io::Cursor::new(Vec::new())); + assert!(rbuf_n_bytes(&mut empty, 0).is_ok()); + assert!(rbuf_n_lines(&mut empty, 0, false).is_ok()); + } } diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs new file mode 100644 index 000000000..470d821e0 --- /dev/null +++ b/src/uu/head/src/parse.rs @@ -0,0 +1,282 @@ +use std::convert::TryFrom; +use std::ffi::OsString; + +#[derive(PartialEq, Debug)] +pub enum ParseError { + Syntax, + Overflow, +} +/// Parses obsolete syntax +/// head -NUM[kmzv] +pub fn parse_obsolete(src: &str) -> Option, ParseError>> { + let mut chars = src.char_indices(); + if let Some((_, '-')) = chars.next() { + let mut num_end = 0usize; + let mut has_num = false; + let mut last_char = 0 as char; + while let Some((n, c)) = chars.next() { + if c.is_numeric() { + has_num = true; + num_end = n; + } else { + last_char = c; + break; + } + } + if has_num { + match src[1..=num_end].parse::() { + Ok(num) => { + let mut quiet = false; + let mut verbose = false; + let mut zero_terminated = false; + let mut multiplier = None; + let mut c = last_char; + loop { + // not that here, we only match lower case 'k', 'c', and 'm' + match c { + // we want to preserve order + // this also saves us 1 heap allocation + 'q' => { + quiet = true; + verbose = false + } + 'v' => { + verbose = true; + quiet = false + } + 'z' => zero_terminated = true, + 'c' => multiplier = Some(1), + 'b' => multiplier = Some(512), + 'k' => multiplier = Some(1024), + 'm' => multiplier = Some(1024 * 1024), + '\0' => {} + _ => return Some(Err(ParseError::Syntax)), + } + if let Some((_, next)) = chars.next() { + c = next + } else { + break; + } + } + let mut options = Vec::new(); + if quiet { + options.push(OsString::from("-q")) + } + if verbose { + options.push(OsString::from("-v")) + } + if zero_terminated { + options.push(OsString::from("-z")) + } + if let Some(n) = multiplier { + options.push(OsString::from("-c")); + let num = match num.checked_mul(n) { + Some(n) => n, + None => return Some(Err(ParseError::Overflow)), + }; + options.push(OsString::from(format!("{}", num))); + } else { + options.push(OsString::from("-n")); + options.push(OsString::from(format!("{}", num))); + } + Some(Ok(options.into_iter())) + } + Err(_) => Some(Err(ParseError::Overflow)), + } + } else { + None + } + } else { + None + } +} +/// Parses an -c or -n argument, +/// the bool specifies whether to read from the end +pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> { + let mut num_start = 0; + let mut chars = src.char_indices(); + let (mut chars, all_but_last) = match chars.next() { + Some((_, c)) => { + if c == '-' { + num_start += 1; + (chars, true) + } else { + (src.char_indices(), false) + } + } + None => return Err(ParseError::Syntax), + }; + let mut num_end = 0usize; + let mut last_char = 0 as char; + let mut num_count = 0usize; + while let Some((n, c)) = chars.next() { + if c.is_numeric() { + num_end = n; + num_count += 1; + } else { + last_char = c; + break; + } + } + + let num = if num_count > 0 { + match src[num_start..=num_end].parse::() { + Ok(n) => Some(n), + Err(_) => return Err(ParseError::Overflow), + } + } else { + None + }; + + if last_char == 0 as char { + if let Some(n) = num { + Ok((n, all_but_last)) + } else { + Err(ParseError::Syntax) + } + } else { + let base: u128 = match chars.next() { + Some((_, c)) => { + let b = match c { + 'B' if last_char != 'b' => 1000, + 'i' if last_char != 'b' => { + if let Some((_, 'B')) = chars.next() { + 1024 + } else { + return Err(ParseError::Syntax); + } + } + _ => return Err(ParseError::Syntax), + }; + if chars.next().is_some() { + return Err(ParseError::Syntax); + } else { + b + } + } + None => 1024, + }; + let mul = match last_char.to_lowercase().next().unwrap() { + 'b' => 512, + 'k' => base.pow(1), + 'm' => base.pow(2), + 'g' => base.pow(3), + 't' => base.pow(4), + 'p' => base.pow(5), + 'e' => base.pow(6), + 'z' => base.pow(7), + 'y' => base.pow(8), + _ => return Err(ParseError::Syntax), + }; + let mul = match usize::try_from(mul) { + Ok(n) => n, + Err(_) => return Err(ParseError::Overflow), + }; + match num.unwrap_or(1).checked_mul(mul) { + Some(n) => Ok((n, all_but_last)), + None => Err(ParseError::Overflow), + } + } +} +#[cfg(test)] +mod tests { + use super::*; + fn obsolete(src: &str) -> Option, ParseError>> { + let r = parse_obsolete(src); + match r { + Some(s) => match s { + Ok(v) => Some(Ok(v.map(|s| s.to_str().unwrap().to_owned()).collect())), + Err(e) => Some(Err(e)), + }, + None => None, + } + } + fn obsolete_result(src: &[&str]) -> Option, ParseError>> { + Some(Ok(src.iter().map(|s| s.to_string()).collect())) + } + #[test] + #[cfg(not(target_pointer_width = "128"))] + fn test_parse_overflow_x64() { + assert_eq!(parse_num("1Y"), Err(ParseError::Overflow)); + assert_eq!(parse_num("1Z"), Err(ParseError::Overflow)); + assert_eq!(parse_num("100E"), Err(ParseError::Overflow)); + assert_eq!(parse_num("100000P"), Err(ParseError::Overflow)); + assert_eq!(parse_num("1000000000T"), Err(ParseError::Overflow)); + assert_eq!( + parse_num("10000000000000000000000"), + Err(ParseError::Overflow) + ); + } + #[test] + #[cfg(target_pointer_width = "32")] + fn test_parse_overflow_x32() { + assert_eq!(parse_num("1T"), Err(ParseError::Overflow)); + assert_eq!(parse_num("1000G"), Err(ParseError::Overflow)); + } + #[test] + fn test_parse_bad_syntax() { + assert_eq!(parse_num("5MiB nonsense"), Err(ParseError::Syntax)); + assert_eq!(parse_num("Nonsense string"), Err(ParseError::Syntax)); + assert_eq!(parse_num("5mib"), Err(ParseError::Syntax)); + assert_eq!(parse_num("biB"), Err(ParseError::Syntax)); + assert_eq!(parse_num("-"), Err(ParseError::Syntax)); + assert_eq!(parse_num(""), Err(ParseError::Syntax)); + } + #[test] + fn test_parse_numbers() { + assert_eq!(parse_num("k"), Ok((1024, false))); + assert_eq!(parse_num("MiB"), Ok((1024 * 1024, false))); + assert_eq!(parse_num("-5"), Ok((5, true))); + assert_eq!(parse_num("b"), Ok((512, false))); + assert_eq!(parse_num("-2GiB"), Ok((2 * 1024 * 1024 * 1024, true))); + assert_eq!(parse_num("5M"), Ok((5 * 1024 * 1024, false))); + assert_eq!(parse_num("5MB"), Ok((5 * 1000 * 1000, false))); + } + #[test] + fn test_parse_numbers_obsolete() { + assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"])); + assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"])); + assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "5242880"])); + assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "1024"])); + assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "1024"])); + assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "1024"])); + assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "1"])); + assert_eq!( + obsolete("-1vzqvq"), + obsolete_result(&["-q", "-z", "-n", "1"]) + ); + assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "1"])); + assert_eq!( + obsolete("-105kzm"), + obsolete_result(&["-z", "-c", "110100480"]) + ); + } + #[test] + fn test_parse_errors_obsolete() { + assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax))); + assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax))); + } + #[test] + fn test_parse_obsolete_nomatch() { + assert_eq!(obsolete("-k"), None); + assert_eq!(obsolete("asd"), None); + } + #[test] + #[cfg(target_pointer_width = "64")] + fn test_parse_obsolete_overflow_x64() { + assert_eq!( + obsolete("-1000000000000000m"), + Some(Err(ParseError::Overflow)) + ); + assert_eq!( + obsolete("-10000000000000000000000"), + Some(Err(ParseError::Overflow)) + ); + } + #[test] + #[cfg(target_pointer_width = "32")] + fn test_parse_obsolete_overflow_x32() { + assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow))); + assert_eq!(obsolete("-42949672k"), Some(Err(ParseError::Overflow))); + } +} diff --git a/src/uu/head/src/split.rs b/src/uu/head/src/split.rs new file mode 100644 index 000000000..9e9a0c685 --- /dev/null +++ b/src/uu/head/src/split.rs @@ -0,0 +1,60 @@ +#[derive(Debug)] +pub enum Event<'a> { + Data(&'a [u8]), + Line, +} +/// Loops over the lines read from a BufRead. +/// # Arguments +/// * `input` the ReadBuf to read from +/// * `zero` whether to use 0u8 as a line delimiter +/// * `on_event` a closure receiving some bytes read in a slice, or +/// event signalling a line was just read. +/// this is guaranteed to be signalled *directly* after the +/// slice containing the (CR on win)LF / 0 is passed +/// +/// Return whether to continue +pub fn walk_lines( + input: &mut impl std::io::BufRead, + zero: bool, + mut on_event: F, +) -> std::io::Result<()> +where + F: FnMut(Event) -> std::io::Result, +{ + let mut buffer = [0u8; super::BUF_SIZE]; + loop { + let read = loop { + match input.read(&mut buffer) { + Ok(n) => break n, + Err(e) => match e.kind() { + std::io::ErrorKind::Interrupted => {} + _ => return Err(e), + }, + } + }; + if read == 0 { + return Ok(()); + } + let mut base = 0usize; + for (i, byte) in buffer[..read].iter().enumerate() { + match byte { + b'\n' if !zero => { + on_event(Event::Data(&buffer[base..=i]))?; + base = i + 1; + if !on_event(Event::Line)? { + return Ok(()); + } + } + 0u8 if zero => { + on_event(Event::Data(&buffer[base..=i]))?; + base = i + 1; + if !on_event(Event::Line)? { + return Ok(()); + } + } + _ => {} + } + } + on_event(Event::Data(&buffer[base..read]))?; + } +} diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index b3870652e..ab6954104 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostid" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "hostid ~ (uutils) display the numeric identifier of the current host" @@ -16,7 +16,7 @@ path = "src/hostid.rs" [dependencies] libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index 75b9b5f9a..fb1d00682 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostname" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "hostname ~ (uutils) display or set the host name of the current host" @@ -18,7 +18,7 @@ path = "src/hostname.rs" clap = "2.33" libc = "0.2.42" hostname = { version = "0.3", features = ["set"] } -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["wide"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["wide"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } winapi = { version="0.3", features=["sysinfoapi", "winsock2"] } diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index f37b6a74d..d6f70be16 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -78,7 +78,7 @@ fn execute(args: impl uucore::Args) -> i32 { ) .arg(Arg::with_name(OPT_SHORT).short("s").long("short").help( "Display the short hostname (the portion before the first dot) if \ - possible", + possible", )) .arg(Arg::with_name(OPT_HOST)) .get_matches_from(args); diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index 46e5cb578..308d6089d 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_id" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "id ~ (uutils) display user and group information for USER" @@ -16,7 +16,7 @@ path = "src/id.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "process"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "process"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 9841ac64a..91463199a 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_install" -version = "0.0.4" +version = "0.0.6" authors = [ "Ben Eills ", "uutils developers", @@ -20,8 +20,9 @@ path = "src/install.rs" [dependencies] clap = "2.33" filetime = "0.2" +file_diff = "1.0.0" libc = ">= 0.2" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["mode", "perms", "entries"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["mode", "perms", "entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [dev-dependencies] diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 1ac9bc743..a4f1ca6e6 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -13,10 +13,12 @@ mod mode; extern crate uucore; use clap::{App, Arg, ArgMatches}; +use file_diff::diff; use filetime::{set_file_times, FileTime}; use uucore::entries::{grp2gid, usr2uid}; use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity}; +use libc::{getegid, geteuid}; use std::fs; use std::fs::File; use std::os::unix::fs::MetadataExt; @@ -34,6 +36,7 @@ pub struct Behavior { group: String, verbose: bool, preserve_timestamps: bool, + compare: bool, } #[derive(Clone, Eq, PartialEq)] @@ -112,11 +115,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("ignored") ) .arg( - // TODO implement flag Arg::with_name(OPT_COMPARE) .short("C") .long(OPT_COMPARE) - .help("(unimplemented) compare each pair of source and destination files, and in some cases, do not modify the destination at all") + .help("compare each pair of source and destination files, and in some cases, do not modify the destination at all") ) .arg( Arg::with_name(OPT_DIRECTORY) @@ -262,8 +264,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> { Err("--backup") } else if matches.is_present(OPT_BACKUP_2) { Err("-b") - } else if matches.is_present(OPT_COMPARE) { - Err("--compare, -C") } else if matches.is_present(OPT_CREATED) { Err("-D") } else if matches.is_present(OPT_STRIP) { @@ -338,6 +338,7 @@ fn behavior(matches: &ArgMatches) -> Result { group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(), verbose: matches.is_present(OPT_VERBOSE), preserve_timestamps: matches.is_present(OPT_PRESERVE_TIMESTAMPS), + compare: matches.is_present(OPT_COMPARE), }) } @@ -355,23 +356,29 @@ fn directory(paths: Vec, b: Behavior) -> i32 { } else { let mut all_successful = true; - for directory in paths.iter() { - let path = Path::new(directory); - + for path in paths.iter().map(Path::new) { // if the path already exist, don't try to create it again if !path.exists() { - if let Err(e) = fs::create_dir(directory) { - show_info!("{}: {}", path.display(), e.to_string()); + // Differently than the primary functionality (MainFunction::Standard), the directory + // functionality should create all ancestors (or components) of a directory regardless + // of the presence of the "-D" flag. + // NOTE: the GNU "install" sets the expected mode only for the target directory. All + // created ancestor directories will have the default mode. Hence it is safe to use + // fs::create_dir_all and then only modify the target's dir mode. + if let Err(e) = fs::create_dir_all(path) { + show_info!("{}: {}", path.display(), e); all_successful = false; + continue; + } + + if b.verbose { + show_info!("creating directory '{}'", path.display()); } } if mode::chmod(&path, b.mode()).is_err() { all_successful = false; - } - - if b.verbose { - show_info!("created directory '{}'", path.display()); + continue; } } if all_successful { @@ -487,6 +494,10 @@ fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behavior) -> i32 { /// If the copy system call fails, we print a verbose error and return an empty error value. /// fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { + if b.compare && !need_copy(from, to, b) { + return Ok(()); + } + if from.to_string_lossy() == "/dev/null" { /* workaround a limitation of fs::copy * https://github.com/rust-lang/rust/issues/79390 @@ -583,3 +594,81 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { Ok(()) } + +/// Return true if a file is necessary to copy. This is the case when: +/// - _from_ or _to_ is nonexistent; +/// - either file has a sticky bit or set[ug]id bit, or the user specified one; +/// - either file isn't a regular file; +/// - the sizes of _from_ and _to_ differ; +/// - _to_'s owner differs from intended; or +/// - the contents of _from_ and _to_ differ. +/// +/// # Parameters +/// +/// _from_ and _to_, if existent, must be non-directories. +/// +/// # Errors +/// +/// Crashes the program if a nonexistent owner or group is specified in _b_. +/// +fn need_copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> bool { + let from_meta = match fs::metadata(from) { + Ok(meta) => meta, + Err(_) => return true, + }; + let to_meta = match fs::metadata(to) { + Ok(meta) => meta, + Err(_) => return true, + }; + + // setuid || setgid || sticky + let extra_mode: u32 = 0o7000; + + if b.specified_mode.unwrap_or(0) & extra_mode != 0 + || from_meta.mode() & extra_mode != 0 + || to_meta.mode() & extra_mode != 0 + { + return true; + } + + if !from_meta.is_file() || !to_meta.is_file() { + return true; + } + + if from_meta.len() != to_meta.len() { + return true; + } + + // TODO: if -P (#1809) and from/to contexts mismatch, return true. + + if !b.owner.is_empty() { + let owner_id = match usr2uid(&b.owner) { + Ok(id) => id, + _ => crash!(1, "no such user: {}", b.owner), + }; + if owner_id != to_meta.uid() { + return true; + } + } else if !b.group.is_empty() { + let group_id = match grp2gid(&b.group) { + Ok(id) => id, + _ => crash!(1, "no such group: {}", b.group), + }; + if group_id != to_meta.gid() { + return true; + } + } else { + #[cfg(not(target_os = "windows"))] + unsafe { + if to_meta.uid() != geteuid() || to_meta.gid() != getegid() { + return true; + } + } + } + + if !diff(from.to_str().unwrap(), to.to_str().unwrap()) { + return true; + } + + false +} diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index d02703a62..9371b7601 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_join" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "join ~ (uutils) merge lines from inputs with matching join fields" @@ -16,7 +16,7 @@ path = "src/join.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index dd69cd35d..6b66806bc 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_kill" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "kill ~ (uutils) send a signal to a process" @@ -16,7 +16,7 @@ path = "src/kill.rs" [dependencies] libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["signals"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["signals"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index aaa183981..13c3453cf 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_link" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "link ~ (uutils) create a hard (file system) link to FILE" @@ -16,7 +16,7 @@ path = "src/link.rs" [dependencies] libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/ln/Cargo.toml b/src/uu/ln/Cargo.toml index 2b97f025a..c19d8fb52 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ln" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "ln ~ (uutils) create a (file system) link to TARGET" @@ -17,7 +17,7 @@ path = "src/ln.rs" [dependencies] clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index ab07a2b08..96a0df813 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -111,7 +111,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_BACKUP) .help( "make a backup of each file that would otherwise be overwritten \ - or removed", + or removed", ) .takes_value(true) .possible_value("simple") @@ -145,7 +145,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_NO_DEREFERENCE) .help( "treat LINK_executable!() as a normal file if it is a \ - symbolic link to a directory", + symbolic link to a directory", ), ) // TODO: opts.arg( @@ -223,7 +223,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } else if matches.is_present(OPT_BACKUP) { match matches.value_of(OPT_BACKUP) { None => BackupMode::ExistingBackup, - Some(mode) => match &mode[..] { + Some(mode) => match mode { "simple" | "never" => BackupMode::SimpleBackup, "numbered" | "t" => BackupMode::NumberedBackup, "existing" | "nil" => BackupMode::ExistingBackup, diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index d58576c77..416f817d7 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_logname" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "logname ~ (uutils) display the login name of the current user" @@ -16,7 +16,7 @@ path = "src/logname.rs" [dependencies] libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 59901f807..dacdc7cd9 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ls" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "ls ~ (uutils) display directory contents" @@ -22,7 +22,8 @@ term_grid = "0.1.5" termsize = "0.1.6" time = "0.1.40" unicode-width = "0.1.5" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "fs"] } +globset = "0.4.6" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(unix)'.dependencies] diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 8714a0fa1..fdc11144a 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -13,10 +13,13 @@ extern crate lazy_static; #[macro_use] extern crate uucore; +mod quoting_style; mod version_cmp; use clap::{App, Arg}; +use globset::{self, Glob, GlobSet, GlobSetBuilder}; use number_prefix::NumberPrefix; +use quoting_style::{escape_name, QuotingStyle}; #[cfg(unix)] use std::collections::HashMap; use std::fs; @@ -84,6 +87,7 @@ pub mod options { pub static COMMAS: &str = "m"; pub static LONG_NO_OWNER: &str = "g"; pub static LONG_NO_GROUP: &str = "o"; + pub static LONG_NUMERIC_UID_GID: &str = "numeric-uid-gid"; } pub mod files { pub static ALL: &str = "all"; @@ -103,6 +107,21 @@ pub mod options { pub static HUMAN_READABLE: &str = "human-readable"; pub static SI: &str = "si"; } + pub mod quoting { + pub static ESCAPE: &str = "escape"; + pub static LITERAL: &str = "literal"; + pub static C: &str = "quote-name"; + } + pub static QUOTING_STYLE: &str = "quoting-style"; + + pub mod indicator_style { + pub static NONE: &str = "none"; + pub static SLASH: &str = "slash"; + pub static FILE_TYPE: &str = "file-type"; + pub static CLASSIFY: &str = "classify"; + } + pub static HIDE_CONTROL_CHARS: &str = "hide-control-chars"; + pub static SHOW_CONTROL_CHARS: &str = "show-control-chars"; pub static WIDTH: &str = "width"; pub static AUTHOR: &str = "author"; pub static NO_GROUP: &str = "no-group"; @@ -112,13 +131,17 @@ pub mod options { pub static IGNORE_BACKUPS: &str = "ignore-backups"; pub static DIRECTORY: &str = "directory"; pub static CLASSIFY: &str = "classify"; + pub static FILE_TYPE: &str = "file-type"; + pub static SLASH: &str = "p"; pub static INODE: &str = "inode"; pub static DEREFERENCE: &str = "dereference"; - pub static NUMERIC_UID_GID: &str = "numeric-uid-gid"; pub static REVERSE: &str = "reverse"; pub static RECURSIVE: &str = "recursive"; pub static COLOR: &str = "color"; pub static PATHS: &str = "paths"; + pub static INDICATOR_STYLE: &str = "indicator-style"; + pub static HIDE: &str = "hide"; + pub static IGNORE: &str = "ignore"; } #[derive(PartialEq, Eq)] @@ -157,6 +180,14 @@ enum Time { Change, } +#[derive(PartialEq, Eq)] +enum IndicatorStyle { + None, + Slash, + FileType, + Classify, +} + struct Config { format: Format, files: Files, @@ -164,10 +195,8 @@ struct Config { recursive: bool, reverse: bool, dereference: bool, - classify: bool, - ignore_backups: bool, + ignore_patterns: GlobSet, size_format: SizeFormat, - numeric_uid_gid: bool, directory: bool, time: Time, #[cfg(unix)] @@ -176,6 +205,8 @@ struct Config { color: bool, long: LongFormat, width: Option, + quoting_style: QuotingStyle, + indicator_style: IndicatorStyle, } // Fields that can be removed or added to the long format @@ -183,6 +214,8 @@ struct LongFormat { author: bool, group: bool, owner: bool, + #[cfg(unix)] + numeric_uid_gid: bool, } impl Config { @@ -210,7 +243,7 @@ impl Config { (Format::Columns, options::format::COLUMNS) }; - // The -o and -g options are tricky. They cannot override with each + // The -o, -n and -g options are tricky. They cannot override with each // other because it's possible to combine them. For example, the option // -og should hide both owner and group. Furthermore, they are not // reset if -l or --format=long is used. So these should just show the @@ -223,41 +256,29 @@ impl Config { // which always applies. // // The idea here is to not let these options override with the other - // options, but manually check the last index they occur. If this index - // is larger than the index for the other format options, we apply the - // long format. - match options.indices_of(opt).map(|x| x.max().unwrap()) { - None => { - if options.is_present(options::format::LONG_NO_GROUP) - || options.is_present(options::format::LONG_NO_OWNER) - { - format = Format::Long; - } else if options.is_present(options::format::ONELINE) { + // options, but manually whether they have an index that's greater than + // the other format options. If so, we set the appropriate format. + if format != Format::Long { + let idx = options + .indices_of(opt) + .map(|x| x.max().unwrap()) + .unwrap_or(0); + if [ + options::format::LONG_NO_OWNER, + options::format::LONG_NO_GROUP, + options::format::LONG_NUMERIC_UID_GID, + ] + .iter() + .flat_map(|opt| options.indices_of(opt)) + .flatten() + .any(|i| i >= idx) + { + format = Format::Long; + } else if let Some(mut indices) = options.indices_of(options::format::ONELINE) { + if indices.any(|i| i > idx) { format = Format::OneLine; } } - Some(mut idx) => { - if let Some(indices) = options.indices_of(options::format::LONG_NO_OWNER) { - let i = indices.max().unwrap(); - if i > idx { - format = Format::Long; - idx = i; - } - } - if let Some(indices) = options.indices_of(options::format::LONG_NO_GROUP) { - let i = indices.max().unwrap(); - if i > idx { - format = Format::Long; - idx = i; - } - } - if let Some(indices) = options.indices_of(options::format::ONELINE) { - let i = indices.max().unwrap(); - if i > idx && format != Format::Long { - format = Format::OneLine; - } - } - } } let files = if options.is_present(options::files::ALL) { @@ -328,10 +349,14 @@ impl Config { let group = !options.is_present(options::NO_GROUP) && !options.is_present(options::format::LONG_NO_GROUP); let owner = !options.is_present(options::format::LONG_NO_OWNER); + #[cfg(unix)] + let numeric_uid_gid = options.is_present(options::format::LONG_NUMERIC_UID_GID); LongFormat { author, group, owner, + #[cfg(unix)] + numeric_uid_gid, } }; @@ -345,6 +370,118 @@ impl Config { }) .or_else(|| termsize::get().map(|s| s.cols)); + let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) { + false + } else if options.is_present(options::SHOW_CONTROL_CHARS) { + true + } else { + false // TODO: only if output is a terminal and the program is `ls` + }; + + let quoting_style = if let Some(style) = options.value_of(options::QUOTING_STYLE) { + match style { + "literal" => QuotingStyle::Literal { show_control }, + "shell" => QuotingStyle::Shell { + escape: false, + always_quote: false, + show_control, + }, + "shell-always" => QuotingStyle::Shell { + escape: false, + always_quote: true, + show_control, + }, + "shell-escape" => QuotingStyle::Shell { + escape: true, + always_quote: false, + show_control, + }, + "shell-escape-always" => QuotingStyle::Shell { + escape: true, + always_quote: true, + show_control, + }, + "c" => QuotingStyle::C { + quotes: quoting_style::Quotes::Double, + }, + "escape" => QuotingStyle::C { + quotes: quoting_style::Quotes::None, + }, + _ => unreachable!("Should have been caught by Clap"), + } + } else if options.is_present(options::quoting::LITERAL) { + QuotingStyle::Literal { show_control } + } else if options.is_present(options::quoting::ESCAPE) { + QuotingStyle::C { + quotes: quoting_style::Quotes::None, + } + } else if options.is_present(options::quoting::C) { + QuotingStyle::C { + quotes: quoting_style::Quotes::Double, + } + } else { + // TODO: use environment variable if available + QuotingStyle::Shell { + escape: true, + always_quote: false, + show_control, + } + }; + + let indicator_style = if let Some(field) = options.value_of(options::INDICATOR_STYLE) { + match field { + "none" => IndicatorStyle::None, + "file-type" => IndicatorStyle::FileType, + "classify" => IndicatorStyle::Classify, + "slash" => IndicatorStyle::Slash, + &_ => IndicatorStyle::None, + } + } else if options.is_present(options::indicator_style::NONE) { + IndicatorStyle::None + } else if options.is_present(options::indicator_style::CLASSIFY) + || options.is_present(options::CLASSIFY) + { + IndicatorStyle::Classify + } else if options.is_present(options::indicator_style::SLASH) + || options.is_present(options::SLASH) + { + IndicatorStyle::Slash + } else if options.is_present(options::indicator_style::FILE_TYPE) + || options.is_present(options::FILE_TYPE) + { + IndicatorStyle::FileType + } else { + IndicatorStyle::None + }; + + let mut ignore_patterns = GlobSetBuilder::new(); + if options.is_present(options::IGNORE_BACKUPS) { + ignore_patterns.add(Glob::new("*~").unwrap()); + ignore_patterns.add(Glob::new(".*~").unwrap()); + } + + for pattern in options.values_of(options::IGNORE).into_iter().flatten() { + match Glob::new(pattern) { + Ok(p) => { + ignore_patterns.add(p); + } + Err(_) => show_warning!("Invalid pattern for ignore: '{}'", pattern), + } + } + + if files == Files::Normal { + for pattern in options.values_of(options::HIDE).into_iter().flatten() { + match Glob::new(pattern) { + Ok(p) => { + ignore_patterns.add(p); + } + Err(_) => show_warning!("Invalid pattern for hide: '{}'", pattern), + } + } + } + + let ignore_patterns = ignore_patterns.build().unwrap(); + Config { format, files, @@ -352,10 +489,8 @@ impl Config { recursive: options.is_present(options::RECURSIVE), reverse: options.is_present(options::REVERSE), dereference: options.is_present(options::DEREFERENCE), - classify: options.is_present(options::CLASSIFY), - ignore_backups: options.is_present(options::IGNORE_BACKUPS), + ignore_patterns, size_format, - numeric_uid_gid: options.is_present(options::NUMERIC_UID_GID), directory: options.is_present(options::DIRECTORY), time, #[cfg(unix)] @@ -364,6 +499,8 @@ impl Config { inode: options.is_present(options::INODE), long, width, + quoting_style, + indicator_style, } } } @@ -444,22 +581,108 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::format::COLUMNS, ]), ) - // The next three arguments do not override with the other format + // The next four arguments do not override with the other format // options, see the comment in Config::from for the reason. + // Ideally, they would use Arg::override_with, with their own name + // but that doesn't seem to work in all cases. Example: + // ls -1g1 + // even though `ls -11` and `ls -1 -g -1` work. .arg( Arg::with_name(options::format::ONELINE) .short(options::format::ONELINE) .help("List one file per line.") + .multiple(true) ) .arg( Arg::with_name(options::format::LONG_NO_GROUP) .short(options::format::LONG_NO_GROUP) .help("Long format without group information. Identical to --format=long with --no-group.") + .multiple(true) ) .arg( Arg::with_name(options::format::LONG_NO_OWNER) .short(options::format::LONG_NO_OWNER) .help("Long format without owner information.") + .multiple(true) + ) + .arg( + Arg::with_name(options::format::LONG_NUMERIC_UID_GID) + .short("n") + .long(options::format::LONG_NUMERIC_UID_GID) + .help("-l with numeric UIDs and GIDs.") + .multiple(true) + ) + + // Quoting style + .arg( + Arg::with_name(options::QUOTING_STYLE) + .long(options::QUOTING_STYLE) + .takes_value(true) + .help("Set quoting style.") + .possible_values(&["literal", "shell", "shell-always", "shell-escape", "shell-escape-always", "c", "escape"]) + .overrides_with_all(&[ + options::QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + ) + .arg( + Arg::with_name(options::quoting::LITERAL) + .short("N") + .long(options::quoting::LITERAL) + .help("Use literal quoting style. Equivalent to `--quoting-style=literal`") + .overrides_with_all(&[ + options::QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + ) + .arg( + Arg::with_name(options::quoting::ESCAPE) + .short("b") + .long(options::quoting::ESCAPE) + .help("Use escape quoting style. Equivalent to `--quoting-style=escape`") + .overrides_with_all(&[ + options::QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + ) + .arg( + Arg::with_name(options::quoting::C) + .short("Q") + .long(options::quoting::C) + .help("Use C quoting style. Equivalent to `--quoting-style=c`") + .overrides_with_all(&[ + options::QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + ) + + // Control characters + .arg( + Arg::with_name(options::HIDE_CONTROL_CHARS) + .short("q") + .long(options::HIDE_CONTROL_CHARS) + .help("Replace control characters with '?' if they are not escaped.") + .overrides_with_all(&[ + options::HIDE_CONTROL_CHARS, + options::SHOW_CONTROL_CHARS, + ]) + ) + .arg( + Arg::with_name(options::SHOW_CONTROL_CHARS) + .long(options::SHOW_CONTROL_CHARS) + .help("Show control characters 'as is' if they are not escaped.") + .overrides_with_all(&[ + options::HIDE_CONTROL_CHARS, + options::SHOW_CONTROL_CHARS, + ]) ) // Time arguments @@ -507,6 +730,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ]) ) + // Hide and ignore + .arg( + Arg::with_name(options::HIDE) + .long(options::HIDE) + .takes_value(true) + .multiple(true) + ) + .arg( + Arg::with_name(options::IGNORE) + .short("I") + .long(options::IGNORE) + .takes_value(true) + .multiple(true) + ) + .arg( + Arg::with_name(options::IGNORE_BACKUPS) + .short("B") + .long(options::IGNORE_BACKUPS) + .help("Ignore entries which end with ~."), + ) + // Sort arguments .arg( Arg::with_name(options::SORT) @@ -604,12 +848,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { '.' and '..'.", ), ) - .arg( - Arg::with_name(options::IGNORE_BACKUPS) - .short("B") - .long(options::IGNORE_BACKUPS) - .help("Ignore entries which end with ~."), - ) .arg( Arg::with_name(options::DIRECTORY) .short("d") @@ -621,15 +859,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { specified.", ), ) - .arg( - Arg::with_name(options::CLASSIFY) - .short("F") - .long(options::CLASSIFY) - .help("Append a character to each file name indicating the file type. Also, for \ - regular files that are executable, append '*'. The file type indicators are \ - '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ - '>' for doors, and nothing for regular files.", - )) .arg( Arg::with_name(options::size::HUMAN_READABLE) .short("h") @@ -657,12 +886,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { file the link references rather than the link itself.", ), ) - .arg( - Arg::with_name(options::NUMERIC_UID_GID) - .short("n") - .long(options::NUMERIC_UID_GID) - .help("-l with numeric UIDs and GIDs."), - ) .arg( Arg::with_name(options::REVERSE) .short("r") @@ -692,8 +915,57 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .require_equals(true) .min_values(0), ) + .arg( + Arg::with_name(options::INDICATOR_STYLE) + .long(options::INDICATOR_STYLE) + .help(" append indicator with style WORD to entry names: none (default), slash\ + (-p), file-type (--file-type), classify (-F)") + .takes_value(true) + .possible_values(&["none", "slash", "file-type", "classify"]) + .overrides_with_all(&[ + options::FILE_TYPE, + options::SLASH, + options::CLASSIFY, + options::INDICATOR_STYLE, + ])) + .arg( + Arg::with_name(options::CLASSIFY) + .short("F") + .long(options::CLASSIFY) + .help("Append a character to each file name indicating the file type. Also, for \ + regular files that are executable, append '*'. The file type indicators are \ + '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ + '>' for doors, and nothing for regular files.") + .overrides_with_all(&[ + options::FILE_TYPE, + options::SLASH, + options::CLASSIFY, + options::INDICATOR_STYLE, + ]) + ) + .arg( + Arg::with_name(options::FILE_TYPE) + .long(options::FILE_TYPE) + .help("Same as --classify, but do not append '*'") + .overrides_with_all(&[ + options::FILE_TYPE, + options::SLASH, + options::CLASSIFY, + options::INDICATOR_STYLE, + ])) + .arg( + Arg::with_name(options::SLASH) + .short(options::SLASH) + .help("Append / indicator to directories." + ) + .overrides_with_all(&[ + options::FILE_TYPE, + options::SLASH, + options::CLASSIFY, + options::INDICATOR_STYLE, + ])) - // Positional arguments + // Positional arguments .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)); let matches = app.get_matches_from(args); @@ -793,11 +1065,12 @@ fn is_hidden(file_path: &DirEntry) -> bool { fn should_display(entry: &DirEntry, config: &Config) -> bool { let ffi_name = entry.file_name(); - let name = ffi_name.to_string_lossy(); + if config.files == Files::Normal && is_hidden(entry) { return false; } - if config.ignore_backups && name.ends_with('~') { + + if config.ignore_patterns.is_match(&ffi_name) { return false; } true @@ -852,7 +1125,7 @@ fn pad_left(string: String, count: usize) -> String { } fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) { - if config.format == Format::Long || config.numeric_uid_gid { + if config.format == Format::Long { let (mut max_links, mut max_size) = (1, 1); for item in items { let (links, size) = display_dir_entry_size(item, config); @@ -994,7 +1267,7 @@ use uucore::entries; #[cfg(unix)] fn display_uname(metadata: &Metadata, config: &Config) -> String { - if config.numeric_uid_gid { + if config.long.numeric_uid_gid { metadata.uid().to_string() } else { entries::uid2usr(metadata.uid()).unwrap_or_else(|_| metadata.uid().to_string()) @@ -1003,7 +1276,7 @@ fn display_uname(metadata: &Metadata, config: &Config) -> String { #[cfg(unix)] fn display_group(metadata: &Metadata, config: &Config) -> String { - if config.numeric_uid_gid { + if config.long.numeric_uid_gid { metadata.gid().to_string() } else { entries::gid2grp(metadata.gid()).unwrap_or_else(|_| metadata.gid().to_string()) @@ -1119,16 +1392,25 @@ fn display_file_name( metadata: &Metadata, config: &Config, ) -> Cell { - let mut name = get_file_name(path, strip); + let mut name = escape_name(get_file_name(path, strip), &config.quoting_style); + let file_type = metadata.file_type(); - if config.classify { - let file_type = metadata.file_type(); - if file_type.is_dir() { - name.push('/'); - } else if file_type.is_symlink() { - name.push('@'); + match config.indicator_style { + IndicatorStyle::Classify | IndicatorStyle::FileType => { + if file_type.is_dir() { + name.push('/'); + } + if file_type.is_symlink() { + name.push('@'); + } } - } + IndicatorStyle::Slash => { + if file_type.is_dir() { + name.push('/'); + } + } + _ => (), + }; if config.format == Format::Long && metadata.file_type().is_symlink() { if let Ok(target) = path.read_link() { @@ -1177,15 +1459,14 @@ fn display_file_name( metadata: &Metadata, config: &Config, ) -> Cell { - let mut name = get_file_name(path, strip); + let mut name = escape_name(get_file_name(path, strip), &config.quoting_style); if config.format != Format::Long && config.inode { name = get_inode(metadata) + " " + &name; } let mut width = UnicodeWidthStr::width(&*name); let ext; - - if config.color || config.classify { + if config.color || config.indicator_style != IndicatorStyle::None { let file_type = metadata.file_type(); let (code, sym) = if file_type.is_dir() { @@ -1238,11 +1519,29 @@ fn display_file_name( if config.color { name = color_name(name, code); } - if config.classify { - if let Some(s) = sym { - name.push(s); - width += 1; + + let char_opt = match config.indicator_style { + IndicatorStyle::Classify => sym, + IndicatorStyle::FileType => { + // Don't append an asterisk. + match sym { + Some('*') => None, + _ => sym, + } } + IndicatorStyle::Slash => { + // Append only a slash. + match sym { + Some('/') => Some('/'), + _ => None, + } + } + IndicatorStyle::None => None, + }; + + if let Some(c) = char_opt { + name.push(c); + width += 1; } } diff --git a/src/uu/ls/src/quoting_style.rs b/src/uu/ls/src/quoting_style.rs new file mode 100644 index 000000000..ceb54466c --- /dev/null +++ b/src/uu/ls/src/quoting_style.rs @@ -0,0 +1,630 @@ +use std::char::from_digit; + +const SPECIAL_SHELL_CHARS: &str = "~`#$&*()\\|[]{};'\"<>?! "; + +pub(super) enum QuotingStyle { + Shell { + escape: bool, + always_quote: bool, + show_control: bool, + }, + C { + quotes: Quotes, + }, + Literal { + show_control: bool, + }, +} + +#[derive(Clone, Copy)] +pub(super) enum Quotes { + None, + Single, + Double, + // TODO: Locale +} + +// This implementation is heavily inspired by the std::char::EscapeDefault implementation +// in the Rust standard library. This custom implementation is needed because the +// characters \a, \b, \e, \f & \v are not recognized by Rust. +#[derive(Clone, Debug)] +struct EscapedChar { + state: EscapeState, +} + +#[derive(Clone, Debug)] +enum EscapeState { + Done, + Char(char), + Backslash(char), + ForceQuote(char), + Octal(EscapeOctal), +} + +#[derive(Clone, Debug)] +struct EscapeOctal { + c: char, + state: EscapeOctalState, + idx: usize, +} + +#[derive(Clone, Debug)] +enum EscapeOctalState { + Done, + Backslash, + Value, +} + +impl Iterator for EscapeOctal { + type Item = char; + + fn next(&mut self) -> Option { + match self.state { + EscapeOctalState::Done => None, + EscapeOctalState::Backslash => { + self.state = EscapeOctalState::Value; + Some('\\') + } + EscapeOctalState::Value => { + let octal_digit = ((self.c as u32) >> (self.idx * 3)) & 0o7; + if self.idx == 0 { + self.state = EscapeOctalState::Done; + } else { + self.idx -= 1; + } + Some(from_digit(octal_digit, 8).unwrap()) + } + } + } +} + +impl EscapeOctal { + fn from(c: char) -> EscapeOctal { + EscapeOctal { + c, + idx: 2, + state: EscapeOctalState::Backslash, + } + } +} + +impl EscapedChar { + fn new_literal(c: char) -> Self { + Self { + state: EscapeState::Char(c), + } + } + + fn new_c(c: char, quotes: Quotes) -> Self { + use EscapeState::*; + let init_state = match c { + '\x07' => Backslash('a'), + '\x08' => Backslash('b'), + '\t' => Backslash('t'), + '\n' => Backslash('n'), + '\x0B' => Backslash('v'), + '\x0C' => Backslash('f'), + '\r' => Backslash('r'), + '\\' => Backslash('\\'), + '\'' => match quotes { + Quotes::Single => Backslash('\''), + _ => Char('\''), + }, + '"' => match quotes { + Quotes::Double => Backslash('"'), + _ => Char('"'), + }, + ' ' => match quotes { + Quotes::None => Backslash(' '), + _ => Char(' '), + }, + _ if c.is_ascii_control() => Octal(EscapeOctal::from(c)), + _ => Char(c), + }; + Self { state: init_state } + } + + fn new_shell(c: char, escape: bool, quotes: Quotes) -> Self { + use EscapeState::*; + let init_state = match c { + _ if !escape && c.is_control() => Char(c), + '\x07' => Backslash('a'), + '\x08' => Backslash('b'), + '\t' => Backslash('t'), + '\n' => Backslash('n'), + '\x0B' => Backslash('v'), + '\x0C' => Backslash('f'), + '\r' => Backslash('r'), + '\\' => Backslash('\\'), + '\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c)), + '\'' => match quotes { + Quotes::Single => Backslash('\''), + _ => Char('\''), + }, + _ if SPECIAL_SHELL_CHARS.contains(c) => ForceQuote(c), + _ => Char(c), + }; + Self { state: init_state } + } + + fn hide_control(self) -> Self { + match self.state { + EscapeState::Char(c) if c.is_control() => Self { + state: EscapeState::Char('?'), + }, + _ => self, + } + } +} + +impl Iterator for EscapedChar { + type Item = char; + + fn next(&mut self) -> Option { + match self.state { + EscapeState::Backslash(c) => { + self.state = EscapeState::Char(c); + Some('\\') + } + EscapeState::Char(c) | EscapeState::ForceQuote(c) => { + self.state = EscapeState::Done; + Some(c) + } + EscapeState::Done => None, + EscapeState::Octal(ref mut iter) => iter.next(), + } + } +} + +fn shell_without_escape(name: String, quotes: Quotes, show_control_chars: bool) -> (String, bool) { + let mut must_quote = false; + let mut escaped_str = String::with_capacity(name.len()); + + for c in name.chars() { + let escaped = { + let ec = EscapedChar::new_shell(c, false, quotes); + if show_control_chars { + ec + } else { + ec.hide_control() + } + }; + + match escaped.state { + EscapeState::Backslash('\'') => escaped_str.push_str("'\\''"), + EscapeState::ForceQuote(x) => { + must_quote = true; + escaped_str.push(x); + } + _ => { + for char in escaped { + escaped_str.push(char); + } + } + } + } + (escaped_str, must_quote) +} + +fn shell_with_escape(name: String, quotes: Quotes) -> (String, bool) { + // We need to keep track of whether we are in a dollar expression + // because e.g. \b\n is escaped as $'\b\n' and not like $'b'$'n' + let mut in_dollar = false; + let mut must_quote = false; + let mut escaped_str = String::with_capacity(name.len()); + + for c in name.chars() { + let escaped = EscapedChar::new_shell(c, true, quotes); + match escaped.state { + EscapeState::Char(x) => { + if in_dollar { + escaped_str.push_str("''"); + in_dollar = false; + } + escaped_str.push(x); + } + EscapeState::ForceQuote(x) => { + if in_dollar { + escaped_str.push_str("''"); + in_dollar = false; + } + must_quote = true; + escaped_str.push(x); + } + // Single quotes are not put in dollar expressions, but are escaped + // if the string also contains double quotes. In that case, they must + // be handled separately. + EscapeState::Backslash('\'') => { + must_quote = true; + in_dollar = false; + escaped_str.push_str("'\\''"); + } + _ => { + if !in_dollar { + escaped_str.push_str("'$'"); + in_dollar = true; + } + must_quote = true; + for char in escaped { + escaped_str.push(char); + } + } + } + } + (escaped_str, must_quote) +} + +pub(super) fn escape_name(name: String, style: &QuotingStyle) -> String { + match style { + QuotingStyle::Literal { show_control } => { + if !show_control { + name.chars() + .flat_map(|c| EscapedChar::new_literal(c).hide_control()) + .collect() + } else { + name + } + } + QuotingStyle::C { quotes } => { + let escaped_str: String = name + .chars() + .flat_map(|c| EscapedChar::new_c(c, *quotes)) + .collect(); + + match quotes { + Quotes::Single => format!("'{}'", escaped_str), + Quotes::Double => format!("\"{}\"", escaped_str), + _ => escaped_str, + } + } + QuotingStyle::Shell { + escape, + always_quote, + show_control, + } => { + let (quotes, must_quote) = if name.contains('"') { + (Quotes::Single, true) + } else if name.contains('\'') { + (Quotes::Double, true) + } else if *always_quote { + (Quotes::Single, true) + } else { + (Quotes::Single, false) + }; + + let (escaped_str, contains_quote_chars) = if *escape { + shell_with_escape(name, quotes) + } else { + shell_without_escape(name, quotes, *show_control) + }; + + match (must_quote | contains_quote_chars, quotes) { + (true, Quotes::Single) => format!("'{}'", escaped_str), + (true, Quotes::Double) => format!("\"{}\"", escaped_str), + _ => escaped_str, + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::quoting_style::{escape_name, Quotes, QuotingStyle}; + fn get_style(s: &str) -> QuotingStyle { + match s { + "literal" => QuotingStyle::Literal { + show_control: false, + }, + "literal-show" => QuotingStyle::Literal { show_control: true }, + "escape" => QuotingStyle::C { + quotes: Quotes::None, + }, + "c" => QuotingStyle::C { + quotes: Quotes::Double, + }, + "shell" => QuotingStyle::Shell { + escape: false, + always_quote: false, + show_control: false, + }, + "shell-show" => QuotingStyle::Shell { + escape: false, + always_quote: false, + show_control: true, + }, + "shell-always" => QuotingStyle::Shell { + escape: false, + always_quote: true, + show_control: false, + }, + "shell-always-show" => QuotingStyle::Shell { + escape: false, + always_quote: true, + show_control: true, + }, + "shell-escape" => QuotingStyle::Shell { + escape: true, + always_quote: false, + show_control: false, + }, + "shell-escape-always" => QuotingStyle::Shell { + escape: true, + always_quote: true, + show_control: false, + }, + _ => panic!("Invalid name!"), + } + } + + fn check_names(name: &str, map: Vec<(&str, &str)>) { + assert_eq!( + map.iter() + .map(|(_, style)| escape_name(name.to_string(), &get_style(style))) + .collect::>(), + map.iter() + .map(|(correct, _)| correct.to_string()) + .collect::>() + ); + } + + #[test] + fn test_simple_names() { + check_names( + "one_two", + vec![ + ("one_two", "literal"), + ("one_two", "literal-show"), + ("one_two", "escape"), + ("\"one_two\"", "c"), + ("one_two", "shell"), + ("one_two", "shell-show"), + ("\'one_two\'", "shell-always"), + ("\'one_two\'", "shell-always-show"), + ("one_two", "shell-escape"), + ("\'one_two\'", "shell-escape-always"), + ], + ); + } + + #[test] + fn test_spaces() { + check_names( + "one two", + vec![ + ("one two", "literal"), + ("one two", "literal-show"), + ("one\\ two", "escape"), + ("\"one two\"", "c"), + ("\'one two\'", "shell"), + ("\'one two\'", "shell-show"), + ("\'one two\'", "shell-always"), + ("\'one two\'", "shell-always-show"), + ("\'one two\'", "shell-escape"), + ("\'one two\'", "shell-escape-always"), + ], + ); + + check_names( + " one", + vec![ + (" one", "literal"), + (" one", "literal-show"), + ("\\ one", "escape"), + ("\" one\"", "c"), + ("' one'", "shell"), + ("' one'", "shell-show"), + ("' one'", "shell-always"), + ("' one'", "shell-always-show"), + ("' one'", "shell-escape"), + ("' one'", "shell-escape-always"), + ], + ); + } + + #[test] + fn test_quotes() { + // One double quote + check_names( + "one\"two", + vec![ + ("one\"two", "literal"), + ("one\"two", "literal-show"), + ("one\"two", "escape"), + ("\"one\\\"two\"", "c"), + ("'one\"two'", "shell"), + ("'one\"two'", "shell-show"), + ("'one\"two'", "shell-always"), + ("'one\"two'", "shell-always-show"), + ("'one\"two'", "shell-escape"), + ("'one\"two'", "shell-escape-always"), + ], + ); + + // One single quote + check_names( + "one\'two", + vec![ + ("one'two", "literal"), + ("one'two", "literal-show"), + ("one'two", "escape"), + ("\"one'two\"", "c"), + ("\"one'two\"", "shell"), + ("\"one'two\"", "shell-show"), + ("\"one'two\"", "shell-always"), + ("\"one'two\"", "shell-always-show"), + ("\"one'two\"", "shell-escape"), + ("\"one'two\"", "shell-escape-always"), + ], + ); + + // One single quote and one double quote + check_names( + "one'two\"three", + vec![ + ("one'two\"three", "literal"), + ("one'two\"three", "literal-show"), + ("one'two\"three", "escape"), + ("\"one'two\\\"three\"", "c"), + ("'one'\\''two\"three'", "shell"), + ("'one'\\''two\"three'", "shell-show"), + ("'one'\\''two\"three'", "shell-always"), + ("'one'\\''two\"three'", "shell-always-show"), + ("'one'\\''two\"three'", "shell-escape"), + ("'one'\\''two\"three'", "shell-escape-always"), + ], + ); + + // Consecutive quotes + check_names( + "one''two\"\"three", + vec![ + ("one''two\"\"three", "literal"), + ("one''two\"\"three", "literal-show"), + ("one''two\"\"three", "escape"), + ("\"one''two\\\"\\\"three\"", "c"), + ("'one'\\'''\\''two\"\"three'", "shell"), + ("'one'\\'''\\''two\"\"three'", "shell-show"), + ("'one'\\'''\\''two\"\"three'", "shell-always"), + ("'one'\\'''\\''two\"\"three'", "shell-always-show"), + ("'one'\\'''\\''two\"\"three'", "shell-escape"), + ("'one'\\'''\\''two\"\"three'", "shell-escape-always"), + ], + ); + } + + #[test] + fn test_control_chars() { + // A simple newline + check_names( + "one\ntwo", + vec![ + ("one?two", "literal"), + ("one\ntwo", "literal-show"), + ("one\\ntwo", "escape"), + ("\"one\\ntwo\"", "c"), + ("one?two", "shell"), + ("one\ntwo", "shell-show"), + ("'one?two'", "shell-always"), + ("'one\ntwo'", "shell-always-show"), + ("'one'$'\\n''two'", "shell-escape"), + ("'one'$'\\n''two'", "shell-escape-always"), + ], + ); + + // The first 16 control characters. NUL is also included, even though it is of + // no importance for file names. + check_names( + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", + vec![ + ("????????????????", "literal"), + ( + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", + "literal-show", + ), + ( + "\\000\\001\\002\\003\\004\\005\\006\\a\\b\\t\\n\\v\\f\\r\\016\\017", + "escape", + ), + ( + "\"\\000\\001\\002\\003\\004\\005\\006\\a\\b\\t\\n\\v\\f\\r\\016\\017\"", + "c", + ), + ("????????????????", "shell"), + ( + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", + "shell-show", + ), + ("'????????????????'", "shell-always"), + ( + "'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F'", + "shell-always-show", + ), + ( + "''$'\\000\\001\\002\\003\\004\\005\\006\\a\\b\\t\\n\\v\\f\\r\\016\\017'", + "shell-escape", + ), + ( + "''$'\\000\\001\\002\\003\\004\\005\\006\\a\\b\\t\\n\\v\\f\\r\\016\\017'", + "shell-escape-always", + ), + ], + ); + + // The last 16 control characters. + check_names( + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F", + vec![ + ("????????????????", "literal"), + ( + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F", + "literal-show", + ), + ( + "\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037", + "escape", + ), + ( + "\"\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\"", + "c", + ), + ("????????????????", "shell"), + ( + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F", + "shell-show", + ), + ("'????????????????'", "shell-always"), + ( + "'\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F'", + "shell-always-show", + ), + ( + "''$'\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037'", + "shell-escape", + ), + ( + "''$'\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037'", + "shell-escape-always", + ), + ], + ); + + // DEL + check_names( + "\x7F", + vec![ + ("?", "literal"), + ("\x7F", "literal-show"), + ("\\177", "escape"), + ("\"\\177\"", "c"), + ("?", "shell"), + ("\x7F", "shell-show"), + ("'?'", "shell-always"), + ("'\x7F'", "shell-always-show"), + ("''$'\\177'", "shell-escape"), + ("''$'\\177'", "shell-escape-always"), + ], + ); + } + + #[test] + fn test_question_mark() { + // A question mark must force quotes in shell and shell-always, unless + // it is in place of a control character (that case is already covered + // in other tests) + check_names( + "one?two", + vec![ + ("one?two", "literal"), + ("one?two", "literal-show"), + ("one?two", "escape"), + ("\"one?two\"", "c"), + ("'one?two'", "shell"), + ("'one?two'", "shell-show"), + ("'one?two'", "shell-always"), + ("'one?two'", "shell-always-show"), + ("'one?two'", "shell-escape"), + ("'one?two'", "shell-escape-always"), + ], + ); + } +} diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index 9cedb5c03..a8d374bf9 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkdir" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "mkdir ~ (uutils) create DIRECTORY" @@ -17,7 +17,7 @@ path = "src/mkdir.rs" [dependencies] clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs", "mode"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 627d4a721..d66003b10 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkfifo" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "mkfifo ~ (uutils) create FIFOs (named pipes)" @@ -15,9 +15,9 @@ edition = "2018" path = "src/mkfifo.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 41582b14a..14701af4d 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -8,56 +8,55 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use libc::mkfifo; use std::ffi::CString; -use std::io::Error; static NAME: &str = "mkfifo"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static USAGE: &str = "mkfifo [OPTION]... NAME..."; +static SUMMARY: &str = "Create a FIFO with the given name."; + +mod options { + pub static MODE: &str = "mode"; + pub static SE_LINUX_SECURITY_CONTEXT: &str = "Z"; + pub static CONTEXT: &str = "context"; + pub static FIFO: &str = "fifo"; +} pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let mut opts = getopts::Options::new(); + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .usage(USAGE) + .about(SUMMARY) + .arg( + Arg::with_name(options::MODE) + .short("m") + .long(options::MODE) + .help("file permissions for the fifo") + .default_value("0666") + .value_name("0666"), + ) + .arg( + Arg::with_name(options::SE_LINUX_SECURITY_CONTEXT) + .short(options::SE_LINUX_SECURITY_CONTEXT) + .help("set the SELinux security context to default type") + ) + .arg(Arg::with_name(options::CONTEXT).long(options::CONTEXT).value_name("CTX").help("like -Z, or if CTX is specified then set the SELinux\nor SMACK security context to CTX")) + .arg(Arg::with_name(options::FIFO).hidden(true).multiple(true)) + .get_matches_from(args); - opts.optopt( - "m", - "mode", - "file permissions for the fifo", - "(default 0666)", - ); - 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(err) => panic!("{}", err), - }; - - if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - return 0; + if matches.is_present(options::CONTEXT) { + crash!(1, "--context is not implemented"); + } + if matches.is_present(options::SE_LINUX_SECURITY_CONTEXT) { + crash!(1, "-Z is not implemented"); } - if matches.opt_present("help") || matches.free.is_empty() { - let msg = format!( - "{0} {1} - -Usage: - {0} [OPTIONS] NAME... - -Create a FIFO with the given name.", - NAME, VERSION - ); - - print!("{}", opts.usage(&msg)); - if matches.free.is_empty() { - return 1; - } - return 0; - } - - let mode = match matches.opt_str("m") { + let mode = match matches.value_of(options::MODE) { Some(m) => match usize::from_str_radix(&m, 8) { Ok(m) => m, Err(e) => { @@ -68,21 +67,22 @@ Create a FIFO with the given name.", None => 0o666, }; - let mut exit_status = 0; - for f in &matches.free { + let fifos: Vec = match matches.values_of(options::FIFO) { + Some(v) => v.clone().map(|s| s.to_owned()).collect(), + None => crash!(1, "missing operand"), + }; + + let mut exit_code = 0; + for f in fifos { let err = unsafe { let name = CString::new(f.as_bytes()).unwrap(); mkfifo(name.as_ptr(), mode as libc::mode_t) }; if err == -1 { - show_error!( - "creating '{}': {}", - f, - Error::last_os_error().raw_os_error().unwrap() - ); - exit_status = 1; + show_error!("cannot create fifo '{}': File exists", f); + exit_code = 1; } } - exit_status + exit_code } diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 426534e75..2c3ac8fb9 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mknod" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "mknod ~ (uutils) create special file NAME of TYPE" @@ -18,7 +18,7 @@ path = "src/mknod.rs" [dependencies] getopts = "0.2.18" libc = "^0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["mode"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["mode"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/mktemp/Cargo.toml b/src/uu/mktemp/Cargo.toml index c48306a40..c669f0acc 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mktemp" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "mktemp ~ (uutils) create and display a temporary file or directory from TEMPLATE" @@ -18,7 +18,7 @@ path = "src/mktemp.rs" clap = "2.33" rand = "0.5" tempfile = "3.1" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 194b6625f..ed767ffe0 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -71,7 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_SUFFIX) .help( "append SUFF to TEMPLATE; SUFF must not contain a path separator. \ - This option is implied if TEMPLATE does not end with X.", + This option is implied if TEMPLATE does not end with X.", ) .value_name("SUFF"), ) @@ -81,15 +81,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_TMPDIR) .help( "interpret TEMPLATE relative to DIR; if DIR is not specified, use \ - $TMPDIR if set, else /tmp. With this option, TEMPLATE must not \ - be an absolute name; unlike with -t, TEMPLATE may contain \ - slashes, but mktemp creates only the final component", + $TMPDIR if set, else /tmp. With this option, TEMPLATE must not \ + be an absolute name; unlike with -t, TEMPLATE may contain \ + slashes, but mktemp creates only the final component", ) .value_name("DIR"), ) .arg(Arg::with_name(OPT_T).short(OPT_T).help( "Generate a template (using the supplied prefix and TMPDIR if set) \ - to create a filename template [deprecated]", + to create a filename template [deprecated]", )) .arg( Arg::with_name(ARG_TEMPLATE) diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index acd9378b2..1f4bfed68 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_more" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "more ~ (uutils) input perusal filter" @@ -15,7 +15,7 @@ edition = "2018" path = "src/more.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" } diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 524b0fbc4..59e7d0faa 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -10,9 +10,8 @@ #[macro_use] extern crate uucore; -use getopts::Options; use std::fs::File; -use std::io::{stdout, Read, Write}; +use std::io::{stdin, stdout, BufRead, BufReader, Read, Write}; #[cfg(all(unix, not(target_os = "fuchsia")))] extern crate nix; @@ -24,70 +23,52 @@ extern crate redox_termios; #[cfg(target_os = "redox")] extern crate syscall; -#[derive(Clone, Eq, PartialEq)] -pub enum Mode { - More, - Help, - Version, +use clap::{App, Arg, ArgMatches}; + +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "A file perusal filter for CRT viewing."; + +mod options { + pub const FILE: &str = "file"; } -static NAME: &str = "more"; -static VERSION: &str = env!("CARGO_PKG_VERSION"); +fn get_usage() -> String { + format!("{} [options] ...", executable!()) +} pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let usage = get_usage(); - let mut opts = Options::new(); + let matches = App::new(executable!()) + .version(VERSION) + .usage(usage.as_str()) + .about(ABOUT) + .arg( + Arg::with_name(options::FILE) + .number_of_values(1) + .multiple(true), + ) + .get_matches_from(args); // FixME: fail without panic for now; but `more` should work with no arguments (ie, for piped input) - if args.len() < 2 { - println!("{}: incorrect usage", args[0]); + if let None | Some("-") = matches.value_of(options::FILE) { + show_usage_error!("Reading from stdin isn't supported yet."); return 1; } - 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(e) => { - show_error!("{}", e); - panic!() + if let Some(x) = matches.value_of(options::FILE) { + let path = std::path::Path::new(x); + if path.is_dir() { + show_usage_error!("'{}' is a directory.", x); + return 1; } - }; - let usage = opts.usage("more TARGET."); - let mode = if matches.opt_present("version") { - Mode::Version - } else if matches.opt_present("help") { - Mode::Help - } else { - Mode::More - }; - - match mode { - Mode::More => more(matches), - Mode::Help => help(&usage), - Mode::Version => version(), } + more(matches); + 0 } -fn version() { - println!("{} {}", NAME, VERSION); -} - -fn help(usage: &str) { - let msg = format!( - "{0} {1}\n\n\ - Usage: {0} TARGET\n \ - \n\ - {2}", - NAME, VERSION, usage - ); - println!("{}", msg); -} - #[cfg(all(unix, not(target_os = "fuchsia")))] fn setup_term() -> termios::Termios { let mut term = termios::tcgetattr(0).unwrap(); @@ -138,9 +119,11 @@ fn reset_term(term: &mut redox_termios::Termios) { let _ = syscall::close(fd); } -fn more(matches: getopts::Matches) { - let files = matches.free; - let mut f = File::open(files.first().unwrap()).unwrap(); +fn more(matches: ArgMatches) { + let mut f: Box = match matches.value_of(options::FILE) { + None | Some("-") => Box::new(BufReader::new(stdin())), + Some(filename) => Box::new(BufReader::new(File::open(filename).unwrap())), + }; let mut buffer = [0; 1024]; let mut term = setup_term(); diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index 1a4f90801..8f1e7b9ee 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mv" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "mv ~ (uutils) move (rename) SOURCE to DESTINATION" @@ -17,7 +17,7 @@ path = "src/mv.rs" [dependencies] clap = "2.33" fs_extra = "1.1.0" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index c851daa5a..279e79ae3 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nice" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "nice ~ (uutils) run PROGRAM with modified scheduling priority" @@ -18,7 +18,7 @@ path = "src/nice.rs" clap = "2.33" libc = "0.2.42" nix = { version="<=0.13" } -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index d5cddbde2..a51a2555e 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nl" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "nl ~ (uutils) display input with added line numbers" @@ -15,13 +15,13 @@ edition = "2018" path = "src/nl.rs" [dependencies] +clap = "2.33.3" aho-corasick = "0.7.3" -getopts = "0.2.18" libc = "0.2.42" memchr = "2.2.0" regex = "1.0.1" regex-syntax = "0.6.7" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index 9b98129f1..e31477c62 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -1,5 +1,7 @@ // spell-checker:ignore (ToDO) conv +use crate::options; + // parse_style parses a style string into a NumberingStyle. fn parse_style(chars: &[char]) -> Result { if chars.len() == 1 && chars[0] == 'a' { @@ -23,19 +25,19 @@ fn parse_style(chars: &[char]) -> Result { // parse_options loads the options into the settings, returning an array of // error messages. -pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) -> Vec { +pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> Vec { // This vector holds error messages encountered. let mut errs: Vec = vec![]; - settings.renumber = !opts.opt_present("p"); - match opts.opt_str("s") { + settings.renumber = !opts.is_present(options::NO_RENUMBER); + match opts.value_of(options::NUMER_SEPARATOR) { None => {} Some(val) => { - settings.number_separator = val; + settings.number_separator = val.to_owned(); } } - match opts.opt_str("n") { + match opts.value_of(options::NUMBER_FORMAT) { None => {} - Some(val) => match val.as_ref() { + Some(val) => match val { "ln" => { settings.number_format = crate::NumberFormat::Left; } @@ -50,7 +52,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) -> } }, } - match opts.opt_str("b") { + match opts.value_of(options::BODY_NUMBERING) { None => {} Some(val) => { let chars: Vec = val.chars().collect(); @@ -64,7 +66,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) -> } } } - match opts.opt_str("f") { + match opts.value_of(options::FOOTER_NUMBERING) { None => {} Some(val) => { let chars: Vec = val.chars().collect(); @@ -78,7 +80,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) -> } } } - match opts.opt_str("h") { + match opts.value_of(options::HEADER_NUMBERING) { None => {} Some(val) => { let chars: Vec = val.chars().collect(); @@ -92,7 +94,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) -> } } } - match opts.opt_str("i") { + match opts.value_of(options::LINE_INCREMENT) { None => {} Some(val) => { let conv: Option = val.parse().ok(); @@ -104,7 +106,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) -> } } } - match opts.opt_str("w") { + match opts.value_of(options::NUMBER_WIDTH) { None => {} Some(val) => { let conv: Option = val.parse().ok(); @@ -116,7 +118,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) -> } } } - match opts.opt_str("v") { + match opts.value_of(options::STARTING_LINE_NUMER) { None => {} Some(val) => { let conv: Option = val.parse().ok(); @@ -128,7 +130,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) -> } } } - match opts.opt_str("l") { + match opts.value_of(options::JOIN_BLANK_LINES) { None => {} Some(val) => { let conv: Option = val.parse().ok(); diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 47b6c3ae9..3b5b5a2e8 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -11,6 +11,7 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::iter::repeat; @@ -67,78 +68,106 @@ enum NumberFormat { RightZero, } +pub mod options { + pub const FILE: &str = "file"; + pub const BODY_NUMBERING: &str = "body-numbering"; + pub const SECTION_DELIMITER: &str = "section-delimiter"; + pub const FOOTER_NUMBERING: &str = "footer-numbering"; + pub const HEADER_NUMBERING: &str = "header-numbering"; + pub const LINE_INCREMENT: &str = "line-increment"; + pub const JOIN_BLANK_LINES: &str = "join-blank-lines"; + pub const NUMBER_FORMAT: &str = "number-format"; + pub const NO_RENUMBER: &str = "no-renumber"; + pub const NUMER_SEPARATOR: &str = "number-separator"; + pub const STARTING_LINE_NUMER: &str = "starting-line-number"; + pub const NUMBER_WIDTH: &str = "number-width"; +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let mut opts = getopts::Options::new(); - - opts.optopt( - "b", - "body-numbering", - "use STYLE for numbering body lines", - "STYLE", - ); - opts.optopt( - "d", - "section-delimiter", - "use CC for separating logical pages", - "CC", - ); - opts.optopt( - "f", - "footer-numbering", - "use STYLE for numbering footer lines", - "STYLE", - ); - opts.optopt( - "h", - "header-numbering", - "use STYLE for numbering header lines", - "STYLE", - ); - opts.optopt( - "i", - "line-increment", - "line number increment at each line", - "", - ); - opts.optopt( - "l", - "join-blank-lines", - "group of NUMBER empty lines counted as one", - "NUMBER", - ); - opts.optopt( - "n", - "number-format", - "insert line numbers according to FORMAT", - "FORMAT", - ); - opts.optflag( - "p", - "no-renumber", - "do not reset line numbers at logical pages", - ); - opts.optopt( - "s", - "number-separator", - "add STRING after (possible) line number", - "STRING", - ); - opts.optopt( - "v", - "starting-line-number", - "first line number on each logical page", - "NUMBER", - ); - opts.optopt( - "w", - "number-width", - "use NUMBER columns for line numbers", - "NUMBER", - ); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("V", "version", "version"); + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .usage(USAGE) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .arg( + Arg::with_name(options::BODY_NUMBERING) + .short("b") + .long(options::BODY_NUMBERING) + .help("use STYLE for numbering body lines") + .value_name("SYNTAX"), + ) + .arg( + Arg::with_name(options::SECTION_DELIMITER) + .short("d") + .long(options::SECTION_DELIMITER) + .help("use CC for separating logical pages") + .value_name("CC"), + ) + .arg( + Arg::with_name(options::FOOTER_NUMBERING) + .short("f") + .long(options::FOOTER_NUMBERING) + .help("use STYLE for numbering footer lines") + .value_name("STYLE"), + ) + .arg( + Arg::with_name(options::HEADER_NUMBERING) + .short("h") + .long(options::HEADER_NUMBERING) + .help("use STYLE for numbering header lines") + .value_name("STYLE"), + ) + .arg( + Arg::with_name(options::LINE_INCREMENT) + .short("i") + .long(options::LINE_INCREMENT) + .help("line number increment at each line") + .value_name("NUMBER"), + ) + .arg( + Arg::with_name(options::JOIN_BLANK_LINES) + .short("l") + .long(options::JOIN_BLANK_LINES) + .help("group of NUMBER empty lines counted as one") + .value_name("NUMBER"), + ) + .arg( + Arg::with_name(options::NUMBER_FORMAT) + .short("n") + .long(options::NUMBER_FORMAT) + .help("insert line numbers according to FORMAT") + .value_name("FORMAT"), + ) + .arg( + Arg::with_name(options::NO_RENUMBER) + .short("p") + .long(options::NO_RENUMBER) + .help("do not reset line numbers at logical pages"), + ) + .arg( + Arg::with_name(options::NUMER_SEPARATOR) + .short("s") + .long(options::NUMER_SEPARATOR) + .help("add STRING after (possible) line number") + .value_name("STRING"), + ) + .arg( + Arg::with_name(options::STARTING_LINE_NUMER) + .short("v") + .long(options::STARTING_LINE_NUMER) + .help("first line number on each logical page") + .value_name("NUMBER"), + ) + .arg( + Arg::with_name(options::NUMBER_WIDTH) + .short("w") + .long(options::NUMBER_WIDTH) + .help("use NUMBER columns for line numbers") + .value_name("NUMBER"), + ) + .get_matches_from(args); // A mutable settings object, initialized with the defaults. let mut settings = Settings { @@ -155,27 +184,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { number_separator: String::from("\t"), }; - let given_options = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => { - show_error!("{}", f); - print_usage(&opts); - return 1; - } - }; - - if given_options.opt_present("help") { - print_usage(&opts); - return 0; - } - if given_options.opt_present("version") { - version(); - return 0; - } - // Update the settings from the command line options, and terminate the // program if some options could not successfully be parsed. - let parse_errors = helper::parse_options(&mut settings, &given_options); + let parse_errors = helper::parse_options(&mut settings, &matches); if !parse_errors.is_empty() { show_error!("Invalid arguments supplied."); for message in &parse_errors { @@ -184,8 +195,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 1; } - let files = given_options.free; - let mut read_stdin = files.is_empty(); + let mut read_stdin = false; + let files: Vec = match matches.values_of(options::FILE) { + Some(v) => v.clone().map(|v| v.to_owned()).collect(), + None => vec!["-".to_owned()], + }; for file in &files { if file == "-" { @@ -370,11 +384,3 @@ fn pass_none(_: &str, _: ®ex::Regex) -> bool { fn pass_all(_: &str, _: ®ex::Regex) -> bool { true } - -fn print_usage(opts: &getopts::Options) { - println!("{}", opts.usage(USAGE)); -} - -fn version() { - println!("{} {}", NAME, VERSION); -} diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index e9b6f8bd4..5bbbd9dff 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nohup" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "nohup ~ (uutils) run COMMAND, ignoring hangup signals" @@ -17,7 +17,7 @@ path = "src/nohup.rs" [dependencies] clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/nproc/Cargo.toml b/src/uu/nproc/Cargo.toml index 98f9a4afa..be9d8f2e3 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nproc" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "nproc ~ (uutils) display the number of processing units available" @@ -18,7 +18,7 @@ path = "src/nproc.rs" libc = "0.2.42" num_cpus = "1.10" clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index b8e54656c..ac5266d68 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_numfmt" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "numfmt ~ (uutils) reformat NUMBER" @@ -16,7 +16,7 @@ path = "src/numfmt.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 29c422a89..e9a476956 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -187,9 +187,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::PADDING) .help( "pad the output to N characters; positive N will \ - right-align; negative N will left-align; padding is \ - ignored if the output is wider than N; the default is \ - to automatically pad if a whitespace is found", + right-align; negative N will left-align; padding is \ + ignored if the output is wider than N; the default is \ + to automatically pad if a whitespace is found", ) .value_name("N"), ) @@ -198,7 +198,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::HEADER) .help( "print (without converting) the first N header lines; \ - N defaults to 1 if not specified", + N defaults to 1 if not specified", ) .value_name("N") .default_value(options::HEADER_DEFAULT) diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index e4db9faf0..6f9a75318 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_od" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "od ~ (uutils) display formatted representation of input" @@ -19,7 +19,7 @@ byteorder = "1.3.2" clap = "2.33" half = "1.6" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/od/src/inputdecoder.rs b/src/uu/od/src/inputdecoder.rs index 3b36c28fb..f6ba59885 100644 --- a/src/uu/od/src/inputdecoder.rs +++ b/src/uu/od/src/inputdecoder.rs @@ -166,39 +166,30 @@ mod tests { let mut input = PeekReader::new(Cursor::new(&data)); let mut sut = InputDecoder::new(&mut input, 8, 2, ByteOrder::Little); - match sut.peek_read() { - Ok(mut mem) => { - assert_eq!(8, mem.length()); + // Peek normal length + let mut mem = sut.peek_read().unwrap(); - assert_eq!(-2.0, mem.read_float(0, 8)); - assert_eq!(-2.0, mem.read_float(4, 4)); - assert_eq!(0xc000000000000000, mem.read_uint(0, 8)); - assert_eq!(0xc0000000, mem.read_uint(4, 4)); - assert_eq!(0xc000, mem.read_uint(6, 2)); - assert_eq!(0xc0, mem.read_uint(7, 1)); - assert_eq!(&[0, 0xc0], mem.get_buffer(6)); - assert_eq!(&[0, 0xc0, 0xff, 0xff], mem.get_full_buffer(6)); + assert_eq!(8, mem.length()); - let mut copy: Vec = Vec::new(); - mem.clone_buffer(&mut copy); - assert_eq!(vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0], copy); + assert_eq!(-2.0, mem.read_float(0, 8)); + assert_eq!(-2.0, mem.read_float(4, 4)); + assert_eq!(0xc000000000000000, mem.read_uint(0, 8)); + assert_eq!(0xc0000000, mem.read_uint(4, 4)); + assert_eq!(0xc000, mem.read_uint(6, 2)); + assert_eq!(0xc0, mem.read_uint(7, 1)); + assert_eq!(&[0, 0xc0], mem.get_buffer(6)); + assert_eq!(&[0, 0xc0, 0xff, 0xff], mem.get_full_buffer(6)); - mem.zero_out_buffer(7, 8); - assert_eq!(&[0, 0, 0xff, 0xff], mem.get_full_buffer(6)); - } - Err(e) => { - assert!(false, e); - } - } + let mut copy: Vec = Vec::new(); + mem.clone_buffer(&mut copy); + assert_eq!(vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0], copy); - match sut.peek_read() { - Ok(mem) => { - assert_eq!(2, mem.length()); - assert_eq!(0xffff, mem.read_uint(0, 2)); - } - Err(e) => { - assert!(false, e); - } - } + mem.zero_out_buffer(7, 8); + assert_eq!(&[0, 0, 0xff, 0xff], mem.get_full_buffer(6)); + + // Peek tail + let mem = sut.peek_read().unwrap(); + assert_eq!(2, mem.length()); + assert_eq!(0xffff, mem.read_uint(0, 2)); } } diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index 3787ed270..4e9971368 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_paste" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "paste ~ (uutils) merge lines from inputs" @@ -16,7 +16,7 @@ path = "src/paste.rs" [dependencies] clap = "2.33.3" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index c1d0d0dfa..8c4e61d2b 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pathchk" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "pathchk ~ (uutils) diagnose invalid or non-portable PATHNAME" @@ -17,7 +17,7 @@ path = "src/pathchk.rs" [dependencies] clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index 1618eab57..3f4a75241 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pinky" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "pinky ~ (uutils) display user information" @@ -15,7 +15,7 @@ edition = "2018" path = "src/pinky.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["utmpx", "entries"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx", "entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index 5266c1e03..be95b8157 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printenv" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "printenv ~ (uutils) display value of environment VAR" @@ -16,7 +16,7 @@ path = "src/printenv.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index c6737d178..bc77d31be 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printf" -version = "0.0.4" +version = "0.0.6" authors = [ "Nathan Ross", "uutils developers", @@ -19,7 +19,7 @@ path = "src/printf.rs" [dependencies] itertools = "0.8.0" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs b/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs index bf21bf8f9..10e58cc32 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs @@ -59,7 +59,7 @@ fn get_primitive_hex( // assign 0 let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos { Some(pos) => (&str_in[..pos], &str_in[pos + 1..]), - None => (&str_in[..], "0"), + None => (str_in, "0"), }; if first_segment_raw.is_empty() { first_segment_raw = "0"; diff --git a/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs b/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs index 65fe5b6ea..a107078ae 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/float_common.rs @@ -218,7 +218,7 @@ pub fn get_primitive_dec( // assign 0 let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos { Some(pos) => (&str_in[..pos], &str_in[pos + 1..]), - None => (&str_in[..], "0"), + None => (str_in, "0"), }; if first_segment_raw.is_empty() { first_segment_raw = "0"; diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index d1e0267b6..eb4413cbd 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ptx" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "ptx ~ (uutils) display a permuted index of input" @@ -17,12 +17,11 @@ path = "src/ptx.rs" [dependencies] clap = "2.33" aho-corasick = "0.7.3" -getopts = "0.2.18" libc = "0.2.42" memchr = "2.2.0" regex = "1.0.1" regex-syntax = "0.6.7" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 989ab52ef..38327e4e3 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -169,9 +169,9 @@ fn get_config(matches: &clap::ArgMatches) -> Config { .expect(err_msg) .to_string(); } - if matches.is_present(options::IGNORE_CASE) { + if matches.is_present(options::FLAG_TRUNCATION) { config.trunc_str = matches - .value_of(options::IGNORE_CASE) + .value_of(options::FLAG_TRUNCATION) .expect(err_msg) .to_string(); } @@ -195,8 +195,16 @@ fn get_config(matches: &clap::ArgMatches) -> Config { config } -fn read_input(input_files: &[String], config: &Config) -> HashMap, usize)> { - let mut file_map: HashMap, usize)> = HashMap::new(); +struct FileContent { + lines: Vec, + chars_lines: Vec>, + offset: usize, +} + +type FileMap = HashMap; + +fn read_input(input_files: &[String], config: &Config) -> FileMap { + let mut file_map: FileMap = HashMap::new(); let mut files = Vec::new(); if input_files.is_empty() { files.push("-"); @@ -207,7 +215,7 @@ fn read_input(input_files: &[String], config: &Config) -> HashMap> = BufReader::new(if filename == "-" { Box::new(stdin()) @@ -216,25 +224,33 @@ fn read_input(input_files: &[String], config: &Config) -> HashMap = reader.lines().map(|x| crash_if_err!(1, x)).collect(); + + // Indexing UTF-8 string requires walking from the beginning, which can hurts performance badly when the line is long. + // Since we will be jumping around the line a lot, we dump the content into a Vec, which can be indexed in constant time. + let chars_lines: Vec> = lines.iter().map(|x| x.chars().collect()).collect(); let size = lines.len(); - file_map.insert(filename.to_owned(), (lines, lines_so_far)); - lines_so_far += size + file_map.insert( + filename.to_owned(), + FileContent { + lines, + chars_lines, + offset, + }, + ); + offset += size } file_map } -fn create_word_set( - config: &Config, - filter: &WordFilter, - file_map: &HashMap, usize)>, -) -> BTreeSet { +/// Go through every lines in the input files and record each match occurance as a `WordRef`. +fn create_word_set(config: &Config, filter: &WordFilter, file_map: &FileMap) -> BTreeSet { let reg = Regex::new(&filter.word_regex).unwrap(); let ref_reg = Regex::new(&config.context_regex).unwrap(); let mut word_set: BTreeSet = BTreeSet::new(); for (file, lines) in file_map.iter() { let mut count: usize = 0; - let offs = lines.1; - for line in &lines.0 { + let offs = lines.offset; + for line in &lines.lines { // if -r, exclude reference from word set let (ref_beg, ref_end) = match ref_reg.find(line) { Some(x) => (x.start(), x.end()), @@ -271,12 +287,11 @@ fn create_word_set( word_set } -fn get_reference(config: &Config, word_ref: &WordRef, line: &str) -> String { +fn get_reference(config: &Config, word_ref: &WordRef, line: &str, context_reg: &Regex) -> String { if config.auto_ref { format!("{}:{}", word_ref.filename, word_ref.local_line_nr + 1) } else if config.input_ref { - let reg = Regex::new(&config.context_regex).unwrap(); - let (beg, end) = match reg.find(line) { + let (beg, end) = match context_reg.find(line) { Some(x) => (x.start(), x.end()), None => (0, 0), }; @@ -329,57 +344,107 @@ fn trim_idx(s: &[char], beg: usize, end: usize) -> (usize, usize) { } fn get_output_chunks( - all_before: &str, + all_before: &[char], keyword: &str, - all_after: &str, + all_after: &[char], config: &Config, ) -> (String, String, String, String) { - assert_eq!(all_before.trim(), all_before); - assert_eq!(keyword.trim(), keyword); - assert_eq!(all_after.trim(), all_after); - let mut head = String::new(); - let mut before = String::new(); - let mut after = String::new(); - let mut tail = String::new(); - - let half_line_size = cmp::max( - (config.line_width / 2) as isize - (2 * config.trunc_str.len()) as isize, + // Chunk size logics are mostly copied from the GNU ptx source. + // https://github.com/MaiZure/coreutils-8.3/blob/master/src/ptx.c#L1234 + let half_line_size = (config.line_width / 2) as usize; + let max_before_size = cmp::max(half_line_size as isize - config.gap_size as isize, 0) as usize; + let max_after_size = cmp::max( + half_line_size as isize + - (2 * config.trunc_str.len()) as isize + - keyword.len() as isize + - 1, 0, ) as usize; - let max_after_size = cmp::max(half_line_size as isize - keyword.len() as isize - 1, 0) as usize; - let max_before_size = half_line_size; - let all_before_vec: Vec = all_before.chars().collect(); - let all_after_vec: Vec = all_after.chars().collect(); - // get before - let mut bb_tmp = cmp::max(all_before.len() as isize - max_before_size as isize, 0) as usize; - bb_tmp = trim_broken_word_left(&all_before_vec, bb_tmp, all_before.len()); - let (before_beg, before_end) = trim_idx(&all_before_vec, bb_tmp, all_before.len()); - before.push_str(&all_before[before_beg..before_end]); + // Allocate plenty space for all the chunks. + let mut head = String::with_capacity(half_line_size); + let mut before = String::with_capacity(half_line_size); + let mut after = String::with_capacity(half_line_size); + let mut tail = String::with_capacity(half_line_size); + + // the before chunk + + // trim whitespace away from all_before to get the index where the before chunk should end. + let (_, before_end) = trim_idx(all_before, 0, all_before.len()); + + // the minimum possible begin index of the before_chunk is the end index minus the length. + let before_beg = cmp::max(before_end as isize - max_before_size as isize, 0) as usize; + // in case that falls in the middle of a word, trim away the word. + let before_beg = trim_broken_word_left(all_before, before_beg, before_end); + + // trim away white space. + let (before_beg, before_end) = trim_idx(all_before, before_beg, before_end); + + // and get the string. + let before_str: String = all_before[before_beg..before_end].iter().collect(); + before.push_str(&before_str); assert!(max_before_size >= before.len()); - // get after - let mut ae_tmp = cmp::min(max_after_size, all_after.len()); - ae_tmp = trim_broken_word_right(&all_after_vec, 0, ae_tmp); - let (after_beg, after_end) = trim_idx(&all_after_vec, 0, ae_tmp); - after.push_str(&all_after[after_beg..after_end]); + // the after chunk + + // must be no longer than the minimum between the max size and the total available string. + let after_end = cmp::min(max_after_size, all_after.len()); + // in case that falls in the middle of a word, trim away the word. + let after_end = trim_broken_word_right(all_after, 0, after_end); + + // trim away white space. + let (_, after_end) = trim_idx(all_after, 0, after_end); + + // and get the string + let after_str: String = all_after[0..after_end].iter().collect(); + after.push_str(&after_str); assert!(max_after_size >= after.len()); - // get tail - let max_tail_size = max_before_size - before.len(); - let (tb, _) = trim_idx(&all_after_vec, after_end, all_after.len()); - let mut te_tmp = cmp::min(tb + max_tail_size, all_after.len()); - te_tmp = trim_broken_word_right(&all_after_vec, tb, te_tmp); - let (tail_beg, tail_end) = trim_idx(&all_after_vec, tb, te_tmp); - tail.push_str(&all_after[tail_beg..tail_end]); + // the tail chunk - // get head - let max_head_size = max_after_size - after.len(); - let (_, he) = trim_idx(&all_before_vec, 0, before_beg); - let mut hb_tmp = cmp::max(he as isize - max_head_size as isize, 0) as usize; - hb_tmp = trim_broken_word_left(&all_before_vec, hb_tmp, he); - let (head_beg, head_end) = trim_idx(&all_before_vec, hb_tmp, he); - head.push_str(&all_before[head_beg..head_end]); + // max size of the tail chunk = max size of left half - space taken by before chunk - gap size. + let max_tail_size = cmp::max( + max_before_size as isize - before.len() as isize - config.gap_size as isize, + 0, + ) as usize; + + // the tail chunk takes text starting from where the after chunk ends (with whitespaces trimmed). + let (tail_beg, _) = trim_idx(all_after, after_end, all_after.len()); + + // end = begin + max length + let tail_end = cmp::min(all_after.len(), tail_beg + max_tail_size) as usize; + // in case that falls in the middle of a word, trim away the word. + let tail_end = trim_broken_word_right(all_after, tail_beg, tail_end); + + // trim away whitespace again. + let (tail_beg, tail_end) = trim_idx(all_after, tail_beg, tail_end); + + // and get the string + let tail_str: String = all_after[tail_beg..tail_end].iter().collect(); + tail.push_str(&tail_str); + + // the head chunk + + // max size of the head chunk = max size of right half - space taken by after chunk - gap size. + let max_head_size = cmp::max( + max_after_size as isize - after.len() as isize - config.gap_size as isize, + 0, + ) as usize; + + // the head chunk takes text from before the before chunk + let (_, head_end) = trim_idx(all_before, 0, before_beg); + + // begin = end - max length + let head_beg = cmp::max(head_end as isize - max_head_size as isize, 0) as usize; + // in case that falls in the middle of a word, trim away the word. + let head_beg = trim_broken_word_left(all_before, head_beg, head_end); + + // trim away white space again. + let (head_beg, head_end) = trim_idx(all_before, head_beg, head_end); + + // and get the string. + let head_str: String = all_before[head_beg..head_end].iter().collect(); + head.push_str(&head_str); // put right context truncation string if needed if after_end != all_after.len() && tail_beg == tail_end { @@ -395,11 +460,6 @@ fn get_output_chunks( head = format!("{}{}", config.trunc_str, head); } - // add space before "after" if needed - if !after.is_empty() { - after = format!(" {}", after); - } - (tail, before, after, head) } @@ -412,70 +472,95 @@ fn tex_mapper(x: char) -> String { } } -fn adjust_tex_str(context: &str) -> String { - let ws_reg = Regex::new(r"[\t\n\v\f\r ]").unwrap(); - let mut fix: String = ws_reg.replace_all(context, " ").trim().to_owned(); - let mapped_chunks: Vec = fix.chars().map(tex_mapper).collect(); - fix = mapped_chunks.join(""); - fix +/// Escape special characters for TeX. +fn format_tex_field(s: &str) -> String { + let mapped_chunks: Vec = s.chars().map(tex_mapper).collect(); + mapped_chunks.join("") } -fn format_tex_line(config: &Config, word_ref: &WordRef, line: &str, reference: &str) -> String { +fn format_tex_line( + config: &Config, + word_ref: &WordRef, + line: &str, + chars_line: &[char], + reference: &str, +) -> String { let mut output = String::new(); output.push_str(&format!("\\{} ", config.macro_name)); let all_before = if config.input_ref { let before = &line[0..word_ref.position]; - adjust_tex_str(before.trim().trim_start_matches(reference)) + let before_start_trimoff = + word_ref.position - before.trim_start_matches(reference).trim_start().len(); + let before_end_index = before.len(); + &chars_line[before_start_trimoff..cmp::max(before_end_index, before_start_trimoff)] } else { - adjust_tex_str(&line[0..word_ref.position]) + let before_chars_trim_idx = (0, word_ref.position); + &chars_line[before_chars_trim_idx.0..before_chars_trim_idx.1] }; - let keyword = adjust_tex_str(&line[word_ref.position..word_ref.position_end]); - let all_after = adjust_tex_str(&line[word_ref.position_end..line.len()]); + let keyword = &line[word_ref.position..word_ref.position_end]; + let after_chars_trim_idx = (word_ref.position_end, chars_line.len()); + let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1]; let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config); output.push_str(&format!( "{5}{0}{6}{5}{1}{6}{5}{2}{6}{5}{3}{6}{5}{4}{6}", - tail, before, keyword, after, head, "{", "}" + format_tex_field(&tail), + format_tex_field(&before), + format_tex_field(keyword), + format_tex_field(&after), + format_tex_field(&head), + "{", + "}" )); if config.auto_ref || config.input_ref { - output.push_str(&format!("{}{}{}", "{", adjust_tex_str(&reference), "}")); + output.push_str(&format!("{}{}{}", "{", format_tex_field(&reference), "}")); } output } -fn adjust_roff_str(context: &str) -> String { - let ws_reg = Regex::new(r"[\t\n\v\f\r]").unwrap(); - ws_reg - .replace_all(context, " ") - .replace("\"", "\"\"") - .trim() - .to_owned() +fn format_roff_field(s: &str) -> String { + s.replace("\"", "\"\"") } -fn format_roff_line(config: &Config, word_ref: &WordRef, line: &str, reference: &str) -> String { +fn format_roff_line( + config: &Config, + word_ref: &WordRef, + line: &str, + chars_line: &[char], + reference: &str, +) -> String { let mut output = String::new(); output.push_str(&format!(".{}", config.macro_name)); let all_before = if config.input_ref { let before = &line[0..word_ref.position]; - adjust_roff_str(before.trim().trim_start_matches(reference)) + let before_start_trimoff = + word_ref.position - before.trim_start_matches(reference).trim_start().len(); + let before_end_index = before.len(); + &chars_line[before_start_trimoff..cmp::max(before_end_index, before_start_trimoff)] } else { - adjust_roff_str(&line[0..word_ref.position]) + let before_chars_trim_idx = (0, word_ref.position); + &chars_line[before_chars_trim_idx.0..before_chars_trim_idx.1] }; - let keyword = adjust_roff_str(&line[word_ref.position..word_ref.position_end]); - let all_after = adjust_roff_str(&line[word_ref.position_end..line.len()]); + let keyword = &line[word_ref.position..word_ref.position_end]; + let after_chars_trim_idx = (word_ref.position_end, chars_line.len()); + let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1]; let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config); output.push_str(&format!( " \"{}\" \"{}\" \"{}{}\" \"{}\"", - tail, before, keyword, after, head + format_roff_field(&tail), + format_roff_field(&before), + format_roff_field(keyword), + format_roff_field(&after), + format_roff_field(&head) )); if config.auto_ref || config.input_ref { - output.push_str(&format!(" \"{}\"", adjust_roff_str(&reference))); + output.push_str(&format!(" \"{}\"", format_roff_field(&reference))); } output } fn write_traditional_output( config: &Config, - file_map: &HashMap, usize)>, + file_map: &FileMap, words: &BTreeSet, output_filename: &str, ) { @@ -485,19 +570,39 @@ fn write_traditional_output( let file = crash_if_err!(1, File::create(output_filename)); Box::new(file) }); + + let context_reg = Regex::new(&config.context_regex).unwrap(); + for word_ref in words.iter() { - let file_map_value: &(Vec, usize) = file_map + let file_map_value: &FileContent = file_map .get(&(word_ref.filename)) .expect("Missing file in file map"); - let (ref lines, _) = *(file_map_value); - let reference = get_reference(config, word_ref, &lines[word_ref.local_line_nr]); + let FileContent { + ref lines, + ref chars_lines, + offset: _, + } = *(file_map_value); + let reference = get_reference( + config, + word_ref, + &lines[word_ref.local_line_nr], + &context_reg, + ); let output_line: String = match config.format { - OutFormat::Tex => { - format_tex_line(config, word_ref, &lines[word_ref.local_line_nr], &reference) - } - OutFormat::Roff => { - format_roff_line(config, word_ref, &lines[word_ref.local_line_nr], &reference) - } + OutFormat::Tex => format_tex_line( + config, + word_ref, + &lines[word_ref.local_line_nr], + &chars_lines[word_ref.local_line_nr], + &reference, + ), + OutFormat::Roff => format_roff_line( + config, + word_ref, + &lines[word_ref.local_line_nr], + &chars_lines[word_ref.local_line_nr], + &reference, + ), OutFormat::Dumb => crash!(1, "There is no dumb format with GNU extensions disabled"), }; crash_if_err!(1, writeln!(writer, "{}", output_line)); diff --git a/src/uu/pwd/Cargo.toml b/src/uu/pwd/Cargo.toml index e074dc618..f4350d54c 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pwd" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "pwd ~ (uutils) display current working directory" @@ -16,7 +16,7 @@ path = "src/pwd.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index 8e31f66f1..6e4be4dd8 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_readlink" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "readlink ~ (uutils) display resolved path of PATHNAME" @@ -17,7 +17,7 @@ path = "src/readlink.rs" [dependencies] clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 8bc9a2aeb..727c2cce5 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -46,7 +46,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_CANONICALIZE) .help( "canonicalize by following every symlink in every component of the \ - given name recursively; all but the last component must exist", + given name recursively; all but the last component must exist", ), ) .arg( @@ -55,7 +55,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long("canonicalize-existing") .help( "canonicalize by following every symlink in every component of the \ - given name recursively, all components must exist", + given name recursively, all components must exist", ), ) .arg( @@ -64,7 +64,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_CANONICALIZE_MISSING) .help( "canonicalize by following every symlink in every component of the \ - given name recursively, without requirements on components existence", + given name recursively, without requirements on components existence", ), ) .arg( diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index 6f03086ba..327a875f8 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_realpath" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "realpath ~ (uutils) display resolved absolute path of PATHNAME" @@ -16,7 +16,7 @@ path = "src/realpath.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/relpath/Cargo.toml b/src/uu/relpath/Cargo.toml index 1dc296ec6..7a316c29c 100644 --- a/src/uu/relpath/Cargo.toml +++ b/src/uu/relpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_relpath" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "relpath ~ (uutils) display relative path of PATHNAME_TO from PATHNAME_FROM" @@ -15,8 +15,8 @@ edition = "2018" path = "src/relpath.rs" [dependencies] -getopts = "0.2.18" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] } +clap = "2.33.3" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index 4e165df1f..82779107a 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -10,62 +10,60 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::env; use std::path::{Path, PathBuf}; use uucore::fs::{canonicalize, CanonicalizeMode}; -static NAME: &str = "relpath"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Convert TO destination to the relative path from the FROM dir. +If FROM path is omitted, current working dir will be used."; + +mod options { + pub const DIR: &str = "DIR"; + pub const TO: &str = "TO"; + pub const FROM: &str = "FROM"; +} + +fn get_usage() -> String { + format!("{} [-d DIR] TO [FROM]", executable!()) +} 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[..]) + .arg( + Arg::with_name(options::DIR) + .short("d") + .takes_value(true) + .help("If any of FROM and TO is not subpath of DIR, output absolute path instead of relative"), + ) + .arg( + Arg::with_name(options::TO) + .required(true) + .takes_value(true), + ) + .arg( + Arg::with_name(options::FROM) + .takes_value(true), + ) + .get_matches_from(args); - opts.optflag("h", "help", "Show help and exit"); - opts.optflag("V", "version", "Show version and exit"); - opts.optopt( - "d", - "", - "If any of FROM and TO is not subpath of DIR, output absolute path instead of relative", - "DIR", - ); - - 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") { - version(); - return 0; - } - if matches.opt_present("h") { - show_usage(&opts); - return 0; - } - - if matches.free.is_empty() { - show_error!("Missing operand: TO"); - println!("Try `{} --help` for more information.", NAME); - return 1; - } - - let to = Path::new(&matches.free[0]); - let from = if matches.free.len() > 1 { - Path::new(&matches.free[1]).to_path_buf() - } else { - env::current_dir().unwrap() + let to = Path::new(matches.value_of(options::TO).unwrap()).to_path_buf(); // required + let from = match matches.value_of(options::FROM) { + Some(p) => Path::new(p).to_path_buf(), + None => env::current_dir().unwrap(), }; let absto = canonicalize(to, CanonicalizeMode::Normal).unwrap(); let absfrom = canonicalize(from, CanonicalizeMode::Normal).unwrap(); - if matches.opt_present("d") { - let base = Path::new(&matches.opt_str("d").unwrap()).to_path_buf(); + if matches.is_present(options::DIR) { + let base = Path::new(&matches.value_of(options::DIR).unwrap()).to_path_buf(); let absbase = canonicalize(base, CanonicalizeMode::Normal).unwrap(); if !absto.as_path().starts_with(absbase.as_path()) || !absfrom.as_path().starts_with(absbase.as_path()) @@ -99,24 +97,3 @@ pub fn uumain(args: impl uucore::Args) -> i32 { println!("{}", result.display()); 0 } - -fn version() { - println!("{} {}", NAME, VERSION) -} - -fn show_usage(opts: &getopts::Options) { - version(); - println!(); - println!("Usage:"); - println!(" {} [-d DIR] TO [FROM]", NAME); - println!(" {} -V|--version", NAME); - println!(" {} -h|--help", NAME); - println!(); - print!( - "{}", - opts.usage( - "Convert TO destination to the relative path from the FROM dir.\n\ - If FROM path is omitted, current working dir will be used." - ) - ); -} diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index b916412d8..9974111aa 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rm" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "rm ~ (uutils) remove PATHNAME" @@ -18,7 +18,8 @@ path = "src/rm.rs" clap = "2.33" walkdir = "2.2" remove_dir_all = "0.5.1" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } + +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 466a8d6c1..09671768b 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -16,7 +16,7 @@ use std::collections::VecDeque; use std::fs; use std::io::{stderr, stdin, BufRead, Write}; use std::ops::BitOr; -use std::path::Path; +use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; #[derive(Eq, PartialEq, Clone, Copy)] @@ -233,7 +233,7 @@ fn remove(files: Vec, options: Options) -> bool { // (e.g., permission), even rm -f should fail with // outputting the error, but there's no easy eay. if !options.force { - show_error!("no such file or directory '{}'", filename); + show_error!("cannot remove '{}': No such file or directory", filename); true } else { false @@ -251,7 +251,7 @@ fn handle_dir(path: &Path, options: &Options) -> bool { let is_root = path.has_root() && path.parent().is_none(); if options.recursive && (!is_root || !options.preserve_root) { - if options.interactive != InteractiveMode::Always { + if options.interactive != InteractiveMode::Always && !options.verbose { // we need the extra crate because apparently fs::remove_dir_all() does not function // correctly on Windows if let Err(e) = remove_dir_all(path) { @@ -289,7 +289,7 @@ fn handle_dir(path: &Path, options: &Options) -> bool { had_err = true; } else { show_error!( - "could not remove directory '{}' (did you mean to pass '-r' or '-R'?)", + "cannot remove '{}': Is a directory", // GNU's rm error message does not include help path.display() ); had_err = true; @@ -305,16 +305,34 @@ fn remove_dir(path: &Path, options: &Options) -> bool { true }; if response { - match fs::remove_dir(path) { - Ok(_) => { - if options.verbose { - println!("removed '{}'", path.display()); + if let Ok(mut read_dir) = fs::read_dir(path) { + if options.dir || options.recursive { + if read_dir.next().is_none() { + match fs::remove_dir(path) { + Ok(_) => { + if options.verbose { + println!("removed directory '{}'", normalize(path).display()); + } + } + Err(e) => { + show_error!("cannot remove '{}': {}", path.display(), e); + return true; + } + } + } else { + // directory can be read but is not empty + show_error!("cannot remove '{}': Directory not empty", path.display()); + return true; } - } - Err(e) => { - show_error!("removing '{}': {}", path.display(), e); + } else { + // called to remove a symlink_dir (windows) without "-r"/"-R" or "-d" + show_error!("cannot remove '{}': Is a directory", path.display()); return true; } + } else { + // GNU's rm shows this message if directory is empty but not readable + show_error!("cannot remove '{}': Directory not empty", path.display()); + return true; } } @@ -331,7 +349,7 @@ fn remove_file(path: &Path, options: &Options) -> bool { match fs::remove_file(path) { Ok(_) => { if options.verbose { - println!("removed '{}'", path.display()); + println!("removed '{}'", normalize(path).display()); } } Err(e) => { @@ -352,6 +370,14 @@ fn prompt_file(path: &Path, is_dir: bool) -> bool { } } +fn normalize(path: &Path) -> PathBuf { + // copied from https://github.com/rust-lang/cargo/blob/2e4cfc2b7d43328b207879228a2ca7d427d188bb/src/cargo/util/paths.rs#L65-L90 + // both projects are MIT https://github.com/rust-lang/cargo/blob/master/LICENSE-MIT + // for std impl progress see rfc https://github.com/rust-lang/rfcs/issues/2208 + // TODO: replace this once that lands + uucore::fs::normalize_path(path) +} + fn prompt(msg: &str) -> bool { let _ = stderr().write_all(msg.as_bytes()); let _ = stderr().flush(); diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index 9d6bb03cc..b6e04f71c 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rmdir" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "rmdir ~ (uutils) remove empty DIRECTORY" @@ -16,7 +16,7 @@ path = "src/rmdir.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index d7ee72b68..96c629c68 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_seq" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "seq ~ (uutils) display a sequence of numbers" @@ -16,7 +16,7 @@ path = "src/seq.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index 72e6119a8..dda68b45b 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shred" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "shred ~ (uutils) hide former FILE contents with repeated overwrites" @@ -15,12 +15,12 @@ edition = "2018" path = "src/shred.rs" [dependencies] +clap = "2.33" filetime = "0.2.1" -getopts = "0.2.18" libc = "0.2.42" rand = "0.5" time = "0.1.40" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 4946ed35d..d5f910297 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -8,6 +8,7 @@ // spell-checker:ignore (ToDO) NAMESET FILESIZE fstab coeff journaling writeback REiser journaled +use clap::{App, Arg}; use rand::{Rng, ThreadRng}; use std::cell::{Cell, RefCell}; use std::fs; @@ -212,138 +213,174 @@ impl<'a> BytesGenerator<'a> { } } +static ABOUT: &str = "Overwrite the specified FILE(s) repeatedly, in order to make it harder\n\ +for even very expensive hardware probing to recover the data. +"; + +fn get_usage() -> String { + format!("{} [OPTION]... FILE...", executable!()) +} + +static AFTER_HELP: &str = + "Delete FILE(s) if --remove (-u) is specified. The default is not to remove\n\ + the files because it is common to operate on device files like /dev/hda,\n\ + and those files usually should not be removed.\n\ + \n\ + CAUTION: Note that shred relies on a very important assumption:\n\ + that the file system overwrites data in place. This is the traditional\n\ + way to do things, but many modern file system designs do not satisfy this\n\ + assumption. The following are examples of file systems on which shred is\n\ + not effective, or is not guaranteed to be effective in all file system modes:\n\ + \n\ + * log-structured or journaled file systems, such as those supplied with\n\ + AIX and Solaris (and JFS, ReiserFS, XFS, Ext3, etc.)\n\ + \n\ + * file systems that write redundant data and carry on even if some writes\n\ + fail, such as RAID-based file systems\n\ + \n\ + * file systems that make snapshots, such as Network Appliance's NFS server\n\ + \n\ + * file systems that cache in temporary locations, such as NFS\n\ + version 3 clients\n\ + \n\ + * compressed file systems\n\ + \n\ + In the case of ext3 file systems, the above disclaimer applies\n\ + and shred is thus of limited effectiveness) only in data=journal mode,\n\ + which journals file data in addition to just metadata. In both the\n\ + data=ordered (default) and data=writeback modes, shred works as usual.\n\ + Ext3 journaling modes can be changed by adding the data=something option\n\ + to the mount options for a particular file system in the /etc/fstab file,\n\ + as documented in the mount man page (man mount).\n\ + \n\ + In addition, file system backups and remote mirrors may contain copies\n\ + of the file that cannot be removed, and that will allow a shredded file\n\ + to be recovered later.\n\ + "; + +pub mod options { + pub const FILE: &str = "file"; + pub const ITERATIONS: &str = "iterations"; + pub const SIZE: &str = "size"; + pub const REMOVE: &str = "remove"; + pub const VERBOSE: &str = "verbose"; + pub const EXACT: &str = "exact"; + pub const ZERO: &str = "zero"; +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let mut opts = getopts::Options::new(); + let usage = get_usage(); - // TODO: Add force option - opts.optopt( - "n", - "iterations", - "overwrite N times instead of the default (3)", - "N", - ); - opts.optopt( - "s", - "size", - "shred this many bytes (suffixes like K, M, G accepted)", - "FILESIZE", - ); - opts.optflag( - "u", - "remove", - "truncate and remove the file after overwriting; See below", - ); - opts.optflag("v", "verbose", "show progress"); - opts.optflag( - "x", - "exact", - "do not round file sizes up to the next full block; \ - this is the default for non-regular files", - ); - opts.optflag( - "z", - "zero", - "add a final overwrite with zeros to hide shredding", - ); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + let app = App::new(executable!()) + .version(VERSION_STR) + .about(ABOUT) + .after_help(AFTER_HELP) + .usage(&usage[..]) + .arg( + Arg::with_name(options::ITERATIONS) + .long(options::ITERATIONS) + .short("n") + .help("overwrite N times instead of the default (3)") + .value_name("NUMBER") + .default_value("3"), + ) + .arg( + Arg::with_name(options::SIZE) + .long(options::SIZE) + .short("s") + .takes_value(true) + .value_name("N") + .help("shred this many bytes (suffixes like K, M, G accepted)"), + ) + .arg( + Arg::with_name(options::REMOVE) + .short("u") + .long(options::REMOVE) + .help("truncate and remove file after overwriting; See below"), + ) + .arg( + Arg::with_name(options::VERBOSE) + .long(options::VERBOSE) + .short("v") + .help("show progress"), + ) + .arg( + Arg::with_name(options::EXACT) + .long(options::EXACT) + .short("x") + .help( + "do not round file sizes up to the next full block;\n\ + this is the default for non-regular files", + ), + ) + .arg( + Arg::with_name(options::ZERO) + .long(options::ZERO) + .short("z") + .help("add a final overwrite with zeros to hide shredding"), + ) + // Positional arguments + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(e) => panic!("Invalid options\n{}", e), + let matches = app.get_matches_from(args); + + let mut errs: Vec = vec![]; + + if !matches.is_present(options::FILE) { + show_error!("Missing an argument"); + show_error!("For help, try '{} --help'", NAME); + return 0; + } + + let iterations = match matches.value_of(options::ITERATIONS) { + Some(s) => match s.parse::() { + Ok(u) => u, + Err(_) => { + errs.push(format!("invalid number of passes: '{}'", s)); + 0 + } + }, + None => unreachable!(), }; - if matches.opt_present("help") { - show_help(&opts); - return 0; - } else if matches.opt_present("version") { - println!("{} {}", NAME, VERSION_STR); - return 0; - } else if matches.free.is_empty() { - println!("{}: Missing an argument", NAME); - println!("For help, try '{} --help'", NAME); - return 0; - } else { - let iterations = match matches.opt_str("iterations") { - Some(s) => match s.parse::() { - Ok(u) => u, - Err(_) => { - println!("{}: Invalid number of passes", NAME); - return 1; - } - }, - None => 3, - }; - let remove = matches.opt_present("remove"); - let size = get_size(matches.opt_str("size")); - let exact = matches.opt_present("exact") && size.is_none(); // if -s is given, ignore -x - let zero = matches.opt_present("zero"); - let verbose = matches.opt_present("verbose"); - for path_str in matches.free.into_iter() { - wipe_file(&path_str, iterations, remove, size, exact, zero, verbose); + // TODO: implement --remove HOW + // The optional HOW parameter indicates how to remove a directory entry: + // - 'unlink' => use a standard unlink call. + // - 'wipe' => also first obfuscate bytes in the name. + // - 'wipesync' => also sync each obfuscated byte to disk. + // The default mode is 'wipesync', but note it can be expensive. + + // TODO: implement --random-source + + // TODO: implement --force + + let remove = matches.is_present(options::REMOVE); + let size_arg = match matches.value_of(options::SIZE) { + Some(s) => Some(s.to_string()), + None => None, + }; + let size = get_size(size_arg); + let exact = matches.is_present(options::EXACT) && size.is_none(); // if -s is given, ignore -x + let zero = matches.is_present(options::ZERO); + let verbose = matches.is_present(options::VERBOSE); + + if !errs.is_empty() { + show_error!("Invalid arguments supplied."); + for message in errs { + show_error!("{}", message); } + return 1; + } + + for path_str in matches.values_of(options::FILE).unwrap() { + wipe_file(&path_str, iterations, remove, size, exact, zero, verbose); } 0 } -fn show_help(opts: &getopts::Options) { - println!("Usage: {} [OPTION]... FILE...", NAME); - println!( - "Overwrite the specified FILE(s) repeatedly, in order to make it harder \ - for even very expensive hardware probing to recover the data." - ); - println!("{}", opts.usage("")); - println!("Delete FILE(s) if --remove (-u) is specified. The default is not to remove"); - println!("the files because it is common to operate on device files like /dev/hda,"); - println!("and those files usually should not be removed."); - println!(); - println!( - "CAUTION: Note that {} relies on a very important assumption:", - NAME - ); - println!("that the file system overwrites data in place. This is the traditional"); - println!("way to do things, but many modern file system designs do not satisfy this"); - println!( - "assumption. The following are examples of file systems on which {} is", - NAME - ); - println!("not effective, or is not guaranteed to be effective in all file system modes:"); - println!(); - println!("* log-structured or journaled file systems, such as those supplied with"); - println!("AIX and Solaris (and JFS, ReiserFS, XFS, Ext3, etc.)"); - println!(); - println!("* file systems that write redundant data and carry on even if some writes"); - println!("fail, such as RAID-based file systems"); - println!(); - println!("* file systems that make snapshots, such as Network Appliance's NFS server"); - println!(); - println!("* file systems that cache in temporary locations, such as NFS"); - println!("version 3 clients"); - println!(); - println!("* compressed file systems"); - println!(); - println!("In the case of ext3 file systems, the above disclaimer applies"); - println!( - "(and {} is thus of limited effectiveness) only in data=journal mode,", - NAME - ); - println!("which journals file data in addition to just metadata. In both the"); - println!( - "data=ordered (default) and data=writeback modes, {} works as usual.", - NAME - ); - println!("Ext3 journaling modes can be changed by adding the data=something option"); - println!("to the mount options for a particular file system in the /etc/fstab file,"); - println!("as documented in the mount man page (man mount)."); - println!(); - println!("In addition, file system backups and remote mirrors may contain copies"); - println!("of the file that cannot be removed, and that will allow a shredded file"); - println!("to be recovered later."); -} - // TODO: Add support for all postfixes here up to and including EiB // http://www.gnu.org/software/coreutils/manual/coreutils.html#Block-size fn get_size(size_str_opt: Option) -> Option { diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index b78d4fc04..dbf559454 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shuf" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "shuf ~ (uutils) display random permutations of input lines" @@ -15,9 +15,9 @@ edition = "2018" path = "src/shuf.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" rand = "0.5" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 278e872fb..f7af05214 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -10,132 +10,162 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use rand::Rng; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; -use std::usize::MAX as MAX_USIZE; - -enum Mode { - Default, - Echo, - InputRange((usize, usize)), -} static NAME: &str = "shuf"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static USAGE: &str = r#"shuf [OPTION]... [FILE] + or: shuf -e [OPTION]... [ARG]... + or: shuf -i LO-HI [OPTION]... +Write a random permutation of the input lines to standard output. + +With no FILE, or when FILE is -, read standard input. +"#; +static TEMPLATE: &str = "Usage: {usage}\nMandatory arguments to long options are mandatory for short options too.\n{unified}"; + +struct Options { + head_count: usize, + output: Option, + random_source: Option, + repeat: bool, + sep: u8, +} + +enum Mode { + Default(String), + Echo(Vec), + InputRange((usize, usize)), +} + +mod options { + pub static ECHO: &str = "echo"; + pub static INPUT_RANGE: &str = "input-range"; + pub static HEAD_COUNT: &str = "head-count"; + pub static OUTPUT: &str = "output"; + pub static RANDOM_SOURCE: &str = "random-source"; + pub static REPEAT: &str = "repeat"; + pub static ZERO_TERMINATED: &str = "zero-terminated"; + pub static FILE: &str = "file"; +} pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .template(TEMPLATE) + .usage(USAGE) + .arg( + Arg::with_name(options::ECHO) + .short("e") + .long(options::ECHO) + .takes_value(true) + .value_name("ARG") + .help("treat each ARG as an input line") + .multiple(true) + .use_delimiter(false) + .min_values(0) + .conflicts_with(options::INPUT_RANGE), + ) + .arg( + Arg::with_name(options::INPUT_RANGE) + .short("i") + .long(options::INPUT_RANGE) + .takes_value(true) + .value_name("LO-HI") + .help("treat each number LO through HI as an input line") + .conflicts_with(options::FILE), + ) + .arg( + Arg::with_name(options::HEAD_COUNT) + .short("n") + .long(options::HEAD_COUNT) + .takes_value(true) + .value_name("COUNT") + .help("output at most COUNT lines"), + ) + .arg( + Arg::with_name(options::OUTPUT) + .short("o") + .long(options::OUTPUT) + .takes_value(true) + .value_name("FILE") + .help("write result to FILE instead of standard output"), + ) + .arg( + Arg::with_name(options::RANDOM_SOURCE) + .long(options::RANDOM_SOURCE) + .takes_value(true) + .value_name("FILE") + .help("get random bytes from FILE"), + ) + .arg( + Arg::with_name(options::REPEAT) + .short("r") + .long(options::REPEAT) + .help("output lines can be repeated"), + ) + .arg( + Arg::with_name(options::ZERO_TERMINATED) + .short("z") + .long(options::ZERO_TERMINATED) + .help("line delimiter is NUL, not newline"), + ) + .arg(Arg::with_name(options::FILE).takes_value(true)) + .get_matches_from(args); - let mut opts = getopts::Options::new(); - opts.optflag("e", "echo", "treat each ARG as an input line"); - opts.optopt( - "i", - "input-range", - "treat each number LO through HI as an input line", - "LO-HI", - ); - opts.optopt("n", "head-count", "output at most COUNT lines", "COUNT"); - opts.optopt( - "o", - "output", - "write result to FILE instead of standard output", - "FILE", - ); - opts.optopt("", "random-source", "get random bytes from FILE", "FILE"); - opts.optflag("r", "repeat", "output lines can be repeated"); - opts.optflag("z", "zero-terminated", "end lines with 0 byte, not newline"); - opts.optflag("h", "help", "display this help and exit"); - opts.optflag("V", "version", "output version information and exit"); - let mut matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => crash!(1, "{}", f), - }; - if matches.opt_present("help") { - let msg = format!( - "{0} {1} - -Usage: - {0} [OPTION]... [FILE] - {0} -e [OPTION]... [ARG]... - {0} -i LO-HI [OPTION]... - -Write a random permutation of the input lines to standard output. -With no FILE, or when FILE is -, read standard input.", - NAME, VERSION - ); - print!("{}", opts.usage(&msg)); - } else if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); + let mode = if let Some(args) = matches.values_of(options::ECHO) { + Mode::Echo(args.map(String::from).collect()) + } else if let Some(range) = matches.value_of(options::INPUT_RANGE) { + match parse_range(range) { + Ok(m) => Mode::InputRange(m), + Err(msg) => { + crash!(1, "{}", msg); + } + } } else { - let echo = matches.opt_present("echo"); - let mode = match matches.opt_str("input-range") { - Some(range) => { - if echo { - show_error!("cannot specify more than one mode"); - return 1; - } - match parse_range(range) { - Ok(m) => Mode::InputRange(m), - Err(msg) => { - crash!(1, "{}", msg); - } - } - } - None => { - if echo { - Mode::Echo - } else { - if matches.free.is_empty() { - matches.free.push("-".to_owned()); - } else if matches.free.len() > 1 { - show_error!("extra operand '{}'", &matches.free[1][..]); - } - Mode::Default - } - } - }; - let repeat = matches.opt_present("repeat"); - let sep = if matches.opt_present("zero-terminated") { - 0x00_u8 - } else { - 0x0a_u8 - }; - let count = match matches.opt_str("head-count") { - Some(cnt) => match cnt.parse::() { + Mode::Default(matches.value_of(options::FILE).unwrap_or("-").to_string()) + }; + + let options = Options { + head_count: match matches.value_of(options::HEAD_COUNT) { + Some(count) => match count.parse::() { Ok(val) => val, - Err(e) => { - show_error!("'{}' is not a valid count: {}", cnt, e); + Err(_) => { + show_error!("invalid line count: '{}'", count); return 1; } }, - None => MAX_USIZE, - }; - let output = matches.opt_str("output"); - let random = matches.opt_str("random-source"); + None => std::usize::MAX, + }, + output: matches.value_of(options::OUTPUT).map(String::from), + random_source: matches.value_of(options::RANDOM_SOURCE).map(String::from), + repeat: matches.is_present(options::REPEAT), + sep: if matches.is_present(options::ZERO_TERMINATED) { + 0x00_u8 + } else { + 0x0a_u8 + }, + }; - match mode { - Mode::Echo => { - // XXX: this doesn't correctly handle non-UTF-8 cmdline args - let mut evec = matches - .free - .iter() - .map(String::as_bytes) - .collect::>(); - find_seps(&mut evec, sep); - shuf_bytes(&mut evec, repeat, count, sep, output, random); - } - Mode::InputRange((b, e)) => { - let rvec = (b..e).map(|x| format!("{}", x)).collect::>(); - let mut rvec = rvec.iter().map(String::as_bytes).collect::>(); - shuf_bytes(&mut rvec, repeat, count, sep, output, random); - } - Mode::Default => { - let fdata = read_input_file(&matches.free[0][..]); - let mut fdata = vec![&fdata[..]]; - find_seps(&mut fdata, sep); - shuf_bytes(&mut fdata, repeat, count, sep, output, random); - } + match mode { + Mode::Echo(args) => { + let mut evec = args.iter().map(String::as_bytes).collect::>(); + find_seps(&mut evec, options.sep); + shuf_bytes(&mut evec, options); + } + Mode::InputRange((b, e)) => { + let rvec = (b..e).map(|x| format!("{}", x)).collect::>(); + let mut rvec = rvec.iter().map(String::as_bytes).collect::>(); + shuf_bytes(&mut rvec, options); + } + Mode::Default(filename) => { + let fdata = read_input_file(&filename); + let mut fdata = vec![&fdata[..]]; + find_seps(&mut fdata, options.sep); + shuf_bytes(&mut fdata, options); } } @@ -193,15 +223,8 @@ fn find_seps(data: &mut Vec<&[u8]>, sep: u8) { } } -fn shuf_bytes( - input: &mut Vec<&[u8]>, - repeat: bool, - count: usize, - sep: u8, - output: Option, - random: Option, -) { - let mut output = BufWriter::new(match output { +fn shuf_bytes(input: &mut Vec<&[u8]>, opts: Options) { + let mut output = BufWriter::new(match opts.output { None => Box::new(stdout()) as Box, Some(s) => match File::create(&s[..]) { Ok(f) => Box::new(f) as Box, @@ -209,7 +232,7 @@ fn shuf_bytes( }, }); - let mut rng = match random { + let mut rng = match opts.random_source { Some(r) => WrappedRng::RngFile(rand::read::ReadRng::new(match File::open(&r[..]) { Ok(f) => f, Err(e) => crash!(1, "failed to open random source '{}': {}", &r[..], e), @@ -225,7 +248,7 @@ fn shuf_bytes( len_mod <<= 1; } - let mut count = count; + let mut count = opts.head_count; while count > 0 && !input.is_empty() { let mut r = input.len(); while r >= input.len() { @@ -237,11 +260,11 @@ fn shuf_bytes( .write_all(input[r]) .unwrap_or_else(|e| crash!(1, "write failed: {}", e)); output - .write_all(&[sep]) + .write_all(&[opts.sep]) .unwrap_or_else(|e| crash!(1, "write failed: {}", e)); // if we do not allow repeats, remove the chosen value from the input vector - if !repeat { + if !opts.repeat { // shrink the mask if we will drop below a power of 2 if input.len() % 2 == 0 && len_mod > 2 { len_mod >>= 1; @@ -253,18 +276,18 @@ fn shuf_bytes( } } -fn parse_range(input_range: String) -> Result<(usize, usize), String> { +fn parse_range(input_range: &str) -> Result<(usize, usize), String> { let split: Vec<&str> = input_range.split('-').collect(); if split.len() != 2 { - Err("invalid range format".to_owned()) + Err(format!("invalid input range: '{}'", input_range)) } else { let begin = match split[0].parse::() { Ok(m) => m, - Err(e) => return Err(format!("{} is not a valid number: {}", split[0], e)), + Err(_) => return Err(format!("invalid input range: '{}'", split[0])), }; let end = match split[1].parse::() { Ok(m) => m, - Err(e) => return Err(format!("{} is not a valid number: {}", split[1], e)), + Err(_) => return Err(format!("invalid input range: '{}'", split[1])), }; Ok((begin, end + 1)) } diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index 0ac5d6519..fe7ee2941 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sleep" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "sleep ~ (uutils) pause for DURATION" @@ -16,7 +16,7 @@ path = "src/sleep.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["parse_time"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 5158f6e52..814e4bbba 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sort" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "sort ~ (uutils) sort input lines" @@ -15,10 +15,13 @@ edition = "2018" path = "src/sort.rs" [dependencies] +rayon = "1.5" +rand = "0.7" clap = "2.33" +fnv = "1.0.7" itertools = "0.8.0" semver = "0.9.0" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 8e79ff947..4e0e25d65 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1,21 +1,32 @@ // * This file is part of the uutils coreutils package. // * // * (c) Michael Yin +// * (c) Robert Swinford // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. #![allow(dead_code)] +// Although these links don't always seem to describe reality, check out the POSIX and GNU specs: +// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sort.html +// https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html + // spell-checker:ignore (ToDO) outfile nondictionary #[macro_use] extern crate uucore; use clap::{App, Arg}; +use fnv::FnvHasher; use itertools::Itertools; +use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; +use rayon::prelude::*; use semver::Version; use std::cmp::Ordering; use std::collections::BinaryHeap; +use std::env; use std::fs::File; +use std::hash::{Hash, Hasher}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write}; use std::mem::replace; use std::path::Path; @@ -28,25 +39,37 @@ static VERSION: &str = env!("CARGO_PKG_VERSION"); static OPT_HUMAN_NUMERIC_SORT: &str = "human-numeric-sort"; static OPT_MONTH_SORT: &str = "month-sort"; static OPT_NUMERIC_SORT: &str = "numeric-sort"; +static OPT_GENERAL_NUMERIC_SORT: &str = "general-numeric-sort"; static OPT_VERSION_SORT: &str = "version-sort"; static OPT_DICTIONARY_ORDER: &str = "dictionary-order"; static OPT_MERGE: &str = "merge"; static OPT_CHECK: &str = "check"; +static OPT_CHECK_SILENT: &str = "check-silent"; static OPT_IGNORE_CASE: &str = "ignore-case"; +static OPT_IGNORE_BLANKS: &str = "ignore-blanks"; +static OPT_IGNORE_NONPRINTING: &str = "ignore-nonprinting"; static OPT_OUTPUT: &str = "output"; static OPT_REVERSE: &str = "reverse"; static OPT_STABLE: &str = "stable"; static OPT_UNIQUE: &str = "unique"; +static OPT_RANDOM: &str = "random-sort"; +static OPT_ZERO_TERMINATED: &str = "zero-terminated"; +static OPT_PARALLEL: &str = "parallel"; +static OPT_FILES0_FROM: &str = "files0-from"; static ARG_FILES: &str = "files"; static DECIMAL_PT: char = '.'; static THOUSANDS_SEP: char = ','; +static NEGATIVE: char = '-'; +static POSITIVE: char = '+'; +#[derive(Eq, Ord, PartialEq, PartialOrd)] enum SortMode { Numeric, HumanNumeric, + GeneralNumeric, Month, Version, Default, @@ -60,8 +83,13 @@ struct Settings { stable: bool, unique: bool, check: bool, - compare_fns: Vec Ordering>, + check_silent: bool, + random: bool, + compare_fn: fn(&str, &str) -> Ordering, transform_fns: Vec String>, + threads: String, + salt: String, + zero_terminated: bool, } impl Default for Settings { @@ -74,8 +102,13 @@ impl Default for Settings { stable: false, unique: false, check: false, - compare_fns: Vec::new(), + check_silent: false, + random: false, + compare_fn: default_compare, transform_fns: Vec::new(), + threads: String::new(), + salt: String::new(), + zero_terminated: false, } } } @@ -155,17 +188,14 @@ impl<'a> Iterator for FileMerger<'a> { } } } + fn get_usage() -> String { format!( "{0} {1} - Usage: {0} [OPTION]... [FILE]... - Write the sorted concatenation of all FILE(s) to standard output. - Mandatory arguments for long options are mandatory for short options too. - With no FILE, or when FILE is -, read standard input.", NAME, VERSION ) @@ -198,6 +228,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_NUMERIC_SORT) .help("compare according to string numerical value"), ) + .arg( + Arg::with_name(OPT_GENERAL_NUMERIC_SORT) + .short("g") + .long(OPT_GENERAL_NUMERIC_SORT) + .help("compare according to string general numerical value"), + ) .arg( Arg::with_name(OPT_VERSION_SORT) .short("V") @@ -222,12 +258,30 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_CHECK) .help("check for sorted input; do not sort"), ) + .arg( + Arg::with_name(OPT_CHECK_SILENT) + .short("C") + .long(OPT_CHECK_SILENT) + .help("exit successfully if the given file is already sorted, and exit with status 1 otherwise. "), + ) .arg( Arg::with_name(OPT_IGNORE_CASE) .short("f") .long(OPT_IGNORE_CASE) .help("fold lower case to upper case characters"), ) + .arg( + Arg::with_name(OPT_IGNORE_NONPRINTING) + .short("-i") + .long(OPT_IGNORE_NONPRINTING) + .help("ignore nonprinting characters"), + ) + .arg( + Arg::with_name(OPT_IGNORE_BLANKS) + .short("b") + .long(OPT_IGNORE_BLANKS) + .help("ignore leading blanks when finding sort keys in each line"), + ) .arg( Arg::with_name(OPT_OUTPUT) .short("o") @@ -236,6 +290,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .value_name("FILENAME"), ) + .arg( + Arg::with_name(OPT_RANDOM) + .short("R") + .long(OPT_RANDOM) + .help("shuffle in random order"), + ) .arg( Arg::with_name(OPT_REVERSE) .short("r") @@ -254,18 +314,65 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(OPT_UNIQUE) .help("output only the first of an equal run"), ) + .arg( + Arg::with_name(OPT_ZERO_TERMINATED) + .short("z") + .long(OPT_ZERO_TERMINATED) + .help("line delimiter is NUL, not newline"), + ) + .arg( + Arg::with_name(OPT_PARALLEL) + .long(OPT_PARALLEL) + .help("change the number of threads running concurrently to N") + .takes_value(true) + .value_name("NUM_THREADS"), + ) + .arg( + Arg::with_name(OPT_FILES0_FROM) + .long(OPT_FILES0_FROM) + .help("read input from the files specified by NUL-terminated NUL_FILES") + .takes_value(true) + .value_name("NUL_FILES") + .multiple(true), + ) .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) .get_matches_from(args); - let mut files: Vec = matches - .values_of(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); + // check whether user specified a zero terminated list of files for input, otherwise read files from args + let mut files: Vec = if matches.is_present(OPT_FILES0_FROM) { + let files0_from: Vec = matches + .values_of(OPT_FILES0_FROM) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + let mut files = Vec::new(); + for path in &files0_from { + let (reader, _) = open(path.as_str()).expect("Could not read from file specified."); + let buf_reader = BufReader::new(reader); + for line in buf_reader.split(b'\0') { + if let Ok(n) = line { + files.push( + std::str::from_utf8(&n) + .expect("Could not parse zero terminated string from input.") + .to_string(), + ); + } + } + } + files + } else { + matches + .values_of(ARG_FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default() + }; settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT) { SortMode::HumanNumeric } else if matches.is_present(OPT_MONTH_SORT) { SortMode::Month + } else if matches.is_present(OPT_GENERAL_NUMERIC_SORT) { + SortMode::GeneralNumeric } else if matches.is_present(OPT_NUMERIC_SORT) { SortMode::Numeric } else if matches.is_present(OPT_VERSION_SORT) { @@ -274,22 +381,48 @@ pub fn uumain(args: impl uucore::Args) -> i32 { SortMode::Default }; - if matches.is_present(OPT_DICTIONARY_ORDER) { - settings.transform_fns.push(remove_nondictionary_chars); + if matches.is_present(OPT_PARALLEL) { + // "0" is default - threads = num of cores + settings.threads = matches + .value_of(OPT_PARALLEL) + .map(String::from) + .unwrap_or("0".to_string()); + env::set_var("RAYON_NUM_THREADS", &settings.threads); } + if matches.is_present(OPT_DICTIONARY_ORDER) { + settings.transform_fns.push(remove_nondictionary_chars); + } else if matches.is_present(OPT_IGNORE_NONPRINTING) { + settings.transform_fns.push(remove_nonprinting_chars); + } + + settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED); settings.merge = matches.is_present(OPT_MERGE); + settings.check = matches.is_present(OPT_CHECK); + if matches.is_present(OPT_CHECK_SILENT) { + settings.check_silent = matches.is_present(OPT_CHECK_SILENT); + settings.check = true; + }; if matches.is_present(OPT_IGNORE_CASE) { settings.transform_fns.push(|s| s.to_uppercase()); } + if matches.is_present(OPT_IGNORE_BLANKS) { + settings.transform_fns.push(|s| s.trim_start().to_string()); + } + settings.outfile = matches.value_of(OPT_OUTPUT).map(String::from); settings.reverse = matches.is_present(OPT_REVERSE); settings.stable = matches.is_present(OPT_STABLE); settings.unique = matches.is_present(OPT_UNIQUE); + if matches.is_present(OPT_RANDOM) { + settings.random = matches.is_present(OPT_RANDOM); + settings.salt = get_rand_string(); + } + //let mut files = matches.free; if files.is_empty() { /* if no file, default to stdin */ @@ -298,25 +431,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!(1, "sort: extra operand `{}' not allowed with -c", files[1]) } - settings.compare_fns.push(match settings.mode { + settings.compare_fn = match settings.mode { SortMode::Numeric => numeric_compare, + SortMode::GeneralNumeric => general_numeric_compare, SortMode::HumanNumeric => human_numeric_size_compare, SortMode::Month => month_compare, SortMode::Version => version_compare, SortMode::Default => default_compare, - }); + }; - if !settings.stable { - match settings.mode { - SortMode::Default => {} - _ => settings.compare_fns.push(default_compare), - } - } - - exec(files, &settings) + exec(files, &mut settings) } -fn exec(files: Vec, settings: &Settings) -> i32 { +fn exec(files: Vec, settings: &mut Settings) -> i32 { let mut lines = Vec::new(); let mut file_merger = FileMerger::new(&settings); @@ -330,60 +457,79 @@ fn exec(files: Vec, settings: &Settings) -> i32 { if settings.merge { file_merger.push_file(buf_reader.lines()); - } else if settings.check { - return exec_check_file(buf_reader.lines(), &settings); + } else if settings.zero_terminated { + for line in buf_reader.split(b'\0') { + if let Ok(n) = line { + lines.push( + std::str::from_utf8(&n) + .expect("Could not parse string from zero terminated input.") + .to_string(), + ); + } + } } else { for line in buf_reader.lines() { if let Ok(n) = line { lines.push(n); - } else { - break; } } } } - sort_by(&mut lines, &settings); + if settings.check { + return exec_check_file(lines, &settings); + } else { + sort_by(&mut lines, &settings); + } if settings.merge { if settings.unique { - print_sorted(file_merger.dedup(), &settings.outfile) + print_sorted(file_merger.dedup(), &settings) } else { - print_sorted(file_merger, &settings.outfile) + print_sorted(file_merger, &settings) } + } else if settings.mode == SortMode::Month && settings.unique { + print_sorted( + lines + .iter() + .dedup_by(|a, b| get_months_dedup(a) == get_months_dedup(b)), + &settings, + ) } else if settings.unique { - print_sorted(lines.iter().dedup(), &settings.outfile) + print_sorted( + lines + .iter() + .dedup_by(|a, b| get_nums_dedup(a) == get_nums_dedup(b)), + &settings, + ) } else { - print_sorted(lines.iter(), &settings.outfile) + print_sorted(lines.iter(), &settings) } 0 } -fn exec_check_file(lines: Lines>>, settings: &Settings) -> i32 { +fn exec_check_file(unwrapped_lines: Vec, settings: &Settings) -> i32 { // errors yields the line before each disorder, // plus the last line (quirk of .coalesce()) - let unwrapped_lines = lines.filter_map(|maybe_line| { - if let Ok(line) = maybe_line { - Some(line) - } else { - None - } - }); - let mut errors = unwrapped_lines - .enumerate() - .coalesce(|(last_i, last_line), (i, line)| { - if compare_by(&last_line, &line, &settings) == Ordering::Greater { - Err(((last_i, last_line), (i, line))) - } else { - Ok((i, line)) - } - }); + let mut errors = + unwrapped_lines + .iter() + .enumerate() + .coalesce(|(last_i, last_line), (i, line)| { + if compare_by(&last_line, &line, &settings) == Ordering::Greater { + Err(((last_i, last_line), (i, line))) + } else { + Ok((i, line)) + } + }); if let Some((first_error_index, _line)) = errors.next() { // Check for a second "error", as .coalesce() always returns the last // line, no matter what our merging function does. if let Some(_last_line_or_next_error) = errors.next() { - println!("sort: disorder in line {}", first_error_index); + if !settings.check_silent { + println!("sort: disorder in line {}", first_error_index); + }; 1 } else { // first "error" was actually the last line. @@ -395,8 +541,9 @@ fn exec_check_file(lines: Lines>>, settings: &Settings) } } +#[inline(always)] fn transform(line: &str, settings: &Settings) -> String { - let mut transformed = line.to_string(); + let mut transformed = line.to_owned(); for transform_fn in &settings.transform_fns { transformed = transform_fn(&transformed); } @@ -404,8 +551,9 @@ fn transform(line: &str, settings: &Settings) -> String { transformed } +#[inline(always)] fn sort_by(lines: &mut Vec, settings: &Settings) { - lines.sort_by(|a, b| compare_by(a, b, &settings)) + lines.par_sort_by(|a, b| compare_by(a, b, &settings)) } fn compare_by(a: &str, b: &str, settings: &Settings) -> Ordering { @@ -418,79 +566,200 @@ fn compare_by(a: &str, b: &str, settings: &Settings) -> Ordering { (a, b) }; - for compare_fn in &settings.compare_fns { - let cmp = compare_fn(a, b); - if cmp != Ordering::Equal { - if settings.reverse { - return cmp.reverse(); - } else { - return cmp; - } - } - } - Ordering::Equal -} + // 1st Compare + let mut cmp: Ordering = if settings.random { + random_shuffle(a, b, settings.salt.clone()) + } else { + (settings.compare_fn)(a, b) + }; -/// Parse the beginning string into an f64, returning -inf instead of NaN on errors. -fn permissive_f64_parse(a: &str) -> f64 { - // Maybe should be split on non-digit, but then 10e100 won't parse properly. - // On the flip side, this will give NEG_INFINITY for "1,234", which might be OK - // because there's no way to handle both CSV and thousands separators without a new flag. - // GNU sort treats "1,234" as "1" in numeric, so maybe it's fine. - // GNU sort treats "NaN" as non-number in numeric, so it needs special care. - match a.split_whitespace().next() { - None => std::f64::NEG_INFINITY, - Some(sa) => match sa.parse::() { - Ok(a) if a.is_nan() => std::f64::NEG_INFINITY, - Ok(a) => a, - Err(_) => std::f64::NEG_INFINITY, - }, + // Call "last resort compare" on any equal + if cmp == Ordering::Equal { + if settings.random || settings.stable || settings.unique { + cmp = Ordering::Equal + } else { + cmp = default_compare(a, b) + }; + }; + + if settings.reverse { + return cmp.reverse(); + } else { + return cmp; } } +// Test output against BSDs and GNU with their locale +// env var set to lc_ctype=utf-8 to enjoy the exact same output. +#[inline(always)] fn default_compare(a: &str, b: &str) -> Ordering { a.cmp(b) } -/// Compares two floating point numbers, with errors being assumed to be -inf. -/// Stops coercing at the first whitespace char, so 1e2 will parse as 100 but -/// 1,000 will parse as -inf. -fn numeric_compare(a: &str, b: &str) -> Ordering { - #![allow(clippy::comparison_chain)] - let fa = permissive_f64_parse(a); - let fb = permissive_f64_parse(b); - // f64::cmp isn't implemented because NaN messes with it - // but we sidestep that with permissive_f64_parse so just fake it - if fa > fb { - Ordering::Greater - } else if fa < fb { - Ordering::Less +// This function does the initial detection of numeric lines. +// Lines starting with a number or positive or negative sign. +// It also strips the string of any thing that could never +// be a number for the purposes of any type of numeric comparison. +#[inline(always)] +fn leading_num_common(a: &str) -> &str { + let mut s = ""; + for (idx, c) in a.char_indices() { + // check whether char is numeric, whitespace or decimal point or thousand seperator + if !c.is_numeric() + && !c.is_whitespace() + && !c.eq(&DECIMAL_PT) + && !c.eq(&THOUSANDS_SEP) + // check for e notation + && !c.eq(&'e') + && !c.eq(&'E') + // check whether first char is + or - + && !a.chars().nth(0).unwrap_or('\0').eq(&POSITIVE) + && !a.chars().nth(0).unwrap_or('\0').eq(&NEGATIVE) + { + // Strip string of non-numeric trailing chars + s = &a[..idx]; + break; + } + // If line is not a number line, return the line as is + s = a; + } + s +} + +// This function cleans up the initial comparison done by leading_num_common for a numeric compare. +// GNU sort does its numeric comparison through strnumcmp. However, we don't have or +// may not want to use libc. Instead we emulate the GNU sort numeric compare by ignoring +// those leading number lines GNU sort would not recognize. GNU numeric compare would +// not recognize a positive sign or scientific/E notation so we strip those elements here. +fn get_leading_num(a: &str) -> &str { + let mut s = ""; + let b = leading_num_common(a); + + // GNU numeric sort doesn't recognize '+' or 'e' notation so we strip + for (idx, c) in b.char_indices() { + if c.eq(&'e') || c.eq(&'E') || b.chars().nth(0).unwrap_or('\0').eq(&POSITIVE) { + s = &b[..idx]; + break; + } + // If no further processing needed to be done, return the line as-is to be sorted + s = b; + } + + // And empty number or non-number lines are to be treated as ‘0’ but only for numeric sort + // All '0'-ed lines will be sorted later, but only amongst themselves, during the so-called 'last resort comparison.' + if s.is_empty() { + s = "0"; + }; + s +} + +// This function cleans up the initial comparison done by leading_num_common for a general numeric compare. +// In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and +// scientific notation, so we strip those lines only after the end of the following numeric string. +// For example, 5e10KFD would be 5e10 or 5x10^10 and +10000HFKJFK would become 10000. +fn get_leading_gen(a: &str) -> String { + // Make this iter peekable to see if next char is numeric + let mut p_iter = leading_num_common(a).chars().peekable(); + let mut r = String::new(); + // Cleanup raw stripped strings + for c in p_iter.to_owned() { + let next_char_numeric = p_iter.peek().unwrap_or(&'\0').is_numeric(); + // Only general numeric recognizes e notation and, see block below, the '+' sign + if (c.eq(&'e') && !next_char_numeric) || (c.eq(&'E') && !next_char_numeric) { + r = a.split(c).next().unwrap_or("").to_owned(); + break; + // If positive sign and next char is not numeric, split at postive sign at keep trailing numbers + // There is a more elegant way to do this in Rust 1.45, std::str::strip_prefix + } else if c.eq(&POSITIVE) && !next_char_numeric { + let mut v: Vec<&str> = a.split(c).collect(); + let x = v.split_off(1); + r = x.join(""); + break; + // If no further processing needed to be done, return the line as-is to be sorted + } else { + r = a.to_owned(); + } + } + r +} + +fn get_months_dedup(a: &str) -> String { + let pattern = if a.trim().len().ge(&3) { + // Split at 3rd char and get first element of tuple ".0" + a.split_at(3).0 } else { - Ordering::Equal + "" + }; + + let month = match pattern.to_uppercase().as_ref() { + "JAN" => Month::January, + "FEB" => Month::February, + "MAR" => Month::March, + "APR" => Month::April, + "MAY" => Month::May, + "JUN" => Month::June, + "JUL" => Month::July, + "AUG" => Month::August, + "SEP" => Month::September, + "OCT" => Month::October, + "NOV" => Month::November, + "DEC" => Month::December, + _ => Month::Unknown, + }; + + if month == Month::Unknown { + "".to_owned() + } else { + pattern.to_uppercase() } } -fn human_numeric_convert(a: &str) -> f64 { - let int_str: String = a.chars().take_while(|c| c.is_numeric()).collect(); - let suffix = a.chars().find(|c| !c.is_numeric()); - let int_part = int_str.parse::().unwrap_or(-1f64) as f64; - let suffix: f64 = match suffix.unwrap_or('\0') { - 'K' => 1000f64, - 'M' => 1E6, - 'G' => 1E9, - 'T' => 1E12, - 'P' => 1E15, - _ => 1f64, - }; - int_part * suffix +// *For all dedups/uniques we must compare leading numbers* +// Also note numeric compare and unique output is specifically *not* the same as a "sort | uniq" +// See: https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html +fn get_nums_dedup(a: &str) -> &str { + // Trim and remove any leading zeros + let s = a.trim().trim_start_matches('0'); + + // Get first char + let c = s.chars().nth(0).unwrap_or('\0'); + + // Empty lines and non-number lines are treated as the same for dedup + if s.is_empty() { + "" + } else if !c.eq(&NEGATIVE) && !c.is_numeric() { + "" + // Prepare lines for comparison of only the numerical leading numbers + } else { + get_leading_num(s) + } } -/// Compare two strings as if they are human readable sizes. -/// AKA 1M > 100k -fn human_numeric_size_compare(a: &str, b: &str) -> Ordering { +/// Parse the beginning string into an f64, returning -inf instead of NaN on errors. +#[inline(always)] +fn permissive_f64_parse(a: &str) -> f64 { + // Remove thousands seperators + let a = a.replace(THOUSANDS_SEP, ""); + + // GNU sort treats "NaN" as non-number in numeric, so it needs special care. + // *Keep this trim before parse* despite what POSIX may say about -b and -n + // because GNU and BSD both seem to require it to match their behavior + match a.trim().parse::() { + Ok(a) if a.is_nan() => std::f64::NEG_INFINITY, + Ok(a) => a, + Err(_) => std::f64::NEG_INFINITY, + } +} + +fn numeric_compare(a: &str, b: &str) -> Ordering { #![allow(clippy::comparison_chain)] - let fa = human_numeric_convert(a); - let fb = human_numeric_convert(b); + + let sa = get_leading_num(a); + let sb = get_leading_num(b); + + let fa = permissive_f64_parse(sa); + let fb = permissive_f64_parse(sb); + // f64::cmp isn't implemented (due to NaN issues); implement directly instead if fa > fb { Ordering::Greater @@ -501,6 +770,93 @@ fn human_numeric_size_compare(a: &str, b: &str) -> Ordering { } } +/// Compares two floats, with errors and non-numerics assumed to be -inf. +/// Stops coercing at the first non-numeric char. +fn general_numeric_compare(a: &str, b: &str) -> Ordering { + #![allow(clippy::comparison_chain)] + + let sa = get_leading_gen(a); + let sb = get_leading_gen(b); + + let fa = permissive_f64_parse(&sa); + let fb = permissive_f64_parse(&sb); + + // f64::cmp isn't implemented (due to NaN issues); implement directly instead + if fa > fb { + Ordering::Greater + } else if fa < fb { + Ordering::Less + } else { + Ordering::Equal + } +} + +// GNU/BSD does not handle converting numbers to an equal scale +// properly. GNU/BSD simply recognize that there is a human scale and sorts +// those numbers ahead of other number inputs. There are perhaps limits +// to the type of behavior we should emulate, and this might be such a limit. +// Properly handling these units seems like a value add to me. And when sorting +// these types of numbers, we rarely care about pure performance. +fn human_numeric_convert(a: &str) -> f64 { + let num_str = get_leading_num(a); + let suffix = a.trim_start_matches(num_str); + let num_part = permissive_f64_parse(num_str); + let suffix: f64 = match suffix.parse().unwrap_or('\0') { + // SI Units + 'K' => 1E3, + 'M' => 1E6, + 'G' => 1E9, + 'T' => 1E12, + 'P' => 1E15, + 'E' => 1E18, + 'Z' => 1E21, + 'Y' => 1E24, + _ => 1f64, + }; + num_part * suffix +} + +/// Compare two strings as if they are human readable sizes. +/// AKA 1M > 100k +fn human_numeric_size_compare(a: &str, b: &str) -> Ordering { + #![allow(clippy::comparison_chain)] + let fa = human_numeric_convert(a); + let fb = human_numeric_convert(b); + + // f64::cmp isn't implemented (due to NaN issues); implement directly instead + if fa > fb { + Ordering::Greater + } else if fa < fb { + Ordering::Less + } else { + Ordering::Equal + } +} + +fn get_rand_string() -> String { + thread_rng() + .sample_iter(&Alphanumeric) + .take(16) + .map(char::from) + .collect::() +} + +fn get_hash(t: &T) -> u64 { + let mut s: FnvHasher = Default::default(); + t.hash(&mut s); + s.finish() +} + +fn random_shuffle(a: &str, b: &str, x: String) -> Ordering { + #![allow(clippy::comparison_chain)] + let salt_slice = x.as_str(); + + let da = get_hash(&[a, salt_slice].concat()); + let db = get_hash(&[b, salt_slice].concat()); + + da.cmp(&db) +} + #[derive(Eq, Ord, PartialEq, PartialOrd)] enum Month { Unknown, @@ -520,13 +876,15 @@ enum Month { /// Parse the beginning string into a Month, returning Month::Unknown on errors. fn month_parse(line: &str) -> Month { - match line - .split_whitespace() - .next() - .unwrap() - .to_uppercase() - .as_ref() - { + // GNU splits at any 3 letter match "JUNNNN" is JUN + let pattern = if line.trim().len().ge(&3) { + // Split a 3 and get first element of tuple ".0" + line.split_at(3).0 + } else { + "" + }; + + match pattern.to_uppercase().as_ref() { "JAN" => Month::January, "FEB" => Month::February, "MAR" => Month::March, @@ -544,7 +902,16 @@ fn month_parse(line: &str) -> Month { } fn month_compare(a: &str, b: &str) -> Ordering { - month_parse(a).cmp(&month_parse(b)) + let ma = month_parse(a); + let mb = month_parse(b); + + if ma > mb { + Ordering::Greater + } else if ma < mb { + Ordering::Less + } else { + Ordering::Equal + } } fn version_compare(a: &str, b: &str) -> Ordering { @@ -562,19 +929,26 @@ fn version_compare(a: &str, b: &str) -> Ordering { } fn remove_nondictionary_chars(s: &str) -> String { - // Using 'is_ascii_whitespace()' instead of 'is_whitespace()', because it - // uses only symbols compatible with UNIX sort (space, tab, newline). - // 'is_whitespace()' uses more symbols as whitespace (e.g. vertical tab). + // According to GNU, dictionary chars are those of ASCII + // and a blank is a space or a tab s.chars() - .filter(|c| c.is_alphanumeric() || c.is_ascii_whitespace()) + .filter(|c| c.is_ascii_alphanumeric() || c.is_ascii_whitespace()) .collect::() } -fn print_sorted>(iter: T, outfile: &Option) +fn remove_nonprinting_chars(s: &str) -> String { + // However, GNU says nonprinting chars are more permissive. + // All of ASCII except control chars ie, escape, newline + s.chars() + .filter(|c| c.is_ascii() && !c.is_ascii_control()) + .collect::() +} + +fn print_sorted>(iter: T, settings: &Settings) where S: std::fmt::Display, { - let mut file: Box = match *outfile { + let mut file: Box = match settings.outfile { Some(ref filename) => match File::create(Path::new(&filename)) { Ok(f) => Box::new(BufWriter::new(f)) as Box, Err(e) => { @@ -585,9 +959,16 @@ where None => Box::new(stdout()) as Box, }; - for line in iter { - let str = format!("{}\n", line); - crash_if_err!(1, file.write_all(str.as_bytes())) + if settings.zero_terminated { + for line in iter { + let str = format!("{}\0", line); + crash_if_err!(1, file.write_all(str.as_bytes())); + } + } else { + for line in iter { + let str = format!("{}\n", line); + crash_if_err!(1, file.write_all(str.as_bytes())); + } } } @@ -606,3 +987,72 @@ fn open(path: &str) -> Option<(Box, bool)> { } } } + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_get_hash() { + let a = "Ted".to_string(); + + assert_eq!(2646829031758483623, get_hash(&a)); + } + + #[test] + fn test_random_shuffle() { + let a = "Ted"; + let b = "Ted"; + let c = get_rand_string(); + + assert_eq!(Ordering::Equal, random_shuffle(a, b, c)); + } + + #[test] + fn test_default_compare() { + let a = "your own"; + let b = "your place"; + + assert_eq!(Ordering::Less, default_compare(a, b)); + } + + #[test] + fn test_numeric_compare1() { + let a = "149:7"; + let b = "150:5"; + + assert_eq!(Ordering::Less, numeric_compare(a, b)); + } + + #[test] + fn test_numeric_compare2() { + let a = "-1.02"; + let b = "1"; + + assert_eq!(Ordering::Less, numeric_compare(a, b)); + } + + #[test] + fn test_human_numeric_compare() { + let a = "300K"; + let b = "1M"; + + assert_eq!(Ordering::Less, human_numeric_size_compare(a, b)); + } + + #[test] + fn test_month_compare() { + let a = "JaN"; + let b = "OCt"; + + assert_eq!(Ordering::Less, month_compare(a, b)); + } + #[test] + fn test_version_compare() { + let a = "1.2.3-alpha2"; + let b = "1.4.0"; + + assert_eq!(Ordering::Less, version_compare(a, b)); + } +} diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index 7c3f1a56e..056fbe034 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_split" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "split ~ (uutils) split input into output files" @@ -16,7 +16,7 @@ path = "src/split.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index 52ea88110..96bf63ffe 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stat" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "stat ~ (uutils) display FILE status" @@ -17,7 +17,7 @@ path = "src/stat.rs" [dependencies] clap = "2.33" time = "0.1.40" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "libc"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "libc"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 7a80e99ae..22ce4de6a 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "stdbuf ~ (uutils) run COMMAND with modified standard stream buffering" @@ -17,11 +17,11 @@ path = "src/stdbuf.rs" [dependencies] getopts = "0.2.18" tempfile = "3.1" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [build-dependencies] -libstdbuf = { version="0.0.4", package="uu_stdbuf_libstdbuf", path="src/libstdbuf" } +libstdbuf = { version="0.0.6", package="uu_stdbuf_libstdbuf", path="src/libstdbuf" } [[bin]] name = "stdbuf" diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index ac9c7230f..86eb09d46 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf_libstdbuf" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "stdbuf/libstdbuf ~ (uutils); dynamic library required for stdbuf" @@ -19,7 +19,7 @@ crate-type = ["cdylib", "rlib"] # XXX: note: the rlib is just to prevent Cargo f [dependencies] cpp = "0.5" libc = "0.2" -uucore = { version=">=0.0.7", package="uucore", path="../../../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../../../uucore_procs" } [build-dependencies] diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index a880ba1f7..64b6d3de9 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sum" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "sum ~ (uutils) display checksum and block counts for input" @@ -15,8 +15,8 @@ edition = "2018" path = "src/sum.rs" [dependencies] -getopts = "0.2.18" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +clap = "2.33" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 5c1652196..ed5655a3d 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -10,12 +10,16 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::fs::File; use std::io::{stdin, Read, Result}; use std::path::Path; static NAME: &str = "sum"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static USAGE: &str = + "[OPTION]... [FILE]...\nWith no FILE, or when FILE is -, read standard input."; +static SUMMARY: &str = "Checksum and count the blocks in a file."; fn bsd_sum(mut reader: Box) -> (usize, u16) { let mut buf = [0; 1024]; @@ -64,55 +68,59 @@ fn open(name: &str) -> Result> { match name { "-" => Ok(Box::new(stdin()) as Box), _ => { - let f = File::open(&Path::new(name))?; + let path = &Path::new(name); + if path.is_dir() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Is a directory", + )); + }; + if !path.metadata().is_ok() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "No such file or directory", + )); + }; + let f = File::open(path)?; Ok(Box::new(f) as Box) } } } +mod options { + pub static FILE: &str = "file"; + pub static BSD_COMPATIBLE: &str = "r"; + pub static SYSTEM_V_COMPATIBLE: &str = "sysv"; +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let mut opts = getopts::Options::new(); + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .usage(USAGE) + .about(SUMMARY) + .arg(Arg::with_name(options::FILE).multiple(true).hidden(true)) + .arg( + Arg::with_name(options::BSD_COMPATIBLE) + .short(options::BSD_COMPATIBLE) + .help("use the BSD compatible algorithm (default)"), + ) + .arg( + Arg::with_name(options::SYSTEM_V_COMPATIBLE) + .short("s") + .long(options::SYSTEM_V_COMPATIBLE) + .help("use the BSD compatible algorithm (default)"), + ) + .get_matches_from(args); - opts.optflag("r", "", "use the BSD compatible algorithm (default)"); - opts.optflag("s", "sysv", "use System V compatible algorithm"); - opts.optflag("h", "help", "show this help message"); - opts.optflag("v", "version", "print the version and exit"); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => crash!(1, "Invalid options\n{}", f), + let files: Vec = match matches.values_of(options::FILE) { + Some(v) => v.clone().map(|v| v.to_owned()).collect(), + None => vec!["-".to_owned()], }; - if matches.opt_present("help") { - let msg = format!( - "{0} {1} - -Usage: - {0} [OPTION]... [FILE]... - -Checksum and count the blocks in a file.", - NAME, VERSION - ); - println!( - "{}\nWith no FILE, or when FILE is -, read standard input.", - opts.usage(&msg) - ); - return 0; - } - if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - return 0; - } - - let sysv = matches.opt_present("sysv"); - - let files = if matches.free.is_empty() { - vec!["-".to_owned()] - } else { - matches.free - }; + let sysv = matches.is_present(options::SYSTEM_V_COMPATIBLE); let print_names = if sysv { files.len() > 1 || files[0] != "-" @@ -120,10 +128,15 @@ Checksum and count the blocks in a file.", files.len() > 1 }; + let mut exit_code = 0; for file in &files { let reader = match open(file) { Ok(f) => f, - _ => crash!(1, "unable to open file"), + Err(error) => { + show_error!("'{}' {}", file, error); + exit_code = 2; + continue; + } }; let (blocks, sum) = if sysv { sysv_sum(reader) @@ -138,5 +151,5 @@ Checksum and count the blocks in a file.", } } - 0 + exit_code } diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index 4cbf69a41..fcff6002e 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sync" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "sync ~ (uutils) synchronize cache writes to storage" @@ -17,7 +17,7 @@ path = "src/sync.rs" [dependencies] clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["wide"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["wide"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "std", "winbase", "winerror"] } diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 15e743006..3a530d0ce 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tac" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "tac ~ (uutils) concatenate and display input lines in reverse order" @@ -15,8 +15,8 @@ edition = "2018" path = "src/tac.rs" [dependencies] -getopts = "0.2.18" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +clap = "2.33" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 843babac9..68dae94e2 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -10,79 +10,77 @@ #[macro_use] extern crate uucore; -use std::fs::File; +use clap::{App, Arg}; use std::io::{stdin, stdout, BufReader, Read, Stdout, Write}; +use std::{fs::File, path::Path}; static NAME: &str = "tac"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static USAGE: &str = "[OPTION]... [FILE]..."; +static SUMMARY: &str = "Write each file to standard output, last line first."; + +mod options { + pub static BEFORE: &str = "before"; + pub static REGEX: &str = "regex"; + pub static SEPARATOR: &str = "separator"; + pub static 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!()) + .name(NAME) + .version(VERSION) + .usage(USAGE) + .about(SUMMARY) + .arg( + Arg::with_name(options::BEFORE) + .short("b") + .long(options::BEFORE) + .help("attach the separator before instead of after") + .takes_value(false), + ) + .arg( + Arg::with_name(options::REGEX) + .short("r") + .long(options::REGEX) + .help("interpret the sequence as a regular expression (NOT IMPLEMENTED)") + .takes_value(false), + ) + .arg( + Arg::with_name(options::SEPARATOR) + .short("s") + .long(options::SEPARATOR) + .help("use STRING as the separator instead of newline") + .takes_value(true), + ) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .get_matches_from(args); - opts.optflag( - "b", - "before", - "attach the separator before instead of after", - ); - opts.optflag( - "r", - "regex", - "interpret the sequence as a regular expression (NOT IMPLEMENTED)", - ); - opts.optopt( - "s", - "separator", - "use STRING as the separator instead of newline", - "STRING", - ); - 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") { - let msg = format!( - "{0} {1} - -Usage: - {0} [OPTION]... [FILE]... - -Write each file to standard output, last line first.", - NAME, VERSION - ); - - print!("{}", opts.usage(&msg)); - } else if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - } else { - let before = matches.opt_present("b"); - let regex = matches.opt_present("r"); - let separator = match matches.opt_str("s") { - Some(m) => { - if m.is_empty() { - crash!(1, "separator cannot be empty") - } else { - m - } + let before = matches.is_present(options::BEFORE); + let regex = matches.is_present(options::REGEX); + let separator = match matches.value_of(options::SEPARATOR) { + Some(m) => { + if m.is_empty() { + crash!(1, "separator cannot be empty") + } else { + m.to_owned() } - None => "\n".to_owned(), - }; - let files = if matches.free.is_empty() { - vec!["-".to_owned()] - } else { - matches.free - }; - tac(files, before, regex, &separator[..]); - } + } + None => "\n".to_owned(), + }; - 0 + let files: Vec = match matches.values_of(options::FILE) { + Some(v) => v.map(|v| v.to_owned()).collect(), + None => vec!["-".to_owned()], + }; + + tac(files, before, regex, &separator[..]) } -fn tac(filenames: Vec, before: bool, _: bool, separator: &str) { +fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { + let mut exit_code = 0; let mut out = stdout(); let sbytes = separator.as_bytes(); let slen = sbytes.len(); @@ -91,10 +89,19 @@ fn tac(filenames: Vec, before: bool, _: bool, separator: &str) { let mut file = BufReader::new(if filename == "-" { Box::new(stdin()) as Box } else { - match File::open(filename) { + let path = Path::new(filename); + if path.is_dir() || !path.metadata().is_ok() { + show_error!( + "failed to open '{}' for reading: No such file or directory", + filename + ); + continue; + } + match File::open(path) { Ok(f) => Box::new(f) as Box, Err(e) => { - show_warning!("failed to open '{}' for reading: {}", filename, e); + show_error!("failed to open '{}' for reading: {}", filename, e); + exit_code = 1; continue; } } @@ -102,7 +109,8 @@ fn tac(filenames: Vec, before: bool, _: bool, separator: &str) { let mut data = Vec::new(); if let Err(e) = file.read_to_end(&mut data) { - show_warning!("failed to read '{}': {}", filename, e); + show_error!("failed to read '{}': {}", filename, e); + exit_code = 1; continue; }; @@ -141,6 +149,8 @@ fn tac(filenames: Vec, before: bool, _: bool, separator: &str) { } show_line(&mut out, sbytes, &data[0..prev], before); } + + exit_code } fn show_line(out: &mut Stdout, sep: &[u8], dat: &[u8], before: bool) { diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index 6e51e3e35..d3f60e09b 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tail" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "tail ~ (uutils) display the last lines of input" @@ -17,7 +17,7 @@ path = "src/tail.rs" [dependencies] clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] } diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index 99a6ec23e..7ac81adc4 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tee" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "tee ~ (uutils) display input and copy to FILE" @@ -17,7 +17,8 @@ path = "src/tee.rs" [dependencies] clap = "2.33.3" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["libc"] } +retain_mut = "0.1.2" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index c54fa0d16..7c6a86b4c 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -9,6 +9,7 @@ extern crate uucore; use clap::{App, Arg}; +use retain_mut::RetainMut; use std::fs::OpenOptions; use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; use std::path::{Path, PathBuf}; @@ -93,18 +94,32 @@ fn tee(options: Options) -> Result<()> { if options.ignore_interrupts { ignore_interrupts()? } - let mut writers: Vec> = options + let mut writers: Vec = options .files .clone() .into_iter() - .map(|file| open(file, options.append)) + .map(|file| NamedWriter { + name: file.clone(), + inner: open(file, options.append), + }) .collect(); - writers.push(Box::new(stdout())); - let output = &mut MultiWriter { writers }; + + writers.insert( + 0, + NamedWriter { + name: "'standard output'".to_owned(), + inner: Box::new(stdout()), + }, + ); + + let mut output = MultiWriter::new(writers); let input = &mut NamedReader { inner: Box::new(stdin()) as Box, }; - if copy(input, output).is_err() || output.flush().is_err() { + + // TODO: replaced generic 'copy' call to be able to stop copying + // if all outputs are closed (due to errors) + if copy(input, &mut output).is_err() || output.flush().is_err() || output.error_occured() { Err(Error::new(ErrorKind::Other, "")) } else { Ok(()) @@ -112,7 +127,7 @@ fn tee(options: Options) -> Result<()> { } fn open(name: String, append: bool) -> Box { - let path = PathBuf::from(name); + let path = PathBuf::from(name.clone()); let inner: Box = { let mut options = OpenOptions::new(); let mode = if append { @@ -125,55 +140,68 @@ fn open(name: String, append: bool) -> Box { Err(_) => Box::new(sink()), } }; - Box::new(NamedWriter { inner, path }) as Box + Box::new(NamedWriter { inner, name }) as Box } struct MultiWriter { - writers: Vec>, + writers: Vec, + initial_len: usize, +} + +impl MultiWriter { + fn new(writers: Vec) -> Self { + Self { + initial_len: writers.len(), + writers, + } + } + fn error_occured(&self) -> bool { + self.writers.len() != self.initial_len + } } impl Write for MultiWriter { fn write(&mut self, buf: &[u8]) -> Result { - for writer in &mut self.writers { - writer.write_all(buf)?; - } + self.writers.retain_mut(|writer| { + let result = writer.write_all(buf); + match result { + Err(f) => { + show_info!("{}: {}", writer.name, f.to_string()); + false + } + _ => true, + } + }); Ok(buf.len()) } fn flush(&mut self) -> Result<()> { - for writer in &mut self.writers { - writer.flush()?; - } + self.writers.retain_mut(|writer| { + let result = writer.flush(); + match result { + Err(f) => { + show_info!("{}: {}", writer.name, f.to_string()); + false + } + _ => true, + } + }); Ok(()) } } struct NamedWriter { inner: Box, - path: PathBuf, + pub name: String, } impl Write for NamedWriter { fn write(&mut self, buf: &[u8]) -> Result { - match self.inner.write(buf) { - Err(f) => { - self.inner = Box::new(sink()) as Box; - show_warning!("{}: {}", self.path.display(), f.to_string()); - Err(f) - } - okay => okay, - } + self.inner.write(buf) } fn flush(&mut self) -> Result<()> { - match self.inner.flush() { - Err(f) => { - self.inner = Box::new(sink()) as Box; - show_warning!("{}: {}", self.path.display(), f.to_string()); - Err(f) - } - okay => okay, - } + self.inner.flush() } } @@ -185,7 +213,7 @@ impl Read for NamedReader { fn read(&mut self, buf: &mut [u8]) -> Result { match self.inner.read(buf) { Err(f) => { - show_warning!("{}: {}", Path::new("stdin").display(), f.to_string()); + show_info!("{}: {}", Path::new("stdin").display(), f.to_string()); Err(f) } okay => okay, diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index 6471c9e62..e1f6e62e7 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_test" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "test ~ (uutils) evaluate comparison and file type expressions" @@ -16,7 +16,7 @@ path = "src/test.rs" [dependencies] libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(target_os = "redox")'.dependencies] diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index c13a98d19..206a98c08 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_timeout" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "timeout ~ (uutils) run COMMAND with a DURATION time limit" @@ -15,11 +15,13 @@ edition = "2018" path = "src/timeout.rs" [dependencies] +clap = "2.33" getopts = "0.2.18" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } + [[bin]] name = "timeout" path = "src/main.rs" diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 0dd7c2016..23e2ec842 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -10,99 +10,154 @@ #[macro_use] extern crate uucore; +extern crate clap; + +use clap::{App, AppSettings, Arg}; use std::io::ErrorKind; use std::process::{Command, Stdio}; use std::time::Duration; use uucore::process::ChildExt; +use uucore::signals::signal_by_name_or_value; -static NAME: &str = "timeout"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; + +fn get_usage() -> String { + format!("{0} [OPTION]... [FILE]...", executable!()) +} const ERR_EXIT_STATUS: i32 = 125; -pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); +pub mod options { + pub static FOREGROUND: &str = "foreground"; + pub static KILL_AFTER: &str = "kill-after"; + pub static SIGNAL: &str = "signal"; + pub static VERSION: &str = "version"; + pub static PRESERVE_STATUS: &str = "preserve-status"; - let program = args[0].clone(); + // Positional args. + pub static DURATION: &str = "duration"; + pub static COMMAND: &str = "command"; + pub static ARGS: &str = "args"; +} - let mut opts = getopts::Options::new(); - opts.optflag( - "", - "preserve-status", - "exit with the same status as COMMAND, even when the command times out", - ); - opts.optflag("", "foreground", "when not running timeout directly from a shell prompt, allow COMMAND to read from the TTY and get TTY signals; in this mode, children of COMMAND will not be timed out"); - opts.optopt("k", "kill-after", "also send a KILL signal if COMMAND is still running this long after the initial signal was sent", "DURATION"); - opts.optflag("s", "signal", "specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals"); - 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!(ERR_EXIT_STATUS, "{}", f), - }; - if matches.opt_present("help") { - print!( - "{} {} +struct Config { + foreground: bool, + kill_after: Duration, + signal: usize, + duration: Duration, + preserve_status: bool, -Usage: - {} [OPTION] DURATION COMMAND [ARG]... + command: String, + command_args: Vec, +} -{}", - NAME, - VERSION, - program, - &opts.usage("Start COMMAND, and kill it if still running after DURATION.") - ); - } else if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - } else if matches.free.len() < 2 { - show_error!("missing an argument"); - show_error!("for help, try '{0} --help'", program); - return ERR_EXIT_STATUS; - } else { - let status = matches.opt_present("preserve-status"); - let foreground = matches.opt_present("foreground"); - let kill_after = match matches.opt_str("kill-after") { - Some(tstr) => match uucore::parse_time::from_str(&tstr) { - Ok(time) => time, - Err(f) => { - show_error!("{}", f); - return ERR_EXIT_STATUS; +impl Config { + fn from(options: clap::ArgMatches) -> Config { + let signal = match options.value_of(options::SIGNAL) { + Some(signal_) => { + let signal_result = signal_by_name_or_value(&signal_); + match signal_result { + None => { + unreachable!("invalid signal '{}'", signal_); + } + Some(signal_value) => signal_value, } - }, + } + _ => uucore::signals::signal_by_name_or_value("TERM").unwrap(), + }; + + let kill_after: Duration = match options.value_of(options::KILL_AFTER) { + Some(time) => uucore::parse_time::from_str(&time).unwrap(), None => Duration::new(0, 0), }; - let signal = match matches.opt_str("signal") { - Some(sigstr) => match uucore::signals::signal_by_name_or_value(&sigstr) { - Some(sig) => sig, - None => { - show_error!("invalid signal '{}'", sigstr); - return ERR_EXIT_STATUS; - } - }, - None => uucore::signals::signal_by_name_or_value("TERM").unwrap(), - }; - let duration = match uucore::parse_time::from_str(&matches.free[0]) { - Ok(time) => time, - Err(f) => { - show_error!("{}", f); - return ERR_EXIT_STATUS; - } - }; - return timeout( - &matches.free[1], - &matches.free[2..], - duration, - signal, - kill_after, - foreground, - status, - ); - } - 0 + let duration: Duration = + uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()).unwrap(); + + let preserve_status: bool = options.is_present(options::PRESERVE_STATUS); + let foreground = options.is_present(options::FOREGROUND); + + let command: String = options.value_of(options::COMMAND).unwrap().to_string(); + + let command_args: Vec = match options.values_of(options::ARGS) { + Some(values) => values.map(|x| x.to_owned()).collect(), + None => vec![], + }; + + Config { + foreground, + kill_after, + signal, + duration, + preserve_status, + command, + command_args, + } + } } +pub fn uumain(args: impl uucore::Args) -> i32 { + let args = args.collect_str(); + let usage = get_usage(); + + let app = App::new("timeout") + .version(VERSION) + .usage(&usage[..]) + .about(ABOUT) + .arg( + Arg::with_name(options::FOREGROUND) + .long(options::FOREGROUND) + .help("when not running timeout directly from a shell prompt, allow COMMAND to read from the TTY and get TTY signals; in this mode, children of COMMAND will not be timed out") + ) + .arg( + Arg::with_name(options::KILL_AFTER) + .short("k") + .takes_value(true)) + .arg( + Arg::with_name(options::PRESERVE_STATUS) + .long(options::PRESERVE_STATUS) + .help("exit with the same status as COMMAND, even when the command times out") + ) + .arg( + Arg::with_name(options::SIGNAL) + .short("s") + .long(options::SIGNAL) + .help("specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals") + .takes_value(true) + ) + .arg( + Arg::with_name(options::DURATION) + .index(1) + .required(true) + ) + .arg( + Arg::with_name(options::COMMAND) + .index(2) + .required(true) + ) + .arg( + Arg::with_name(options::ARGS).multiple(true) + ) + .setting(AppSettings::TrailingVarArg); + + let matches = app.get_matches_from(args); + + let config = Config::from(matches); + timeout( + &config.command, + &config.command_args, + config.duration, + config.signal, + config.kill_after, + config.foreground, + config.preserve_status, + ) +} + +/// TODO: Improve exit codes, and make them consistent with the GNU Coreutil +/// exit codes. + fn timeout( cmdname: &str, args: &[String], @@ -126,10 +181,10 @@ fn timeout( Err(err) => { show_error!("failed to execute process: {}", err); if err.kind() == ErrorKind::NotFound { - // XXX: not sure which to use + // FIXME: not sure which to use return 127; } else { - // XXX: this may not be 100% correct... + // FIXME: this may not be 100% correct... return 126; } } diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index fc021c096..0608a7b7c 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_touch" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "touch ~ (uutils) change FILE timestamps" @@ -18,7 +18,7 @@ path = "src/touch.rs" filetime = "0.2.1" clap = "2.33" time = "0.1.40" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["libc"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index 918698e24..a3d066bfb 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tr" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "tr ~ (uutils) translate characters within input and display" @@ -17,8 +17,8 @@ path = "src/tr.rs" [dependencies] bit-set = "0.5.0" fnv = "1.0.5" -getopts = "0.2.18" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +clap = "2.33" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index d60d2559a..b94b11b9d 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -16,16 +16,27 @@ extern crate uucore; mod expand; use bit_set::BitSet; +use clap::{App, Arg}; use fnv::FnvHashMap; -use getopts::Options; use std::io::{stdin, stdout, BufRead, BufWriter, Write}; use crate::expand::ExpandSet; static NAME: &str = "tr"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "translate or delete characters"; +static LONG_HELP: &str = "Translate, squeeze, and/or delete characters from standard input, +writing to standard output."; const BUFFER_LEN: usize = 1024; +mod options { + pub const COMPLEMENT: &str = "complement"; + pub const DELETE: &str = "delete"; + pub const SQUEEZE: &str = "squeeze-repeats"; + pub const TRUNCATE: &str = "truncate"; + pub const SETS: &str = "sets"; +} + trait SymbolTranslator { fn translate(&self, c: char, prev_c: char) -> Option; } @@ -170,56 +181,60 @@ fn translate_input( } } -fn usage(opts: &Options) { - println!("{} {}", NAME, VERSION); - println!(); - println!("Usage:"); - println!(" {} [OPTIONS] SET1 [SET2]", NAME); - println!(); - println!("{}", opts.usage("Translate or delete characters.")); +fn get_usage() -> String { + format!("{} [OPTION]... SET1 [SET2]", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let usage = get_usage(); - let mut opts = Options::new(); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::COMPLEMENT) + .short("C") + .short("c") + .long(options::COMPLEMENT) + .help("use the complement of SET1"), + ) + .arg( + Arg::with_name(options::DELETE) + .short("d") + .long(options::DELETE) + .help("delete characters in SET1, do not translate"), + ) + .arg( + Arg::with_name(options::SQUEEZE) + .long(options::SQUEEZE) + .short("s") + .help( + "replace each sequence of a repeated character that is + listed in the last specified SET, with a single occurrence + of that character", + ), + ) + .arg( + Arg::with_name(options::TRUNCATE) + .long(options::TRUNCATE) + .short("t") + .help("first truncate SET1 to length of SET2"), + ) + .arg(Arg::with_name(options::SETS).multiple(true)) + .get_matches_from(args); - opts.optflag("c", "complement", "use the complement of SET1"); - opts.optflag("C", "", "same as -c"); - opts.optflag("d", "delete", "delete characters in SET1"); - opts.optflag("h", "help", "display this help and exit"); - opts.optflag("s", "squeeze", "replace each sequence of a repeated character that is listed in the last specified SET, with a single occurrence of that character"); - opts.optflag( - "t", - "truncate-set1", - "first truncate SET1 to length of SET2", - ); - opts.optflag("V", "version", "output version information and exit"); + let delete_flag = matches.is_present(options::DELETE); + let complement_flag = matches.is_present(options::COMPLEMENT); + let squeeze_flag = matches.is_present(options::SQUEEZE); + let truncate_flag = matches.is_present(options::TRUNCATE); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(err) => { - show_error!("{}", err); - return 1; - } + let sets: Vec = match matches.values_of(options::SETS) { + Some(v) => v.map(|v| v.to_string()).collect(), + None => vec![], }; - if matches.opt_present("help") { - usage(&opts); - return 0; - } - - if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - return 0; - } - - let dflag = matches.opt_present("d"); - let cflag = matches.opts_present(&["c".to_owned(), "C".to_owned()]); - let sflag = matches.opt_present("s"); - let tflag = matches.opt_present("t"); - let sets = matches.free; - if sets.is_empty() { show_error!( "missing operand\nTry `{} --help` for more information.", @@ -228,7 +243,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 1; } - if !(dflag || sflag) && sets.len() < 2 { + if !(delete_flag || squeeze_flag) && sets.len() < 2 { show_error!( "missing operand after ‘{}’\nTry `{} --help` for more information.", sets[0], @@ -237,7 +252,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 1; } - if cflag && !dflag && !sflag { + if complement_flag && !delete_flag && !squeeze_flag { show_error!("-c is only supported with -d or -s"); return 1; } @@ -249,21 +264,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mut buffered_stdout = BufWriter::new(locked_stdout); let set1 = ExpandSet::new(sets[0].as_ref()); - if dflag { - if sflag { + if delete_flag { + if squeeze_flag { let set2 = ExpandSet::new(sets[1].as_ref()); - let op = DeleteAndSqueezeOperation::new(set1, set2, cflag); + let op = DeleteAndSqueezeOperation::new(set1, set2, complement_flag); translate_input(&mut locked_stdin, &mut buffered_stdout, op); } else { - let op = DeleteOperation::new(set1, cflag); + let op = DeleteOperation::new(set1, complement_flag); translate_input(&mut locked_stdin, &mut buffered_stdout, op); } - } else if sflag { - let op = SqueezeOperation::new(set1, cflag); + } else if squeeze_flag { + let op = SqueezeOperation::new(set1, complement_flag); translate_input(&mut locked_stdin, &mut buffered_stdout, op); } else { let mut set2 = ExpandSet::new(sets[1].as_ref()); - let op = TranslateOperation::new(set1, &mut set2, tflag); + let op = TranslateOperation::new(set1, &mut set2, truncate_flag); translate_input(&mut locked_stdin, &mut buffered_stdout, op) } diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index a73bfddd5..9f13318fd 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_true" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "true ~ (uutils) do nothing and succeed" @@ -15,7 +15,7 @@ edition = "2018" path = "src/true.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index 4f770e666..e2c0afadc 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_truncate" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "truncate ~ (uutils) truncate (or extend) FILE to SIZE" @@ -16,7 +16,7 @@ path = "src/truncate.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index d870e0155..37f543012 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tsort" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "tsort ~ (uutils) topologically sort input (partially ordered) pairs" @@ -16,7 +16,7 @@ path = "src/tsort.rs" [dependencies] clap= "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index 9912a3b9a..7be27a900 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tty" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "tty ~ (uutils) display the name of the terminal connected to standard input" @@ -15,9 +15,9 @@ edition = "2018" path = "src/tty.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 = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 110f76d30..18d69db46 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -12,68 +12,62 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::ffi::CStr; use uucore::fs::is_stdin_interactive; -extern "C" { - fn ttyname(filedesc: libc::c_int) -> *const libc::c_char; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Print the file name of the terminal connected to standard input."; + +mod options { + pub const SILENT: &str = "silent"; } -static NAME: &str = "tty"; -static VERSION: &str = env!("CARGO_PKG_VERSION"); +fn get_usage() -> String { + format!("{0} [OPTION]...", executable!()) +} 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[..]) + .arg( + Arg::with_name(options::SILENT) + .long(options::SILENT) + .visible_alias("quiet") + .short("s") + .help("print nothing, only return an exit status") + .required(false), + ) + .get_matches_from(args); - opts.optflag("s", "silent", "print nothing, only return an exit status"); - opts.optflag("h", "help", "display this help and exit"); - opts.optflag("V", "version", "output version information and exit"); + let silent = matches.is_present(options::SILENT); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => crash!(2, "{}", f), + // Call libc function ttyname + let tty = unsafe { + let ptr = libc::ttyname(libc::STDIN_FILENO); + if !ptr.is_null() { + String::from_utf8_lossy(CStr::from_ptr(ptr).to_bytes()).to_string() + } else { + "".to_owned() + } }; - if matches.opt_present("help") { - println!("{} {}", NAME, VERSION); - println!(); - println!("Usage:"); - println!(" {} [OPTION]...", NAME); - println!(); - print!( - "{}", - opts.usage("Print the file name of the terminal connected to standard input.") - ); - } else if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - } else { - let silent = matches.opt_present("s"); - - let tty = unsafe { - let ptr = ttyname(libc::STDIN_FILENO); - if !ptr.is_null() { - String::from_utf8_lossy(CStr::from_ptr(ptr).to_bytes()).to_string() - } else { - "".to_owned() - } - }; - - if !silent { - if !tty.chars().all(|c| c.is_whitespace()) { - println!("{}", tty); - } else { - println!("not a tty"); - } - } - - return if is_stdin_interactive() { - libc::EXIT_SUCCESS + if !silent { + if !tty.chars().all(|c| c.is_whitespace()) { + println!("{}", tty); } else { - libc::EXIT_FAILURE - }; + println!("not a tty"); + } } - 0 + return if is_stdin_interactive() { + libc::EXIT_SUCCESS + } else { + libc::EXIT_FAILURE + }; } diff --git a/src/uu/uname/Cargo.toml b/src/uu/uname/Cargo.toml index 87754f45a..9707d8444 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uname" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "uname ~ (uutils) display system information" @@ -17,7 +17,7 @@ path = "src/uname.rs" [dependencies] clap = "2.33" platform-info = "0.1" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index d66d335bf..e39dd87ca 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unexpand" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "unexpand ~ (uutils) convert input spaces to tabs" @@ -17,7 +17,7 @@ path = "src/unexpand.rs" [dependencies] clap = "2.33" unicode-width = "0.1.5" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index d3cf4309d..3fe89b450 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uniq" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "uniq ~ (uutils) filter identical adjacent lines from input" @@ -16,7 +16,9 @@ path = "src/uniq.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +strum = "0.20" +strum_macros = "0.20" +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index a1809f0f0..a61a78a61 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -13,6 +13,7 @@ use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Result, Write}; use std::path::Path; use std::str::FromStr; +use strum_macros::{AsRefStr, EnumString}; static ABOUT: &str = "Report or omit repeated lines."; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -26,14 +27,18 @@ pub mod options { pub static SKIP_CHARS: &str = "skip-chars"; pub static UNIQUE: &str = "unique"; pub static ZERO_TERMINATED: &str = "zero-terminated"; + pub static GROUP: &str = "group"; } static ARG_FILES: &str = "files"; -#[derive(PartialEq)] +#[derive(PartialEq, Clone, Copy, AsRefStr, EnumString)] +#[strum(serialize_all = "snake_case")] enum Delimiters { + Append, Prepend, Separate, + Both, None, } @@ -58,22 +63,33 @@ impl Uniq { ) { let mut lines: Vec = vec![]; let mut first_line_printed = false; - let delimiters = &self.delimiters; + let delimiters = self.delimiters; let line_terminator = self.get_line_terminator(); + // Don't print any delimiting lines before, after or between groups if delimiting method is 'none' + let no_delimiters = delimiters == Delimiters::None; + // The 'prepend' and 'both' delimit methods will cause output to start with delimiter line + let prepend_delimiter = delimiters == Delimiters::Prepend || delimiters == Delimiters::Both; + // The 'append' and 'both' delimit methods will cause output to end with delimiter line + let append_delimiter = delimiters == Delimiters::Append || delimiters == Delimiters::Both; for line in reader.split(line_terminator).map(get_line_string) { if !lines.is_empty() && self.cmp_keys(&lines[0], &line) { - let print_delimiter = delimiters == &Delimiters::Prepend - || (delimiters == &Delimiters::Separate && first_line_printed); + // Print delimiter if delimit method is not 'none' and any line has been output + // before or if we need to start output with delimiter + let print_delimiter = !no_delimiters && (prepend_delimiter || first_line_printed); first_line_printed |= self.print_lines(writer, &lines, print_delimiter); lines.truncate(0); } lines.push(line); } if !lines.is_empty() { - let print_delimiter = delimiters == &Delimiters::Prepend - || (delimiters == &Delimiters::Separate && first_line_printed); - self.print_lines(writer, &lines, print_delimiter); + // Print delimiter if delimit method is not 'none' and any line has been output + // before or if we need to start output with delimiter + let print_delimiter = !no_delimiters && (prepend_delimiter || first_line_printed); + first_line_printed |= self.print_lines(writer, &lines, print_delimiter); + } + if append_delimiter && first_line_printed { + crash_if_err!(1, writer.write_all(&[line_terminator])); } } @@ -233,10 +249,30 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::ALL_REPEATED) .short("D") .long(options::ALL_REPEATED) - .possible_values(&["none", "prepend", "separate"]) - .help("print all duplicate lines. Delimiting is done with blank lines") + .possible_values(&[ + Delimiters::None.as_ref(), Delimiters::Prepend.as_ref(), Delimiters::Separate.as_ref() + ]) + .help("print all duplicate lines. Delimiting is done with blank lines. [default: none]") .value_name("delimit-method") - .default_value("none"), + .min_values(0) + .max_values(1), + ) + .arg( + Arg::with_name(options::GROUP) + .long(options::GROUP) + .possible_values(&[ + Delimiters::Separate.as_ref(), Delimiters::Prepend.as_ref(), + Delimiters::Append.as_ref(), Delimiters::Both.as_ref() + ]) + .help("show all items, separating groups with an empty line. [default: separate]") + .value_name("group-method") + .min_values(0) + .max_values(1) + .conflicts_with_all(&[ + options::REPEATED, + options::ALL_REPEATED, + options::UNIQUE, + ]), ) .arg( Arg::with_name(options::CHECK_CHARS) @@ -314,17 +350,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let uniq = Uniq { repeats_only: matches.is_present(options::REPEATED) - || matches.occurrences_of(options::ALL_REPEATED) > 0, + || matches.is_present(options::ALL_REPEATED), uniques_only: matches.is_present(options::UNIQUE), - all_repeated: matches.occurrences_of(options::ALL_REPEATED) > 0, - delimiters: match matches.value_of(options::ALL_REPEATED).map(String::from) { - Some(ref opt_arg) if opt_arg != "none" => match &(*opt_arg.as_str()) { - "prepend" => Delimiters::Prepend, - "separate" => Delimiters::Separate, - _ => crash!(1, "Incorrect argument for all-repeated: {}", opt_arg), - }, - _ => Delimiters::None, - }, + all_repeated: matches.is_present(options::ALL_REPEATED) + || matches.is_present(options::GROUP), + delimiters: get_delimiter(&matches), show_counts: matches.is_present(options::COUNT), skip_fields: opt_parsed(options::SKIP_FIELDS, &matches), slice_start: opt_parsed(options::SKIP_CHARS, &matches), @@ -340,6 +370,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } +fn get_delimiter(matches: &ArgMatches) -> Delimiters { + let value = matches + .value_of(options::ALL_REPEATED) + .or_else(|| matches.value_of(options::GROUP)); + if let Some(delimiter_arg) = value { + crash_if_err!(1, Delimiters::from_str(delimiter_arg)) + } else if matches.is_present(options::GROUP) { + Delimiters::Separate + } else { + Delimiters::None + } +} + fn open_input_file(in_file_name: String) -> BufReader> { let in_file = if in_file_name == "-" { Box::new(stdin()) as Box diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index 68edec54d..b193bd1b5 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unlink" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "unlink ~ (uutils) remove a (file system) link to FILE" @@ -17,7 +17,7 @@ path = "src/unlink.rs" [dependencies] getopts = "0.2.18" libc = "0.2.42" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index d66acc77f..1136e6420 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uptime" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "uptime ~ (uutils) display dynamic system information" @@ -17,7 +17,7 @@ path = "src/uptime.rs" [dependencies] chrono = "0.4" clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["libc", "utmpx"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc", "utmpx"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 670d7845b..418b8317f 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -19,8 +19,8 @@ use uucore::libc::time_t; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Display the current time, the length of time the system has been up,\n\ -the number of users on the system, and the average number of jobs\n\ -in the run queue over the last 1, 5 and 15 minutes."; + the number of users on the system, and the average number of jobs\n\ + in the run queue over the last 1, 5 and 15 minutes."; pub mod options { pub static SINCE: &str = "since"; } diff --git a/src/uu/users/Cargo.toml b/src/uu/users/Cargo.toml index ed6f110b6..84da13020 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_users" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "users ~ (uutils) display names of currently logged-in users" @@ -16,7 +16,7 @@ path = "src/users.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["utmpx"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 894ac44dd..8ae79dc08 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_wc" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "wc ~ (uutils) display newline, word, and byte counts for input" @@ -16,8 +16,13 @@ path = "src/wc.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +thiserror = "1.0" + +[target.'cfg(unix)'.dependencies] +nix = "0.20" +libc = "0.2" [[bin]] name = "wc" diff --git a/src/uu/wc/src/count_bytes.rs b/src/uu/wc/src/count_bytes.rs new file mode 100644 index 000000000..dc90f67cc --- /dev/null +++ b/src/uu/wc/src/count_bytes.rs @@ -0,0 +1,95 @@ +use super::{WcResult, WordCountable}; + +#[cfg(any(target_os = "linux", target_os = "android"))] +use std::fs::OpenOptions; +use std::io::ErrorKind; + +#[cfg(unix)] +use libc::S_IFREG; +#[cfg(unix)] +use nix::sys::stat::fstat; +#[cfg(any(target_os = "linux", target_os = "android"))] +use std::os::unix::io::{AsRawFd, RawFd}; + +#[cfg(any(target_os = "linux", target_os = "android"))] +use libc::S_IFIFO; +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::fcntl::{splice, SpliceFFlags}; +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::unistd::pipe; + +const BUF_SIZE: usize = 16384; + +/// This is a Linux-specific function to count the number of bytes using the +/// `splice` system call, which is faster than using `read`. +#[inline] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn count_bytes_using_splice(fd: RawFd) -> nix::Result { + let null_file = OpenOptions::new() + .write(true) + .open("/dev/null") + .map_err(|_| nix::Error::last())?; + let null = null_file.as_raw_fd(); + let (pipe_rd, pipe_wr) = pipe()?; + + let mut byte_count = 0; + loop { + let res = splice(fd, None, pipe_wr, None, BUF_SIZE, SpliceFFlags::empty())?; + if res == 0 { + break; + } + byte_count += res; + splice(pipe_rd, None, null, None, res, SpliceFFlags::empty())?; + } + + Ok(byte_count) +} + +/// In the special case where we only need to count the number of bytes. There +/// are several optimizations we can do: +/// 1. On Unix, we can simply `stat` the file if it is regular. +/// 2. On Linux -- if the above did not work -- we can use splice to count +/// the number of bytes if the file is a FIFO. +/// 3. Otherwise, we just read normally, but without the overhead of counting +/// other things such as lines and words. +#[inline] +pub(crate) fn count_bytes_fast(handle: &mut T) -> WcResult { + #[cfg(unix)] + { + let fd = handle.as_raw_fd(); + match fstat(fd) { + Ok(stat) => { + // If the file is regular, then the `st_size` should hold + // the file's size in bytes. + if (stat.st_mode & S_IFREG) != 0 { + return Ok(stat.st_size as usize); + } + #[cfg(any(target_os = "linux", target_os = "android"))] + { + // Else, if we're on Linux and our file is a FIFO pipe + // (or stdin), we use splice to count the number of bytes. + if (stat.st_mode & S_IFIFO) != 0 { + if let Ok(n) = count_bytes_using_splice(fd) { + return Ok(n); + } + } + } + } + _ => {} + } + } + + // Fall back on `read`, but without the overhead of counting words and lines. + let mut buf = [0 as u8; BUF_SIZE]; + let mut byte_count = 0; + loop { + match handle.read(&mut buf) { + Ok(0) => return Ok(byte_count), + Ok(n) => { + byte_count += n; + } + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e.into()), + } + } +} diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 972802f81..22463caa4 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -10,14 +10,31 @@ #[macro_use] extern crate uucore; -use clap::{App, Arg, ArgMatches}; +mod count_bytes; +use count_bytes::count_bytes_fast; +use clap::{App, Arg, ArgMatches}; +use thiserror::Error; + +use std::cmp::max; use std::fs::File; -use std::io::{stdin, BufRead, BufReader, Read}; +use std::io::{self, BufRead, BufReader, Read, StdinLock, Write}; +use std::ops::{Add, AddAssign}; +#[cfg(unix)] +use std::os::unix::io::AsRawFd; use std::path::Path; -use std::result::Result as StdResult; use std::str::from_utf8; +#[derive(Error, Debug)] +pub enum WcError { + #[error("{0}")] + Io(#[from] io::Error), + #[error("Expected a file, found directory {0}")] + IsDirectory(String), +} + +type WcResult = Result; + struct Settings { show_bytes: bool, show_chars: bool, @@ -53,10 +70,46 @@ impl Settings { show_max_line_length: false, } } + + fn number_enabled(&self) -> u32 { + let mut result = 0; + result += self.show_bytes as u32; + result += self.show_chars as u32; + result += self.show_lines as u32; + result += self.show_max_line_length as u32; + result += self.show_words as u32; + result + } } -struct Result { - title: String, +#[cfg(unix)] +trait WordCountable: AsRawFd + Read { + type Buffered: BufRead; + fn get_buffered(self) -> Self::Buffered; +} +#[cfg(not(unix))] +trait WordCountable: Read { + type Buffered: BufRead; + fn get_buffered(self) -> Self::Buffered; +} + +impl WordCountable for StdinLock<'_> { + type Buffered = Self; + + fn get_buffered(self) -> Self::Buffered { + self + } +} +impl WordCountable for File { + type Buffered = BufReader; + + fn get_buffered(self) -> Self::Buffered { + BufReader::new(self) + } +} + +#[derive(Debug, Default, Copy, Clone)] +struct WordCount { bytes: usize, chars: usize, lines: usize, @@ -64,6 +117,45 @@ struct Result { max_line_length: usize, } +impl Add for WordCount { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + bytes: self.bytes + other.bytes, + chars: self.chars + other.chars, + lines: self.lines + other.lines, + words: self.words + other.words, + max_line_length: max(self.max_line_length, other.max_line_length), + } + } +} + +impl AddAssign for WordCount { + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +impl WordCount { + fn with_title<'a>(self, title: &'a str) -> TitledWordCount<'a> { + return TitledWordCount { + title: title, + count: self, + }; + } +} + +/// This struct supplements the actual word count with a title that is displayed +/// to the user at the end of the program. +/// The reason we don't simply include title in the `WordCount` struct is that +/// it would result in unneccesary copying of `String`. +#[derive(Debug, Default, Clone)] +struct TitledWordCount<'a> { + title: &'a str, + count: WordCount, +} + static ABOUT: &str = "Display newline, word, and byte counts for each FILE, and a total line if more than one FILE is specified."; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -137,12 +229,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let settings = Settings::new(&matches); - match wc(files, &settings) { - Ok(()) => ( /* pass */ ), - Err(e) => return e, + if wc(files, &settings).is_ok() { + 0 + } else { + 1 } - - 0 } const CR: u8 = b'\r'; @@ -157,151 +248,187 @@ fn is_word_separator(byte: u8) -> bool { byte == SPACE || byte == TAB || byte == CR || byte == SYN || byte == FF } -fn wc(files: Vec, settings: &Settings) -> StdResult<(), i32> { - let mut total_line_count: usize = 0; - let mut total_word_count: usize = 0; - let mut total_char_count: usize = 0; - let mut total_byte_count: usize = 0; - let mut total_longest_line_length: usize = 0; - - let mut results = vec![]; - let mut max_width: usize = 0; +fn word_count_from_reader( + mut reader: T, + settings: &Settings, + path: &String, +) -> WcResult { + let only_count_bytes = settings.show_bytes + && (!(settings.show_chars + || settings.show_lines + || settings.show_max_line_length + || settings.show_words)); + if only_count_bytes { + return Ok(WordCount { + bytes: count_bytes_fast(&mut reader)?, + ..WordCount::default() + }); + } // we do not need to decode the byte stream if we're only counting bytes/newlines let decode_chars = settings.show_chars || settings.show_words || settings.show_max_line_length; + let mut line_count: usize = 0; + let mut word_count: usize = 0; + let mut byte_count: usize = 0; + let mut char_count: usize = 0; + let mut longest_line_length: usize = 0; + let mut raw_line = Vec::new(); + let mut ends_lf: bool; + + // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. + // hence the option wrapped in a result here + let mut buffered_reader = reader.get_buffered(); + loop { + match buffered_reader.read_until(LF, &mut raw_line) { + Ok(n) => { + if n == 0 { + break; + } + } + Err(ref e) => { + if !raw_line.is_empty() { + show_warning!("Error while reading {}: {}", path, e); + } else { + break; + } + } + }; + + // GNU 'wc' only counts lines that end in LF as lines + ends_lf = *raw_line.last().unwrap() == LF; + line_count += ends_lf as usize; + + byte_count += raw_line.len(); + + if decode_chars { + // try and convert the bytes to UTF-8 first + let current_char_count; + match from_utf8(&raw_line[..]) { + Ok(line) => { + word_count += line.split_whitespace().count(); + current_char_count = line.chars().count(); + } + Err(..) => { + word_count += raw_line.split(|&x| is_word_separator(x)).count(); + current_char_count = raw_line.iter().filter(|c| c.is_ascii()).count() + } + } + char_count += current_char_count; + if current_char_count > longest_line_length { + // -L is a GNU 'wc' extension so same behavior on LF + longest_line_length = current_char_count - (ends_lf as usize); + } + } + + raw_line.truncate(0); + } + + Ok(WordCount { + bytes: byte_count, + chars: char_count, + lines: line_count, + words: word_count, + max_line_length: longest_line_length, + }) +} + +fn word_count_from_path(path: &String, settings: &Settings) -> WcResult { + if path == "-" { + let stdin = io::stdin(); + let stdin_lock = stdin.lock(); + return Ok(word_count_from_reader(stdin_lock, settings, path)?); + } else { + let path_obj = Path::new(path); + if path_obj.is_dir() { + return Err(WcError::IsDirectory(path.clone())); + } else { + let file = File::open(path)?; + return Ok(word_count_from_reader(file, settings, path)?); + } + } +} + +fn wc(files: Vec, settings: &Settings) -> Result<(), u32> { + let mut total_word_count = WordCount::default(); + let mut results = vec![]; + let mut max_width: usize = 0; + let mut error_count = 0; + + let num_files = files.len(); + for path in &files { - let mut reader = open(&path[..])?; - - let mut line_count: usize = 0; - let mut word_count: usize = 0; - let mut byte_count: usize = 0; - let mut char_count: usize = 0; - let mut longest_line_length: usize = 0; - let mut raw_line = Vec::new(); - let mut ends_lf: bool; - // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. - // hence the option wrapped in a result here - while match reader.read_until(LF, &mut raw_line) { - Ok(n) if n > 0 => true, - Err(ref e) if !raw_line.is_empty() => { - show_warning!("Error while reading {}: {}", path, e); - !raw_line.is_empty() - } - _ => false, - } { - // GNU 'wc' only counts lines that end in LF as lines - ends_lf = *raw_line.last().unwrap() == LF; - line_count += ends_lf as usize; - - byte_count += raw_line.len(); - - if decode_chars { - // try and convert the bytes to UTF-8 first - let current_char_count; - match from_utf8(&raw_line[..]) { - Ok(line) => { - word_count += line.split_whitespace().count(); - current_char_count = line.chars().count(); - } - Err(..) => { - word_count += raw_line.split(|&x| is_word_separator(x)).count(); - current_char_count = raw_line.iter().filter(|c| c.is_ascii()).count() - } - } - char_count += current_char_count; - if current_char_count > longest_line_length { - // -L is a GNU 'wc' extension so same behavior on LF - longest_line_length = current_char_count - (ends_lf as usize); - } - } - - raw_line.truncate(0); - } - - results.push(Result { - title: path.clone(), - bytes: byte_count, - chars: char_count, - lines: line_count, - words: word_count, - max_line_length: longest_line_length, + let word_count = word_count_from_path(&path, settings).unwrap_or_else(|err| { + show_error!("{}", err); + error_count += 1; + WordCount::default() }); - - total_line_count += line_count; + max_width = max(max_width, word_count.bytes.to_string().len() + 1); total_word_count += word_count; - total_char_count += char_count; - total_byte_count += byte_count; - - if longest_line_length > total_longest_line_length { - total_longest_line_length = longest_line_length; - } - - // used for formatting - max_width = total_byte_count.to_string().len() + 1; + results.push(word_count.with_title(path)); } for result in &results { - print_stats(settings, &result, max_width); + if let Err(err) = print_stats(settings, &result, max_width) { + show_warning!("failed to print result for {}: {}", result.title, err); + error_count += 1; + } } - if files.len() > 1 { - let result = Result { - title: "total".to_owned(), - bytes: total_byte_count, - chars: total_char_count, - lines: total_line_count, - words: total_word_count, - max_line_length: total_longest_line_length, - }; - print_stats(settings, &result, max_width); + if num_files > 1 { + let total_result = total_word_count.with_title("total"); + if let Err(err) = print_stats(settings, &total_result, max_width) { + show_warning!("failed to print total: {}", err); + error_count += 1; + } + } + + if error_count == 0 { + Ok(()) + } else { + Err(error_count) + } +} + +fn print_stats( + settings: &Settings, + result: &TitledWordCount, + mut min_width: usize, +) -> WcResult<()> { + let stdout = io::stdout(); + let mut stdout_lock = stdout.lock(); + + if settings.number_enabled() <= 1 { + // Prevent a leading space in case we only need to display a single + // number. + min_width = 0; + } + + if settings.show_lines { + write!(stdout_lock, "{:1$}", result.count.lines, min_width)?; + } + if settings.show_words { + write!(stdout_lock, "{:1$}", result.count.words, min_width)?; + } + if settings.show_bytes { + write!(stdout_lock, "{:1$}", result.count.bytes, min_width)?; + } + if settings.show_chars { + write!(stdout_lock, "{:1$}", result.count.chars, min_width)?; + } + if settings.show_max_line_length { + write!( + stdout_lock, + "{:1$}", + result.count.max_line_length, min_width + )?; + } + + if result.title == "-" { + writeln!(stdout_lock, "")?; + } else { + writeln!(stdout_lock, " {}", result.title)?; } Ok(()) } - -fn print_stats(settings: &Settings, result: &Result, max_width: usize) { - if settings.show_lines { - print!("{:1$}", result.lines, max_width); - } - if settings.show_words { - print!("{:1$}", result.words, max_width); - } - if settings.show_bytes { - print!("{:1$}", result.bytes, max_width); - } - if settings.show_chars { - print!("{:1$}", result.chars, max_width); - } - if settings.show_max_line_length { - print!("{:1$}", result.max_line_length, max_width); - } - - if result.title != "-" { - println!(" {}", result.title); - } else { - println!(); - } -} - -fn open(path: &str) -> StdResult>, i32> { - if "-" == path { - let reader = Box::new(stdin()) as Box; - return Ok(BufReader::new(reader)); - } - - let fpath = Path::new(path); - if fpath.is_dir() { - show_info!("{}: is a directory", path); - } - match File::open(&fpath) { - Ok(fd) => { - let reader = Box::new(fd) as Box; - Ok(BufReader::new(reader)) - } - Err(e) => { - show_error!("wc: {}: {}", path, e); - Err(1) - } - } -} diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index fce0178aa..c0cd63795 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_who" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "who ~ (uutils) display information about currently logged-in users" @@ -15,7 +15,7 @@ edition = "2018" path = "src/who.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["utmpx"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index a3158f62a..f8dc01440 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_whoami" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "whoami ~ (uutils) display user name of current effective user ID" @@ -16,7 +16,7 @@ path = "src/whoami.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "wide"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "wide"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(target_os = "windows")'.dependencies] diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index 729ce693a..4a843ddd8 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_yes" -version = "0.0.4" +version = "0.0.6" authors = ["uutils developers"] license = "MIT" description = "yes ~ (uutils) repeatedly display a line with STRING (or 'y')" @@ -16,7 +16,7 @@ path = "src/yes.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["zero-copy"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["zero-copy"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [features] diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index a5fbe4c79..855e64b36 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uucore" -version = "0.0.7" +version = "0.0.8" authors = ["uutils developers"] license = "MIT" description = "uutils ~ 'core' uutils code library (cross-platform)" diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index adf4f6f82..a72d6ea82 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -60,6 +60,37 @@ pub enum CanonicalizeMode { Missing, } +// copied from https://github.com/rust-lang/cargo/blob/2e4cfc2b7d43328b207879228a2ca7d427d188bb/src/cargo/util/paths.rs#L65-L90 +// both projects are MIT https://github.com/rust-lang/cargo/blob/master/LICENSE-MIT +// for std impl progress see rfc https://github.com/rust-lang/rfcs/issues/2208 +// replace this once that lands +pub fn normalize_path(path: &Path) -> PathBuf { + let mut components = path.components().peekable(); + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + PathBuf::from(c.as_os_str()) + } else { + PathBuf::new() + }; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + ret.push(component.as_os_str()); + } + Component::CurDir => {} + Component::ParentDir => { + ret.pop(); + } + Component::Normal(c) => { + ret.push(c); + } + } + } + ret +} + fn resolve>(original: P) -> IOResult { const MAX_LINKS_FOLLOWED: u32 = 255; let mut followed = 0; @@ -266,3 +297,62 @@ pub fn display_permissions_unix(mode: u32) -> String { result } + +#[cfg(test)] +mod tests { + // Note this useful idiom: importing names from outer (for mod tests) scope. + use super::*; + + struct NormalizePathTestCase<'a> { + path: &'a str, + test: &'a str, + } + + const NORMALIZE_PATH_TESTS: [NormalizePathTestCase; 8] = [ + NormalizePathTestCase { + path: "./foo/bar.txt", + test: "foo/bar.txt", + }, + NormalizePathTestCase { + path: "bar/../foo/bar.txt", + test: "foo/bar.txt", + }, + NormalizePathTestCase { + path: "foo///bar.txt", + test: "foo/bar.txt", + }, + NormalizePathTestCase { + path: "foo///bar", + test: "foo/bar", + }, + NormalizePathTestCase { + path: "foo//./bar", + test: "foo/bar", + }, + NormalizePathTestCase { + path: "/foo//./bar", + test: "/foo/bar", + }, + NormalizePathTestCase { + path: r"C:/you/later/", + test: "C:/you/later", + }, + NormalizePathTestCase { + path: "\\networkshare/a//foo//./bar", + test: "\\networkshare/a/foo/bar", + }, + ]; + + #[test] + fn test_normalize_path() { + for test in NORMALIZE_PATH_TESTS.iter() { + let path = Path::new(test.path); + let normalized = normalize_path(path); + assert_eq!( + test.test + .replace("/", std::path::MAIN_SEPARATOR.to_string().as_str()), + normalized.to_str().expect("Path is not valid utf-8!") + ); + } + } +} diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 6836f81aa..24b392ebd 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -108,7 +108,7 @@ macro_rules! safe_write( ($fd:expr, $($args:tt)+) => ( match write!($fd, $($args)+) { Ok(_) => {} - Err(f) => panic!(f.to_string()) + Err(f) => panic!("{}", f) } ) ); @@ -118,7 +118,7 @@ macro_rules! safe_writeln( ($fd:expr, $($args:tt)+) => ( match writeln!($fd, $($args)+) { Ok(_) => {} - Err(f) => panic!(f.to_string()) + Err(f) => panic!("{}", f) } ) ); diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index b194eb9b0..481b1683d 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -3,6 +3,14 @@ extern crate unix_socket; use crate::common::util::*; +#[test] +fn test_output_simple() { + new_ucmd!() + .args(&["alpha.txt"]) + .succeeds() + .stdout_only("abcde\nfghij\nklmno\npqrst\nuvwxyz\n"); +} + #[test] fn test_output_multi_files_print_all_chars() { new_ucmd!() diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index e7bc72677..b85567166 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -4,6 +4,8 @@ use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; use std::sync::Mutex; extern crate libc; +use self::chmod::strip_minus_from_mode; +extern crate chmod; use self::libc::umask; static TEST_FILE: &'static str = "file"; @@ -35,10 +37,10 @@ fn run_single_test(test: &TestCase, at: AtPath, mut ucmd: UCommand) { mkfile(&at.plus_as_string(TEST_FILE), test.before); let perms = at.metadata(TEST_FILE).permissions().mode(); if perms != test.before { - panic!(format!( + panic!( "{}: expected: {:o} got: {:o}", "setting permissions on test files before actual test run failed", test.after, perms - )); + ); } for arg in &test.args { @@ -47,15 +49,15 @@ fn run_single_test(test: &TestCase, at: AtPath, mut ucmd: UCommand) { let r = ucmd.run(); if !r.success { println!("{}", r.stderr); - panic!(format!("{:?}: failed", ucmd.raw)); + panic!("{:?}: failed", ucmd.raw); } let perms = at.metadata(TEST_FILE).permissions().mode(); if perms != test.after { - panic!(format!( + panic!( "{:?}: expected: {:o} got: {:o}", ucmd.raw, test.after, perms - )); + ); } } @@ -327,10 +329,9 @@ fn test_chmod_non_existing_file() { .arg("-r,a+w") .arg("dont-exist") .fails(); - assert_eq!( - result.stderr, - "chmod: error: no such file or directory 'dont-exist'\n" - ); + assert!(result + .stderr + .contains("cannot access 'dont-exist': No such file or directory")); } #[test] @@ -349,28 +350,138 @@ fn test_chmod_preserve_root() { #[test] fn test_chmod_symlink_non_existing_file() { - let (at, mut ucmd) = at_and_ucmd!(); - at.symlink_file("/non-existing", "test-long.link"); + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; - let _result = ucmd - .arg("-R") + let non_existing = "test_chmod_symlink_non_existing_file"; + let test_symlink = "test_chmod_symlink_non_existing_file_symlink"; + let expected_stdout = &format!( + "failed to change mode of '{}' from 0000 (---------) to 0000 (---------)", + test_symlink + ); + let expected_stderr = &format!("cannot operate on dangling symlink '{}'", test_symlink); + + at.symlink_file(non_existing, test_symlink); + let mut result; + + // this cannot succeed since the symbolic link dangles + result = scene.ucmd().arg("755").arg("-v").arg(test_symlink).fails(); + + println!("stdout = {:?}", result.stdout); + println!("stderr = {:?}", result.stderr); + + assert!(result.stdout.contains(expected_stdout)); + assert!(result.stderr.contains(expected_stderr)); + assert_eq!(result.code, Some(1)); + + // this should be the same than with just '-v' but without stderr + result = scene + .ucmd() .arg("755") .arg("-v") - .arg("test-long.link") + .arg("-f") + .arg(test_symlink) .fails(); + + println!("stdout = {:?}", result.stdout); + println!("stderr = {:?}", result.stderr); + + assert!(result.stdout.contains(expected_stdout)); + assert!(result.stderr.is_empty()); + assert_eq!(result.code, Some(1)); } #[test] -fn test_chmod_symlink_non_existing_recursive() { - let (at, mut ucmd) = at_and_ucmd!(); - at.mkdir("tmp"); - at.symlink_file("/non-existing", "tmp/test-long.link"); +fn test_chmod_symlink_non_existing_file_recursive() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; - let result = ucmd.arg("-R").arg("755").arg("-v").arg("tmp").succeeds(); - // it should be a success - println!("stderr {}", result.stderr); - println!("stdout {}", result.stdout); - assert!(result - .stderr - .contains("neither symbolic link 'tmp/test-long.link' nor referent has been changed")); + let non_existing = "test_chmod_symlink_non_existing_file_recursive"; + let test_symlink = "test_chmod_symlink_non_existing_file_recursive_symlink"; + let test_directory = "test_chmod_symlink_non_existing_file_directory"; + + at.mkdir(test_directory); + at.symlink_file( + non_existing, + &format!("{}/{}", test_directory, test_symlink), + ); + let mut result; + + // this should succeed + result = scene + .ucmd() + .arg("-R") + .arg("755") + .arg(test_directory) + .succeeds(); + assert_eq!(result.code, Some(0)); + assert!(result.stdout.is_empty()); + assert!(result.stderr.is_empty()); + + let expected_stdout = &format!( + "mode of '{}' retained as 0755 (rwxr-xr-x)\nneither symbolic link '{}/{}' nor referent has been changed", + test_directory, test_directory, test_symlink + ); + + // '-v': this should succeed without stderr + result = scene + .ucmd() + .arg("-R") + .arg("-v") + .arg("755") + .arg(test_directory) + .succeeds(); + + println!("stdout = {:?}", result.stdout); + println!("stderr = {:?}", result.stderr); + + assert!(result.stdout.contains(expected_stdout)); + assert!(result.stderr.is_empty()); + assert_eq!(result.code, Some(0)); + + // '-vf': this should be the same than with just '-v' + result = scene + .ucmd() + .arg("-R") + .arg("-v") + .arg("-f") + .arg("755") + .arg(test_directory) + .succeeds(); + + println!("stdout = {:?}", result.stdout); + println!("stderr = {:?}", result.stderr); + + assert!(result.stdout.contains(expected_stdout)); + assert!(result.stderr.is_empty()); + assert_eq!(result.code, Some(0)); +} + +#[test] +fn test_chmod_strip_minus_from_mode() { + let tests = vec![ + // ( before, after ) + ("chmod -v -xw -R FILE", "chmod -v xw -R FILE"), + ("chmod g=rwx FILE -c", "chmod g=rwx FILE -c"), + ( + "chmod -c -R -w,o+w FILE --preserve-root", + "chmod -c -R w,o+w FILE --preserve-root", + ), + ("chmod -c -R +w FILE ", "chmod -c -R +w FILE "), + ("chmod a=r,=xX FILE", "chmod a=r,=xX FILE"), + ( + "chmod -v --reference RFILE -R FILE", + "chmod -v --reference RFILE -R FILE", + ), + ("chmod -Rvc -w-x FILE", "chmod -Rvc w-x FILE"), + ("chmod 755 -v FILE", "chmod 755 -v FILE"), + ("chmod -v +0004 FILE -R", "chmod -v +0004 FILE -R"), + ("chmod -v -0007 FILE -R", "chmod -v 0007 FILE -R"), + ]; + + for test in tests { + let mut args: Vec = test.0.split(" ").map(|v| v.to_string()).collect(); + let _mode_had_minus_prefix = strip_minus_from_mode(&mut args); + assert_eq!(test.1, args.join(" ")); + } } diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index d0bf5ffc8..8b41c782c 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -24,3 +24,80 @@ fn test_stdin() { .succeeds() .stdout_is_fixture("stdin.expected"); } + +#[test] +fn test_empty() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("a"); + + ucmd.arg("a").succeeds().stdout.ends_with("0 a"); +} + +#[test] +#[ignore] +fn test_arg_overrides_stdin() { + let (at, mut ucmd) = at_and_ucmd!(); + let input = "foobarfoobar"; + + at.touch("a"); + + let result = ucmd.arg("a").pipe_in(input.as_bytes()).run(); + + println!("{}, {}", result.stdout, result.stderr); + + assert!(result.stdout.ends_with("0 a\n")) +} + +#[test] +fn test_invalid_file() { + let (_, mut ucmd) = at_and_ucmd!(); + + let ls = TestScenario::new("ls"); + let files = ls.cmd("ls").arg("-l").run(); + println!("{:?}", files.stdout); + println!("{:?}", files.stderr); + + let folder_name = "asdf".to_string(); + + let result = ucmd.arg(&folder_name).run(); + + println!("stdout: {:?}", result.stdout); + println!("stderr: {:?}", result.stderr); + assert!(result.stderr.contains("cksum: error: 'asdf'")); + assert!(!result.success); +} + +// Make sure crc is correct for files larger than 32 bytes +// but <128 bytes (1 fold pclmul) +#[test] +fn test_crc_for_bigger_than_32_bytes() { + let (_, mut ucmd) = at_and_ucmd!(); + + let result = ucmd.arg("chars.txt").run(); + + let mut stdout_splitted = result.stdout.split(" "); + + let cksum: i64 = stdout_splitted.next().unwrap().parse().unwrap(); + let bytes_cnt: i64 = stdout_splitted.next().unwrap().parse().unwrap(); + + assert!(result.success); + assert_eq!(cksum, 586047089); + assert_eq!(bytes_cnt, 16); +} + +#[test] +fn test_stdin_larger_than_128_bytes() { + let (_, mut ucmd) = at_and_ucmd!(); + + let result = ucmd.arg("larger_than_2056_bytes.txt").run(); + + let mut stdout_splitted = result.stdout.split(" "); + + let cksum: i64 = stdout_splitted.next().unwrap().parse().unwrap(); + let bytes_cnt: i64 = stdout_splitted.next().unwrap().parse().unwrap(); + + assert!(result.success); + assert_eq!(cksum, 945881979); + assert_eq!(bytes_cnt, 2058); +} diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index a00ed2fd2..1fa8212ca 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1038,7 +1038,7 @@ fn test_cp_one_file_system() { .arg("tmpfs") .arg(mountpoint_path) .run(); - assert!(_r.code == Some(0), _r.stderr); + assert!(_r.code == Some(0), "{}", _r.stderr); at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE); @@ -1052,7 +1052,7 @@ fn test_cp_one_file_system() { // Ditch the mount before the asserts let _r = scene.cmd("umount").arg(mountpoint_path).run(); - assert!(_r.code == Some(0), _r.stderr); + assert!(_r.code == Some(0), "{}", _r.stderr); assert!(result.success); assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE)); diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index fc0f1b1f9..875317721 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -139,3 +139,21 @@ fn test_zero_terminated_only_delimited() { .succeeds() .stdout_only("82\n7\0"); } + +#[test] +fn test_directory_and_no_such_file() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("some"); + + ucmd.arg("-b1") + .arg("some") + .run() + .stderr_is("cut: error: some: Is a directory\n"); + + new_ucmd!() + .arg("-b1") + .arg("some") + .run() + .stderr_is("cut: error: some: No such file or directory\n"); +} diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index a79f820fb..30dcd9bb3 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -45,7 +45,11 @@ fn test_du_basics_subdir() { fn _du_basics_subdir(s: String) { assert_eq!(s, "4\tsubdir/deeper\n"); } -#[cfg(not(target_vendor = "apple"))] +#[cfg(target_os = "windows")] +fn _du_basics_subdir(s: String) { + assert_eq!(s, "0\tsubdir/deeper\n"); +} +#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] fn _du_basics_subdir(s: String) { // MS-WSL linux has altered expected output if !is_wsl() { @@ -71,7 +75,7 @@ fn test_du_basics_bad_name() { fn test_du_soft_link() { let ts = TestScenario::new("du"); - let link = ts.cmd("ln").arg("-s").arg(SUB_FILE).arg(SUB_LINK).run(); + let link = ts.ccmd("ln").arg("-s").arg(SUB_FILE).arg(SUB_LINK).run(); assert!(link.success); let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); @@ -85,7 +89,11 @@ 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_vendor = "apple"))] +#[cfg(target_os = "windows")] +fn _du_soft_link(s: String) { + assert_eq!(s, "8\tsubdir/links\n"); +} +#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] fn _du_soft_link(s: String) { // MS-WSL linux has altered expected output if !is_wsl() { @@ -99,7 +107,7 @@ fn _du_soft_link(s: String) { fn test_du_hard_link() { let ts = TestScenario::new("du"); - let link = ts.cmd("ln").arg(SUB_FILE).arg(SUB_LINK).run(); + let link = ts.ccmd("ln").arg(SUB_FILE).arg(SUB_LINK).run(); assert!(link.success); let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); @@ -113,7 +121,11 @@ fn test_du_hard_link() { fn _du_hard_link(s: String) { assert_eq!(s, "12\tsubdir/links\n") } -#[cfg(not(target_vendor = "apple"))] +#[cfg(target_os = "windows")] +fn _du_hard_link(s: String) { + assert_eq!(s, "8\tsubdir/links\n") +} +#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] fn _du_hard_link(s: String) { // MS-WSL linux has altered expected output if !is_wsl() { @@ -137,7 +149,11 @@ fn test_du_d_flag() { fn _du_d_flag(s: String) { assert_eq!(s, "16\t./subdir\n20\t./\n"); } -#[cfg(not(target_vendor = "apple"))] +#[cfg(target_os = "windows")] +fn _du_d_flag(s: String) { + assert_eq!(s, "8\t./subdir\n8\t./\n"); +} +#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] fn _du_d_flag(s: String) { // MS-WSL linux has altered expected output if !is_wsl() { @@ -146,3 +162,31 @@ fn _du_d_flag(s: String) { assert_eq!(s, "8\t./subdir\n8\t./\n"); } } + +#[test] +fn test_du_h_flag_empty_file() { + let ts = TestScenario::new("du"); + + let result = ts.ucmd().arg("-h").arg("empty.txt").run(); + assert!(result.success); + assert_eq!(result.stderr, ""); + assert_eq!(result.stdout, "0\tempty.txt\n"); +} + +#[cfg(feature = "touch")] +#[test] +fn test_du_time() { + let ts = TestScenario::new("du"); + + let touch = ts.ccmd("touch").arg("-a").arg("-m").arg("-t").arg("201505150000").arg("date_test").run(); + assert!(touch.success); + + let result = ts.ucmd().arg("--time").arg("date_test").run(); + + // cleanup by removing test file + ts.cmd("rm").arg("date_test").run(); + + assert!(result.success); + assert_eq!(result.stderr, ""); + assert_eq!(result.stdout, "0\t2015-05-15 00:00\tdate_test\n"); +} diff --git a/tests/by-util/test_fold.rs b/tests/by-util/test_fold.rs index c17f300d2..ffcd65737 100644 --- a/tests/by-util/test_fold.rs +++ b/tests/by-util/test_fold.rs @@ -25,9 +25,521 @@ fn test_40_column_word_boundary() { } #[test] -fn test_default_warp_with_newlines() { +fn test_default_wrap_with_newlines() { new_ucmd!() .arg("lorem_ipsum_new_line.txt") .run() .stdout_is_fixture("lorem_ipsum_new_line_80_column.expected"); } + +#[test] +fn test_should_preserve_empty_line_without_final_newline() { + new_ucmd!() + .arg("-w2") + .pipe_in("12\n\n34") + .succeeds() + .stdout_is("12\n\n34"); +} + +#[test] +fn test_should_preserve_empty_line_and_final_newline() { + new_ucmd!() + .arg("-w2") + .pipe_in("12\n\n34\n") + .succeeds() + .stdout_is("12\n\n34\n"); +} + +#[test] +fn test_should_preserve_empty_lines() { + new_ucmd!().pipe_in("\n").succeeds().stdout_is("\n"); + + new_ucmd!() + .arg("-w1") + .pipe_in("0\n1\n\n2\n\n\n") + .succeeds() + .stdout_is("0\n1\n\n2\n\n\n"); +} + +#[test] +fn test_word_boundary_split_should_preserve_empty_lines() { + new_ucmd!() + .arg("-s") + .pipe_in("\n") + .succeeds() + .stdout_is("\n"); + + new_ucmd!() + .args(&["-w1", "-s"]) + .pipe_in("0\n1\n\n2\n\n\n") + .succeeds() + .stdout_is("0\n1\n\n2\n\n\n"); +} + +#[test] +fn test_should_not_add_newline_when_line_less_than_fold() { + new_ucmd!().pipe_in("1234").succeeds().stdout_is("1234"); +} + +#[test] +fn test_should_not_add_newline_when_line_longer_than_fold() { + new_ucmd!() + .arg("-w2") + .pipe_in("1234") + .succeeds() + .stdout_is("12\n34"); +} + +#[test] +fn test_should_not_add_newline_when_line_equal_to_fold() { + new_ucmd!() + .arg("-w1") + .pipe_in(" ") + .succeeds() + .stdout_is(" "); +} + +#[test] +fn test_should_preserve_final_newline_when_line_less_than_fold() { + new_ucmd!().pipe_in("1234\n").succeeds().stdout_is("1234\n"); +} + +#[test] +fn test_should_preserve_final_newline_when_line_longer_than_fold() { + new_ucmd!() + .arg("-w2") + .pipe_in("1234\n") + .succeeds() + .stdout_is("12\n34\n"); +} + +#[test] +fn test_should_preserve_final_newline_when_line_equal_to_fold() { + new_ucmd!() + .arg("-w2") + .pipe_in("1\n") + .succeeds() + .stdout_is("1\n"); +} + +#[test] +fn test_single_tab_should_not_add_extra_newline() { + new_ucmd!() + .arg("-w1") + .pipe_in("\t") + .succeeds() + .stdout_is("\t"); +} + +#[test] +fn test_initial_tab_counts_as_8_columns() { + new_ucmd!() + .arg("-w8") + .pipe_in("\t1") + .succeeds() + .stdout_is("\t\n1"); +} + +#[test] +fn test_tab_should_advance_to_next_tab_stop() { + // tab advances the column count to the next tab stop, i.e. the width + // of the tab varies based on the leading text + new_ucmd!() + .args(&["-w8", "tab_stops.input"]) + .succeeds() + .stdout_is_fixture("tab_stops_w8.expected"); +} + +#[test] +fn test_all_tabs_should_advance_to_next_tab_stops() { + new_ucmd!() + .args(&["-w16", "tab_stops.input"]) + .succeeds() + .stdout_is_fixture("tab_stops_w16.expected"); +} + +#[test] +fn test_fold_before_tab_with_narrow_width() { + new_ucmd!() + .arg("-w7") + .pipe_in("a\t1") + .succeeds() + .stdout_is("a\n\t\n1"); +} + +#[test] +fn test_fold_at_word_boundary() { + new_ucmd!() + .args(&["-w4", "-s"]) + .pipe_in("one two") + .succeeds() + .stdout_is("one \ntwo"); +} + +#[test] +fn test_fold_at_leading_word_boundary() { + new_ucmd!() + .args(&["-w3", "-s"]) + .pipe_in(" aaa") + .succeeds() + .stdout_is(" \naaa"); +} + +#[test] +fn test_fold_at_word_boundary_preserve_final_newline() { + new_ucmd!() + .args(&["-w4", "-s"]) + .pipe_in("one two\n") + .succeeds() + .stdout_is("one \ntwo\n"); +} + +#[test] +fn test_fold_at_tab() { + new_ucmd!() + .arg("-w8") + .pipe_in("a\tbbb\n") + .succeeds() + .stdout_is("a\t\nbbb\n"); +} + +#[test] +fn test_fold_after_tab() { + new_ucmd!() + .arg("-w10") + .pipe_in("a\tbbb\n") + .succeeds() + .stdout_is("a\tbb\nb\n"); +} + +#[test] +fn test_fold_at_tab_as_word_boundary() { + new_ucmd!() + .args(&["-w8", "-s"]) + .pipe_in("a\tbbb\n") + .succeeds() + .stdout_is("a\t\nbbb\n"); +} + +#[test] +fn test_fold_after_tab_as_word_boundary() { + new_ucmd!() + .args(&["-w10", "-s"]) + .pipe_in("a\tbbb\n") + .succeeds() + .stdout_is("a\t\nbbb\n"); +} + +#[test] +fn test_fold_at_word_boundary_only_whitespace() { + new_ucmd!() + .args(&["-w2", "-s"]) + .pipe_in(" ") + .succeeds() + .stdout_is(" \n "); +} + +#[test] +fn test_fold_at_word_boundary_only_whitespace_preserve_final_newline() { + new_ucmd!() + .args(&["-w2", "-s"]) + .pipe_in(" \n") + .succeeds() + .stdout_is(" \n \n"); +} + +#[test] +fn test_backspace_should_be_preserved() { + new_ucmd!().pipe_in("\x08").succeeds().stdout_is("\x08"); +} + +#[test] +fn test_backspaced_char_should_be_preserved() { + new_ucmd!().pipe_in("x\x08").succeeds().stdout_is("x\x08"); +} + +#[test] +fn test_backspace_should_decrease_column_count() { + new_ucmd!() + .arg("-w2") + .pipe_in("1\x08345") + .succeeds() + .stdout_is("1\x0834\n5"); +} + +#[test] +fn test_backspace_should_not_decrease_column_count_past_zero() { + new_ucmd!() + .arg("-w2") + .pipe_in("1\x08\x083456") + .succeeds() + .stdout_is("1\x08\x0834\n56"); +} + +#[test] +fn test_backspace_is_not_word_boundary() { + new_ucmd!() + .args(&["-w10", "-s"]) + .pipe_in("foobar\x086789abcdef") + .succeeds() + .stdout_is("foobar\x086789a\nbcdef"); +} + +#[test] +fn test_carriage_return_should_be_preserved() { + new_ucmd!().pipe_in("\r").succeeds().stdout_is("\r"); +} + +#[test] +fn test_carriage_return_overwrriten_char_should_be_preserved() { + new_ucmd!().pipe_in("x\ry").succeeds().stdout_is("x\ry"); +} + +#[test] +fn test_carriage_return_should_reset_column_count() { + new_ucmd!() + .arg("-w6") + .pipe_in("12345\r123456789abcdef") + .succeeds() + .stdout_is("12345\r123456\n789abc\ndef"); +} + +#[test] +fn test_carriage_return_is_not_word_boundary() { + new_ucmd!() + .args(&["-w6", "-s"]) + .pipe_in("fizz\rbuzz\rfizzbuzz") + .succeeds() + .stdout_is("fizz\rbuzz\rfizzbu\nzz"); +} + +// +// bytewise tests + +#[test] +fn test_bytewise_should_preserve_empty_line_without_final_newline() { + new_ucmd!() + .args(&["-w2", "-b"]) + .pipe_in("123\n\n45") + .succeeds() + .stdout_is("12\n3\n\n45"); +} + +#[test] +fn test_bytewise_should_preserve_empty_line_and_final_newline() { + new_ucmd!() + .args(&["-w2", "-b"]) + .pipe_in("12\n\n34\n") + .succeeds() + .stdout_is("12\n\n34\n"); +} + +#[test] +fn test_bytewise_should_preserve_empty_lines() { + new_ucmd!() + .arg("-b") + .pipe_in("\n") + .succeeds() + .stdout_is("\n"); + + new_ucmd!() + .args(&["-w1", "-b"]) + .pipe_in("0\n1\n\n2\n\n\n") + .succeeds() + .stdout_is("0\n1\n\n2\n\n\n"); +} + +#[test] +fn test_bytewise_word_boundary_split_should_preserve_empty_lines() { + new_ucmd!() + .args(&["-s", "-b"]) + .pipe_in("\n") + .succeeds() + .stdout_is("\n"); + + new_ucmd!() + .args(&["-w1", "-s", "-b"]) + .pipe_in("0\n1\n\n2\n\n\n") + .succeeds() + .stdout_is("0\n1\n\n2\n\n\n"); +} + +#[test] +fn test_bytewise_should_not_add_newline_when_line_less_than_fold() { + new_ucmd!() + .arg("-b") + .pipe_in("1234") + .succeeds() + .stdout_is("1234"); +} + +#[test] +fn test_bytewise_should_not_add_newline_when_line_longer_than_fold() { + new_ucmd!() + .args(&["-w2", "-b"]) + .pipe_in("1234") + .succeeds() + .stdout_is("12\n34"); +} + +#[test] +fn test_bytewise_should_not_add_newline_when_line_equal_to_fold() { + new_ucmd!() + .args(&["-w1", "-b"]) + .pipe_in(" ") + .succeeds() + .stdout_is(" "); +} + +#[test] +fn test_bytewise_should_preserve_final_newline_when_line_less_than_fold() { + new_ucmd!() + .arg("-b") + .pipe_in("1234\n") + .succeeds() + .stdout_is("1234\n"); +} + +#[test] +fn test_bytewise_should_preserve_final_newline_when_line_longer_than_fold() { + new_ucmd!() + .args(&["-w2", "-b"]) + .pipe_in("1234\n") + .succeeds() + .stdout_is("12\n34\n"); +} + +#[test] +fn test_bytewise_should_preserve_final_newline_when_line_equal_to_fold() { + new_ucmd!() + .args(&["-w2", "-b"]) + .pipe_in("1\n") + .succeeds() + .stdout_is("1\n"); +} + +#[test] +fn test_bytewise_single_tab_should_not_add_extra_newline() { + new_ucmd!() + .args(&["-w1", "-b"]) + .pipe_in("\t") + .succeeds() + .stdout_is("\t"); +} + +#[test] +fn test_tab_counts_as_one_byte() { + new_ucmd!() + .args(&["-w2", "-b"]) + .pipe_in("1\t2\n") + .succeeds() + .stdout_is("1\t\n2\n"); +} + +#[test] +fn test_bytewise_fold_before_tab_with_narrow_width() { + new_ucmd!() + .args(&["-w7", "-b"]) + .pipe_in("a\t1") + .succeeds() + .stdout_is("a\t1"); +} + +#[test] +fn test_bytewise_fold_at_word_boundary_only_whitespace() { + new_ucmd!() + .args(&["-w2", "-s", "-b"]) + .pipe_in(" ") + .succeeds() + .stdout_is(" \n "); +} + +#[test] +fn test_bytewise_fold_at_word_boundary_only_whitespace_preserve_final_newline() { + new_ucmd!() + .args(&["-w2", "-s", "-b"]) + .pipe_in(" \n") + .succeeds() + .stdout_is(" \n \n"); +} + +#[test] +fn test_bytewise_backspace_should_be_preserved() { + new_ucmd!() + .arg("-b") + .pipe_in("\x08") + .succeeds() + .stdout_is("\x08"); +} + +#[test] +fn test_bytewise_backspaced_char_should_be_preserved() { + new_ucmd!() + .arg("-b") + .pipe_in("x\x08") + .succeeds() + .stdout_is("x\x08"); +} + +#[test] +fn test_bytewise_backspace_should_not_decrease_column_count() { + new_ucmd!() + .args(&["-w2", "-b"]) + .pipe_in("1\x08345") + .succeeds() + .stdout_is("1\x08\n34\n5"); +} + +#[test] +fn test_bytewise_backspace_is_not_word_boundary() { + new_ucmd!() + .args(&["-w10", "-s", "-b"]) + .pipe_in("foobar\x0889abcdef") + .succeeds() + .stdout_is("foobar\x0889a\nbcdef"); +} + +#[test] +fn test_bytewise_carriage_return_should_be_preserved() { + new_ucmd!() + .arg("-b") + .pipe_in("\r") + .succeeds() + .stdout_is("\r"); +} + +#[test] +fn test_bytewise_carriage_return_overwrriten_char_should_be_preserved() { + new_ucmd!() + .arg("-b") + .pipe_in("x\ry") + .succeeds() + .stdout_is("x\ry"); +} + +#[test] +fn test_bytewise_carriage_return_should_not_reset_column_count() { + new_ucmd!() + .args(&["-w6", "-b"]) + .pipe_in("12345\r123456789abcdef") + .succeeds() + .stdout_is("12345\r\n123456\n789abc\ndef"); +} + +#[test] +fn test_bytewise_carriage_return_is_not_word_boundary() { + new_ucmd!() + .args(&["-w6", "-s", "-b"]) + .pipe_in("fizz\rbuzz\rfizzbuzz") + .succeeds() + .stdout_is("fizz\rb\nuzz\rfi\nzzbuzz"); +} +#[test] +fn test_obsolete_syntax() { + new_ucmd!() + .arg("-5") + .arg("-s") + .arg("space_separated_words.txt") + .succeeds() + .stdout_is("test1\n \ntest2\n \ntest3\n \ntest4\n \ntest5\n \ntest6\n "); +} \ No newline at end of file diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs old mode 100644 new mode 100755 index a1086c004..d91cc1289 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -86,88 +86,74 @@ 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() { + //this test is does not mirror what GNU does new_ucmd!().pipe_in("a").succeeds().stdout_is("a\n"); } #[test] -#[ignore] -fn test_unsupported_byte_syntax() { +fn test_byte_syntax() { new_ucmd!() .args(&["-1c"]) .pipe_in("abc") - .fails() - //GNU head returns "a" - .stdout_is("") - .stderr_is("head: error: Unrecognized option: \'1\'"); + .run() + .stdout_is("a"); } #[test] -#[ignore] -fn test_unsupported_line_syntax() { +fn test_line_syntax() { new_ucmd!() .args(&["-n", "2048m"]) .pipe_in("a\n") - .fails() - //.stdout_is("a\n"); What GNU head returns. - .stdout_is("") - .stderr_is("head: error: invalid line count \'2048m\': invalid digit found in string"); + .run() + .stdout_is("a\n"); } #[test] -#[ignore] -fn test_unsupported_zero_terminated_syntax() { +fn test_zero_terminated_syntax() { new_ucmd!() - .args(&["-z -n 1"]) + .args(&["-z", "-n", "1"]) .pipe_in("x\0y") - .fails() - //GNU Head returns "x\0" - .stderr_is("head: error: Unrecognized option: \'z\'"); + .run() + .stdout_is("x\0"); } #[test] -#[ignore] -fn test_unsupported_zero_terminated_syntax_2() { +fn test_zero_terminated_syntax_2() { new_ucmd!() - .args(&["-z -n 2"]) + .args(&["-z", "-n", "2"]) .pipe_in("x\0y") - .fails() - //GNU Head returns "x\0y" - .stderr_is("head: error: Unrecognized option: \'z\'"); + .run() + .stdout_is("x\0y"); } #[test] -#[ignore] -fn test_unsupported_negative_byte_syntax() { +fn test_negative_byte_syntax() { new_ucmd!() .args(&["--bytes=-2"]) .pipe_in("a\n") - .fails() - //GNU Head returns "" - .stderr_is("head: error: invalid byte count \'-2\': invalid digit found in string"); + .run() + .stdout_is(""); } #[test] -#[ignore] -fn test_bug_in_negative_zero_lines() { +fn test_negative_zero_lines() { new_ucmd!() .args(&["--lines=-0"]) .pipe_in("a\nb\n") .succeeds() - //GNU Head returns "a\nb\n" - .stdout_is(""); + .stdout_is("a\nb\n"); +} +#[test] +fn test_negative_zero_bytes() { + new_ucmd!() + .args(&["--bytes=-0"]) + .pipe_in("qwerty") + .succeeds() + .stdout_is("qwerty"); } - #[test] fn test_no_such_file_or_directory() { let result = new_ucmd!().arg("no_such_file.toml").run(); @@ -179,3 +165,38 @@ fn test_no_such_file_or_directory() { .contains("cannot open 'no_such_file.toml' for reading: No such file or directory") ) } + +// there was a bug not caught by previous tests +// where for negative n > 3, the total amount of lines +// was correct, but it would eat from the second line +#[test] +fn test_sequence_fixture() { + new_ucmd!() + .args(&["-n", "-10", "sequence"]) + .run() + .stdout_is_fixture("sequence.expected"); +} +#[test] +fn test_file_backwards() { + new_ucmd!() + .args(&["-c", "-10", "lorem_ipsum.txt"]) + .run() + .stdout_is_fixture("lorem_ipsum_backwards_file.expected"); +} + +#[test] +fn test_zero_terminated() { + new_ucmd!() + .args(&["-z", "zero_terminated.txt"]) + .run() + .stdout_is_fixture("zero_terminated.expected"); +} + +#[test] +fn test_obsolete_extras() { + new_ucmd!() + .args(&["-5zv"]) + .pipe_in("1\02\03\04\05\06") + .succeeds() + .stdout_is("==> standard input <==\n1\02\03\04\05\0"); +} diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 89dfb0e56..8ac6396fd 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -1,6 +1,9 @@ use crate::common::util::*; +use filetime::FileTime; use rust_users::*; use std::os::unix::fs::PermissionsExt; +#[cfg(target_os = "linux")] +use std::thread::sleep; #[test] fn test_install_help() { @@ -84,20 +87,81 @@ fn test_install_unimplemented_arg() { } #[test] -fn test_install_component_directories() { +fn test_install_ancestors_directories() { let (at, mut ucmd) = at_and_ucmd!(); - let component1 = "component1"; - let component2 = "component2"; - let component3 = "component3"; + let ancestor1 = "ancestor1"; + let ancestor2 = "ancestor1/ancestor2"; + let target_dir = "ancestor1/ancestor2/target_dir"; let directories_arg = "-d"; - ucmd.args(&[directories_arg, component1, component2, component3]) + ucmd.args(&[directories_arg, target_dir]) .succeeds() .no_stderr(); - assert!(at.dir_exists(component1)); - assert!(at.dir_exists(component2)); - assert!(at.dir_exists(component3)); + assert!(at.dir_exists(ancestor1)); + assert!(at.dir_exists(ancestor2)); + assert!(at.dir_exists(target_dir)); +} + +#[test] +fn test_install_ancestors_mode_directories() { + let (at, mut ucmd) = at_and_ucmd!(); + let ancestor1 = "ancestor1"; + let ancestor2 = "ancestor1/ancestor2"; + let target_dir = "ancestor1/ancestor2/target_dir"; + let directories_arg = "-d"; + let mode_arg = "--mode=700"; + + ucmd.args(&[mode_arg, directories_arg, target_dir]) + .succeeds() + .no_stderr(); + + assert!(at.dir_exists(ancestor1)); + assert!(at.dir_exists(ancestor2)); + assert!(at.dir_exists(target_dir)); + + assert_ne!(0o40700 as u32, at.metadata(ancestor1).permissions().mode()); + assert_ne!(0o40700 as u32, at.metadata(ancestor2).permissions().mode()); + + // Expected mode only on the target_dir. + assert_eq!(0o40700 as u32, at.metadata(target_dir).permissions().mode()); +} + +#[test] +fn test_install_parent_directories() { + let (at, mut ucmd) = at_and_ucmd!(); + let ancestor1 = "ancestor1"; + let ancestor2 = "ancestor1/ancestor2"; + let target_dir = "ancestor1/ancestor2/target_dir"; + let directories_arg = "-d"; + + // Here one of the ancestors already exist and only the target_dir and + // its parent must be created. + at.mkdir(ancestor1); + + ucmd.args(&[directories_arg, target_dir]) + .succeeds() + .no_stderr(); + + assert!(at.dir_exists(ancestor2)); + assert!(at.dir_exists(target_dir)); +} + +#[test] +fn test_install_several_directories() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir1 = "dir1"; + let dir2 = "dir2"; + let dir3 = "dir3"; + let directories_arg = "-d"; + + ucmd.args(&[directories_arg, dir1, dir2, dir3]) + .succeeds() + .no_stderr(); + + assert!(at.dir_exists(dir1)); + assert!(at.dir_exists(dir2)); + assert!(at.dir_exists(dir3)); } #[test] @@ -351,11 +415,19 @@ fn test_install_copy_file() { #[test] #[cfg(target_os = "linux")] fn test_install_target_file_dev_null() { - let (at, mut ucmd) = at_and_ucmd!(); + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let file1 = "/dev/null"; let file2 = "target_file"; - ucmd.arg(file1).arg(file2).succeeds().no_stderr(); + let result = scene.ucmd().arg(file1).arg(file2).run(); + + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + + assert!(result.success); + assert!(at.file_exists(file2)); } @@ -407,3 +479,90 @@ fn test_install_failing_no_such_file() { assert!(r.code == Some(1)); assert!(r.stderr.contains("No such file or directory")); } + +#[test] +fn test_install_copy_then_compare_file() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let file1 = "test_install_copy_then_compare_file_a1"; + let file2 = "test_install_copy_then_compare_file_a2"; + + at.touch(file1); + scene + .ucmd() + .arg("-C") + .arg(file1) + .arg(file2) + .succeeds() + .no_stderr(); + + let mut file2_meta = at.metadata(file2); + let before = FileTime::from_last_modification_time(&file2_meta); + + scene + .ucmd() + .arg("-C") + .arg(file1) + .arg(file2) + .succeeds() + .no_stderr(); + + file2_meta = at.metadata(file2); + let after = FileTime::from_last_modification_time(&file2_meta); + + assert!(before == after); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_install_copy_then_compare_file_with_extra_mode() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + // XXX: can't tests introspect on their own names? + let file1 = "test_install_copy_then_compare_file_with_extra_mode_a1"; + let file2 = "test_install_copy_then_compare_file_with_extra_mode_a2"; + + at.touch(file1); + scene + .ucmd() + .arg("-C") + .arg(file1) + .arg(file2) + .succeeds() + .no_stderr(); + + let mut file2_meta = at.metadata(file2); + let before = FileTime::from_last_modification_time(&file2_meta); + sleep(std::time::Duration::from_millis(1000)); + + scene + .ucmd() + .arg("-C") + .arg(file1) + .arg(file2) + .arg("-m") + .arg("1644") + .succeeds() + .no_stderr(); + + file2_meta = at.metadata(file2); + let after_install_sticky = FileTime::from_last_modification_time(&file2_meta); + + assert!(before != after_install_sticky); + + sleep(std::time::Duration::from_millis(1000)); + + // dest file still 1644, so need_copy ought to return `true` + scene + .ucmd() + .arg("-C") + .arg(file1) + .arg(file2) + .succeeds() + .no_stderr(); + + file2_meta = at.metadata(file2); + let after_install_sticky_again = FileTime::from_last_modification_time(&file2_meta); + + assert!(after_install_sticky != after_install_sticky_again); +} diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 091d47234..f0db7ca9c 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1,3 +1,5 @@ +#[cfg(unix)] +extern crate unix_socket; use crate::common::util::*; extern crate regex; @@ -11,7 +13,13 @@ extern crate libc; #[cfg(not(windows))] use self::libc::umask; #[cfg(not(windows))] +use std::path::PathBuf; +#[cfg(not(windows))] use std::sync::Mutex; +#[cfg(not(windows))] +extern crate tempdir; +#[cfg(not(windows))] +use self::tempdir::TempDir; #[cfg(not(windows))] lazy_static! { @@ -35,26 +43,22 @@ fn test_ls_a() { let at = &scene.fixtures; at.touch(".test-1"); - let result = scene.ucmd().run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); - assert!(!result.stdout.contains(".test-1")); - assert!(!result.stdout.contains("..")); + let result = scene.ucmd().succeeds(); + let stdout = result.stdout_str(); + assert!(!stdout.contains(".test-1")); + assert!(!stdout.contains("..")); - let result = scene.ucmd().arg("-a").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); - assert!(result.stdout.contains(".test-1")); - assert!(result.stdout.contains("..")); + scene + .ucmd() + .arg("-a") + .succeeds() + .stdout_contains(&".test-1") + .stdout_contains(&".."); - let result = scene.ucmd().arg("-A").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); - assert!(result.stdout.contains(".test-1")); - assert!(!result.stdout.contains("..")); + let result = scene.ucmd().arg("-A").succeeds(); + result.stdout_contains(".test-1"); + let stdout = result.stdout_str(); + assert!(!stdout.contains("..")); } #[test] @@ -67,29 +71,19 @@ fn test_ls_width() { at.touch(&at.plus_as_string("test-width-4")); for option in &["-w 100", "-w=100", "--width=100", "--width 100"] { - let result = scene + scene .ucmd() .args(&option.split(" ").collect::>()) - .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", - ) + .succeeds() + .stdout_only("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 + scene .ucmd() .args(&option.split(" ").collect::>()) - .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", - ) + .succeeds() + .stdout_only("test-width-1 test-width-3\ntest-width-2 test-width-4\n"); } for option in &[ @@ -102,16 +96,11 @@ fn test_ls_width() { "--width=0", "--width 0", ] { - let result = scene + scene .ucmd() .args(&option.split(" ").collect::>()) - .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", - ) + .succeeds() + .stdout_only("test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n"); } } @@ -125,48 +114,28 @@ fn test_ls_columns() { 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); + let result = scene.ucmd().succeeds(); #[cfg(not(windows))] - assert_eq!( - result.stdout, - "test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n" - ); + result.stdout_only("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" - ); + result.stdout_only("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); + let result = scene.ucmd().arg(option).succeeds(); #[cfg(not(windows))] - assert_eq!( - result.stdout, - "test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n" - ); + result.stdout_only("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" - ); + result.stdout_only("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" - ); + scene + .ucmd() + .arg("-w=40") + .arg(option) + .succeeds() + .stdout_only("test-columns-1 test-columns-3\ntest-columns-2 test-columns-4\n"); } } @@ -183,31 +152,22 @@ fn test_ls_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" - ); + result.stdout_only("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" - ); + result.stdout_only("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" - ); + scene + .ucmd() + .arg("-w=30") + .arg(option) + .succeeds() + .stdout_only("test-across-1 test-across-2\ntest-across-3 test-across-4\n"); } } @@ -223,31 +183,27 @@ fn test_ls_commas() { 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" - ); + result.stdout_only("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" - ); + result.stdout_only("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" - ); + scene + .ucmd() + .arg("-w=30") + .arg(option) + .succeeds() + .stdout_only("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" - ); + scene + .ucmd() + .arg("-w=45") + .arg(option) + .succeeds() + .stdout_only("test-commas-1, test-commas-2, test-commas-3,\ntest-commas-4\n"); } } @@ -271,13 +227,11 @@ fn test_ls_long() { 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--")); + result.stdout_contains("-rw-rw-r--"); #[cfg(windows)] - assert!(result.stdout.contains("---------- 1 somebody somegroup")); + result.stdout_contains("---------- 1 somebody somegroup"); } #[cfg(not(windows))] @@ -297,15 +251,24 @@ fn test_ls_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(); + #[cfg(unix)] + let re_three_num = Regex::new(r"[xrw-]{9} \d (\d+ ){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(); + #[cfg(unix)] + let re_two_num = Regex::new(r"[xrw-]{9} \d (\d+ ){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(); + #[cfg(unix)] + let re_one_num = Regex::new(r"[xrw-]{9} \d \d+ 0").unwrap(); + // Regex for no names let re_zero = Regex::new(r"[xrw-]{9} \d 0").unwrap(); @@ -314,20 +277,27 @@ fn test_ls_long_formats() { .arg("-l") .arg("--author") .arg("test-long-formats") - .run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(re_three.is_match(&result.stdout)); + .succeeds(); + assert!(re_three.is_match(result.stdout_str())); 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)); + .succeeds(); + assert!(re_three.is_match(&result.stdout_str())); + + #[cfg(unix)] + { + let result = scene + .ucmd() + .arg("-n") + .arg("--author") + .arg("test-long-formats") + .succeeds(); + assert!(re_three_num.is_match(result.stdout_str())); + } for arg in &[ "-l", // only group and owner @@ -341,9 +311,18 @@ fn test_ls_long_formats() { .args(&arg.split(" ").collect::>()) .arg("test-long-formats") .succeeds(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(re_two.is_match(&result.stdout)); + assert!(re_two.is_match(result.stdout_str())); + + #[cfg(unix)] + { + let result = scene + .ucmd() + .arg("-n") + .args(&arg.split(" ").collect::>()) + .arg("test-long-formats") + .succeeds(); + assert!(re_two_num.is_match(result.stdout_str())); + } } for arg in &[ @@ -361,9 +340,18 @@ fn test_ls_long_formats() { .args(&arg.split(" ").collect::>()) .arg("test-long-formats") .succeeds(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(re_one.is_match(&result.stdout)); + assert!(re_one.is_match(result.stdout_str())); + + #[cfg(unix)] + { + let result = scene + .ucmd() + .arg("-n") + .args(&arg.split(" ").collect::>()) + .arg("test-long-formats") + .succeeds(); + assert!(re_one_num.is_match(result.stdout_str())); + } } for arg in &[ @@ -384,9 +372,18 @@ fn test_ls_long_formats() { .args(&arg.split(" ").collect::>()) .arg("test-long-formats") .succeeds(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(re_zero.is_match(&result.stdout)); + assert!(re_zero.is_match(result.stdout_str())); + + #[cfg(unix)] + { + let result = scene + .ucmd() + .arg("-n") + .args(&arg.split(" ").collect::>()) + .arg("test-long-formats") + .succeeds(); + assert!(re_zero.is_match(result.stdout_str())); + } } } @@ -400,11 +397,11 @@ fn test_ls_oneline() { // 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"); + scene + .ucmd() + .arg(option) + .succeeds() + .stdout_only("test-oneline-1\ntest-oneline-2\n"); } } @@ -425,11 +422,8 @@ fn test_ls_deref() { .arg("--color=never") .arg("test-long") .arg("test-long.link") - .run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); - assert!(re.is_match(&result.stdout.trim())); + .succeeds(); + assert!(re.is_match(result.stdout_str().trim())); let result = scene .ucmd() @@ -437,11 +431,8 @@ fn test_ls_deref() { .arg("--color=never") .arg("test-long") .arg("test-long.link") - .run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); - assert!(!re.is_match(&result.stdout.trim())); + .succeeds(); + assert!(!re.is_match(result.stdout_str().trim())); } #[test] @@ -459,28 +450,19 @@ fn test_ls_order_size() { at.touch("test-4"); at.append("test-4", "4444"); - let result = scene.ucmd().arg("-al").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); + scene.ucmd().arg("-al").succeeds(); - let result = scene.ucmd().arg("-S").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); + let result = scene.ucmd().arg("-S").succeeds(); #[cfg(not(windows))] - assert_eq!(result.stdout, "test-4\ntest-3\ntest-2\ntest-1\n"); + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); #[cfg(windows)] - assert_eq!(result.stdout, "test-4 test-3 test-2 test-1\n"); + result.stdout_only("test-4 test-3 test-2 test-1\n"); - let result = scene.ucmd().arg("-S").arg("-r").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); + let result = scene.ucmd().arg("-S").arg("-r").succeeds(); #[cfg(not(windows))] - assert_eq!(result.stdout, "test-1\ntest-2\ntest-3\ntest-4\n"); + result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); #[cfg(windows)] - assert_eq!(result.stdout, "test-1 test-2 test-3 test-4\n"); + result.stdout_only("test-1 test-2 test-3 test-4\n"); } #[test] @@ -493,9 +475,9 @@ fn test_ls_long_ctime() { // Should show the time on Unix, but question marks on windows. #[cfg(unix)] - assert!(result.stdout.contains(":")); + result.stdout_contains(":"); #[cfg(not(unix))] - assert!(result.stdout.contains("???")); + result.stdout_contains("???"); } #[test] @@ -527,51 +509,41 @@ fn test_ls_order_time() { ) .unwrap(); - let result = scene.ucmd().arg("-al").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); + scene.ucmd().arg("-al").succeeds(); // 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); - assert!(result.success); + let result = scene.ucmd().arg("-t").succeeds(); #[cfg(not(windows))] - assert_eq!(result.stdout, "test-4\ntest-3\ntest-2\ntest-1\n"); + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); #[cfg(windows)] - assert_eq!(result.stdout, "test-4 test-3 test-2 test-1\n"); + result.stdout_only("test-4 test-3 test-2 test-1\n"); - let result = scene.ucmd().arg("-tr").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); + let result = scene.ucmd().arg("-tr").succeeds(); #[cfg(not(windows))] - assert_eq!(result.stdout, "test-1\ntest-2\ntest-3\ntest-4\n"); + result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); #[cfg(windows)] - assert_eq!(result.stdout, "test-1 test-2 test-3 test-4\n"); + result.stdout_only("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 result = scene.ucmd().arg("-tu").succeeds(); let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap(); let file4_access = at.open("test-4").metadata().unwrap().accessed().unwrap(); + + // It seems to be dependent on the platform whether the access time is actually set if file3_access > file4_access { if cfg!(not(windows)) { - assert_eq!(result.stdout, "test-3\ntest-4\ntest-2\ntest-1\n"); + result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n"); } else { - assert_eq!(result.stdout, "test-3 test-4 test-2 test-1\n"); + result.stdout_only("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"); + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); } else { - assert_eq!(result.stdout, "test-4 test-3 test-2 test-1\n"); + result.stdout_only("test-4 test-3 test-2 test-1\n"); } } @@ -579,11 +551,8 @@ fn test_ls_order_time() { // 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"); + let result = scene.ucmd().arg("-tc").succeeds(); + result.stdout_only("test-2\ntest-4\ntest-3\ntest-1\n"); } } @@ -607,18 +576,21 @@ fn test_ls_files_dirs() { scene.ucmd().arg("a/a").succeeds(); scene.ucmd().arg("a").arg("z").succeeds(); - let result = scene.ucmd().arg("doesntexist").fails(); // Doesn't exist - assert!(result - .stderr - .contains("error: 'doesntexist': No such file or directory")); + scene + .ucmd() + .arg("doesntexist") + .fails() + .stderr_contains(&"error: 'doesntexist': No such file or directory"); - let result = scene.ucmd().arg("a").arg("doesntexist").fails(); // One exists, the other doesn't - assert!(result - .stderr - .contains("error: 'doesntexist': No such file or directory")); - assert!(result.stdout.contains("a:")); + scene + .ucmd() + .arg("a") + .arg("doesntexist") + .fails() + .stderr_contains(&"error: 'doesntexist': No such file or directory") + .stdout_contains(&"a:"); } #[test] @@ -642,13 +614,10 @@ fn test_ls_recursive() { .arg("z") .succeeds(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); #[cfg(not(windows))] - assert!(result.stdout.contains("a/b:\nb")); + result.stdout_contains(&"a/b:\nb"); #[cfg(windows)] - assert!(result.stdout.contains("a\\b:\nb")); + result.stdout_contains(&"a\\b:\nb"); } #[cfg(unix)] @@ -668,39 +637,53 @@ fn test_ls_ls_color() { // Color is disabled by default let result = scene.ucmd().succeeds(); - assert!(!result.stdout.contains(a_with_colors)); - assert!(!result.stdout.contains(z_with_colors)); + assert!(!result.stdout_str().contains(a_with_colors)); + assert!(!result.stdout_str().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)); + scene + .ucmd() + .arg("--color") + .succeeds() + .stdout_contains(a_with_colors) + .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)); + scene + .ucmd() + .arg("--color=always") + .succeeds() + .stdout_contains(a_with_colors) + .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)); + assert!(!result.stdout_str().contains(a_with_colors)); + assert!(!result.stdout_str().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)); + scene + .ucmd() + .arg("--color") + .arg("a") + .succeeds() + .stdout_contains(nested_dir_with_colors); // Color has no effect - let result = scene + scene .ucmd() .arg("--color=always") .arg("a/nested_file") - .succeeds(); - assert!(result.stdout.contains("a/nested_file\n")); + .succeeds() + .stdout_contains("a/nested_file\n"); // No output - let result = scene.ucmd().arg("--color=never").arg("z").succeeds(); - assert_eq!(result.stdout, ""); + scene + .ucmd() + .arg("--color=never") + .arg("z") + .succeeds() + .stdout_only(""); } #[cfg(unix)] @@ -715,68 +698,179 @@ fn test_ls_inode() { 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); - assert!(re_short.is_match(&result.stdout)); + let result = scene.ucmd().arg("test_inode").arg("-i").succeeds(); + assert!(re_short.is_match(result.stdout_str())); let inode_short = re_short - .captures(&result.stdout) + .captures(result.stdout_str()) .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("test_inode").succeeds(); + assert!(!re_short.is_match(result.stdout_str())); + assert!(!result.stdout_str().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 result = scene.ucmd().arg("-li").arg("test_inode").succeeds(); + assert!(re_long.is_match(result.stdout_str())); let inode_long = re_long - .captures(&result.stdout) + .captures(result.stdout_str()) .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)); + let result = scene.ucmd().arg("-l").arg("test_inode").succeeds(); + assert!(!re_long.is_match(result.stdout_str())); + assert!(!result.stdout_str().contains(inode_long)); assert_eq!(inode_short, inode_long) } +#[test] +#[cfg(not(windows))] +fn test_ls_indicator_style() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + // Setup: Directory, Symlink, and Pipes. + at.mkdir("directory"); + assert!(at.dir_exists("directory")); + + at.touch(&at.plus_as_string("link-src")); + at.symlink_file("link-src", "link-dest.link"); + assert!(at.is_symlink("link-dest.link")); + + at.mkfifo("named-pipe.fifo"); + assert!(at.is_fifo("named-pipe.fifo")); + + // Classify, File-Type, and Slash all contain indicators for directories. + let options = vec!["classify", "file-type", "slash"]; + for opt in options { + // Verify that classify and file-type both contain indicators for symlinks. + scene + .ucmd() + .arg(format!("--indicator-style={}", opt)) + .succeeds() + .stdout_contains(&"/"); + } + + // Same test as above, but with the alternate flags. + let options = vec!["--classify", "--file-type", "-p"]; + for opt in options { + scene + .ucmd() + .arg(format!("{}", opt)) + .succeeds() + .stdout_contains(&"/"); + } + + // Classify and File-Type all contain indicators for pipes and links. + let options = vec!["classify", "file-type"]; + for opt in options { + // Verify that classify and file-type both contain indicators for symlinks. + scene + .ucmd() + .arg(format!("--indicator-style={}", opt)) + .succeeds() + .stdout_contains(&"@") + .stdout_contains(&"|"); + } + + // Test sockets. Because the canonical way of making sockets to test is with + // TempDir, we need a separate test. + { + use self::unix_socket::UnixListener; + + let dir = TempDir::new("unix_socket").expect("failed to create dir"); + let socket_path = dir.path().join("sock"); + let _listener = UnixListener::bind(&socket_path).expect("failed to create socket"); + + new_ucmd!() + .args(&[ + PathBuf::from(dir.path().to_str().unwrap()), + PathBuf::from("--indicator-style=classify"), + ]) + .succeeds() + .stdout_only("sock=\n"); + } +} + +// Essentially the same test as above, but only test symlinks and directories, +// not pipes or sockets. +#[test] +#[cfg(not(unix))] +fn test_ls_indicator_style() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + // Setup: Directory, Symlink. + at.mkdir("directory"); + assert!(at.dir_exists("directory")); + + at.touch(&at.plus_as_string("link-src")); + at.symlink_file("link-src", "link-dest.link"); + assert!(at.is_symlink("link-dest.link")); + + // Classify, File-Type, and Slash all contain indicators for directories. + let options = vec!["classify", "file-type", "slash"]; + for opt in options { + // Verify that classify and file-type both contain indicators for symlinks. + let result = scene + .ucmd() + .arg(format!("--indicator-style={}", opt)) + .succeeds() + .stdout_contains(&"/"); + } + + // Same test as above, but with the alternate flags. + let options = vec!["--classify", "--file-type", "-p"]; + for opt in options { + let result = scene + .ucmd() + .arg(format!("{}", opt)) + .succeeds() + .stdout_contains(&"/"); + } + + // Classify and File-Type all contain indicators for pipes and links. + let options = vec!["classify", "file-type"]; + for opt in options { + // Verify that classify and file-type both contain indicators for symlinks. + let result = scene + .ucmd() + .arg(format!("--indicator-style={}", opt)) + .succeeds() + .stdout_contains(&"@"); + } +} + #[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 + scene .cmd("truncate") .arg("-s") .arg("+1000") .arg(file1) - .run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); + .succeeds(); - let result = scene.ucmd().arg("-hl").arg(file1).run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); - assert!(result.stdout.contains(" 1000 ")); + scene + .ucmd() + .arg("-hl") + .arg(file1) + .succeeds() + .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 + .ucmd() + .arg("-l") + .arg("--si") + .arg(file1) + .succeeds() + .stdout_contains(" 1.0k "); scene .cmd("truncate") @@ -785,63 +879,68 @@ fn test_ls_human_si() { .arg(file1) .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(" 1001K ")); + scene + .ucmd() + .arg("-hl") + .arg(file1) + .succeeds() + .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 ")); + scene + .ucmd() + .arg("-l") + .arg("--si") + .arg(file1) + .succeeds() + .stdout_contains(" 1.1M "); let file2 = "test-human-2"; - let result = scene + 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 ")); + .succeeds(); - 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 ")); + scene + .ucmd() + .arg("-hl") + .arg(file2) + .succeeds() + .stdout_contains(" 13M "); + + // GNU rounds up, so we must too. + scene + .ucmd() + .arg("-l") + .arg("--si") + .arg(file2) + .succeeds() + .stdout_contains(" 13M "); let file3 = "test-human-3"; - let result = scene + scene .cmd("truncate") .arg("-s") .arg("+9999") .arg(file3) - .run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); + .succeeds(); - 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 ")); + scene + .ucmd() + .arg("-hl") + .arg(file3) + .succeeds() + .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 ")); + scene + .ucmd() + .arg("-l") + .arg("--si") + .arg(file3) + .succeeds() + .stdout_contains(" 10k "); } #[cfg(windows)] @@ -858,16 +957,11 @@ fn test_ls_hidden_windows() { .arg("+S") .arg("+r") .arg(file) - .run(); - let result = scene.ucmd().run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); - let result = scene.ucmd().arg("-a").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert!(result.success); - assert!(result.stdout.contains(file)); + .succeeds(); + + let result = scene.ucmd().succeeds(); + assert!(!result.stdout_str().contains(file)); + let result = scene.ucmd().arg("-a").succeeds().stdout_contains(file); } #[test] @@ -927,23 +1021,296 @@ fn test_ls_version_sort() { "", // because of '\n' at the end of the output ]; - let result = scene.ucmd().arg("-1v").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); + let result = scene.ucmd().arg("-1v").succeeds(); + assert_eq!( + result.stdout_str().split('\n').collect::>(), + expected + ); - assert_eq!(result.stdout.split('\n').collect::>(), 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::>(), expected); - - let result = scene.ucmd().arg("-a1v").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); + let result = scene.ucmd().arg("-1").arg("--sort=version").succeeds(); + assert_eq!( + result.stdout_str().split('\n').collect::>(), + expected + ); + let result = scene.ucmd().arg("-a1v").succeeds(); expected.insert(0, ".."); expected.insert(0, "."); - assert_eq!(result.stdout.split('\n').collect::>(), expected,) + assert_eq!( + result.stdout_str().split('\n').collect::>(), + expected, + ) +} + +#[test] +fn test_ls_quoting_style() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("one two"); + at.touch("one"); + + // It seems that windows doesn't allow \n in filenames. + #[cfg(unix)] + { + at.touch("one\ntwo"); + // Default is shell-escape + scene + .ucmd() + .arg("one\ntwo") + .succeeds() + .stdout_only("'one'$'\\n''two'\n"); + + for (arg, correct) in &[ + ("--quoting-style=literal", "one?two"), + ("-N", "one?two"), + ("--literal", "one?two"), + ("--quoting-style=c", "\"one\\ntwo\""), + ("-Q", "\"one\\ntwo\""), + ("--quote-name", "\"one\\ntwo\""), + ("--quoting-style=escape", "one\\ntwo"), + ("-b", "one\\ntwo"), + ("--escape", "one\\ntwo"), + ("--quoting-style=shell-escape", "'one'$'\\n''two'"), + ("--quoting-style=shell-escape-always", "'one'$'\\n''two'"), + ("--quoting-style=shell", "one?two"), + ("--quoting-style=shell-always", "'one?two'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("one\ntwo") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } + + for (arg, correct) in &[ + ("--quoting-style=literal", "one?two"), + ("-N", "one?two"), + ("--literal", "one?two"), + ("--quoting-style=shell", "one?two"), + ("--quoting-style=shell-always", "'one?two'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("--hide-control-chars") + .arg("one\ntwo") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } + + for (arg, correct) in &[ + ("--quoting-style=literal", "one\ntwo"), + ("-N", "one\ntwo"), + ("--literal", "one\ntwo"), + ("--quoting-style=shell", "one\ntwo"), + ("--quoting-style=shell-always", "'one\ntwo'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("--show-control-chars") + .arg("one\ntwo") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } + } + + scene + .ucmd() + .arg("one two") + .succeeds() + .stdout_only("'one two'\n"); + + for (arg, correct) in &[ + ("--quoting-style=literal", "one two"), + ("-N", "one two"), + ("--literal", "one two"), + ("--quoting-style=c", "\"one two\""), + ("-Q", "\"one two\""), + ("--quote-name", "\"one two\""), + ("--quoting-style=escape", "one\\ two"), + ("-b", "one\\ two"), + ("--escape", "one\\ two"), + ("--quoting-style=shell-escape", "'one two'"), + ("--quoting-style=shell-escape-always", "'one two'"), + ("--quoting-style=shell", "'one two'"), + ("--quoting-style=shell-always", "'one two'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("one two") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } + + scene.ucmd().arg("one").succeeds().stdout_only("one\n"); + + for (arg, correct) in &[ + ("--quoting-style=literal", "one"), + ("-N", "one"), + ("--quoting-style=c", "\"one\""), + ("-Q", "\"one\""), + ("--quote-name", "\"one\""), + ("--quoting-style=escape", "one"), + ("-b", "one"), + ("--quoting-style=shell-escape", "one"), + ("--quoting-style=shell-escape-always", "'one'"), + ("--quoting-style=shell", "one"), + ("--quoting-style=shell-always", "'one'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("one") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } +} + +#[test] +fn test_ls_ignore_hide() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("README.md"); + at.touch("CONTRIBUTING.md"); + at.touch("some_other_file"); + at.touch("READMECAREFULLY.md"); + + scene + .ucmd() + .arg("--hide") + .arg("*") + .arg("-1") + .succeeds() + .stdout_only(""); + + scene + .ucmd() + .arg("--ignore") + .arg("*") + .arg("-1") + .succeeds() + .stdout_only(""); + + scene + .ucmd() + .arg("--ignore") + .arg("irrelevant pattern") + .arg("-1") + .succeeds() + .stdout_only("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); + + scene + .ucmd() + .arg("--ignore") + .arg("README*.md") + .arg("-1") + .succeeds() + .stdout_only("CONTRIBUTING.md\nsome_other_file\n"); + + scene + .ucmd() + .arg("--hide") + .arg("README*.md") + .arg("-1") + .succeeds() + .stdout_only("CONTRIBUTING.md\nsome_other_file\n"); + + scene + .ucmd() + .arg("--ignore") + .arg("*.md") + .arg("-1") + .succeeds() + .stdout_only("some_other_file\n"); + + scene + .ucmd() + .arg("-a") + .arg("--ignore") + .arg("*.md") + .arg("-1") + .succeeds() + .stdout_only(".\n..\nsome_other_file\n"); + + scene + .ucmd() + .arg("-a") + .arg("--hide") + .arg("*.md") + .arg("-1") + .succeeds() + .stdout_only(".\n..\nCONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); + + scene + .ucmd() + .arg("-A") + .arg("--ignore") + .arg("*.md") + .arg("-1") + .succeeds() + .stdout_only("some_other_file\n"); + + scene + .ucmd() + .arg("-A") + .arg("--hide") + .arg("*.md") + .arg("-1") + .succeeds() + .stdout_only("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); + + // Stacking multiple patterns + scene + .ucmd() + .arg("--ignore") + .arg("README*") + .arg("--ignore") + .arg("CONTRIBUTING*") + .arg("-1") + .succeeds() + .stdout_only("some_other_file\n"); + + scene + .ucmd() + .arg("--hide") + .arg("README*") + .arg("--ignore") + .arg("CONTRIBUTING*") + .arg("-1") + .succeeds() + .stdout_only("some_other_file\n"); + + scene + .ucmd() + .arg("--hide") + .arg("README*") + .arg("--hide") + .arg("CONTRIBUTING*") + .arg("-1") + .succeeds() + .stdout_only("some_other_file\n"); + + // Invalid patterns + scene + .ucmd() + .arg("--ignore") + .arg("READ[ME") + .arg("-1") + .succeeds() + .stderr_contains(&"Invalid pattern") + .stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); + + scene + .ucmd() + .arg("--hide") + .arg("READ[ME") + .arg("-1") + .succeeds() + .stderr_contains(&"Invalid pattern") + .stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); } diff --git a/tests/by-util/test_mkfifo.rs b/tests/by-util/test_mkfifo.rs index 651491045..f60c0a4b8 100644 --- a/tests/by-util/test_mkfifo.rs +++ b/tests/by-util/test_mkfifo.rs @@ -1 +1,48 @@ -// ToDO: add tests +use crate::common::util::*; + +#[test] +fn test_create_fifo_missing_operand() { + new_ucmd!() + .fails() + .stderr_is("mkfifo: error: missing operand"); +} + +#[test] +fn test_create_one_fifo() { + new_ucmd!().arg("abc").succeeds(); +} + +#[test] +fn test_create_one_fifo_with_invalid_mode() { + new_ucmd!() + .arg("abcd") + .arg("-m") + .arg("invalid") + .fails() + .stderr + .contains("invalid mode"); +} + +#[test] +fn test_create_multiple_fifos() { + new_ucmd!() + .arg("abcde") + .arg("def") + .arg("sed") + .arg("dum") + .succeeds(); +} + +#[test] +fn test_create_one_fifo_with_mode() { + new_ucmd!().arg("abcde").arg("-m600").succeeds(); +} + +#[test] +fn test_create_one_fifo_already_exists() { + new_ucmd!() + .arg("abcdef") + .arg("abcdef") + .fails() + .stderr_is("mkfifo: error: cannot create fifo 'abcdef': File exists"); +} diff --git a/tests/by-util/test_more.rs b/tests/by-util/test_more.rs index 3b7cfa9a8..736fb6956 100644 --- a/tests/by-util/test_more.rs +++ b/tests/by-util/test_more.rs @@ -6,3 +6,14 @@ fn test_more_no_arg() { let result = ucmd.run(); assert!(!result.success); } + +#[test] +fn test_more_dir_arg() { + let (_, mut ucmd) = at_and_ucmd!(); + ucmd.arg("."); + let result = ucmd.run(); + assert!(!result.success); + const EXPECTED_ERROR_MESSAGE: &str = + "more: '.' is a directory.\nTry 'more --help' for more information."; + assert_eq!(result.stderr.trim(), EXPECTED_ERROR_MESSAGE); +} diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index f3b766c26..b49e6f0ca 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -21,6 +21,7 @@ static ALPHA_OUT: &'static str = " // Test that od can read one file and dump with default format #[test] fn test_file() { + // TODO: Can this be replaced by AtPath? use std::env; let temp = env::temp_dir(); let tmpdir = Path::new(&temp); @@ -33,15 +34,12 @@ fn test_file() { } } - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg(file.as_os_str()) - .run(); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, unindent(ALPHA_OUT)); - + .succeeds() + .no_stderr() + .stdout_is(unindent(ALPHA_OUT)); let _ = remove_file(file); } @@ -64,16 +62,14 @@ fn test_2files() { } } - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg(file1.as_os_str()) .arg(file2.as_os_str()) - .run(); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, unindent(ALPHA_OUT)); - + .succeeds() + .no_stderr() + .stdout_is(unindent(ALPHA_OUT)); + // TODO: Handle errors? let _ = remove_file(file1); let _ = remove_file(file2); } @@ -85,22 +81,19 @@ fn test_no_file() { let tmpdir = Path::new(&temp); let file = tmpdir.join("}surely'none'would'thus'a'file'name"); - let result = new_ucmd!().arg(file.as_os_str()).run(); - - assert!(!result.success); + new_ucmd!().arg(file.as_os_str()).fails(); } // Test that od reads from stdin instead of a file #[test] fn test_from_stdin() { let input = "abcdefghijklmnopqrstuvwxyz\n"; - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") - .run_piped_stdin(input.as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, unindent(ALPHA_OUT)); + .run_piped_stdin(input.as_bytes()) + .success() + .no_stderr() + .stdout_is(unindent(ALPHA_OUT)); } // Test that od reads from stdin and also from files @@ -119,40 +112,35 @@ fn test_from_mixed() { } } - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg(file1.as_os_str()) .arg("-") .arg(file3.as_os_str()) - .run_piped_stdin(data2.as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, unindent(ALPHA_OUT)); + .run_piped_stdin(data2.as_bytes()) + .success() + .no_stderr() + .stdout_is(unindent(ALPHA_OUT)); } #[test] fn test_multiple_formats() { let input = "abcdefghijklmnopqrstuvwxyz\n"; - let result = new_ucmd!() + new_ucmd!() .arg("-c") .arg("-b") - .run_piped_stdin(input.as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + .run_piped_stdin(input.as_bytes()) + .success() + .no_stderr() + .stdout_is(unindent( " 0000000 a b c d e f g h i j k l m n o p 141 142 143 144 145 146 147 150 151 152 153 154 155 156 157 160 0000020 q r s t u v w x y z \\n 161 162 163 164 165 166 167 170 171 172 012 0000033 - " - ) - ); + ", + )); } #[test] @@ -166,14 +154,13 @@ fn test_dec() { 0000016 ", ); - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg("-s") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .success() + .no_stderr() + .stdout_is(expected_output); } #[test] @@ -185,14 +172,13 @@ fn test_hex16() { 0000011 ", ); - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg("-x") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .success() + .no_stderr() + .stdout_is(expected_output); } #[test] @@ -204,14 +190,13 @@ fn test_hex32() { 0000011 ", ); - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg("-X") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .success() + .no_stderr() + .stdout_is(expected_output); } #[test] @@ -232,15 +217,14 @@ fn test_f16() { 0000016 ", ); - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg("-tf2") .arg("-w8") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .success() + .no_stderr() + .stdout_is(expected_output); } #[test] @@ -261,14 +245,13 @@ fn test_f32() { 0000034 ", ); - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg("-f") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .success() + .no_stderr() + .stdout_is(expected_output); } #[test] @@ -291,36 +274,31 @@ fn test_f64() { 0000050 ", ); - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg("-F") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .success() + .no_stderr() + .stdout_is(expected_output); } #[test] fn test_multibyte() { - let result = new_ucmd!() + new_ucmd!() .arg("-c") .arg("-w12") - .run_piped_stdin("Universität Tübingen \u{1B000}".as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + .run_piped_stdin("Universität Tübingen \u{1B000}".as_bytes()) + .success() + .no_stderr() + .stdout_is(unindent( " 0000000 U n i v e r s i t ä ** t 0000014 T ü ** b i n g e n \u{1B000} 0000030 ** ** ** 0000033 - " - ) - ); + ", + )); } #[test] @@ -334,11 +312,13 @@ fn test_width() { ", ); - let result = new_ucmd!().arg("-w4").arg("-v").run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + new_ucmd!() + .arg("-w4") + .arg("-v") + .run_piped_stdin(&input[..]) + .success() + .no_stderr() + .stdout_is(expected_output); } #[test] @@ -352,14 +332,13 @@ fn test_invalid_width() { ", ); - let result = new_ucmd!().arg("-w5").arg("-v").run_piped_stdin(&input[..]); - - assert_eq!( - result.stderr, - "od: warning: invalid width 5; using 2 instead\n" - ); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + new_ucmd!() + .arg("-w5") + .arg("-v") + .run_piped_stdin(&input[..]) + .success() + .stderr_is_bytes("od: warning: invalid width 5; using 2 instead\n".as_bytes()) + .stdout_is(expected_output); } #[test] @@ -373,14 +352,13 @@ fn test_zero_width() { ", ); - let result = new_ucmd!().arg("-w0").arg("-v").run_piped_stdin(&input[..]); - - assert_eq!( - result.stderr, - "od: warning: invalid width 0; using 2 instead\n" - ); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + new_ucmd!() + .arg("-w0") + .arg("-v") + .run_piped_stdin(&input[..]) + .success() + .stderr_is_bytes("od: warning: invalid width 0; using 2 instead\n".as_bytes()) + .stdout_is(expected_output); } #[test] @@ -392,11 +370,12 @@ fn test_width_without_value() { 0000050 "); - let result = new_ucmd!().arg("-w").run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + new_ucmd!() + .arg("-w") + .run_piped_stdin(&input[..]) + .success() + .no_stderr() + .stdout_is(expected_output); } #[test] @@ -421,15 +400,14 @@ fn test_suppress_duplicates() { ", ); - let result = new_ucmd!() + new_ucmd!() .arg("-w4") .arg("-O") .arg("-x") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .no_stderr() + .success() + .stdout_is(expected_output); } #[test] @@ -446,17 +424,16 @@ fn test_big_endian() { ", ); - let result = new_ucmd!() + new_ucmd!() .arg("--endian=big") .arg("-F") .arg("-f") .arg("-X") .arg("-x") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .no_stderr() + .success() + .stdout_is(expected_output); } #[test] @@ -474,16 +451,15 @@ fn test_alignment_Xxa() { ); // in this case the width of the -a (8-bit) determines the alignment for the other fields - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg("-X") .arg("-x") .arg("-a") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .no_stderr() + .success() + .stdout_is(expected_output); } #[test] @@ -500,15 +476,14 @@ fn test_alignment_Fx() { ); // in this case the width of the -F (64-bit) determines the alignment for the other field - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg("-F") .arg("-x") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .no_stderr() + .success() + .stdout_is(expected_output); } #[test] @@ -528,16 +503,15 @@ fn test_maxuint() { ", ); - let result = new_ucmd!() + new_ucmd!() .arg("--format=o8") .arg("-Oobtu8") .arg("-Dd") .arg("--format=u1") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .no_stderr() + .success() + .stdout_is(expected_output); } #[test] @@ -553,15 +527,14 @@ fn test_hex_offset() { ", ); - let result = new_ucmd!() + new_ucmd!() .arg("-Ax") .arg("-X") .arg("-X") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .no_stderr() + .success() + .stdout_is(expected_output); } #[test] @@ -577,15 +550,14 @@ fn test_dec_offset() { ", ); - let result = new_ucmd!() + new_ucmd!() .arg("-Ad") .arg("-X") .arg("-X") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .no_stderr() + .success() + .stdout_is(expected_output); } #[test] @@ -594,66 +566,57 @@ fn test_no_offset() { const LINE: &'static str = " 00000000 00000000 00000000 00000000\n"; let expected_output = [LINE, LINE, LINE, LINE].join(""); - let result = new_ucmd!() + new_ucmd!() .arg("-An") .arg("-X") .arg("-X") - .run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, expected_output); + .run_piped_stdin(&input[..]) + .no_stderr() + .success() + .stdout_is(expected_output); } #[test] fn test_invalid_offset() { - let result = new_ucmd!().arg("-Ab").run(); - - assert!(!result.success); + new_ucmd!().arg("-Ab").fails(); } #[test] fn test_skip_bytes() { let input = "abcdefghijklmnopq"; - let result = new_ucmd!() + new_ucmd!() .arg("-c") .arg("--skip-bytes=5") - .run_piped_stdin(input.as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + .run_piped_stdin(input.as_bytes()) + .no_stderr() + .success() + .stdout_is(unindent( " 0000005 f g h i j k l m n o p q 0000021 - " - ) - ); + ", + )); } #[test] fn test_skip_bytes_error() { let input = "12345"; - let result = new_ucmd!() + new_ucmd!() .arg("--skip-bytes=10") - .run_piped_stdin(input.as_bytes()); - - assert!(!result.success); + .run_piped_stdin(input.as_bytes()) + .failure(); } #[test] fn test_read_bytes() { let input = "abcdefghijklmnopqrstuvwxyz\n12345678"; - let result = new_ucmd!() + new_ucmd!() .arg("--endian=little") .arg("--read-bytes=27") - .run_piped_stdin(input.as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!(result.stdout, unindent(ALPHA_OUT)); + .run_piped_stdin(input.as_bytes()) + .no_stderr() + .success() + .stdout_is(unindent(ALPHA_OUT)); } #[test] @@ -662,13 +625,12 @@ fn test_ascii_dump() { 0x00, 0x01, 0x0a, 0x0d, 0x10, 0x1f, 0x20, 0x61, 0x62, 0x63, 0x7d, 0x7e, 0x7f, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0xff, ]; - let result = new_ucmd!().arg("-tx1zacz").run_piped_stdin(&input[..]); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + new_ucmd!() + .arg("-tx1zacz") + .run_piped_stdin(&input[..]) + .no_stderr() + .success() + .stdout_is(unindent( r" 0000000 00 01 0a 0d 10 1f 20 61 62 63 7d 7e 7f 80 90 a0 >...... abc}~....< nul soh nl cr dle us sp a b c } ~ del nul dle sp @@ -677,9 +639,8 @@ fn test_ascii_dump() { 0 @ P ` p del ** 300 320 340 360 377 >......< 0000026 - " - ) - ); + ", + )); } #[test] @@ -687,159 +648,136 @@ fn test_filename_parsing() { // files "a" and "x" both exists, but are no filenames in the commandline below // "-f" must be treated as a filename, it contains the text: minus lowercase f // so "-f" should not be interpreted as a formatting option. - let result = new_ucmd!() + new_ucmd!() .arg("--format") .arg("a") .arg("-A") .arg("x") .arg("--") .arg("-f") - .run(); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + .succeeds() + .no_stderr() + .stdout_is(unindent( " 000000 m i n u s sp l o w e r c a s e sp 000010 f nl 000012 - " - ) - ); + ", + )); } #[test] fn test_stdin_offset() { let input = "abcdefghijklmnopq"; - let result = new_ucmd!() + new_ucmd!() .arg("-c") .arg("+5") - .run_piped_stdin(input.as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + .run_piped_stdin(input.as_bytes()) + .no_stderr() + .success() + .stdout_is(unindent( " 0000005 f g h i j k l m n o p q 0000021 - " - ) - ); + ", + )); } #[test] fn test_file_offset() { - let result = new_ucmd!().arg("-c").arg("--").arg("-f").arg("10").run(); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + new_ucmd!() + .arg("-c") + .arg("--") + .arg("-f") + .arg("10") + .succeeds() + .no_stderr() + .stdout_is(unindent( r" 0000010 w e r c a s e f \n 0000022 - " - ) - ); + ", + )); } #[test] fn test_traditional() { // note gnu od does not align both lines let input = "abcdefghijklmnopq"; - let result = new_ucmd!() + new_ucmd!() .arg("--traditional") .arg("-a") .arg("-c") .arg("-") .arg("10") .arg("0") - .run_piped_stdin(input.as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + .run_piped_stdin(input.as_bytes()) + .no_stderr() + .success() + .stdout_is(unindent( r" 0000010 (0000000) i j k l m n o p q i j k l m n o p q 0000021 (0000011) - " - ) - ); + ", + )); } #[test] fn test_traditional_with_skip_bytes_override() { // --skip-bytes is ignored in this case let input = "abcdefghijklmnop"; - let result = new_ucmd!() + new_ucmd!() .arg("--traditional") .arg("--skip-bytes=10") .arg("-c") .arg("0") - .run_piped_stdin(input.as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + .run_piped_stdin(input.as_bytes()) + .no_stderr() + .success() + .stdout_is(unindent( r" 0000000 a b c d e f g h i j k l m n o p 0000020 - " - ) - ); + ", + )); } #[test] fn test_traditional_with_skip_bytes_non_override() { // no offset specified in the traditional way, so --skip-bytes is used let input = "abcdefghijklmnop"; - let result = new_ucmd!() + new_ucmd!() .arg("--traditional") .arg("--skip-bytes=10") .arg("-c") - .run_piped_stdin(input.as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + .run_piped_stdin(input.as_bytes()) + .no_stderr() + .success() + .stdout_is(unindent( r" 0000012 k l m n o p 0000020 - " - ) - ); + ", + )); } #[test] fn test_traditional_error() { // file "0" exists - don't fail on that, but --traditional only accepts a single input - let result = new_ucmd!() + new_ucmd!() .arg("--traditional") .arg("0") .arg("0") .arg("0") .arg("0") - .run(); - - assert!(!result.success); + .fails(); } #[test] fn test_traditional_only_label() { let input = "abcdefghijklmnopqrstuvwxyz"; - let result = new_ucmd!() + new_ucmd!() .arg("-An") .arg("--traditional") .arg("-a") @@ -847,20 +785,16 @@ fn test_traditional_only_label() { .arg("-") .arg("10") .arg("0x10") - .run_piped_stdin(input.as_bytes()); - - assert_empty_stderr!(result); - assert!(result.success); - assert_eq!( - result.stdout, - unindent( + .run_piped_stdin(input.as_bytes()) + .no_stderr() + .success() + .stdout_is(unindent( r" (0000020) i j k l m n o p q r s t u v w x i j k l m n o p q r s t u v w x (0000040) y z y z (0000042) - " - ) - ); + ", + )); } diff --git a/tests/by-util/test_pathchk.rs b/tests/by-util/test_pathchk.rs index e24a464e0..3bc12f0b6 100644 --- a/tests/by-util/test_pathchk.rs +++ b/tests/by-util/test_pathchk.rs @@ -5,11 +5,152 @@ fn test_default_mode() { // test the default mode // accept some reasonable default - new_ucmd!().args(&["abc/def"]).succeeds().no_stdout(); + new_ucmd!().args(&["dir/file"]).succeeds().no_stdout(); - // fail on long inputs + // accept non-portable chars + new_ucmd!().args(&["dir#/$file"]).succeeds().no_stdout(); + + // accept empty path + new_ucmd!().args(&[""]).succeeds().no_stdout(); + + // fail on long path new_ucmd!() - .args(&["test".repeat(20000)]) + .args(&["dir".repeat(libc::PATH_MAX as usize + 1)]) + .fails() + .no_stdout(); + + // fail on long filename + new_ucmd!() + .args(&[format!( + "dir/{}", + "file".repeat(libc::FILENAME_MAX as usize + 1) + )]) .fails() .no_stdout(); } + +#[test] +fn test_posix_mode() { + // test the posix mode + + // accept some reasonable default + new_ucmd!().args(&["-p", "dir/file"]).succeeds().no_stdout(); + + // fail on long path + new_ucmd!() + .args(&["-p", &"dir".repeat(libc::PATH_MAX as usize + 1).as_str()]) + .fails() + .no_stdout(); + + // fail on long filename + new_ucmd!() + .args(&[ + "-p", + &format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(), + ]) + .fails() + .no_stdout(); + + // fail on non-portable chars + new_ucmd!().args(&["-p", "dir#/$file"]).fails().no_stdout(); +} + +#[test] +fn test_posix_special() { + // test the posix special mode + + // accept some reasonable default + new_ucmd!().args(&["-P", "dir/file"]).succeeds().no_stdout(); + + // accept non-portable chars + new_ucmd!() + .args(&["-P", "dir#/$file"]) + .succeeds() + .no_stdout(); + + // accept non-leading hyphen + new_ucmd!() + .args(&["-P", "dir/file-name"]) + .succeeds() + .no_stdout(); + + // fail on long path + new_ucmd!() + .args(&["-P", &"dir".repeat(libc::PATH_MAX as usize + 1).as_str()]) + .fails() + .no_stdout(); + + // fail on long filename + new_ucmd!() + .args(&[ + "-P", + &format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(), + ]) + .fails() + .no_stdout(); + + // fail on leading hyphen char + new_ucmd!().args(&["-P", "dir/-file"]).fails().no_stdout(); + + // fail on empty path + new_ucmd!().args(&["-P", ""]).fails().no_stdout(); +} + +#[test] +fn test_posix_all() { + // test the posix special mode + + // accept some reasonable default + new_ucmd!() + .args(&["-p", "-P", "dir/file"]) + .succeeds() + .no_stdout(); + + // accept non-leading hyphen + new_ucmd!() + .args(&["-p", "-P", "dir/file-name"]) + .succeeds() + .no_stdout(); + + // fail on long path + new_ucmd!() + .args(&[ + "-p", + "-P", + &"dir".repeat(libc::PATH_MAX as usize + 1).as_str(), + ]) + .fails() + .no_stdout(); + + // fail on long filename + new_ucmd!() + .args(&[ + "-p", + "-P", + &format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(), + ]) + .fails() + .no_stdout(); + + // fail on non-portable chars + new_ucmd!() + .args(&["-p", "-P", "dir#/$file"]) + .fails() + .no_stdout(); + + // fail on leading hyphen char + new_ucmd!() + .args(&["-p", "-P", "dir/-file"]) + .fails() + .no_stdout(); + + // fail on empty path + new_ucmd!().args(&["-p", "-P", ""]).fails().no_stdout(); +} + +#[test] +fn test_args_parsing() { + // fail on no args + let empty_args: [String; 0] = []; + new_ucmd!().args(&empty_args).fails().no_stdout(); +} diff --git a/tests/by-util/test_realpath.rs b/tests/by-util/test_realpath.rs index 750a8db01..e1384ac74 100644 --- a/tests/by-util/test_realpath.rs +++ b/tests/by-util/test_realpath.rs @@ -1,106 +1,108 @@ use crate::common::util::*; #[test] -fn test_current_directory() { +fn test_realpath_current_directory() { let (at, mut ucmd) = at_and_ucmd!(); - let actual = ucmd.arg(".").run().stdout; let expect = at.root_dir_resolved() + "\n"; - println!("actual: {:?}", actual); - println!("expect: {:?}", expect); - assert_eq!(actual, expect); + ucmd.arg(".").succeeds().stdout_is(expect); } #[test] -fn test_long_redirection_to_current_dir() { +fn test_realpath_long_redirection_to_current_dir() { let (at, mut ucmd) = at_and_ucmd!(); // Create a 256-character path to current directory let dir = path_concat!(".", ..128); - let actual = ucmd.arg(dir).run().stdout; let expect = at.root_dir_resolved() + "\n"; - println!("actual: {:?}", actual); - println!("expect: {:?}", expect); - assert_eq!(actual, expect); + ucmd.arg(dir).succeeds().stdout_is(expect); } #[test] -fn test_long_redirection_to_root() { +fn test_realpath_long_redirection_to_root() { // Create a 255-character path to root let dir = path_concat!("..", ..85); - let actual = new_ucmd!().arg(dir).run().stdout; let expect = get_root_path().to_owned() + "\n"; - println!("actual: {:?}", actual); - println!("expect: {:?}", expect); - assert_eq!(actual, expect); + new_ucmd!().arg(dir).succeeds().stdout_is(expect); } #[test] -fn test_file_and_links() { +fn test_realpath_file_and_links() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("foo"); at.symlink_file("foo", "bar"); - let actual = scene.ucmd().arg("foo").run().stdout; - println!("actual: {:?}", actual); - assert!(actual.contains("foo\n")); - - let actual = scene.ucmd().arg("bar").run().stdout; - println!("actual: {:?}", actual); - assert!(actual.contains("foo\n")); + scene.ucmd().arg("foo").succeeds().stdout_contains("foo\n"); + scene.ucmd().arg("bar").succeeds().stdout_contains("foo\n"); } #[test] -fn test_file_and_links_zero() { +fn test_realpath_file_and_links_zero() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("foo"); at.symlink_file("foo", "bar"); - let actual = scene.ucmd().arg("foo").arg("-z").run().stdout; - println!("actual: {:?}", actual); - assert!(actual.contains("foo")); - assert!(!actual.contains("\n")); + scene + .ucmd() + .arg("foo") + .arg("-z") + .succeeds() + .stdout_contains("foo\u{0}"); - let actual = scene.ucmd().arg("bar").arg("-z").run().stdout; - println!("actual: {:?}", actual); - assert!(actual.contains("foo")); - assert!(!actual.contains("\n")); + scene + .ucmd() + .arg("bar") + .arg("-z") + .succeeds() + .stdout_contains("foo\u{0}"); } #[test] -fn test_file_and_links_strip() { +fn test_realpath_file_and_links_strip() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("foo"); at.symlink_file("foo", "bar"); - let actual = scene.ucmd().arg("foo").arg("-s").run().stdout; - println!("actual: {:?}", actual); - assert!(actual.contains("foo\n")); + scene + .ucmd() + .arg("foo") + .arg("-s") + .succeeds() + .stdout_contains("foo\n"); - let actual = scene.ucmd().arg("bar").arg("-s").run().stdout; - println!("actual: {:?}", actual); - assert!(actual.contains("bar\n")); + scene + .ucmd() + .arg("bar") + .arg("-s") + .succeeds() + .stdout_contains("bar\n"); } #[test] -fn test_file_and_links_strip_zero() { +fn test_realpath_file_and_links_strip_zero() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("foo"); at.symlink_file("foo", "bar"); - let actual = scene.ucmd().arg("foo").arg("-s").arg("-z").run().stdout; - println!("actual: {:?}", actual); - assert!(actual.contains("foo")); - assert!(!actual.contains("\n")); + scene + .ucmd() + .arg("foo") + .arg("-s") + .arg("-z") + .succeeds() + .stdout_contains("foo\u{0}"); - let actual = scene.ucmd().arg("bar").arg("-s").arg("-z").run().stdout; - println!("actual: {:?}", actual); - assert!(actual.contains("bar")); - assert!(!actual.contains("\n")); + scene + .ucmd() + .arg("bar") + .arg("-s") + .arg("-z") + .succeeds() + .stdout_contains("bar\u{0}"); } diff --git a/tests/by-util/test_relpath.rs b/tests/by-util/test_relpath.rs index 651491045..cc17b45c3 100644 --- a/tests/by-util/test_relpath.rs +++ b/tests/by-util/test_relpath.rs @@ -1 +1,177 @@ -// ToDO: add tests +use crate::common::util::*; +use std::borrow::Cow; +use std::path::Path; + +struct TestCase<'a> { + from: &'a str, + to: &'a str, + expected: &'a str, +} + +const TESTS: [TestCase; 10] = [ + TestCase { + from: "A/B/C", + to: "A", + expected: "../..", + }, + TestCase { + from: "A/B/C", + to: "A/B", + expected: "..", + }, + TestCase { + from: "A/B/C", + to: "A/B/C", + expected: "", + }, + TestCase { + from: "A/B/C", + to: "A/B/C/D", + expected: "D", + }, + TestCase { + from: "A/B/C", + to: "A/B/C/D/E", + expected: "D/E", + }, + TestCase { + from: "A/B/C", + to: "A/B/D", + expected: "../D", + }, + TestCase { + from: "A/B/C", + to: "A/B/D/E", + expected: "../D/E", + }, + TestCase { + from: "A/B/C", + to: "A/D", + expected: "../../D", + }, + TestCase { + from: "A/B/C", + to: "D/E/F", + expected: "../../../D/E/F", + }, + TestCase { + from: "A/B/C", + to: "A/D/E", + expected: "../../D/E", + }, +]; + +fn convert_path<'a>(path: &'a str) -> Cow<'a, str> { + #[cfg(windows)] + return path.replace("/", "\\").into(); + #[cfg(not(windows))] + return path.into(); +} + +#[test] +fn test_relpath_with_from_no_d() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + for test in TESTS.iter() { + let from: &str = &convert_path(test.from); + let to: &str = &convert_path(test.to); + let expected: &str = &convert_path(test.expected); + + at.mkdir_all(to); + at.mkdir_all(from); + + scene + .ucmd() + .arg(to) + .arg(from) + .succeeds() + .stdout_only(&format!("{}\n", expected)); + } +} + +#[test] +fn test_relpath_with_from_with_d() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + for test in TESTS.iter() { + let from: &str = &convert_path(test.from); + let to: &str = &convert_path(test.to); + let pwd = at.as_string(); + at.mkdir_all(to); + at.mkdir_all(from); + + // d is part of subpath -> expect relative path + let mut result_stdout = scene + .ucmd() + .arg(to) + .arg(from) + .arg(&format!("-d{}", pwd)) + .succeeds() + .stdout_move_str(); + // relax rules for windows test environment + #[cfg(not(windows))] + assert!(Path::new(&result_stdout).is_relative()); + + // d is not part of subpath -> expect absolut path + result_stdout = scene + .ucmd() + .arg(to) + .arg(from) + .arg("-dnon_existing") + .succeeds() + .stdout_move_str(); + assert!(Path::new(&result_stdout).is_absolute()); + } +} + +#[test] +fn test_relpath_no_from_no_d() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + for test in TESTS.iter() { + let to: &str = &convert_path(test.to); + at.mkdir_all(to); + + let result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str(); + #[cfg(not(windows))] + assert_eq!(result_stdout, format!("{}\n", to)); + // relax rules for windows test environment + #[cfg(windows)] + assert!(result_stdout.ends_with(&format!("{}\n", to))); + } +} + +#[test] +fn test_relpath_no_from_with_d() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + for test in TESTS.iter() { + let to: &str = &convert_path(test.to); + let pwd = at.as_string(); + at.mkdir_all(to); + + // d is part of subpath -> expect relative path + let mut result_stdout = scene + .ucmd() + .arg(to) + .arg(&format!("-d{}", pwd)) + .succeeds() + .stdout_move_str(); + // relax rules for windows test environment + #[cfg(not(windows))] + assert!(Path::new(&result_stdout).is_relative()); + + // d is not part of subpath -> expect absolut path + result_stdout = scene + .ucmd() + .arg(to) + .arg("-dnon_existing") + .succeeds() + .stdout_move_str(); + assert!(Path::new(&result_stdout).is_absolute()); + } +} diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index c3635d202..9a068887c 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -15,9 +15,12 @@ fn test_rm_one_file() { #[test] fn test_rm_failed() { let (_at, mut ucmd) = at_and_ucmd!(); - let file = "test_rm_one_file"; + let file = "test_rm_one_file"; // Doesn't exist - ucmd.arg(file).fails(); // Doesn't exist + ucmd.arg(file).fails().stderr_contains(&format!( + "cannot remove '{}': No such file or directory", + file + )); } #[test] @@ -115,6 +118,39 @@ fn test_rm_empty_directory() { assert!(!at.dir_exists(dir)); } +#[test] +fn test_rm_empty_directory_verbose() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rm_empty_directory_verbose"; + + at.mkdir(dir); + + ucmd.arg("-d") + .arg("-v") + .arg(dir) + .succeeds() + .stdout_only(format!("removed directory '{}'\n", dir)); + + assert!(!at.dir_exists(dir)); +} + +#[test] +fn test_rm_non_empty_directory() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rm_non_empty_dir"; + let file_a = &format!("{}/test_rm_non_empty_file_a", dir); + + at.mkdir(dir); + at.touch(file_a); + + ucmd.arg("-d") + .arg(dir) + .fails() + .stderr_contains(&format!("cannot remove '{}': Directory not empty", dir)); + assert!(at.file_exists(file_a)); + assert!(at.dir_exists(dir)); +} + #[test] fn test_rm_recursive() { let (at, mut ucmd) = at_and_ucmd!(); @@ -134,22 +170,15 @@ fn test_rm_recursive() { } #[test] -fn test_rm_errors() { +fn test_rm_directory_without_flag() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_rm_errors_directory"; - let file_a = "test_rm_errors_directory/test_rm_errors_file_a"; - let file_b = "test_rm_errors_directory/test_rm_errors_file_b"; + let dir = "test_rm_directory_without_flag_dir"; at.mkdir(dir); - at.touch(file_a); - at.touch(file_b); - // $ rm test_rm_errors_directory - // rm: error: could not remove directory 'test_rm_errors_directory' (did you mean to pass '-r'?) - ucmd.arg(dir).fails().stderr_is( - "rm: error: could not remove directory 'test_rm_errors_directory' (did you mean \ - to pass '-r' or '-R'?)\n", - ); + ucmd.arg(dir) + .fails() + .stderr_contains(&format!("cannot remove '{}': Is a directory", dir)); } #[test] @@ -169,10 +198,13 @@ fn test_rm_verbose() { } #[test] -fn test_rm_dir_symlink() { +#[cfg(not(windows))] +// on unix symlink_dir is a file +fn test_rm_symlink_dir() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_rm_dir_symlink_dir"; - let link = "test_rm_dir_symlink_link"; + + let dir = "test_rm_symlink_dir_directory"; + let link = "test_rm_symlink_dir_link"; at.mkdir(dir); at.symlink_dir(dir, link); @@ -180,6 +212,30 @@ fn test_rm_dir_symlink() { ucmd.arg(link).succeeds(); } +#[test] +#[cfg(windows)] +// on windows removing symlink_dir requires "-r" or "-d" +fn test_rm_symlink_dir() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let dir = "test_rm_symlink_dir_directory"; + let link = "test_rm_symlink_dir_link"; + + at.mkdir(dir); + at.symlink_dir(dir, link); + + scene + .ucmd() + .arg(link) + .fails() + .stderr_contains(&format!("cannot remove '{}': Is a directory", link)); + + assert!(at.dir_exists(link)); + + scene.ucmd().arg("-r").arg(link).succeeds(); +} + #[test] fn test_rm_invalid_symlink() { let (at, mut ucmd) = at_and_ucmd!(); @@ -204,3 +260,32 @@ fn test_rm_no_operand() { ucmd.fails() .stderr_is("rm: error: missing an argument\nrm: error: for help, try 'rm --help'\n"); } + +#[test] +fn test_rm_verbose_slash() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rm_verbose_slash_directory"; + let file_a = &format!("{}/test_rm_verbose_slash_file_a", dir); + + at.mkdir(dir); + at.touch(file_a); + + let file_a_normalized = &format!( + "{}{}test_rm_verbose_slash_file_a", + dir, + std::path::MAIN_SEPARATOR + ); + + ucmd.arg("-r") + .arg("-f") + .arg("-v") + .arg(&format!("{}///", dir)) + .succeeds() + .stdout_only(format!( + "removed '{}'\nremoved directory '{}'\n", + file_a_normalized, dir + )); + + assert!(!at.dir_exists(dir)); + assert!(!at.file_exists(file_a)); +} diff --git a/tests/by-util/test_shuf.rs b/tests/by-util/test_shuf.rs index 651491045..717971bd4 100644 --- a/tests/by-util/test_shuf.rs +++ b/tests/by-util/test_shuf.rs @@ -1 +1,215 @@ -// ToDO: add tests +use crate::common::util::*; + +#[test] +fn test_output_is_random_permutation() { + let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let input = input_seq + .iter() + .map(|x| x.to_string()) + .collect::>() + .join("\n"); + + let result = new_ucmd!() + .pipe_in(input.as_bytes()) + .succeeds() + .no_stderr() + .stdout + .clone(); + + let mut result_seq: Vec = result + .split("\n") + .filter(|x| !x.is_empty()) + .map(|x| x.parse().unwrap()) + .collect(); + result_seq.sort(); + assert_ne!(result, input, "Output is not randomised"); + assert_eq!(result_seq, input_seq, "Output is not a permutation"); +} + +#[test] +fn test_zero_termination() { + let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let result = new_ucmd!() + .arg("-z") + .arg("-i1-10") + .succeeds() + .no_stderr() + .stdout + .clone(); + + let mut result_seq: Vec = result + .split("\0") + .filter(|x| !x.is_empty()) + .map(|x| x.parse().unwrap()) + .collect(); + result_seq.sort(); + assert_eq!(result_seq, input_seq, "Output is not a permutation"); +} + +#[test] +fn test_echo() { + let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let result = new_ucmd!() + .arg("-e") + .args( + &input_seq + .iter() + .map(|x| x.to_string()) + .collect::>(), + ) + .succeeds() + .no_stderr() + .stdout + .clone(); + + let mut result_seq: Vec = result + .split("\n") + .filter(|x| !x.is_empty()) + .map(|x| x.parse().unwrap()) + .collect(); + result_seq.sort(); + assert_eq!(result_seq, input_seq, "Output is not a permutation"); +} + +#[test] +fn test_head_count() { + let repeat_limit = 5; + let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let input = input_seq + .iter() + .map(|x| x.to_string()) + .collect::>() + .join("\n"); + + let result = new_ucmd!() + .args(&["-n", &repeat_limit.to_string()]) + .pipe_in(input.as_bytes()) + .succeeds() + .no_stderr() + .stdout + .clone(); + + let mut result_seq: Vec = result + .split("\n") + .filter(|x| !x.is_empty()) + .map(|x| x.parse().unwrap()) + .collect(); + result_seq.sort(); + assert_eq!(result_seq.len(), repeat_limit, "Output is not limited"); + assert!( + result_seq.iter().all(|x| input_seq.contains(x)), + "Output includes element not from input: {}", + result + ) +} + +#[test] +fn test_repeat() { + let repeat_limit = 15000; + let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let input = input_seq + .iter() + .map(|x| x.to_string()) + .collect::>() + .join("\n"); + + let result = new_ucmd!() + .arg("-r") + .args(&["-n", &repeat_limit.to_string()]) + .pipe_in(input.as_bytes()) + .succeeds() + .no_stderr() + .stdout + .clone(); + + let result_seq: Vec = result + .split("\n") + .filter(|x| !x.is_empty()) + .map(|x| x.parse().unwrap()) + .collect(); + assert_eq!( + result_seq.len(), + repeat_limit, + "Output is not repeating forever" + ); + assert!( + result_seq.iter().all(|x| input_seq.contains(x)), + "Output includes element not from input: {:?}", + result_seq + .iter() + .filter(|x| !input_seq.contains(x)) + .collect::>() + ) +} + +#[test] +fn test_file_input() { + let expected_seq = vec![11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; + + let result = new_ucmd!() + .arg("file_input.txt") + .succeeds() + .no_stderr() + .stdout + .clone(); + + let mut result_seq: Vec = result + .split("\n") + .filter(|x| !x.is_empty()) + .map(|x| x.parse().unwrap()) + .collect(); + result_seq.sort(); + assert_eq!(result_seq, expected_seq, "Output is not a permutation"); +} + +#[test] +fn test_shuf_echo_and_input_range_not_allowed() { + let result = new_ucmd!().args(&["-e", "0", "-i", "0-2"]).run(); + + assert!(!result.success); + assert!(result + .stderr + .contains("The argument '--input-range ' cannot be used with '--echo ...'")); +} + +#[test] +fn test_shuf_input_range_and_file_not_allowed() { + let result = new_ucmd!().args(&["-i", "0-9", "file"]).run(); + + assert!(!result.success); + assert!(result + .stderr + .contains("The argument '' cannot be used with '--input-range '")); +} + +#[test] +fn test_shuf_invalid_input_range_one() { + let result = new_ucmd!().args(&["-i", "0"]).run(); + + assert!(!result.success); + assert!(result.stderr.contains("invalid input range")); +} + +#[test] +fn test_shuf_invalid_input_range_two() { + let result = new_ucmd!().args(&["-i", "a-9"]).run(); + + assert!(!result.success); + assert!(result.stderr.contains("invalid input range: 'a'")); +} + +#[test] +fn test_shuf_invalid_input_range_three() { + let result = new_ucmd!().args(&["-i", "0-b"]).run(); + + assert!(!result.success); + assert!(result.stderr.contains("invalid input range: 'b'")); +} + +#[test] +fn test_shuf_invalid_input_line_count() { + let result = new_ucmd!().args(&["-n", "a"]).run(); + + assert!(!result.success); + assert!(result.stderr.contains("invalid line count: 'a'")); +} diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 9ff1b3522..43aaf1da1 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1,5 +1,64 @@ use crate::common::util::*; +#[test] +fn test_check_zero_terminated_failure() { + new_ucmd!() + .arg("-z") + .arg("-c") + .arg("zero-terminated.txt") + .fails() + .stdout_is("sort: disorder in line 0\n"); +} + +#[test] +fn test_check_zero_terminated_success() { + new_ucmd!() + .arg("-z") + .arg("-c") + .arg("zero-terminated.expected") + .succeeds(); +} + +#[test] +fn test_random_shuffle_len() { + // check whether output is the same length as the input + const FILE: &'static str = "default_unsorted_ints.expected"; + let (at, _ucmd) = at_and_ucmd!(); + let result = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let expected = at.read(FILE); + + assert_ne!(result, expected); + assert_eq!(result.len(), expected.len()); +} + +#[test] +fn test_random_shuffle_contains_all_lines() { + // check whether lines of input are all in output + const FILE: &'static str = "default_unsorted_ints.expected"; + let (at, _ucmd) = at_and_ucmd!(); + let result = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let expected = at.read(FILE); + let result_sorted = new_ucmd!().pipe_in(result.clone()).run().stdout; + + assert_ne!(result, expected); + assert_eq!(result_sorted, expected); +} + +#[test] +fn test_random_shuffle_contains_two_runs_not_the_same() { + // check to verify that two random shuffles are not equal; this has the + // potential to fail in the unlikely event that random order is the same + // as the starting order, or if both random sorts end up having the same order. + const FILE: &'static str = "default_unsorted_ints.expected"; + let (at, _ucmd) = at_and_ucmd!(); + let result = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let expected = at.read(FILE); + let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout; + + assert_ne!(result, expected); + assert_ne!(result, unexpected); +} + #[test] fn test_numeric_floats_and_ints() { test_helper("numeric_floats_and_ints", "-n"); @@ -70,6 +129,148 @@ fn test_dictionary_order() { test_helper("dictionary_order", "-d"); } +#[test] +fn test_dictionary_order2() { + for non_dictionary_order2_param in vec!["-d"] { + new_ucmd!() + .pipe_in("a👦🏻aa b\naaaa b") + .arg(non_dictionary_order2_param) + .succeeds() + .stdout_only("a👦🏻aa b\naaaa b\n"); + } +} + +#[test] +fn test_non_printing_chars() { + for non_printing_chars_param in vec!["-i"] { + new_ucmd!() + .pipe_in("a👦🏻aa b\naaaa b") + .arg(non_printing_chars_param) + .succeeds() + .stdout_only("aaaa b\na👦🏻aa b\n"); + } +} + +#[test] +fn test_exponents_positive_general_fixed() { + for exponents_positive_general_param in vec!["-g"] { + new_ucmd!() + .pipe_in("100E6\n\n50e10\n+100000\n\n10000K78\n10E\n\n\n1000EDKLD\n\n\n100E6\n\n50e10\n+100000\n\n") + .arg(exponents_positive_general_param) + .succeeds() + .stdout_only("\n\n\n\n\n\n\n\n10000K78\n1000EDKLD\n10E\n+100000\n+100000\n100E6\n100E6\n50e10\n50e10\n"); + } +} + +#[test] +fn test_exponents_positive_numeric() { + test_helper("exponents-positive-numeric", "-n"); +} + +#[test] +fn test_months_dedup() { + test_helper("months-dedup", "-Mu"); +} + +#[test] +fn test_mixed_floats_ints_chars_numeric() { + test_helper("mixed_floats_ints_chars_numeric", "-n"); +} + +#[test] +fn test_mixed_floats_ints_chars_numeric_unique() { + test_helper("mixed_floats_ints_chars_numeric_unique", "-nu"); +} + +#[test] +fn test_mixed_floats_ints_chars_numeric_reverse() { + test_helper("mixed_floats_ints_chars_numeric_unique_reverse", "-nur"); +} + +#[test] +fn test_mixed_floats_ints_chars_numeric_stable() { + test_helper("mixed_floats_ints_chars_numeric_stable", "-ns"); +} + +#[test] +fn test_numeric_floats_and_ints2() { + for numeric_sort_param in vec!["-n", "--numeric-sort"] { + let input = "1.444\n8.013\n1\n-8\n1.04\n-1"; + new_ucmd!() + .arg(numeric_sort_param) + .pipe_in(input) + .succeeds() + .stdout_only("-8\n-1\n1\n1.04\n1.444\n8.013\n"); + } +} + +#[test] +fn test_numeric_floats2() { + for numeric_sort_param in vec!["-n", "--numeric-sort"] { + let input = "1.444\n8.013\n1.58590\n-8.90880\n1.040000000\n-.05"; + new_ucmd!() + .arg(numeric_sort_param) + .pipe_in(input) + .succeeds() + .stdout_only("-8.90880\n-.05\n1.040000000\n1.444\n1.58590\n8.013\n"); + } +} + +#[test] +fn test_numeric_floats_with_nan2() { + test_helper("numeric-floats-with-nan2", "-n"); +} + +#[test] +fn test_human_block_sizes2() { + for human_numeric_sort_param in vec!["-h", "--human-numeric-sort"] { + let input = "8981K\n909991M\n-8T\n21G\n0.8M"; + new_ucmd!() + .arg(human_numeric_sort_param) + .pipe_in(input) + .succeeds() + .stdout_only("-8T\n0.8M\n8981K\n21G\n909991M\n"); + } +} + +#[test] +fn test_month_default2() { + for month_sort_param in vec!["-M", "--month-sort"] { + let input = "JAn\nMAY\n000may\nJun\nFeb"; + new_ucmd!() + .arg(month_sort_param) + .pipe_in(input) + .succeeds() + .stdout_only("000may\nJAn\nFeb\nMAY\nJun\n"); + } +} + +#[test] +fn test_default_unsorted_ints2() { + let input = "9\n1909888\n000\n1\n2"; + new_ucmd!() + .pipe_in(input) + .succeeds() + .stdout_only("000\n1\n1909888\n2\n9\n"); +} + +#[test] +fn test_numeric_unique_ints2() { + for numeric_unique_sort_param in vec!["-nu"] { + let input = "9\n9\n8\n1\n"; + new_ucmd!() + .arg(numeric_unique_sort_param) + .pipe_in(input) + .succeeds() + .stdout_only("1\n8\n9\n"); + } +} + +#[test] +fn test_zero_terminated() { + test_helper("zero-terminated", "-z"); +} + #[test] fn test_multiple_files() { new_ucmd!() @@ -146,6 +347,15 @@ fn test_check() { .stdout_is(""); } +#[test] +fn test_check_silent() { + new_ucmd!() + .arg("-C") + .arg("check_fail.txt") + .fails() + .stdout_is(""); +} + fn test_helper(file_name: &str, args: &str) { new_ucmd!() .arg(args) diff --git a/tests/by-util/test_sum.rs b/tests/by-util/test_sum.rs index 83cf689ac..d12455749 100644 --- a/tests/by-util/test_sum.rs +++ b/tests/by-util/test_sum.rs @@ -52,3 +52,23 @@ fn test_sysv_stdin() { .succeeds() .stdout_only_fixture("sysv_stdin.expected"); } + +#[test] +fn test_invalid_file() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("a"); + + ucmd.arg("a") + .fails() + .stderr_is("sum: error: 'a' Is a directory"); +} + +#[test] +fn test_invalid_metadata() { + let (_, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("b") + .fails() + .stderr_is("sum: error: 'b' No such file or directory"); +} diff --git a/tests/by-util/test_tac.rs b/tests/by-util/test_tac.rs index d46f9aec6..3733adbec 100644 --- a/tests/by-util/test_tac.rs +++ b/tests/by-util/test_tac.rs @@ -49,3 +49,21 @@ fn test_single_non_newline_separator_before() { .run() .stdout_is_fixture("delimited_primes_before.expected"); } + +#[test] +fn test_invalid_input() { + let (_, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("b") + .run() + .stderr + .contains("tac: error: failed to open 'b' for reading"); + + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("a"); + ucmd.arg("a") + .run() + .stderr + .contains("tac: error: failed to read 'a'"); +} diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index 651491045..f2587a11f 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -1 +1,104 @@ -// ToDO: add tests +use crate::common::util::*; + +// tests for basic tee functionality. +// inspired by: +// https://github.com/coreutils/coreutils/tests/misc/tee.sh + +#[test] +fn test_tee_processing_multiple_operands() { + // POSIX says: "Processing of at least 13 file operands shall be supported." + + let content = "tee_sample_content"; + for &n in [1, 2, 12, 13].iter() { + let files = (1..=n).map(|x| x.to_string()).collect::>(); + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.args(&files) + .pipe_in(content) + .succeeds() + .stdout_is(content); + + for file in files.iter() { + assert!(at.file_exists(file)); + assert_eq!(at.read(file), content); + } + } +} + +#[test] +fn test_tee_treat_minus_as_filename() { + // Ensure tee treats '-' as the name of a file, as mandated by POSIX. + + let (at, mut ucmd) = at_and_ucmd!(); + let content = "tee_sample_content"; + let file = "-"; + + ucmd.arg("-").pipe_in(content).succeeds().stdout_is(content); + + assert!(at.file_exists(file)); + assert_eq!(at.read(file), content); +} + +#[test] +fn test_tee_append() { + let (at, mut ucmd) = at_and_ucmd!(); + let content = "tee_sample_content"; + let file = "tee_out"; + + at.touch(file); + at.write(file, content); + assert_eq!(at.read(file), content); + + ucmd.arg("-a") + .arg(file) + .pipe_in(content) + .succeeds() + .stdout_is(content); + assert!(at.file_exists(file)); + assert_eq!(at.read(file), content.repeat(2)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_tee_no_more_writeable_1() { + // equals to 'tee /dev/full out2 (); + let file_out = "tee_file_out"; + + ucmd.arg("/dev/full") + .arg(file_out) + .pipe_in(&content[..]) + .fails() + .stdout_contains(&content) + .stderr_contains(&"No space left on device"); + + assert_eq!(at.read(file_out), content); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_tee_no_more_writeable_2() { + // should be equals to 'tee out1 out2 >/dev/full (); + let file_out_a = "tee_file_out_a"; + let file_out_b = "tee_file_out_b"; + + let _result = ucmd + .arg(file_out_a) + .arg(file_out_b) + .pipe_in("/dev/full") + .succeeds(); // TODO: expected to succeed currently; change to fails() when required + + // TODO: comment in after https://github.com/uutils/coreutils/issues/1805 is fixed + // assert_eq!(at.read(file_out_a), content); + // assert_eq!(at.read(file_out_b), content); + // assert!(result.stderr.contains("No space left on device")); +} diff --git a/tests/by-util/test_tty.rs b/tests/by-util/test_tty.rs index 651491045..6bca54e03 100644 --- a/tests/by-util/test_tty.rs +++ b/tests/by-util/test_tty.rs @@ -1 +1,57 @@ -// ToDO: add tests +use crate::common::util::*; + +#[test] +#[cfg(not(windows))] +fn test_dev_null() { + new_ucmd!() + .pipe_in(" ( if $cond.stderr.len() > 0 { - panic!(format!("stderr: {}", $cond.stderr)) + panic!("stderr: {}", $cond.stderr_str()) } ); ); @@ -17,7 +17,7 @@ macro_rules! assert_empty_stderr( macro_rules! assert_empty_stdout( ($cond:expr) => ( if $cond.stdout.len() > 0 { - panic!(format!("stdout: {}", $cond.stdout)) + panic!("stdout: {}", $cond.stdout_str()) } ); ); @@ -30,7 +30,7 @@ macro_rules! assert_no_error( ($cond:expr) => ( assert!($cond.success); if $cond.stderr.len() > 0 { - panic!(format!("stderr: {}", $cond.stderr)) + panic!("stderr: {}", $cond.stderr_str()) } ); ); diff --git a/tests/common/util.rs b/tests/common/util.rs index 4f0178a39..fa6ae29c5 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -1,6 +1,10 @@ #![allow(dead_code)] +#[cfg(not(windows))] +use libc; use std::env; +#[cfg(not(windows))] +use std::ffi::CString; use std::ffi::OsStr; use std::fs::{self, File, OpenOptions}; use std::io::{Read, Result, Write}; @@ -25,8 +29,8 @@ static TESTS_DIR: &str = "tests"; static FIXTURES_DIR: &str = "fixtures"; static ALREADY_RUN: &str = " you have already run this UCommand, if you want to run \ - another command in the same test, use TestScenario::new instead of \ - testing();"; + another command in the same test, use TestScenario::new instead of \ + 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 is running under CI @@ -52,9 +56,10 @@ pub fn is_wsl() -> bool { false } -fn read_scenario_fixture>(tmpd: &Option>, file_rel_path: S) -> String { +/// Read a test scenario fixture, returning its bytes +fn read_scenario_fixture>(tmpd: &Option>, file_rel_path: S) -> Vec { let tmpdir_path = tmpd.as_ref().unwrap().as_ref().path(); - AtPath::new(tmpdir_path).read(file_rel_path.as_ref().to_str().unwrap()) + AtPath::new(tmpdir_path).read_bytes(file_rel_path.as_ref().to_str().unwrap()) } /// A command result is the outputs of a command (streams and status code) @@ -68,64 +73,135 @@ pub struct CmdResult { /// zero-exit from running the Command? /// see [`success`] pub success: bool, - /// captured utf-8 standard output after running the Command + /// captured standard output after running the Command pub stdout: String, - /// captured utf-8 standard error after running the Command + /// captured standard error after running the Command pub stderr: String, } impl CmdResult { + /// Returns a reference to the program's standard output as a slice of bytes + pub fn stdout(&self) -> &[u8] { + &self.stdout.as_bytes() + } + + /// Returns the program's standard output as a string slice + pub fn stdout_str(&self) -> &str { + &self.stdout + } + + /// Returns the program's standard output as a string + /// consumes self + pub fn stdout_move_str(self) -> String { + self.stdout + } + + /// Returns the program's standard output as a vec of bytes + /// consumes self + pub fn stdout_move_bytes(self) -> Vec { + Vec::from(self.stdout) + } + + /// Returns a reference to the program's standard error as a slice of bytes + pub fn stderr(&self) -> &[u8] { + &self.stderr.as_bytes() + } + + /// Returns the program's standard error as a string slice + pub fn stderr_str(&self) -> &str { + &self.stderr + } + + /// Returns the program's standard error as a string + /// consumes self + pub fn stderr_move_str(self) -> String { + self.stderr + } + + /// Returns the program's standard error as a vec of bytes + /// consumes self + pub fn stderr_move_bytes(self) -> Vec { + Vec::from(self.stderr) + } + + /// Returns the program's exit code + /// Panics if not run + pub fn code(&self) -> i32 { + self.code.expect("Program must be run first") + } + + /// Returns the program's TempDir + /// Panics if not present + pub fn tmpd(&self) -> Rc { + match &self.tmpd { + Some(ptr) => ptr.clone(), + None => panic!("Command not associated with a TempDir"), + } + } + + /// Returns whether the program succeeded + pub fn succeeded(&self) -> bool { + self.success + } + /// asserts that the command resulted in a success (zero) status code - pub fn success(&self) -> Box<&CmdResult> { + pub fn success(&self) -> &CmdResult { assert!(self.success); - Box::new(self) + self } /// asserts that the command resulted in a failure (non-zero) status code - pub fn failure(&self) -> Box<&CmdResult> { + pub fn failure(&self) -> &CmdResult { assert!(!self.success); - Box::new(self) + self } /// asserts that the command's exit code is the same as the given one - pub fn status_code(&self, code: i32) -> Box<&CmdResult> { + pub fn status_code(&self, code: i32) -> &CmdResult { assert!(self.code == Some(code)); - Box::new(self) + self } /// asserts that the command resulted in empty (zero-length) stderr stream output /// generally, it's better to use stdout_only() instead, /// but you might find yourself using this function if - /// 1. you can not know exactly what stdout will be - /// or 2. you know that stdout will also be empty - pub fn no_stderr(&self) -> Box<&CmdResult> { - assert_eq!(self.stderr, ""); - Box::new(self) + /// 1. you can not know exactly what stdout will be or + /// 2. you know that stdout will also be empty + pub fn no_stderr(&self) -> &CmdResult { + assert!(self.stderr.is_empty()); + self } /// asserts that the command resulted in empty (zero-length) stderr stream output /// unless asserting there was neither stdout or stderr, stderr_only is usually a better choice /// generally, it's better to use stderr_only() instead, /// but you might find yourself using this function if - /// 1. you can not know exactly what stderr will be - /// or 2. you know that stderr will also be empty - pub fn no_stdout(&self) -> Box<&CmdResult> { - assert_eq!(self.stdout, ""); - Box::new(self) + /// 1. you can not know exactly what stderr will be or + /// 2. you know that stderr will also be empty + pub fn no_stdout(&self) -> &CmdResult { + assert!(self.stdout.is_empty()); + self } /// asserts that the command resulted in stdout stream output that equals the /// passed in value, trailing whitespace are kept to force strict comparison (#1235) /// stdout_only is a better choice unless stderr may or will be non-empty - pub fn stdout_is>(&self, msg: T) -> Box<&CmdResult> { + pub fn stdout_is>(&self, msg: T) -> &CmdResult { assert_eq!(self.stdout, String::from(msg.as_ref())); - Box::new(self) + self + } + + /// asserts that the command resulted in stdout stream output, + /// whose bytes equal those of the passed in slice + pub fn stdout_is_bytes>(&self, msg: T) -> &CmdResult { + assert_eq!(self.stdout.as_bytes(), msg.as_ref()); + self } /// like stdout_is(...), but expects the contents of the file at the provided relative path - pub fn stdout_is_fixture>(&self, file_rel_path: T) -> Box<&CmdResult> { + pub fn stdout_is_fixture>(&self, file_rel_path: T) -> &CmdResult { let contents = read_scenario_fixture(&self.tmpd, file_rel_path); - self.stdout_is(contents) + self.stdout_is_bytes(contents) } /// like stdout_is_fixture(...), but replaces the data in fixture file based on values provided in template_vars /// command output @@ -140,40 +216,73 @@ impl CmdResult { /// asserts that the command resulted in stderr stream output that equals the /// passed in value, when both are trimmed of trailing whitespace /// stderr_only is a better choice unless stdout may or will be non-empty - pub fn stderr_is>(&self, msg: T) -> Box<&CmdResult> { + pub fn stderr_is>(&self, msg: T) -> &CmdResult { assert_eq!( self.stderr.trim_end(), String::from(msg.as_ref()).trim_end() ); - Box::new(self) + self + } + + /// asserts that the command resulted in stderr stream output, + /// whose bytes equal those of the passed in slice + pub fn stderr_is_bytes>(&self, msg: T) -> &CmdResult { + assert_eq!(self.stderr.as_bytes(), msg.as_ref()); + self } /// asserts that - /// 1. the command resulted in stdout stream output that equals the - /// passed in value, when both are trimmed of trailing whitespace - /// and 2. the command resulted in empty (zero-length) stderr stream output - pub fn stdout_only>(&self, msg: T) -> Box<&CmdResult> { + /// 1. the command resulted in stdout stream output that equals the + /// passed in value + /// 2. the command resulted in empty (zero-length) stderr stream output + pub fn stdout_only>(&self, msg: T) -> &CmdResult { self.no_stderr().stdout_is(msg) } + /// asserts that + /// 1. the command resulted in a stdout stream whose bytes + /// equal those of the passed in value + /// 2. the command resulted in an empty stderr stream + pub fn stdout_only_bytes>(&self, msg: T) -> &CmdResult { + self.no_stderr().stdout_is_bytes(msg) + } + /// like stdout_only(...), but expects the contents of the file at the provided relative path - pub fn stdout_only_fixture>(&self, file_rel_path: T) -> Box<&CmdResult> { + pub fn stdout_only_fixture>(&self, file_rel_path: T) -> &CmdResult { let contents = read_scenario_fixture(&self.tmpd, file_rel_path); - self.stdout_only(contents) + self.stdout_only_bytes(contents) } /// asserts that - /// 1. the command resulted in stderr stream output that equals the - /// passed in value, when both are trimmed of trailing whitespace - /// and 2. the command resulted in empty (zero-length) stdout stream output - pub fn stderr_only>(&self, msg: T) -> Box<&CmdResult> { + /// 1. the command resulted in stderr stream output that equals the + /// passed in value, when both are trimmed of trailing whitespace + /// 2. the command resulted in empty (zero-length) stdout stream output + pub fn stderr_only>(&self, msg: T) -> &CmdResult { self.no_stdout().stderr_is(msg) } - pub fn fails_silently(&self) -> Box<&CmdResult> { + /// asserts that + /// 1. the command resulted in a stderr stream whose bytes equal the ones + /// of the passed value + /// 2. the command resulted in an empty stdout stream + pub fn stderr_only_bytes>(&self, msg: T) -> &CmdResult { + self.no_stderr().stderr_is_bytes(msg) + } + + pub fn fails_silently(&self) -> &CmdResult { assert!(!self.success); - assert_eq!(self.stderr, ""); - Box::new(self) + assert!(self.stderr.is_empty()); + self + } + + pub fn stdout_contains>(&self, cmp: T) -> &CmdResult { + assert!(self.stdout_str().contains(cmp.as_ref())); + self + } + + pub fn stderr_contains>(&self, cmp: &T) -> &CmdResult { + assert!(self.stderr_str().contains(cmp.as_ref())); + self } } @@ -259,13 +368,29 @@ impl AtPath { pub fn read(&self, name: &str) -> String { let mut f = self.open(name); let mut contents = String::new(); - let _ = f.read_to_string(&mut contents); + f.read_to_string(&mut contents) + .unwrap_or_else(|e| panic!("Couldn't read {}: {}", name, e)); + contents + } + + pub fn read_bytes(&self, name: &str) -> Vec { + let mut f = self.open(name); + let mut contents = Vec::new(); + f.read_to_end(&mut contents) + .unwrap_or_else(|e| panic!("Couldn't read {}: {}", name, e)); contents } pub fn write(&self, name: &str, contents: &str) { log_info("open(write)", self.plus_as_string(name)); - let _ = std::fs::write(self.plus(name), contents); + std::fs::write(self.plus(name), contents) + .unwrap_or_else(|e| panic!("Couldn't write {}: {}", name, e)); + } + + pub fn write_bytes(&self, name: &str, contents: &[u8]) { + log_info("open(write)", self.plus_as_string(name)); + std::fs::write(self.plus(name), contents) + .unwrap_or_else(|e| panic!("Couldn't write {}: {}", name, e)); } pub fn append(&self, name: &str, contents: &str) { @@ -275,7 +400,19 @@ impl AtPath { .append(true) .open(self.plus(name)) .unwrap(); - let _ = f.write(contents.as_bytes()); + f.write(contents.as_bytes()) + .unwrap_or_else(|e| panic!("Couldn't write {}: {}", name, e)); + } + + pub fn append_bytes(&self, name: &str, contents: &[u8]) { + log_info("open(append)", self.plus_as_string(name)); + let mut f = OpenOptions::new() + .write(true) + .append(true) + .open(self.plus(name)) + .unwrap(); + f.write_all(contents) + .unwrap_or_else(|e| panic!("Couldn't append to {}: {}", name, e)); } pub fn mkdir(&self, dir: &str) { @@ -299,6 +436,29 @@ impl AtPath { File::create(&self.plus(file)).unwrap(); } + #[cfg(not(windows))] + pub fn mkfifo(&self, fifo: &str) { + let full_path = self.plus_as_string(fifo); + log_info("mkfifo", &full_path); + unsafe { + let fifo_name: CString = CString::new(full_path).expect("CString creation failed."); + libc::mkfifo(fifo_name.as_ptr(), libc::S_IWUSR | libc::S_IRUSR); + } + } + + #[cfg(not(windows))] + pub fn is_fifo(&self, fifo: &str) -> bool { + unsafe { + let name = CString::new(self.plus_as_string(fifo)).unwrap(); + let mut stat: libc::stat = std::mem::zeroed(); + if libc::stat(name.as_ptr(), &mut stat) >= 0 { + libc::S_IFIFO & stat.st_mode != 0 + } else { + false + } + } + } + pub fn symlink_file(&self, src: &str, dst: &str) { log_info( "symlink", @@ -439,6 +599,14 @@ impl TestScenario { UCommand::new_from_tmp(bin, self.tmpd.clone(), true) } + /// Returns builder for invoking any uutils command. Paths given are treated + /// relative to the environment's unique temporary test directory. + pub fn ccmd>(&self, bin: S) -> UCommand { + let mut cmd = self.cmd(&self.bin_path); + cmd.arg(bin); + cmd + } + // different names are used rather than an argument // because the need to keep the environment is exceedingly rare. pub fn ucmd_keepenv(&self) -> UCommand { @@ -506,21 +674,21 @@ impl UCommand { /// Add a parameter to the invocation. Path arguments are treated relative /// to the test environment directory. - pub fn arg>(&mut self, arg: S) -> Box<&mut UCommand> { + pub fn arg>(&mut self, arg: S) -> &mut UCommand { if self.has_run { - panic!(ALREADY_RUN); + panic!("{}", ALREADY_RUN); } self.comm_string.push_str(" "); self.comm_string.push_str(arg.as_ref().to_str().unwrap()); self.raw.arg(arg.as_ref()); - Box::new(self) + self } /// Add multiple parameters to the invocation. Path arguments are treated relative /// to the test environment directory. - pub fn args>(&mut self, args: &[S]) -> Box<&mut UCommand> { + pub fn args>(&mut self, args: &[S]) -> &mut UCommand { if self.has_run { - panic!(MULTIPLE_STDIN_MEANINGLESS); + panic!("{}", MULTIPLE_STDIN_MEANINGLESS); } for s in args { self.comm_string.push_str(" "); @@ -528,41 +696,41 @@ impl UCommand { } self.raw.args(args.as_ref()); - Box::new(self) + self } /// provides stdinput to feed in to the command when spawned - pub fn pipe_in>>(&mut self, input: T) -> Box<&mut UCommand> { + pub fn pipe_in>>(&mut self, input: T) -> &mut UCommand { if self.stdin.is_some() { - panic!(MULTIPLE_STDIN_MEANINGLESS); + panic!("{}", MULTIPLE_STDIN_MEANINGLESS); } self.stdin = Some(input.into()); - Box::new(self) + self } /// like pipe_in(...), but uses the contents of the file at the provided relative path as the piped in data - pub fn pipe_in_fixture>(&mut self, file_rel_path: S) -> Box<&mut UCommand> { + pub fn pipe_in_fixture>(&mut self, file_rel_path: S) -> &mut UCommand { let contents = read_scenario_fixture(&self.tmpd, file_rel_path); self.pipe_in(contents) } - pub fn env(&mut self, key: K, val: V) -> Box<&mut UCommand> + pub fn env(&mut self, key: K, val: V) -> &mut UCommand where K: AsRef, V: AsRef, { if self.has_run { - panic!(ALREADY_RUN); + panic!("{}", ALREADY_RUN); } self.raw.env(key, val); - Box::new(self) + self } /// Spawns the command, feeds the stdin if any, and returns the /// child process immediately. pub fn run_no_wait(&mut self) -> Child { if self.has_run { - panic!(ALREADY_RUN); + panic!("{}", ALREADY_RUN); } self.has_run = true; log_info("run", &self.comm_string); @@ -639,7 +807,7 @@ pub fn read_size(child: &mut Child, size: usize) -> String { .stdout .as_mut() .unwrap() - .read(output.as_mut_slice()) + .read_exact(output.as_mut_slice()) .unwrap(); String::from_utf8(output).unwrap() } diff --git a/tests/fixtures/cksum/chars.txt b/tests/fixtures/cksum/chars.txt new file mode 100644 index 000000000..26eec03fe --- /dev/null +++ b/tests/fixtures/cksum/chars.txt @@ -0,0 +1 @@ +123456789:;<=>?@ \ No newline at end of file diff --git a/tests/fixtures/cksum/larger_than_2056_bytes.txt b/tests/fixtures/cksum/larger_than_2056_bytes.txt new file mode 100644 index 000000000..668de449a --- /dev/null +++ b/tests/fixtures/cksum/larger_than_2056_bytes.txt @@ -0,0 +1 @@ +\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040\041\042\043\044\045\046\047\050\051\052\053\054\055\056\057\060\061\062\063\064\065\066\067\070\071\072\073\074\075\076\077\100\101\102\103\104\105\106\107\110\111\112\113\114\115\116\117\120\121\122\123\124\125\126\127\130\131\132\133\134\135\136\137\140\141\142\143\144\145\146\147\150\151\152\153\154\155\156\157\160\161\162\163\164\165\166\167\170\171\172\173\174\175\176\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377\400\401\402\403\404\405\406\407\410\411\412\413\414\415\416\417\420\421\422\423\424\425\426\427\430\431\432\433\434\435\436\437\440\441\442\443\444\445\446\447\450\451\452\453\454\455\456\457\460\461\462\463\464\465\466\467\470\471\472\473\474\475\476\477\500\501\502\503\504\505\506\507\510\511\512\513\514\515\516\517\520\521\522\523\524\525\526\527\530\531\532\533\534\535\536\537\540\541\542\543\544\545\546\547\550\551\552\553\554\555\556\557\560\561\562\563\564\565\566\567\570\571\572\573\574\575\576\577\600\601\602\603\604\605\606\607\610\611\612\613\614\615\616\617\620\621\622\623\624\625\626\627\630\631\632\633\634\635\636\637\640\641\642\643\644\645\646\647\650\651\652\653\654\655\656\657\660\661\662\663\664\665\666\667\670\671\672\673\674\675\676\677\700\701\702\703\704\705\706\707\710\711\712\713\714\715\716\717\720\721\722\723\724\725\726\727\730\731\732\733\734\735\736\737\740\741\742\743\744\745\746\747\750\751\752\753\754\755\756\757\760\761\762\763\764\765\766\767\770\771\772\773\774\775\776\777\1000\1001 \ No newline at end of file diff --git a/tests/fixtures/du/empty.txt b/tests/fixtures/du/empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/fold/space_separated_words.txt b/tests/fixtures/fold/space_separated_words.txt new file mode 100644 index 000000000..13b980632 --- /dev/null +++ b/tests/fixtures/fold/space_separated_words.txt @@ -0,0 +1 @@ +test1 test2 test3 test4 test5 test6 \ No newline at end of file diff --git a/tests/fixtures/fold/tab_stops.input b/tests/fixtures/fold/tab_stops.input new file mode 100644 index 000000000..a96a378ea --- /dev/null +++ b/tests/fixtures/fold/tab_stops.input @@ -0,0 +1,11 @@ +1 +12 +123 +1234 +12345 +123456 +1234567 +12345678 +123456781 +12345678 2 +12345678 2 4 diff --git a/tests/fixtures/fold/tab_stops_w16.expected b/tests/fixtures/fold/tab_stops_w16.expected new file mode 100644 index 000000000..8122ac16d --- /dev/null +++ b/tests/fixtures/fold/tab_stops_w16.expected @@ -0,0 +1,13 @@ +1 +12 +123 +1234 +12345 +123456 +1234567 +12345678 +123456781 +12345678 +2 +12345678 +2 4 diff --git a/tests/fixtures/fold/tab_stops_w8.expected b/tests/fixtures/fold/tab_stops_w8.expected new file mode 100644 index 000000000..3173a4a22 --- /dev/null +++ b/tests/fixtures/fold/tab_stops_w8.expected @@ -0,0 +1,18 @@ +1 +12 +123 +1234 +12345 +123456 +1234567 +12345678 + +12345678 +1 +12345678 + +2 +12345678 + +2 +4 diff --git a/tests/fixtures/head/lorem_ipsum_backwards_file.expected b/tests/fixtures/head/lorem_ipsum_backwards_file.expected new file mode 100644 index 000000000..fcf432187 --- /dev/null +++ b/tests/fixtures/head/lorem_ipsum_backwards_file.expected @@ -0,0 +1,24 @@ +Lorem ipsum dolor sit amet, +consectetur adipiscing elit. +Nunc interdum suscipit sem vel ornare. +Proin euismod, +justo sed mollis dictum, +eros urna ultricies augue, +eu pharetra mi ex id ante. +Duis convallis porttitor aliquam. +Nunc vitae tincidunt ex. +Suspendisse iaculis ligula ac diam consectetur lacinia. +Donec vel velit dui. +Etiam fringilla, +dolor quis tempor vehicula, +lacus turpis bibendum velit, +et pellentesque elit odio a magna. +Cras vulputate tortor non libero vehicula euismod. +Aliquam tincidunt nisl eget enim cursus, +viverra sagittis magna commodo. +Cras rhoncus egestas leo nec blandit. +Suspendisse potenti. +Etiam ullamcorper leo vel lacus vestibulum, +cursus semper eros efficitur. +In hac habitasse platea dictumst. +Phasellus scelerisque vehicula f \ No newline at end of file diff --git a/tests/fixtures/head/sequence b/tests/fixtures/head/sequence new file mode 100644 index 000000000..190423f88 --- /dev/null +++ b/tests/fixtures/head/sequence @@ -0,0 +1,100 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 diff --git a/tests/fixtures/head/sequence.expected b/tests/fixtures/head/sequence.expected new file mode 100644 index 000000000..17d2a1390 --- /dev/null +++ b/tests/fixtures/head/sequence.expected @@ -0,0 +1,90 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 diff --git a/tests/fixtures/shuf/file_input.txt b/tests/fixtures/shuf/file_input.txt new file mode 100644 index 000000000..fc316949e --- /dev/null +++ b/tests/fixtures/shuf/file_input.txt @@ -0,0 +1,10 @@ +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 diff --git a/tests/fixtures/sort/exponents-positive-general.expected b/tests/fixtures/sort/exponents-positive-general.expected new file mode 100644 index 000000000..3dbc92fe5 --- /dev/null +++ b/tests/fixtures/sort/exponents-positive-general.expected @@ -0,0 +1,12 @@ + + + + + + +10E +1000EDKLD +10000K78 ++100000 +100E6 +50e10 diff --git a/tests/fixtures/sort/exponents-positive-general.txt b/tests/fixtures/sort/exponents-positive-general.txt new file mode 100644 index 000000000..23ea52771 --- /dev/null +++ b/tests/fixtures/sort/exponents-positive-general.txt @@ -0,0 +1,12 @@ +10000K78 +10E + + +1000EDKLD + + +100E6 + +50e10 ++100000 + diff --git a/tests/fixtures/sort/exponents-positive-numeric.expected b/tests/fixtures/sort/exponents-positive-numeric.expected new file mode 100644 index 000000000..174088f63 --- /dev/null +++ b/tests/fixtures/sort/exponents-positive-numeric.expected @@ -0,0 +1,12 @@ + + + + + + ++100000 +10E +50e10 +100E6 +1000EDKLD +10000K78 diff --git a/tests/fixtures/sort/exponents-positive-numeric.txt b/tests/fixtures/sort/exponents-positive-numeric.txt new file mode 100644 index 000000000..23ea52771 --- /dev/null +++ b/tests/fixtures/sort/exponents-positive-numeric.txt @@ -0,0 +1,12 @@ +10000K78 +10E + + +1000EDKLD + + +100E6 + +50e10 ++100000 + diff --git a/tests/fixtures/sort/human-mixed-inputs-reverse.expected b/tests/fixtures/sort/human-mixed-inputs-reverse.expected new file mode 100644 index 000000000..463f44a2a --- /dev/null +++ b/tests/fixtures/sort/human-mixed-inputs-reverse.expected @@ -0,0 +1,37 @@ +.2T +2G +100M +7800900K +51887300- +1890777 +56908-90078 +6780.0009866 +6780.000986 +789----009999 90-0 90-0 +1 +0001 +apr +MAY +JUNNNN +JAN +AUG +APR +0000000 +00 + + + + + + + + + + + + + + + + +-1.4 diff --git a/tests/fixtures/sort/human-mixed-inputs-reverse.txt b/tests/fixtures/sort/human-mixed-inputs-reverse.txt new file mode 100644 index 000000000..ebef388b9 --- /dev/null +++ b/tests/fixtures/sort/human-mixed-inputs-reverse.txt @@ -0,0 +1,37 @@ +JAN + +0000000 + +00 + +0001 + +1 + +-1.4 + +JUNNNN +AUG + +apr + +APR + + +MAY +1890777 + +56908-90078 + +51887300- + +6780.0009866 + +789----009999 90-0 90-0 + +6780.000986 + +100M +7800900K +2G +.2T diff --git a/tests/fixtures/sort/human-mixed-inputs-stable.expected b/tests/fixtures/sort/human-mixed-inputs-stable.expected new file mode 100644 index 000000000..e1c85b8ce --- /dev/null +++ b/tests/fixtures/sort/human-mixed-inputs-stable.expected @@ -0,0 +1,37 @@ +-1.4 +JAN + +0000000 + +00 + + + + +JUNNNN +AUG + +apr + +APR + + +MAY + + + + + + +0001 +1 +789----009999 90-0 90-0 +6780.000986 +6780.0009866 +56908-90078 +1890777 +51887300- +7800900K +100M +2G +.2T diff --git a/tests/fixtures/sort/human-mixed-inputs-stable.txt b/tests/fixtures/sort/human-mixed-inputs-stable.txt new file mode 100644 index 000000000..ebef388b9 --- /dev/null +++ b/tests/fixtures/sort/human-mixed-inputs-stable.txt @@ -0,0 +1,37 @@ +JAN + +0000000 + +00 + +0001 + +1 + +-1.4 + +JUNNNN +AUG + +apr + +APR + + +MAY +1890777 + +56908-90078 + +51887300- + +6780.0009866 + +789----009999 90-0 90-0 + +6780.000986 + +100M +7800900K +2G +.2T diff --git a/tests/fixtures/sort/human-mixed-inputs-unique.expected b/tests/fixtures/sort/human-mixed-inputs-unique.expected new file mode 100644 index 000000000..50f53b6a0 --- /dev/null +++ b/tests/fixtures/sort/human-mixed-inputs-unique.expected @@ -0,0 +1,13 @@ +-1.4 +JAN +0001 +789----009999 90-0 90-0 +6780.000986 +6780.0009866 +56908-90078 +1890777 +51887300- +7800900K +100M +2G +.2T diff --git a/tests/fixtures/sort/human-mixed-inputs-unique.txt b/tests/fixtures/sort/human-mixed-inputs-unique.txt new file mode 100644 index 000000000..ebef388b9 --- /dev/null +++ b/tests/fixtures/sort/human-mixed-inputs-unique.txt @@ -0,0 +1,37 @@ +JAN + +0000000 + +00 + +0001 + +1 + +-1.4 + +JUNNNN +AUG + +apr + +APR + + +MAY +1890777 + +56908-90078 + +51887300- + +6780.0009866 + +789----009999 90-0 90-0 + +6780.000986 + +100M +7800900K +2G +.2T diff --git a/tests/fixtures/sort/human-mixed-inputs.expected b/tests/fixtures/sort/human-mixed-inputs.expected new file mode 100644 index 000000000..3f5692b7b --- /dev/null +++ b/tests/fixtures/sort/human-mixed-inputs.expected @@ -0,0 +1,37 @@ +-1.4 + + + + + + + + + + + + + + + + +00 +0000000 +APR +AUG +JAN +JUNNNN +MAY +apr +0001 +1 +789----009999 90-0 90-0 +6780.000986 +6780.0009866 +56908-90078 +1890777 +51887300- +7800900K +100M +2G +.2T diff --git a/tests/fixtures/sort/human-mixed-inputs.txt b/tests/fixtures/sort/human-mixed-inputs.txt new file mode 100644 index 000000000..ce5986d6e --- /dev/null +++ b/tests/fixtures/sort/human-mixed-inputs.txt @@ -0,0 +1,46 @@ +JAN + +0000000 + +00 + +0001 + +1 + +-1.4 + +JUNNNN +AUG + +apr + +APR + + +MAY +1890777 + +56908-90078 + +51887300- + +6780.0009866 + +789----009999 90-0 90-0 + +6780.000986 + +1M +10M +100M +1000M +10000M + +7800900K +780090K +78009K +7800K +780K +2G +.2T diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected new file mode 100644 index 000000000..a781a36bb --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected @@ -0,0 +1,30 @@ +-2028789030 +-896689 +-8.90880 +-1 +-.05 + + + + + + + + +000 +CARAvan +00000001 +1 +1.040000000 +1.444 +1.58590 +8.013 +45 +46.89 + 4567. + 37800 +576,446.88800000 +576,446.890 +4798908.340000000000 +4798908.45 +4798908.8909800 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric.txt b/tests/fixtures/sort/mixed_floats_ints_chars_numeric.txt new file mode 100644 index 000000000..a5813ea3a --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric.txt @@ -0,0 +1,30 @@ +576,446.890 +576,446.88800000 + + + 4567. +45 +46.89 +-1 +1 +00000001 +4798908.340000000000 +4798908.45 +4798908.8909800 + + + 37800 + +-2028789030 +-896689 +CARAvan + +-8.90880 +-.05 +1.444 +1.58590 +1.040000000 + +8.013 + +000 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse.expected new file mode 100644 index 000000000..6b024210b --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse.expected @@ -0,0 +1,30 @@ +4798908.8909800 +4798908.45 +4798908.340000000000 +576,446.890 +576,446.88800000 + 37800 + 4567. +46.89 +45 +8.013 +1.58590 +1.444 +1.040000000 +1 +00000001 +CARAvan +000 + + + + + + + + +-.05 +-1 +-8.90880 +-896689 +-2028789030 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.expected new file mode 100644 index 000000000..cb1028f0e --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.expected @@ -0,0 +1,30 @@ +4798908.8909800 +4798908.45 +4798908.340000000000 +576,446.890 +576,446.88800000 + 37800 + 4567. +46.89 +45 +8.013 +1.58590 +1.444 +1.040000000 +1 +00000001 + + + + + +CARAvan + + + +000 +-.05 +-1 +-8.90880 +-896689 +-2028789030 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.txt b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.txt new file mode 100644 index 000000000..a5813ea3a --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.txt @@ -0,0 +1,30 @@ +576,446.890 +576,446.88800000 + + + 4567. +45 +46.89 +-1 +1 +00000001 +4798908.340000000000 +4798908.45 +4798908.8909800 + + + 37800 + +-2028789030 +-896689 +CARAvan + +-8.90880 +-.05 +1.444 +1.58590 +1.040000000 + +8.013 + +000 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected new file mode 100644 index 000000000..63a3e646d --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected @@ -0,0 +1,30 @@ +-2028789030 +-896689 +-8.90880 +-1 +-.05 + + + + + +CARAvan + + + +000 +1 +00000001 +1.040000000 +1.444 +1.58590 +8.013 +45 +46.89 + 4567. + 37800 +576,446.88800000 +576,446.890 +4798908.340000000000 +4798908.45 +4798908.8909800 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.txt b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.txt new file mode 100644 index 000000000..a5813ea3a --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.txt @@ -0,0 +1,30 @@ +576,446.890 +576,446.88800000 + + + 4567. +45 +46.89 +-1 +1 +00000001 +4798908.340000000000 +4798908.45 +4798908.8909800 + + + 37800 + +-2028789030 +-896689 +CARAvan + +-8.90880 +-.05 +1.444 +1.58590 +1.040000000 + +8.013 + +000 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected new file mode 100644 index 000000000..cb27c6664 --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected @@ -0,0 +1,20 @@ +-2028789030 +-896689 +-8.90880 +-1 +-.05 + +1 +1.040000000 +1.444 +1.58590 +8.013 +45 +46.89 + 4567. + 37800 +576,446.88800000 +576,446.890 +4798908.340000000000 +4798908.45 +4798908.8909800 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.txt b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.txt new file mode 100644 index 000000000..a5813ea3a --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.txt @@ -0,0 +1,30 @@ +576,446.890 +576,446.88800000 + + + 4567. +45 +46.89 +-1 +1 +00000001 +4798908.340000000000 +4798908.45 +4798908.8909800 + + + 37800 + +-2028789030 +-896689 +CARAvan + +-8.90880 +-.05 +1.444 +1.58590 +1.040000000 + +8.013 + +000 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected new file mode 100644 index 000000000..bbce16934 --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected @@ -0,0 +1,20 @@ +4798908.8909800 +4798908.45 +4798908.340000000000 +576,446.890 +576,446.88800000 + 37800 + 4567. +46.89 +45 +8.013 +1.58590 +1.444 +1.040000000 +1 + +-.05 +-1 +-8.90880 +-896689 +-2028789030 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.txt b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.txt new file mode 100644 index 000000000..a5813ea3a --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.txt @@ -0,0 +1,30 @@ +576,446.890 +576,446.88800000 + + + 4567. +45 +46.89 +-1 +1 +00000001 +4798908.340000000000 +4798908.45 +4798908.8909800 + + + 37800 + +-2028789030 +-896689 +CARAvan + +-8.90880 +-.05 +1.444 +1.58590 +1.040000000 + +8.013 + +000 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.expected new file mode 100644 index 000000000..bbce16934 --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.expected @@ -0,0 +1,20 @@ +4798908.8909800 +4798908.45 +4798908.340000000000 +576,446.890 +576,446.88800000 + 37800 + 4567. +46.89 +45 +8.013 +1.58590 +1.444 +1.040000000 +1 + +-.05 +-1 +-8.90880 +-896689 +-2028789030 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.txt b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.txt new file mode 100644 index 000000000..a5813ea3a --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.txt @@ -0,0 +1,30 @@ +576,446.890 +576,446.88800000 + + + 4567. +45 +46.89 +-1 +1 +00000001 +4798908.340000000000 +4798908.45 +4798908.8909800 + + + 37800 + +-2028789030 +-896689 +CARAvan + +-8.90880 +-.05 +1.444 +1.58590 +1.040000000 + +8.013 + +000 diff --git a/tests/fixtures/sort/months-dedup.expected b/tests/fixtures/sort/months-dedup.expected new file mode 100644 index 000000000..dfb693492 --- /dev/null +++ b/tests/fixtures/sort/months-dedup.expected @@ -0,0 +1,6 @@ + +JAN +apr +MAY +JUNNNN +AUG diff --git a/tests/fixtures/sort/months-dedup.txt b/tests/fixtures/sort/months-dedup.txt new file mode 100644 index 000000000..ebef388b9 --- /dev/null +++ b/tests/fixtures/sort/months-dedup.txt @@ -0,0 +1,37 @@ +JAN + +0000000 + +00 + +0001 + +1 + +-1.4 + +JUNNNN +AUG + +apr + +APR + + +MAY +1890777 + +56908-90078 + +51887300- + +6780.0009866 + +789----009999 90-0 90-0 + +6780.000986 + +100M +7800900K +2G +.2T diff --git a/tests/fixtures/sort/numeric-floats-with-nan2.expected b/tests/fixtures/sort/numeric-floats-with-nan2.expected new file mode 100644 index 000000000..51c9985c3 --- /dev/null +++ b/tests/fixtures/sort/numeric-floats-with-nan2.expected @@ -0,0 +1,23 @@ +-8.90880 +-.05 + + + + + + + + + + + + + + +Karma +1 +1.0/0.0 +1.040000000 +1.2 +1.444 +1.58590 diff --git a/tests/fixtures/sort/numeric-floats-with-nan2.txt b/tests/fixtures/sort/numeric-floats-with-nan2.txt new file mode 100644 index 000000000..9b78741fe --- /dev/null +++ b/tests/fixtures/sort/numeric-floats-with-nan2.txt @@ -0,0 +1,23 @@ +Karma + +1.0/0.0 + + +-8.90880 + + +-.05 + + +1.040000000 + +1.444 + + +1.58590 + + +1 + +1.2 + diff --git a/tests/fixtures/sort/zero-terminated.expected b/tests/fixtures/sort/zero-terminated.expected new file mode 100644 index 000000000..4e53b304b Binary files /dev/null and b/tests/fixtures/sort/zero-terminated.expected differ diff --git a/tests/fixtures/sort/zero-terminated.txt b/tests/fixtures/sort/zero-terminated.txt new file mode 100644 index 000000000..5c547c851 Binary files /dev/null and b/tests/fixtures/sort/zero-terminated.txt differ diff --git a/tests/fixtures/uniq/group-append.expected b/tests/fixtures/uniq/group-append.expected new file mode 100644 index 000000000..62f53e69f --- /dev/null +++ b/tests/fixtures/uniq/group-append.expected @@ -0,0 +1,26 @@ + aaaaa ⅰ + + bbbbb ⅱ + bbbbb ⅱ + + ccccc ⅲ + ccccc ⅲ + ccccc ⅲ + + ddddd ⅲ + ddddd ⅲ + ddddd ⅲ + ddddd ⅲ + + eeeee ⅲ + + fffff ⅲ + fffff ⅲ + + ggggg ⅲ + ggggg ⅲ + ggggg ⅲ + + GGGGG ⅲ + GGGGG ⅲ + diff --git a/tests/fixtures/uniq/group-both.expected b/tests/fixtures/uniq/group-both.expected new file mode 100644 index 000000000..8a0f06bf2 --- /dev/null +++ b/tests/fixtures/uniq/group-both.expected @@ -0,0 +1,27 @@ + + aaaaa ⅰ + + bbbbb ⅱ + bbbbb ⅱ + + ccccc ⅲ + ccccc ⅲ + ccccc ⅲ + + ddddd ⅲ + ddddd ⅲ + ddddd ⅲ + ddddd ⅲ + + eeeee ⅲ + + fffff ⅲ + fffff ⅲ + + ggggg ⅲ + ggggg ⅲ + ggggg ⅲ + + GGGGG ⅲ + GGGGG ⅲ + diff --git a/tests/fixtures/uniq/group-prepend.expected b/tests/fixtures/uniq/group-prepend.expected new file mode 100644 index 000000000..5209f7fbe --- /dev/null +++ b/tests/fixtures/uniq/group-prepend.expected @@ -0,0 +1,26 @@ + + aaaaa ⅰ + + bbbbb ⅱ + bbbbb ⅱ + + ccccc ⅲ + ccccc ⅲ + ccccc ⅲ + + ddddd ⅲ + ddddd ⅲ + ddddd ⅲ + ddddd ⅲ + + eeeee ⅲ + + fffff ⅲ + fffff ⅲ + + ggggg ⅲ + ggggg ⅲ + ggggg ⅲ + + GGGGG ⅲ + GGGGG ⅲ diff --git a/tests/fixtures/uniq/group.expected b/tests/fixtures/uniq/group.expected new file mode 100644 index 000000000..145a78011 --- /dev/null +++ b/tests/fixtures/uniq/group.expected @@ -0,0 +1,25 @@ + aaaaa ⅰ + + bbbbb ⅱ + bbbbb ⅱ + + ccccc ⅲ + ccccc ⅲ + ccccc ⅲ + + ddddd ⅲ + ddddd ⅲ + ddddd ⅲ + ddddd ⅲ + + eeeee ⅲ + + fffff ⅲ + fffff ⅲ + + ggggg ⅲ + ggggg ⅲ + ggggg ⅲ + + GGGGG ⅲ + GGGGG ⅲ diff --git a/util/update-version.sh b/util/update-version.sh index 584d13608..042d43b71 100644 --- a/util/update-version.sh +++ b/util/update-version.sh @@ -3,11 +3,11 @@ # So, it should be triple-checked -FROM="0.0.3" -TO="0.0.4" +FROM="0.0.5" +TO="0.0.6" -UUCORE_FROM="0.0.6" -UUCORE_TO="0.0.7" +UUCORE_FROM="0.0.7" +UUCORE_TO="0.0.8" PROGS=$(ls -1d src/uu/*/Cargo.toml src/uu/stdbuf/src/libstdbuf/Cargo.toml Cargo.toml) @@ -19,8 +19,8 @@ sed -i -e "s|libstdbuf = { version=\"$FROM\"|libstdbuf = { version=\"$TO\"|" src sed -i -e "s|= { optional=true, version=\"$FROM\", package=\"uu_|= { optional=true, version=\"$TO\", package=\"uu_|g" Cargo.toml # Update uucore itself -sed -i -e "s|version = \"$UUCORE_FROM\"|version = \"$UUCORE_TO\"|" src/uucore/Cargo.toml +#sed -i -e "s|version = \"$UUCORE_FROM\"|version = \"$UUCORE_TO\"|" src/uucore/Cargo.toml # Update crates using uucore -sed -i -e "s|uucore = { version=\">=$UUCORE_FROM\",|uucore = { version=\">=$UUCORE_TO\",|" $PROGS +#sed -i -e "s|uucore = { version=\">=$UUCORE_FROM\",|uucore = { version=\">=$UUCORE_TO\",|" $PROGS