Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Wisha Wa 2021-04-06 10:56:19 +00:00
commit f00df3f3d8
384 changed files with 27668 additions and 8443 deletions

View file

@ -11,4 +11,4 @@ task:
- cargo build
test_script:
- . $HOME/.cargo/env
- cargo test
- cargo test -p uucore -p coreutils

18
.github/stale.yml vendored Normal file
View file

@ -0,0 +1,18 @@
# Number of days of inactivity before an issue/PR becomes stale
daysUntilStale: 365
# Number of days of inactivity before a stale issue/PR is closed
daysUntilClose: 365
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- "Good first bug"
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View file

@ -1,7 +1,7 @@
name: CICD
# spell-checker:ignore (acronyms) CICD MSVC musl
# spell-checker:ignore (env/flags) Ccodegen Coverflow Cpanic RUSTDOCFLAGS RUSTFLAGS Zpanic
# spell-checker:ignore (env/flags) Awarnings Ccodegen Coverflow Cpanic RUSTDOCFLAGS RUSTFLAGS Zpanic
# spell-checker:ignore (jargon) SHAs deps softprops toolchain
# spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy
# spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs
@ -11,7 +11,7 @@ env:
PROJECT_NAME: coreutils
PROJECT_DESC: "Core universal (cross-platform) utilities"
PROJECT_AUTH: "uutils"
RUST_MIN_SRV: "1.32.0" ## v1.32.0 - minimum version for half, tempfile, etc
RUST_MIN_SRV: "1.40.0" ## v1.40.0
RUST_COV_SRV: "2020-08-01" ## (~v1.47.0) supported rust version for code coverage; (date required/used by 'coverage') ## !maint: refactor when code coverage support is included in the stable channel
on: [push, pull_request]
@ -119,6 +119,12 @@ jobs:
use-tool-cache: true
env:
RUSTUP_TOOLCHAIN: stable
- name: Confirm compatible 'Cargo.lock'
shell: bash
run: |
# Confirm compatible 'Cargo.lock'
# * 'Cargo.lock' is required to be in a format that `cargo` of MinSRV can interpret (eg, v1-format for MinSRV < v1.38)
cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::Incompatible 'Cargo.lock' format; try \`cargo +${{ env.RUST_MIN_SRV }} update\`" ; exit 1 ; }
- name: Info
shell: bash
run: |
@ -136,17 +142,67 @@ jobs:
cargo-tree tree -V
## dependencies
echo "## dependency list"
cargo fetch --locked --quiet
## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors
RUSTUP_TOOLCHAIN=stable cargo fetch --quiet
RUSTUP_TOOLCHAIN=stable cargo-tree tree --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
RUSTUP_TOOLCHAIN=stable cargo-tree tree --frozen --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
args: --features "feat_os_unix"
args: --features "feat_os_unix" -p uucore -p coreutils
env:
RUSTFLAGS: '-Awarnings'
busybox_test:
name: Busybox test suite
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest }
steps:
- uses: actions/checkout@v1
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: "prepare busytest"
shell: bash
run: |
make prepare-busytest
- name: "run busybox testsuite"
shell: bash
run: |
bindir=$(pwd)/target/debug
cd tmp/busybox-*/testsuite
S=$(bindir=$bindir ./runtest) && printf "%s\n" "$S" || { printf "%s\n" "$S" | grep "FAIL:" | sed -e "s/FAIL: /::warning ::Test failure:/g" ; }
makefile_build:
name: Test the build target of the Makefile
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest }
steps:
- uses: actions/checkout@v1
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: "Run make build"
shell: bash
run: |
sudo apt-get -y update ; sudo apt-get -y install python3-sphinx;
make build
build:
name: Build
runs-on: ${{ matrix.job.os }}
@ -348,7 +404,7 @@ jobs:
cargo-tree tree -V
## dependencies
echo "## dependency list"
cargo fetch --quiet
cargo fetch --locked --quiet
cargo-tree tree --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all --no-dev-dependencies --no-indent | grep -vE "$PWD" | sort --unique
- name: Build
uses: actions-rs/cargo@v1
@ -480,6 +536,17 @@ jobs:
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
echo set-output name=UTILITY_LIST::${UTILITY_LIST}
echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS}
- name: Test uucore
uses: actions-rs/cargo@v1
with:
command: test
args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast -p uucore
env:
CARGO_INCREMENTAL: '0'
RUSTC_WRAPPER: ''
RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort'
RUSTDOCFLAGS: '-Cpanic=abort'
# RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }}
- name: Test
uses: actions-rs/cargo@v1
with:

134
.github/workflows/GNU.yml vendored Normal file
View file

@ -0,0 +1,134 @@
name: GNU
on: [push, pull_request]
jobs:
gnu:
name: Run GNU tests
runs-on: ubuntu-latest
steps:
# Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code uutil
uses: actions/checkout@v2
with:
path: 'uutils'
- name: Chechout GNU coreutils
uses: actions/checkout@v2
with:
repository: 'coreutils/coreutils'
path: 'gnu'
- name: Chechout GNU corelib
uses: actions/checkout@v2
with:
repository: 'coreutils/gnulib'
path: 'gnulib'
fetch-depth: 0 # gnu gets upset if gnulib is a shallow checkout
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt
- name: Build binaries
shell: bash
run: |
sudo apt-get update
sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx
pushd uutils
make PROFILE=release
BUILDDIR="$PWD/target/release/"
cp "${BUILDDIR}/install" "${BUILDDIR}/ginstall" # The GNU tests rename this script before running, to avoid confusion with the make target
# Create *sum binaries
for sum in b2sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum
do
sum_path="${BUILDDIR}/${sum}"
test -f "${sum_path}" || cp "${BUILDDIR}/hashsum" "${sum_path}"
done
test -f "${BUILDDIR}/[" || cp "${BUILDDIR}/test" "${BUILDDIR}/["
popd
GNULIB_SRCDIR="$PWD/gnulib"
pushd gnu/
# Any binaries that aren't built become `false` so their tests fail
for binary in $(./build-aux/gen-lists-of-programs.sh --list-progs)
do
bin_path="${BUILDDIR}/${binary}"
test -f "${bin_path}" || { echo "'${binary}' was not built with uutils, using the 'false' program"; cp "${BUILDDIR}/false" "${bin_path}"; }
done
./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR"
./configure --quiet --disable-gcc-warnings
#Add timeout to to protect against hangs
sed -i 's|"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver
# Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils
sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile
sed -i 's| tr | /usr/bin/tr |' tests/init.sh
make
# Generate the factor tests, so they can be fixed
for i in {00..36}
do
make tests/factor/t${i}.sh
done
grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||'
sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh
# Remove tests checking for --version & --help
# Not really interesting for us and logs are too big
sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \
-e '/tests\/misc\/help-version.sh/ D' \
-e '/tests\/misc\/help-version-getopt.sh/ D' \
Makefile
# Use the system coreutils where the test fails due to error in a util that is not the one being tested
sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh
sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh
sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh
sed -i 's|timeout \([[:digit:]]\)| /usr/bin/timeout \1|' tests/tail-2/inotify-rotate.sh tests/tail-2/inotify-dir-recreate.sh tests/tail-2/inotify-rotate-resources.sh tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.sh tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh tests/tail-2/pid.sh tests/dd/stats.sh tests/tail-2/follow-name.sh tests/misc/shuf.sh # Don't break the function called 'grep_timeout'
sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh tests/chgrp/basic.sh tests/misc/shuf.sh
sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh
sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh
sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh
sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh tests/cp/fiemap-2.sh init.cfg
sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh
sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/ls/abmon-align.sh tests/ls/rt-1.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh
sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh
sed -i 's|printf |/usr/bin/printf |' tests/dd/ascii.sh
sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh
sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh
sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh
#Add specific timeout to tests that currently hang to limit time spent waiting
sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh
sed -i 's|cat |/usr/bin/timeout 0.1 cat |' tests/misc/cat-self.sh
test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}"
- name: Run GNU tests
shell: bash
run: |
BUILDDIR="${PWD}/uutils/target/release"
GNULIB_DIR="${PWD}/gnulib"
pushd gnu
timeout -sKILL 2h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make
- name: Extract tests info
shell: bash
run: |
if test -f gnu/tests/test-suite.log
then
TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-)
FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
XPASS=$( grep "# XPASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
ERROR=$( grep "# ERROR:" gnu/tests/test-suite.log|cut -d' ' -f2-)
echo "::warning ::GNU testsuite = $TOTAL / $PASS / $FAIL / $ERROR"
else
echo "::error ::Failed to get summary of test results"
fi
- uses: actions/upload-artifact@v2
with:
name: test-report
path: gnu/tests/**/*.log

8
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,8 @@
# https://pre-commit.com
repos:
- repo: https://github.com/doublify/pre-commit-rust
rev: v1.0
hooks:
- id: cargo-check
- id: clippy
- id: fmt

View file

@ -18,7 +18,7 @@ matrix:
- rust: nightly
fast_finish: true
include:
- rust: 1.32.0
- rust: 1.40.0
env: FEATURES=unix
# - rust: stable
# os: linux
@ -56,7 +56,7 @@ install:
script:
- cargo build $CARGO_ARGS --features "$FEATURES"
- if [ ! $REDOX ]; then cargo test $CARGO_ARGS --features "$FEATURES" --no-fail-fast; fi
- if [ ! $REDOX ]; then cargo test $CARGO_ARGS -p uucore -p coreutils --features "$FEATURES" --no-fail-fast; fi
- if [ -n "$TEST_INSTALL" ]; then mkdir installdir_test; DESTDIR=installdir_test make install; [ `ls installdir_test/usr/local/bin | wc -l` -gt 0 ]; fi
addons:

55
.vscode/cSpell.json vendored
View file

@ -8,6 +8,8 @@
"Cygwin",
"FreeBSD",
"Gmail",
"GNUEABI",
"GNUEABIhf",
"Irix",
"MacOS",
"MinGW",
@ -23,16 +25,31 @@
"Xenix",
"flac",
"lzma",
// cargo
"cdylib",
"rlib",
// crates
"advapi",
"advapi32-sys",
"aho-corasick",
"backtrace",
"byteorder",
"chacha",
"chrono",
"conv",
"corasick",
"filetime",
"formatteriteminfo",
"getopts",
"itertools",
"memchr",
"multifilereader",
"onig",
"peekreader",
"quickcheck",
"rand_chacha",
"smallvec",
"tempfile",
"termion",
"termios",
"termsize",
@ -64,12 +81,14 @@
"colorize",
"consts",
"dedup",
"demangle",
"deque",
"dequeue",
"enqueue",
"executable",
"executables",
"gibibytes",
"hardfloat",
"hardlink",
"hardlinks",
"hashsums",
@ -87,9 +106,12 @@
"primality",
"pseudoprime",
"pseudoprimes",
"procs",
"readonly",
"seedable",
"semver",
"shortcode",
"shortcodes",
"symlink",
"symlinks",
"syscall",
@ -144,6 +166,7 @@
"Sunrin SHIMURA", "Sunrin", "SHIMURA",
"Smigle00", "Smigle",
"Sylvestre Ledru", "Sylvestre", "Ledru",
"T Jameson Little", "Jameson", "Little",
"Tobias Bohumir Schottdorf", "Tobias", "Bohumir", "Schottdorf",
"Virgile Andreani", "Virgile", "Andreani",
"Vsevolod Velichko", "Vsevolod", "Velichko",
@ -151,27 +174,37 @@
"Yury Krivopalov", "Yury", "Krivopalov",
"anonymousknight",
"kwantam",
"nicoo",
"rivy",
// rust
"clippy",
"concat",
"fract",
"powi",
"println",
"repr",
"rfind",
"rustc",
"rustfmt",
"struct",
"structs",
"substr",
"splitn",
"trunc",
// shell
"passwd",
"pipefail",
"tcsh",
// tags
"Maint",
// uutils
"chcon",
"chgrp",
"chmod",
"chown",
"chroot",
"cksum",
"csplit",
"dircolors",
"hashsum",
"hostid",
@ -190,16 +223,28 @@
"realpath",
"relpath",
"rmdir",
"runcon",
"shuf",
"stdbuf",
"stty",
"tsort",
"uname",
"unexpand",
"whoami",
// vars/errno
"errno",
"EOPNOTSUPP",
// vars/fcntl
"F_GETFL",
"GETFL",
"fcntl",
"vmsplice",
// vars/libc
"FILENO",
"HOSTSIZE",
"IDSIZE",
"IFIFO",
"IFREG",
"IRGRP",
"IROTH",
"IRUSR",
@ -240,10 +285,16 @@
"socktype",
"umask",
"waitpid",
// vars/nix
"iovec",
"unistd",
// vars/signals
"SIGPIPE",
// vars/sync
"Condvar",
// vars/stat
"fstat",
"stat",
// vars/time
"Timespec",
"nsec",
@ -265,9 +316,11 @@
"errhandlingapi",
"fileapi",
"handleapi",
"lmcons",
"minwindef",
"processthreadsapi",
"synchapi",
"sysinfoapi",
"winbase",
"winerror",
"winnt",
@ -285,10 +338,12 @@
"coreopts",
"coreutils",
"libc",
"libstdbuf",
"musl",
"ucmd",
"utmpx",
"uucore",
"uucore_procs",
"uumain",
"uutils"
],

2
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,2 @@
{
}

128
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
sylvestre@debian.org.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View file

@ -5,16 +5,24 @@ standard libraries are stabilized. You may *claim* an item on the to-do list by
following these steps:
1. Open an issue named "Implement [the utility of your choice]", e.g. "Implement ls"
2. State that you are working on this utility.
3. Develop the utility.
4. Add integration tests.
5. Add the reference to your utility into Cargo.toml and Makefile.
6. Remove utility from the to-do list in the README.
7. Submit a pull request and close the issue.
1. State that you are working on this utility.
1. Develop the utility
1. Add integration tests.
1. Add the reference to your utility into Cargo.toml and Makefile.
1. Remove utility from the to-do list in the README.
1. Submit a pull request and close the issue.
The steps above imply that, before starting to work on a utility, you should
search the issues to make sure no one else is working on it.
## Best practices
1. Follow what GNU is doing in term of options and behavior.
1. Use clap for argument management.
1. Make sure that the code coverage is covering all of the cases, including errors.
1. The code must be clippy-warning-free and rustfmt-compliant.
1. Don't hesitate to move common functions into uucore if they can be reused by other binaries.
## Commit messages
To help the project maintainers review pull requests from contributors across

2677
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
[package]
name = "coreutils"
version = "0.0.1" # "0.0.1.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust"
@ -37,11 +37,13 @@ feat_common_core = [
"cksum",
"comm",
"cp",
"csplit",
"cut",
"date",
"df",
"dircolors",
"dirname",
"du",
"echo",
"env",
"expand",
@ -148,7 +150,6 @@ feat_require_unix = [
"chmod",
"chown",
"chroot",
"du",
"groups",
"hostid",
"id",
@ -225,122 +226,132 @@ test = [ "uu_test" ]
[dependencies]
lazy_static = { version="1.3" }
textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="src/uucore" }
# * uutils
uu_test = { optional=true, version="0.0.1", package="uu_test", path="src/uu/test" }
uu_test = { optional=true, version="0.0.6", package="uu_test", path="src/uu/test" }
#
arch = { optional=true, version="0.0.1", package="uu_arch", path="src/uu/arch" }
base32 = { optional=true, version="0.0.1", package="uu_base32", path="src/uu/base32" }
base64 = { optional=true, version="0.0.1", package="uu_base64", path="src/uu/base64" }
basename = { optional=true, version="0.0.1", package="uu_basename", path="src/uu/basename" }
cat = { optional=true, version="0.0.1", package="uu_cat", path="src/uu/cat" }
chgrp = { optional=true, version="0.0.1", package="uu_chgrp", path="src/uu/chgrp" }
chmod = { optional=true, version="0.0.1", package="uu_chmod", path="src/uu/chmod" }
chown = { optional=true, version="0.0.1", package="uu_chown", path="src/uu/chown" }
chroot = { optional=true, version="0.0.1", package="uu_chroot", path="src/uu/chroot" }
cksum = { optional=true, version="0.0.1", package="uu_cksum", path="src/uu/cksum" }
comm = { optional=true, version="0.0.1", package="uu_comm", path="src/uu/comm" }
cp = { optional=true, version="0.0.1", package="uu_cp", path="src/uu/cp" }
cut = { optional=true, version="0.0.1", package="uu_cut", path="src/uu/cut" }
date = { optional=true, version="0.0.1", package="uu_date", path="src/uu/date" }
df = { optional=true, version="0.0.1", package="uu_df", path="src/uu/df" }
dircolors= { optional=true, version="0.0.1", package="uu_dircolors", path="src/uu/dircolors" }
dirname = { optional=true, version="0.0.1", package="uu_dirname", path="src/uu/dirname" }
du = { optional=true, version="0.0.1", package="uu_du", path="src/uu/du" }
echo = { optional=true, version="0.0.1", package="uu_echo", path="src/uu/echo" }
env = { optional=true, version="0.0.1", package="uu_env", path="src/uu/env" }
expand = { optional=true, version="0.0.1", package="uu_expand", path="src/uu/expand" }
expr = { optional=true, version="0.0.1", package="uu_expr", path="src/uu/expr" }
factor = { optional=true, version="0.0.1", package="uu_factor", path="src/uu/factor" }
false = { optional=true, version="0.0.1", package="uu_false", path="src/uu/false" }
fmt = { optional=true, version="0.0.1", package="uu_fmt", path="src/uu/fmt" }
fold = { optional=true, version="0.0.1", package="uu_fold", path="src/uu/fold" }
groups = { optional=true, version="0.0.1", package="uu_groups", path="src/uu/groups" }
hashsum = { optional=true, version="0.0.1", package="uu_hashsum", path="src/uu/hashsum" }
head = { optional=true, version="0.0.1", package="uu_head", path="src/uu/head" }
hostid = { optional=true, version="0.0.1", package="uu_hostid", path="src/uu/hostid" }
hostname = { optional=true, version="0.0.1", package="uu_hostname", path="src/uu/hostname" }
id = { optional=true, version="0.0.1", package="uu_id", path="src/uu/id" }
install = { optional=true, version="0.0.1", package="uu_install", path="src/uu/install" }
join = { optional=true, version="0.0.1", package="uu_join", path="src/uu/join" }
kill = { optional=true, version="0.0.1", package="uu_kill", path="src/uu/kill" }
link = { optional=true, version="0.0.1", package="uu_link", path="src/uu/link" }
ln = { optional=true, version="0.0.1", package="uu_ln", path="src/uu/ln" }
ls = { optional=true, version="0.0.1", package="uu_ls", path="src/uu/ls" }
logname = { optional=true, version="0.0.1", package="uu_logname", path="src/uu/logname" }
mkdir = { optional=true, version="0.0.1", package="uu_mkdir", path="src/uu/mkdir" }
mkfifo = { optional=true, version="0.0.1", package="uu_mkfifo", path="src/uu/mkfifo" }
mknod = { optional=true, version="0.0.1", package="uu_mknod", path="src/uu/mknod" }
mktemp = { optional=true, version="0.0.1", package="uu_mktemp", path="src/uu/mktemp" }
more = { optional=true, version="0.0.1", package="uu_more", path="src/uu/more" }
mv = { optional=true, version="0.0.1", package="uu_mv", path="src/uu/mv" }
nice = { optional=true, version="0.0.1", package="uu_nice", path="src/uu/nice" }
nl = { optional=true, version="0.0.1", package="uu_nl", path="src/uu/nl" }
nohup = { optional=true, version="0.0.1", package="uu_nohup", path="src/uu/nohup" }
nproc = { optional=true, version="0.0.1", package="uu_nproc", path="src/uu/nproc" }
numfmt = { optional=true, version="0.0.1", package="uu_numfmt", path="src/uu/numfmt" }
od = { optional=true, version="0.0.1", package="uu_od", path="src/uu/od" }
paste = { optional=true, version="0.0.1", package="uu_paste", path="src/uu/paste" }
pathchk = { optional=true, version="0.0.1", package="uu_pathchk", path="src/uu/pathchk" }
pinky = { optional=true, version="0.0.1", package="uu_pinky", path="src/uu/pinky" }
printenv = { optional=true, version="0.0.1", package="uu_printenv", path="src/uu/printenv" }
printf = { optional=true, version="0.0.1", package="uu_printf", path="src/uu/printf" }
ptx = { optional=true, version="0.0.1", package="uu_ptx", path="src/uu/ptx" }
pwd = { optional=true, version="0.0.1", package="uu_pwd", path="src/uu/pwd" }
readlink = { optional=true, version="0.0.1", package="uu_readlink", path="src/uu/readlink" }
realpath = { optional=true, version="0.0.1", package="uu_realpath", path="src/uu/realpath" }
relpath = { optional=true, version="0.0.1", package="uu_relpath", path="src/uu/relpath" }
rm = { optional=true, version="0.0.1", package="uu_rm", path="src/uu/rm" }
rmdir = { optional=true, version="0.0.1", package="uu_rmdir", path="src/uu/rmdir" }
seq = { optional=true, version="0.0.1", package="uu_seq", path="src/uu/seq" }
shred = { optional=true, version="0.0.1", package="uu_shred", path="src/uu/shred" }
shuf = { optional=true, version="0.0.1", package="uu_shuf", path="src/uu/shuf" }
sleep = { optional=true, version="0.0.1", package="uu_sleep", path="src/uu/sleep" }
sort = { optional=true, version="0.0.1", package="uu_sort", path="src/uu/sort" }
split = { optional=true, version="0.0.1", package="uu_split", path="src/uu/split" }
stat = { optional=true, version="0.0.1", package="uu_stat", path="src/uu/stat" }
stdbuf = { optional=true, version="0.0.1", package="uu_stdbuf", path="src/uu/stdbuf" }
sum = { optional=true, version="0.0.1", package="uu_sum", path="src/uu/sum" }
sync = { optional=true, version="0.0.1", package="uu_sync", path="src/uu/sync" }
tac = { optional=true, version="0.0.1", package="uu_tac", path="src/uu/tac" }
tail = { optional=true, version="0.0.1", package="uu_tail", path="src/uu/tail" }
tee = { optional=true, version="0.0.1", package="uu_tee", path="src/uu/tee" }
timeout = { optional=true, version="0.0.1", package="uu_timeout", path="src/uu/timeout" }
touch = { optional=true, version="0.0.1", package="uu_touch", path="src/uu/touch" }
tr = { optional=true, version="0.0.1", package="uu_tr", path="src/uu/tr" }
true = { optional=true, version="0.0.1", package="uu_true", path="src/uu/true" }
truncate = { optional=true, version="0.0.1", package="uu_truncate", path="src/uu/truncate" }
tsort = { optional=true, version="0.0.1", package="uu_tsort", path="src/uu/tsort" }
tty = { optional=true, version="0.0.1", package="uu_tty", path="src/uu/tty" }
uname = { optional=true, version="0.0.1", package="uu_uname", path="src/uu/uname" }
unexpand = { optional=true, version="0.0.1", package="uu_unexpand", path="src/uu/unexpand" }
uniq = { optional=true, version="0.0.1", package="uu_uniq", path="src/uu/uniq" }
unlink = { optional=true, version="0.0.1", package="uu_unlink", path="src/uu/unlink" }
uptime = { optional=true, version="0.0.1", package="uu_uptime", path="src/uu/uptime" }
users = { optional=true, version="0.0.1", package="uu_users", path="src/uu/users" }
wc = { optional=true, version="0.0.1", package="uu_wc", path="src/uu/wc" }
who = { optional=true, version="0.0.1", package="uu_who", path="src/uu/who" }
whoami = { optional=true, version="0.0.1", package="uu_whoami", path="src/uu/whoami" }
yes = { optional=true, version="0.0.1", package="uu_yes", path="src/uu/yes" }
arch = { optional=true, version="0.0.6", package="uu_arch", path="src/uu/arch" }
base32 = { optional=true, version="0.0.6", package="uu_base32", path="src/uu/base32" }
base64 = { optional=true, version="0.0.6", package="uu_base64", path="src/uu/base64" }
basename = { optional=true, version="0.0.6", package="uu_basename", path="src/uu/basename" }
cat = { optional=true, version="0.0.6", package="uu_cat", path="src/uu/cat" }
chgrp = { optional=true, version="0.0.6", package="uu_chgrp", path="src/uu/chgrp" }
chmod = { optional=true, version="0.0.6", package="uu_chmod", path="src/uu/chmod" }
chown = { optional=true, version="0.0.6", package="uu_chown", path="src/uu/chown" }
chroot = { optional=true, version="0.0.6", package="uu_chroot", path="src/uu/chroot" }
cksum = { optional=true, version="0.0.6", package="uu_cksum", path="src/uu/cksum" }
comm = { optional=true, version="0.0.6", package="uu_comm", path="src/uu/comm" }
cp = { optional=true, version="0.0.6", package="uu_cp", path="src/uu/cp" }
csplit = { optional=true, version="0.0.6", package="uu_csplit", path="src/uu/csplit" }
cut = { optional=true, version="0.0.6", package="uu_cut", path="src/uu/cut" }
date = { optional=true, version="0.0.6", package="uu_date", path="src/uu/date" }
df = { optional=true, version="0.0.6", package="uu_df", path="src/uu/df" }
dircolors= { optional=true, version="0.0.6", package="uu_dircolors", path="src/uu/dircolors" }
dirname = { optional=true, version="0.0.6", package="uu_dirname", path="src/uu/dirname" }
du = { optional=true, version="0.0.6", package="uu_du", path="src/uu/du" }
echo = { optional=true, version="0.0.6", package="uu_echo", path="src/uu/echo" }
env = { optional=true, version="0.0.6", package="uu_env", path="src/uu/env" }
expand = { optional=true, version="0.0.6", package="uu_expand", path="src/uu/expand" }
expr = { optional=true, version="0.0.6", package="uu_expr", path="src/uu/expr" }
factor = { optional=true, version="0.0.6", package="uu_factor", path="src/uu/factor" }
false = { optional=true, version="0.0.6", package="uu_false", path="src/uu/false" }
fmt = { optional=true, version="0.0.6", package="uu_fmt", path="src/uu/fmt" }
fold = { optional=true, version="0.0.6", package="uu_fold", path="src/uu/fold" }
groups = { optional=true, version="0.0.6", package="uu_groups", path="src/uu/groups" }
hashsum = { optional=true, version="0.0.6", package="uu_hashsum", path="src/uu/hashsum" }
head = { optional=true, version="0.0.6", package="uu_head", path="src/uu/head" }
hostid = { optional=true, version="0.0.6", package="uu_hostid", path="src/uu/hostid" }
hostname = { optional=true, version="0.0.6", package="uu_hostname", path="src/uu/hostname" }
id = { optional=true, version="0.0.6", package="uu_id", path="src/uu/id" }
install = { optional=true, version="0.0.6", package="uu_install", path="src/uu/install" }
join = { optional=true, version="0.0.6", package="uu_join", path="src/uu/join" }
kill = { optional=true, version="0.0.6", package="uu_kill", path="src/uu/kill" }
link = { optional=true, version="0.0.6", package="uu_link", path="src/uu/link" }
ln = { optional=true, version="0.0.6", package="uu_ln", path="src/uu/ln" }
ls = { optional=true, version="0.0.6", package="uu_ls", path="src/uu/ls" }
logname = { optional=true, version="0.0.6", package="uu_logname", path="src/uu/logname" }
mkdir = { optional=true, version="0.0.6", package="uu_mkdir", path="src/uu/mkdir" }
mkfifo = { optional=true, version="0.0.6", package="uu_mkfifo", path="src/uu/mkfifo" }
mknod = { optional=true, version="0.0.6", package="uu_mknod", path="src/uu/mknod" }
mktemp = { optional=true, version="0.0.6", package="uu_mktemp", path="src/uu/mktemp" }
more = { optional=true, version="0.0.6", package="uu_more", path="src/uu/more" }
mv = { optional=true, version="0.0.6", package="uu_mv", path="src/uu/mv" }
nice = { optional=true, version="0.0.6", package="uu_nice", path="src/uu/nice" }
nl = { optional=true, version="0.0.6", package="uu_nl", path="src/uu/nl" }
nohup = { optional=true, version="0.0.6", package="uu_nohup", path="src/uu/nohup" }
nproc = { optional=true, version="0.0.6", package="uu_nproc", path="src/uu/nproc" }
numfmt = { optional=true, version="0.0.6", package="uu_numfmt", path="src/uu/numfmt" }
od = { optional=true, version="0.0.6", package="uu_od", path="src/uu/od" }
paste = { optional=true, version="0.0.6", package="uu_paste", path="src/uu/paste" }
pathchk = { optional=true, version="0.0.6", package="uu_pathchk", path="src/uu/pathchk" }
pinky = { optional=true, version="0.0.6", package="uu_pinky", path="src/uu/pinky" }
printenv = { optional=true, version="0.0.6", package="uu_printenv", path="src/uu/printenv" }
printf = { optional=true, version="0.0.6", package="uu_printf", path="src/uu/printf" }
ptx = { optional=true, version="0.0.6", package="uu_ptx", path="src/uu/ptx" }
pwd = { optional=true, version="0.0.6", package="uu_pwd", path="src/uu/pwd" }
readlink = { optional=true, version="0.0.6", package="uu_readlink", path="src/uu/readlink" }
realpath = { optional=true, version="0.0.6", package="uu_realpath", path="src/uu/realpath" }
relpath = { optional=true, version="0.0.6", package="uu_relpath", path="src/uu/relpath" }
rm = { optional=true, version="0.0.6", package="uu_rm", path="src/uu/rm" }
rmdir = { optional=true, version="0.0.6", package="uu_rmdir", path="src/uu/rmdir" }
seq = { optional=true, version="0.0.6", package="uu_seq", path="src/uu/seq" }
shred = { optional=true, version="0.0.6", package="uu_shred", path="src/uu/shred" }
shuf = { optional=true, version="0.0.6", package="uu_shuf", path="src/uu/shuf" }
sleep = { optional=true, version="0.0.6", package="uu_sleep", path="src/uu/sleep" }
sort = { optional=true, version="0.0.6", package="uu_sort", path="src/uu/sort" }
split = { optional=true, version="0.0.6", package="uu_split", path="src/uu/split" }
stat = { optional=true, version="0.0.6", package="uu_stat", path="src/uu/stat" }
stdbuf = { optional=true, version="0.0.6", package="uu_stdbuf", path="src/uu/stdbuf" }
sum = { optional=true, version="0.0.6", package="uu_sum", path="src/uu/sum" }
sync = { optional=true, version="0.0.6", package="uu_sync", path="src/uu/sync" }
tac = { optional=true, version="0.0.6", package="uu_tac", path="src/uu/tac" }
tail = { optional=true, version="0.0.6", package="uu_tail", path="src/uu/tail" }
tee = { optional=true, version="0.0.6", package="uu_tee", path="src/uu/tee" }
timeout = { optional=true, version="0.0.6", package="uu_timeout", path="src/uu/timeout" }
touch = { optional=true, version="0.0.6", package="uu_touch", path="src/uu/touch" }
tr = { optional=true, version="0.0.6", package="uu_tr", path="src/uu/tr" }
true = { optional=true, version="0.0.6", package="uu_true", path="src/uu/true" }
truncate = { optional=true, version="0.0.6", package="uu_truncate", path="src/uu/truncate" }
tsort = { optional=true, version="0.0.6", package="uu_tsort", path="src/uu/tsort" }
tty = { optional=true, version="0.0.6", package="uu_tty", path="src/uu/tty" }
uname = { optional=true, version="0.0.6", package="uu_uname", path="src/uu/uname" }
unexpand = { optional=true, version="0.0.6", package="uu_unexpand", path="src/uu/unexpand" }
uniq = { optional=true, version="0.0.6", package="uu_uniq", path="src/uu/uniq" }
unlink = { optional=true, version="0.0.6", package="uu_unlink", path="src/uu/unlink" }
uptime = { optional=true, version="0.0.6", package="uu_uptime", path="src/uu/uptime" }
users = { optional=true, version="0.0.6", package="uu_users", path="src/uu/users" }
wc = { optional=true, version="0.0.6", package="uu_wc", path="src/uu/wc" }
who = { optional=true, version="0.0.6", package="uu_who", path="src/uu/who" }
whoami = { optional=true, version="0.0.6", package="uu_whoami", path="src/uu/whoami" }
yes = { optional=true, version="0.0.6", package="uu_yes", path="src/uu/yes" }
#
# * pinned transitive dependencies
pin_same-file = { version="1.0.4, < 1.0.6", package="same-file" } ## same-file v1.0.6 has compiler errors for MinRustV v1.32.0, expects 1.34
pin_winapi-util = { version="0.1.2, < 0.1.3", package="winapi-util" } ## winapi-util v0.1.3 has compiler errors for MinRustV v1.32.0, expects 1.34
# Not needed for now. Keep as examples:
#pin_cc = { version="1.0.61, < 1.0.62", package="cc" } ## cc v1.0.62 has compiler errors for MinRustV v1.32.0, requires 1.34 (for `std::str::split_ascii_whitespace()`)
[dev-dependencies]
conv = "0.3"
filetime = "0.2"
glob = "0.3.0"
libc = "0.2"
rand = "0.6"
nix = "0.20.0"
rand = "0.7"
regex = "1.0"
tempfile = "3.1"
sha1 = { version="0.6", features=["std"] }
## tempfile 3.2 depends on recent version of rand which depends on getrandom v0.2 which has compiler errors for MinRustV v1.32.0
## min dep for tempfile = Rustc 1.40
tempfile = "= 3.1.0"
time = "0.1"
unindent = "0.1"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["entries"] }
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] }
walkdir = "2.2"
tempdir = "0.3"
[target.'cfg(unix)'.dev-dependencies]
rust-users = { version="0.10", package="users" }
unix_socket = "0.5.0"
[[bin]]
name = "coreutils"
path = "src/bin/coreutils.rs"

38
DEVELOPER_INSTRUCTIONS.md Normal file
View file

@ -0,0 +1,38 @@
Code Coverage Report Generation
---------------------------------
Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov).
### Using Nightly Rust
To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-cc) coverage report
```bash
$ export CARGO_INCREMENTAL=0
$ export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
$ export RUSTDOCFLAGS="-Cpanic=abort"
$ cargo build <options...> # e.g., --features feat_os_unix
$ cargo test <options...> # e.g., --features feat_os_unix test_pathchk
$ grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
$ # open target/debug/coverage/index.html in browser
```
if changes are not reflected in the report then run `cargo clean` and run the above commands.
### Using Stable Rust
If you are using stable version of Rust that doesn't enable code coverage instrumentation by default
then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above.
pre-commit hooks
----------------
A configuration for `pre-commit` is provided in the repository. It allows automatically checking every git commit you make to ensure it compiles, and passes `clippy` and `rustfmt` without warnings.
To use the provided hook:
1. [Install `pre-commit`](https://pre-commit.com/#install)
2. Run `pre-commit install` while in the repository directory
Your git commits will then automatically be checked. If a check fails, an error message will explain why, and your commit will be canceled. You can then make the suggested changes, and run `git commit ...` again.

View file

@ -30,7 +30,7 @@ ifneq ($(.SHELLSTATUS),0)
override INSTALLDIR_MAN=$(DESTDIR)$(PREFIX)$(MANDIR)
endif
#prefix to apply to uutils binary and all tool binaries
#prefix to apply to coreutils binary and all tool binaries
PROG_PREFIX ?=
# This won't support any directory with spaces in its name, but you can just
@ -41,7 +41,7 @@ PKG_BUILDDIR := $(BUILDDIR)/deps
DOCSDIR := $(BASEDIR)/docs
BUSYBOX_ROOT := $(BASEDIR)/tmp
BUSYBOX_VER := 1.24.1
BUSYBOX_VER := 1.32.1
BUSYBOX_SRC := $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER)
# Possible programs
@ -53,7 +53,9 @@ PROGS := \
cksum \
comm \
cp \
csplit \
cut \
date \
df \
dircolors \
dirname \
@ -160,7 +162,9 @@ TEST_PROGS := \
cksum \
comm \
cp \
csplit \
cut \
date \
dircolors \
dirname \
echo \
@ -224,7 +228,7 @@ endif
define TEST_BUSYBOX
test_busybox_$(1):
(cd $(BUSYBOX_SRC)/testsuite && bindir=$(BUILDDIR) ./runtest $(RUNTEST_ARGS) $(1) )
-(cd $(BUSYBOX_SRC)/testsuite && bindir=$(BUILDDIR) ./runtest $(RUNTEST_ARGS) $(1))
endef
# Output names
@ -233,19 +237,7 @@ EXES := \
INSTALLEES := ${EXES}
ifeq (${MULTICALL}, y)
INSTALLEES := ${INSTALLEES} uutils
endif
# Shared library extension
SYSTEM := $(shell uname)
DYLIB_EXT :=
ifeq ($(SYSTEM),Linux)
DYLIB_EXT := so
DYLIB_FLAGS := -shared
endif
ifeq ($(SYSTEM),Darwin)
DYLIB_EXT := dylib
DYLIB_FLAGS := -dynamiclib -undefined dynamic_lookup
INSTALLEES := ${INSTALLEES} coreutils
endif
all: build
@ -258,13 +250,13 @@ ifneq (${MULTICALL}, y)
${CARGO} build ${CARGOFLAGS} ${PROFILE_CMD} $(foreach pkg,$(EXES),-p uu_$(pkg))
endif
build-uutils:
build-coreutils:
${CARGO} build ${CARGOFLAGS} --features "${EXES}" ${PROFILE_CMD} --no-default-features
build-manpages:
cd $(DOCSDIR) && $(MAKE) man
build: build-uutils build-pkgs build-manpages
build: build-coreutils build-pkgs build-manpages
$(foreach test,$(filter-out $(SKIP_UTILS),$(PROGS)),$(eval $(call TEST_BUSYBOX,$(test))))
@ -283,10 +275,12 @@ $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
cp $< $@
# Test under the busybox testsuite
$(BUILDDIR)/busybox: busybox-src build-uutils $(BUILDDIR)/.config
cp $(BUILDDIR)/uutils $(BUILDDIR)/busybox; \
$(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config
cp $(BUILDDIR)/coreutils $(BUILDDIR)/busybox; \
chmod +x $@;
prepare-busytest: $(BUILDDIR)/busybox
ifeq ($(EXES),)
busytest:
else
@ -304,10 +298,10 @@ install: build
mkdir -p $(INSTALLDIR_BIN)
mkdir -p $(INSTALLDIR_MAN)
ifeq (${MULTICALL}, y)
$(INSTALL) $(BUILDDIR)/uutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)uutils
cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out uutils, $(INSTALLEES)), \
ln -fs $(PROG_PREFIX)uutils $(PROG_PREFIX)$(prog) &&) :
cat $(DOCSDIR)/_build/man/uutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)uutils.1.gz
$(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils
cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out coreutils, $(INSTALLEES)), \
ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)$(prog) &&) :
cat $(DOCSDIR)/_build/man/coreutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)coreutils.1.gz
else
$(foreach prog, $(INSTALLEES), \
$(INSTALL) $(BUILDDIR)/$(prog) $(INSTALLDIR_BIN)/$(PROG_PREFIX)$(prog);)
@ -317,10 +311,10 @@ endif
uninstall:
ifeq (${MULTICALL}, y)
rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)uutils)
rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)coreutils)
endif
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)uutils.1.gz)
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz)
rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS))
rm -f $(addprefix $(INSTALLDIR_MAN)/$(PROG_PREFIX),$(addsuffix .1.gz,$(PROGS)))
.PHONY: all build build-uutils build-pkgs build-docs test distclean clean busytest install uninstall
.PHONY: all build build-coreutils build-pkgs build-docs test distclean clean busytest install uninstall

View file

@ -1,14 +1,13 @@
uutils coreutils
================
[![Crates.io](https://img.shields.io/crates/v/coreutils.svg)](https://crates.io/crates/coreutils)
[![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ)
[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/coreutils/blob/master/LICENSE)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils?ref=badge_shield)
[![LOC](https://tokei.rs/b1/github/uutils/coreutils?category=code)](https://github.com/Aaronepower/tokei)
[![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils)
[![Build Status](https://api.travis-ci.org/uutils/coreutils.svg?branch=master)](https://travis-ci.org/uutils/coreutils)
[![Build Status (Windows)](https://ci.appveyor.com/api/projects/status/787ltcxgy86r20le?svg=true)](https://ci.appveyor.com/project/Arcterus/coreutils)
[![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master)
[![codecov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils)
@ -24,9 +23,8 @@ Why?
Many GNU, Linux and other utilities are useful, and obviously
[some](http://gnuwin32.sourceforge.net) [effort](http://unxutils.sourceforge.net)
has been spent in the past to port them to Windows. However, those projects
are either old and abandoned, are hosted on CVS (which makes it more difficult
for new contributors to contribute to them), are written in platform-specific C, or
suffer from other issues.
are written in platform-specific C, a language considered unsafe compared to Rust, and
have other issues.
Rust provides a good, platform-agnostic way of writing systems utilities that are easy
to compile anywhere, and this is as good a way as any to try and learn it.
@ -42,7 +40,7 @@ Requirements
### Rust Version ###
uutils follows Rust's release channels and is tested against stable, beta and nightly.
The current oldest supported version of the Rust compiler is `1.32.0`.
The current oldest supported version of the Rust compiler is `1.40.0`.
On both Windows and Redox, only the nightly version is tested currently.
@ -115,7 +113,7 @@ Installation Instructions
Likewise, installing can simply be done using:
```bash
$ cargo install
$ cargo install --path .
```
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
@ -228,6 +226,11 @@ If you would prefer to test a select few utilities:
$ cargo test --features "chmod mv tail" --no-default-features
```
If you also want to test the core utilities:
```bash
$ cargo test -p uucore -p coreutils
```
To debug:
```bash
$ gdb --args target/debug/coreutils ls
@ -289,19 +292,20 @@ Utilities
| Done | Semi-Done | To Do |
|-----------|-----------|--------|
| arch | cp | chcon |
| base32 | expr | csplit |
| base64 | install | dd |
| basename | ls | numfmt |
| cat | more | pr |
| chgrp | od (`--strings` and 128-bit data types missing) | runcon |
| chmod | printf | stty |
| base32 | expr | dd |
| base64 | install | numfmt |
| basename | ls | pr |
| cat | more | runcon |
| chgrp | od (`--strings` and 128-bit data types missing) | stty |
| chmod | printf | |
| chown | sort | |
| chroot | split | |
| cksum | tail | |
| comm | test | |
| cut | date | |
| dircolors | join | |
| dirname | df | |
| csplit | date | |
| cut | join | |
| dircolors | df | |
| dirname | tac | |
| du | | |
| echo | | |
| env | | |
@ -354,7 +358,6 @@ Utilities
| stdbuf | | |
| sum | | |
| sync | | |
| tac | | |
| tee | | |
| timeout | | |
| touch | | |
@ -374,9 +377,37 @@ Utilities
| whoami | | |
| yes | | |
Targets that compile
-------
This is an auto-generated table showing which binaries compile for each target-triple. Note that this **does not** indicate that they are fully implemented, or that the tests pass.
|######OS######|###ARCH####|arch|base32|base64|basename|cat|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|df|dircolors|dirname|du|echo|env|expand|expr|factor|false|fmt|fold|groups|hashsum|head|hostid|hostname|id|install|join|kill|link|ln|logname|ls|mkdir|mkfifo|mknod|mktemp|more|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|printenv|printf|ptx|pwd|readlink|realpath|relpath|rm|rmdir|seq|shred|shuf|sleep|sort|split|stat|stdbuf|sum|sync|tac|tail|tee|test|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|uptime|users|wc|who|whoami|yes|
|--------------|-----------|----|------|------|--------|---|-----|-----|-----|------|-----|----|--|------|---|----|--|---------|-------|--|----|---|------|----|------|-----|---|----|------|-------|----|------|--------|--|-------|----|----|----|--|-------|--|-----|------|-----|------|----|--|----|--|-----|-----|------|--|-----|-------|-----|--------|------|---|---|--------|--------|-------|--|-----|---|-----|----|-----|----|-----|----|------|---|----|---|----|---|----|-------|-----|--|----|--------|-----|---|-----|--------|----|------|------|-----|--|---|------|---|
|linux-gnu|aarch64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|i686|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|powerpc64|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|riscv64gc| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|linux-gnu|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|windows-msvc|aarch64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y| |y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| | |y|
|windows-gnu|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|
|windows-msvc|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|
|windows-gnu|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|
|windows-msvc|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|
|apple MacOS|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|freebsd|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|netbsd|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|
|android|aarch64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|
|android|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|
|solaris|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|wasi|wasm32| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|redox|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|fuchsia|aarch64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|fuchsia|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
License
-------
uutils is licensed under the MIT License - see the `LICENSE` file for details
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils?ref=badge_large)
GNU Coreutils is licensed under the GPL 3.0 or later.

View file

@ -44,10 +44,10 @@ pub fn main() {
mf.write_all(
"type UtilityMap<T> = HashMap<&'static str, fn(T) -> i32>;\n\
\n\
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n\
\tlet mut map = UtilityMap::new();\n\
"
\n\
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n\
\tlet mut map = UtilityMap::new();\n\
"
.as_bytes(),
)
.unwrap();
@ -97,21 +97,21 @@ pub fn main() {
mf.write_all(
format!(
"\
\tmap.insert(\"{krate}\", {krate}::uumain);\n\
\t\tmap.insert(\"md5sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha1sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha224sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha256sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha384sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha512sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha3sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha3-224sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha3-256sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha3-384sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha3-512sum\", {krate}::uumain);\n\
\t\tmap.insert(\"shake128sum\", {krate}::uumain);\n\
\t\tmap.insert(\"shake256sum\", {krate}::uumain);\n\
",
\tmap.insert(\"{krate}\", {krate}::uumain);\n\
\t\tmap.insert(\"md5sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha1sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha224sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha256sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha384sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha512sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha3sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha3-224sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha3-256sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha3-384sum\", {krate}::uumain);\n\
\t\tmap.insert(\"sha3-512sum\", {krate}::uumain);\n\
\t\tmap.insert(\"shake128sum\", {krate}::uumain);\n\
\t\tmap.insert(\"shake256sum\", {krate}::uumain);\n\
",
krate = krate
)
.as_bytes(),

21
docs/compiles_table.csv Normal file
View file

@ -0,0 +1,21 @@
target,arch,base32,base64,basename,cat,chgrp,chmod,chown,chroot,cksum,comm,cp,csplit,cut,date,df,dircolors,dirname,du,echo,env,expand,expr,factor,false,fmt,fold,groups,hashsum,head,hostid,hostname,id,install,join,kill,link,ln,logname,ls,mkdir,mkfifo,mknod,mktemp,more,mv,nice,nl,nohup,nproc,numfmt,od,paste,pathchk,pinky,printenv,printf,ptx,pwd,readlink,realpath,relpath,rm,rmdir,seq,shred,shuf,sleep,sort,split,stat,stdbuf,sum,sync,tac,tail,tee,test,timeout,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,wc,who,whoami,yes
aarch64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
i686-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
powerpc64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
riscv64gc-unknown-linux-gnu,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
x86_64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
aarch64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,101,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,101,0
i686-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0
i686-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0
x86_64-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0
x86_64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0
x86_64-apple-darwin,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
x86_64-unknown-freebsd,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
x86_64-unknown-netbsd,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0
aarch64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0
x86_64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0
x86_64-sun-solaris,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
wasm32-wasi,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
x86_64-unknown-redox,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
aarch64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
x86_64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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
21 x86_64-fuchsia 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101

234
docs/compiles_table.py Normal file
View file

@ -0,0 +1,234 @@
#!/usr/bin/env python3
import multiprocessing
import subprocess
import argparse
import csv
import sys
from collections import defaultdict
from pathlib import Path
# third party dependencies
from tqdm import tqdm
BINS_PATH=Path("../src/uu")
CACHE_PATH=Path("compiles_table.csv")
TARGETS = [
# Linux - GNU
"aarch64-unknown-linux-gnu",
"i686-unknown-linux-gnu",
"powerpc64-unknown-linux-gnu",
"riscv64gc-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# Windows
"aarch64-pc-windows-msvc",
"i686-pc-windows-gnu",
"i686-pc-windows-msvc",
"x86_64-pc-windows-gnu",
"x86_64-pc-windows-msvc",
# Apple
"x86_64-apple-darwin",
"aarch64-apple-ios",
"x86_64-apple-ios",
# BSDs
"x86_64-unknown-freebsd",
"x86_64-unknown-netbsd",
# Android
"aarch64-linux-android",
"x86_64-linux-android",
# Solaris
"x86_64-sun-solaris",
# WASM
"wasm32-wasi",
# Redox
"x86_64-unknown-redox",
# Fuchsia
"aarch64-fuchsia",
"x86_64-fuchsia",
]
class Target(str):
def __new__(cls, content):
obj = super().__new__(cls, content)
obj.arch, obj.platfrom, obj.os = Target.parse(content)
return obj
@staticmethod
def parse(s):
elem = s.split("-")
if len(elem) == 2:
arch, platform, os = elem[0], "n/a", elem[1]
else:
arch, platform, os = elem[0], elem[1], "-".join(elem[2:])
if os == "ios":
os = "apple IOS"
if os == "darwin":
os = "apple MacOS"
return (arch, platform, os)
@staticmethod
def get_heading():
return ["OS", "ARCH"]
def get_row_heading(self):
return [self.os, self.arch]
def requires_nightly(self):
return "redox" in self
# Perform the 'it-compiles' check
def check(self, binary):
if self.requires_nightly():
args = [ "cargo", "+nightly", "check",
"-p", f"uu_{binary}", "--bin", binary,
f"--target={self}"]
else:
args = ["cargo", "check",
"-p", f"uu_{binary}", "--bin", binary,
f"--target={self}"]
res = subprocess.run(args, capture_output=True)
return res.returncode
# Validate that the dependencies for running this target are met
def is_installed(self):
# check IOS sdk is installed, raise exception otherwise
if "ios" in self:
res = subprocess.run(["which", "xcrun"], capture_output=True)
if len(res.stdout) == 0:
raise Exception("Error: IOS sdk does not seem to be installed. Please do that manually")
if not self.requires_nightly():
# check std toolchains are installed
toolchains = subprocess.run(["rustup", "target", "list"], capture_output=True)
toolchains = toolchains.stdout.decode('utf-8').split("\n")
if "installed" not in next(filter(lambda x: self in x, toolchains)):
raise Exception(f"Error: the {t} target is not installed. Please do that manually")
else:
# check nightly toolchains are installed
toolchains = subprocess.run(["rustup", "+nightly", "target", "list"], capture_output=True)
toolchains = toolchains.stdout.decode('utf-8').split("\n")
if "installed" not in next(filter(lambda x: self in x, toolchains)):
raise Exception(f"Error: the {t} nightly target is not installed. Please do that manually")
return True
def install_targets():
cmd = ["rustup", "target", "add"] + TARGETS
print(" ".join(cmd))
ret = subprocess.run(cmd)
assert(ret.returncode == 0)
def get_all_bins():
bins = map(lambda x: x.name, BINS_PATH.iterdir())
return sorted(list(bins))
def get_targets(selection):
if "all" in selection:
return list(map(Target, TARGETS))
else:
# preserve the same order as in TARGETS
return list(map(Target, filter(lambda x: x in selection, TARGETS)))
def test_helper(tup):
bin, target = tup
retcode = target.check(bin)
return (target, bin, retcode)
def test_all_targets(targets, bins):
pool = multiprocessing.Pool()
inputs = [(b, t) for b in bins for t in targets]
outputs = list(tqdm(pool.imap(test_helper, inputs), total=len(inputs)))
table = defaultdict(dict)
for (t, b, r) in outputs:
table[t][b] = r
return table
def save_csv(file, table):
targets = get_targets(table.keys()) # preserve order in CSV
bins = list(list(table.values())[0].keys())
with open(file, "w") as csvfile:
header = ["target"] + bins
writer = csv.DictWriter(csvfile, fieldnames=header)
writer.writeheader()
for t in targets:
d = {"target" : t}
d.update(table[t])
writer.writerow(d)
def load_csv(file):
table = {}
cols = []
rows = []
with open(file, "r") as csvfile:
reader = csv.DictReader(csvfile)
cols = list(filter(lambda x: x!="target", reader.fieldnames))
for row in reader:
t = Target(row["target"])
rows += [t]
del row["target"]
table[t] = dict([k, int(v)] for k, v in row.items())
return (table, rows, cols)
def merge_tables(old, new):
from copy import deepcopy
tmp = deepcopy(old)
tmp.update(deepcopy(new))
return tmp
def render_md(fd, table, headings: str, row_headings: Target):
def print_row(lst, lens=[]):
lens = lens + [0] * (len(lst) - len(lens))
for e, l in zip(lst, lens):
fmt = '|{}' if l == 0 else '|{:>%s}' % len(header[0])
fd.write(fmt.format(e))
fd.write("|\n")
def cell_render(target, bin):
return "y" if table[target][bin] == 0 else " "
# add some 'hard' padding to specific columns
lens = [
max(map(lambda x: len(x.os), row_headings)) + 2,
max(map(lambda x: len(x.arch), row_headings)) + 2
]
header = Target.get_heading()
header[0] = ("{:#^%d}" % lens[0]).format(header[0])
header[1] = ("{:#^%d}" % lens[1]).format(header[1])
header += headings
print_row(header)
lines = list(map(lambda x: '-'*len(x), header))
print_row(lines)
for t in row_headings:
row = list(map(lambda b: cell_render(t, b), headings))
row = t.get_row_heading() + row
print_row(row)
if __name__ == "__main__":
# create the top-level parser
parser = argparse.ArgumentParser(prog='compiles_table.py')
subparsers = parser.add_subparsers(help='sub-command to execute', required=True, dest="cmd")
# create the parser for the "check" command
parser_a = subparsers.add_parser('check', help='run cargo check on specified targets and update csv cache')
parser_a.add_argument("targets", metavar="TARGET", type=str, nargs='+', choices=["all"]+TARGETS,
help="target-triple to check, as shown by 'rustup target list'")
# create the parser for the "render" command
parser_b = subparsers.add_parser('render', help='print a markdown table to stdout')
parser_b.add_argument("--equidistant", action="store_true",
help="NOT IMPLEMENTED: render each column with an equal width (in plaintext)")
args = parser.parse_args()
if args.cmd == "render":
table, targets, bins = load_csv(CACHE_PATH)
render_md(sys.stdout, table, bins, targets)
if args.cmd == "check":
targets = get_targets(args.targets)
bins = get_all_bins()
assert(all(map(Target.is_installed, targets)))
table = test_all_targets(targets, bins)
prev_table, _, _ = load_csv(CACHE_PATH)
new_table = merge_tables(prev_table, table)
save_csv(CACHE_PATH, new_table)

View file

@ -23,6 +23,7 @@
import glob
import os
import re
# -- General configuration ------------------------------------------------
@ -55,11 +56,14 @@ author = 'uutils developers'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# * take version from project "Cargo.toml"
version_file = open(os.path.join("..","Cargo.toml"), "r")
version_file_content = version_file.read()
v = re.search("^\s*version\s*=\s*\"([0-9.]+)\"", version_file_content, re.IGNORECASE | re.MULTILINE)
# The short X.Y version.
version = '0.0.1'
version = v.groups()[0]
# The full version, including alpha/beta/rc tags.
release = '0.0.1'
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -179,6 +183,3 @@ texinfo_documents = [
author, 'uutils', 'A cross-platform reimplementation of GNU coreutils in Rust.',
'Miscellaneous'),
]

View file

@ -5,9 +5,6 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
extern crate textwrap;
extern crate uucore;
use std::cmp;
use std::collections::hash_map::HashMap;
use std::ffi::OsString;
@ -22,10 +19,10 @@ include!(concat!(env!("OUT_DIR"), "/uutils_map.rs"));
fn usage<T>(utils: &UtilityMap<T>, name: &str) {
println!("{} {} (multi-call binary)\n", name, VERSION);
println!("Usage: {} [function [arguments...]]\n", name);
println!("Currently defined functions/utilities:\n");
println!("Currently defined functions:\n");
#[allow(clippy::map_clone)]
let mut utils: Vec<&str> = utils.keys().map(|&s| s).collect();
utils.sort();
utils.sort_unstable();
let display_list = utils.join(", ");
let width = cmp::min(textwrap::termwidth(), 100) - 4 * 2; // (opinion/heuristic) max 100 chars wide with 4 character side indentions
println!(

View file

@ -1,6 +1,6 @@
[package]
name = "uu_arch"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "arch ~ (uutils) display machine architecture"
@ -15,9 +15,9 @@ edition = "2018"
path = "src/arch.rs"
[dependencies]
platform-info = "0.0.1"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
platform-info = "0.1"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "arch"

View file

@ -6,7 +6,6 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
extern crate platform_info;
#[macro_use]
extern crate uucore;

View file

@ -1,6 +1,6 @@
[package]
name = "uu_base32"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "base32 ~ (uutils) decode/encode input (base32-encoding)"
@ -15,8 +15,8 @@ edition = "2018"
path = "src/base32.rs"
[dependencies]
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features = ["encoding"] }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "base32"

View file

@ -8,7 +8,7 @@
// that was distributed with this source code.
use std::fs::File;
use std::io::{stdin, BufReader, Read};
use std::io::{stdin, stdout, BufReader, Read, Write};
use std::path::Path;
use uucore::encoding::{wrap_print, Data, Format};
@ -85,7 +85,12 @@ fn handle_input<R: Read>(
wrap_print(&data, encoded);
} else {
match data.decode() {
Ok(s) => print!("{}", String::from_utf8(s).unwrap()),
Ok(s) => {
if stdout().write_all(&s).is_err() {
// on windows console, writing invalid utf8 returns an error
crash!(1, "Cannot write non-utf8 data");
}
}
Err(_) => crash!(1, "invalid input"),
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_base64"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "base64 ~ (uutils) decode/encode input (base64-encoding)"
@ -15,8 +15,8 @@ edition = "2018"
path = "src/base64.rs"
[dependencies]
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features = ["encoding"] }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "base64"

View file

@ -8,7 +8,7 @@
// that was distributed with this source code.
use std::fs::File;
use std::io::{stdin, BufReader, Read};
use std::io::{stdin, stdout, BufReader, Read, Write};
use std::path::Path;
use uucore::encoding::{wrap_print, Data, Format};
@ -85,7 +85,12 @@ fn handle_input<R: Read>(
wrap_print(&data, encoded);
} else {
match data.decode() {
Ok(s) => print!("{}", String::from_utf8(s).unwrap()),
Ok(s) => {
if stdout().write_all(&s).is_err() {
// on windows console, writing invalid utf8 returns an error
crash!(1, "Cannot write non-utf8 data");
}
}
Err(_) => crash!(1, "invalid input"),
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_basename"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "basename ~ (uutils) display PATHNAME with leading directory components removed"
@ -15,8 +15,8 @@ edition = "2018"
path = "src/basename.rs"
[dependencies]
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "basename"

View file

@ -1,6 +1,6 @@
[package]
name = "uu_cat"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "cat ~ (uutils) concatenate and display input"
@ -15,9 +15,10 @@ edition = "2018"
path = "src/cat.rs"
[dependencies]
clap = "2.33"
quick-error = "1.2.3"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["fs"] }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(unix)'.dependencies]
unix_socket = "0.5.0"

View file

@ -17,6 +17,7 @@ extern crate unix_socket;
extern crate uucore;
// last synced with: cat (GNU coreutils) 8.13
use clap::{App, Arg};
use quick_error::ResultExt;
use std::fs::{metadata, File};
use std::io::{self, stderr, stdin, stdout, BufWriter, Read, Write};
@ -30,10 +31,11 @@ use std::os::unix::fs::FileTypeExt;
#[cfg(unix)]
use unix_socket::UnixStream;
static NAME: &str = "cat";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SYNTAX: &str = "[OPTION]... [FILE]...";
static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output
With no FILE, or when FILE is -, read standard input.";
static LONG_HELP: &str = "";
#[derive(PartialEq)]
enum NumberingMode {
@ -124,50 +126,122 @@ enum InputType {
type CatResult<T> = Result<T, CatError>;
mod options {
pub static FILE: &str = "file";
pub static SHOW_ALL: &str = "show-all";
pub static NUMBER_NONBLANK: &str = "number-nonblank";
pub static SHOW_NONPRINTING_ENDS: &str = "e";
pub static SHOW_ENDS: &str = "show-ends";
pub static NUMBER: &str = "number";
pub static SQUEEZE_BLANK: &str = "squeeze-blank";
pub static SHOW_NONPRINTING_TABS: &str = "t";
pub static SHOW_TABS: &str = "show-tabs";
pub static SHOW_NONPRINTING: &str = "show-nonprinting";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optflag("A", "show-all", "equivalent to -vET")
.optflag(
"b",
"number-nonblank",
"number nonempty output lines, overrides -n",
let matches = App::new(executable!())
.name(NAME)
.version(VERSION)
.usage(SYNTAX)
.about(SUMMARY)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.arg(
Arg::with_name(options::SHOW_ALL)
.short("A")
.long(options::SHOW_ALL)
.help("equivalent to -vET"),
)
.optflag("e", "", "equivalent to -vE")
.optflag("E", "show-ends", "display $ at end of each line")
.optflag("n", "number", "number all output lines")
.optflag("s", "squeeze-blank", "suppress repeated empty output lines")
.optflag("t", "", "equivalent to -vT")
.optflag("T", "show-tabs", "display TAB characters as ^I")
.optflag(
"v",
"show-nonprinting",
"use ^ and M- notation, except for LF (\\n) and TAB (\\t)",
.arg(
Arg::with_name(options::NUMBER_NONBLANK)
.short("b")
.long(options::NUMBER_NONBLANK)
.help("number nonempty output lines, overrides -n")
.overrides_with(options::NUMBER),
)
.parse(args);
.arg(
Arg::with_name(options::SHOW_NONPRINTING_ENDS)
.short("e")
.help("equivalent to -vE"),
)
.arg(
Arg::with_name(options::SHOW_ENDS)
.short("E")
.long(options::SHOW_ENDS)
.help("display $ at end of each line"),
)
.arg(
Arg::with_name(options::NUMBER)
.short("n")
.long(options::NUMBER)
.help("number all output lines"),
)
.arg(
Arg::with_name(options::SQUEEZE_BLANK)
.short("s")
.long(options::SQUEEZE_BLANK)
.help("suppress repeated empty output lines"),
)
.arg(
Arg::with_name(options::SHOW_NONPRINTING_TABS)
.short("t")
.long(options::SHOW_NONPRINTING_TABS)
.help("equivalent to -vT"),
)
.arg(
Arg::with_name(options::SHOW_TABS)
.short("T")
.long(options::SHOW_TABS)
.help("display TAB characters at ^I"),
)
.arg(
Arg::with_name(options::SHOW_NONPRINTING)
.short("v")
.long(options::SHOW_NONPRINTING)
.help("use ^ and M- notation, except for LF (\\n) and TAB (\\t)"),
)
.get_matches_from(args);
let number_mode = if matches.opt_present("b") {
let number_mode = if matches.is_present(options::NUMBER_NONBLANK) {
NumberingMode::NonEmpty
} else if matches.opt_present("n") {
} else if matches.is_present(options::NUMBER) {
NumberingMode::All
} else {
NumberingMode::None
};
let show_nonprint = matches.opts_present(&[
"A".to_owned(),
"e".to_owned(),
"t".to_owned(),
"v".to_owned(),
]);
let show_ends = matches.opts_present(&["E".to_owned(), "A".to_owned(), "e".to_owned()]);
let show_tabs = matches.opts_present(&["A".to_owned(), "T".to_owned(), "t".to_owned()]);
let squeeze_blank = matches.opt_present("s");
let mut files = matches.free;
if files.is_empty() {
files.push("-".to_owned());
}
let show_nonprint = vec![
options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(),
options::SHOW_NONPRINTING.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let show_ends = vec![
options::SHOW_ENDS.to_owned(),
options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let show_tabs = vec![
options::SHOW_ALL.to_owned(),
options::SHOW_TABS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(),
]
.iter()
.any(|v| matches.is_present(v));
let squeeze_blank = matches.is_present(options::SQUEEZE_BLANK);
let files: Vec<String> = match matches.values_of(options::FILE) {
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
None => vec!["-".to_owned()],
};
let can_write_fast = !(show_tabs
|| show_nonprint
@ -361,7 +435,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
}
writer.write_all(options.end_of_line.as_bytes())?;
if handle.is_interactive {
writer.flush().context(&file[..])?;
writer.flush().context(file)?;
}
}
state.at_line_start = true;

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chgrp"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chgrp ~ (uutils) change the group ownership of FILE"
@ -15,9 +15,9 @@ edition = "2018"
path = "src/chgrp.rs"
[dependencies]
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["entries", "fs"] }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
walkdir = "2.2.8"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"
[[bin]]
name = "chgrp"

View file

@ -11,23 +11,18 @@
extern crate uucore;
pub use uucore::entries;
use uucore::fs::resolve_relative_path;
use uucore::libc::{self, gid_t, lchown};
use uucore::libc::gid_t;
use uucore::perms::{wrap_chgrp, Verbosity};
extern crate walkdir;
use walkdir::WalkDir;
use std::io::Error as IOError;
use std::io::Result as IOResult;
use std::fs;
use std::fs::Metadata;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
static SYNTAX: &str =
"chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE...";
static SUMMARY: &str = "Change the group of each FILE to GROUP.";
@ -165,14 +160,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
executor.exec()
}
#[derive(PartialEq, Debug)]
enum Verbosity {
Silent,
Changes,
Verbose,
Normal,
}
struct Chgrper {
dest_gid: gid_t,
bit_flag: u8,
@ -201,23 +188,6 @@ impl Chgrper {
ret
}
fn chgrp<P: AsRef<Path>>(&self, path: P, dgid: gid_t, follow: bool) -> IOResult<()> {
let path = path.as_ref();
let s = CString::new(path.as_os_str().as_bytes()).unwrap();
let ret = unsafe {
if follow {
libc::chown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid)
} else {
lchown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid)
}
};
if ret == 0 {
Ok(())
} else {
Err(IOError::last_os_error())
}
}
#[cfg(windows)]
fn is_bind_root<P: AsRef<Path>>(&self, root: P) -> bool {
// TODO: is there an equivalent on Windows?
@ -269,7 +239,24 @@ impl Chgrper {
}
}
let ret = self.wrap_chgrp(path, &meta, follow_arg);
let ret = match wrap_chgrp(
path,
&meta,
self.dest_gid,
follow_arg,
self.verbosity.clone(),
) {
Ok(n) => {
show_info!("{}", n);
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_info!("{}", e);
}
1
}
};
if !self.recursive {
ret
@ -297,8 +284,22 @@ impl Chgrper {
}
};
ret = self.wrap_chgrp(path, &meta, follow);
ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) {
Ok(n) => {
if n != "" {
show_info!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_info!("{}", e);
}
1
}
}
}
ret
}
@ -324,50 +325,4 @@ impl Chgrper {
};
Some(meta)
}
fn wrap_chgrp<P: AsRef<Path>>(&self, path: P, meta: &Metadata, follow: bool) -> i32 {
use self::Verbosity::*;
let mut ret = 0;
let dest_gid = self.dest_gid;
let path = path.as_ref();
if let Err(e) = self.chgrp(path, dest_gid, follow) {
match self.verbosity {
Silent => (),
_ => {
show_info!("changing group of '{}': {}", path.display(), e);
if self.verbosity == Verbose {
println!(
"failed to change group of {} from {} to {}",
path.display(),
entries::gid2grp(meta.gid()).unwrap(),
entries::gid2grp(dest_gid).unwrap()
);
};
}
}
ret = 1;
} else {
let changed = dest_gid != meta.gid();
if changed {
match self.verbosity {
Changes | Verbose => {
println!(
"changed group of {} from {} to {}",
path.display(),
entries::gid2grp(meta.gid()).unwrap(),
entries::gid2grp(dest_gid).unwrap()
);
}
_ => (),
};
} else if self.verbosity == Verbose {
println!(
"group of {} retained as {}",
path.display(),
entries::gid2grp(dest_gid).unwrap()
);
}
}
ret
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chmod"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chmod ~ (uutils) change mode of FILE"
@ -15,10 +15,11 @@ edition = "2018"
path = "src/chmod.rs"
[dependencies]
clap = "2.33.3"
libc = "0.2.42"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["fs", "mode"] }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
walker = "1.0.0"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"
[[bin]]
name = "chmod"

View file

@ -7,130 +7,185 @@
// spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's
#[cfg(unix)]
extern crate libc;
extern crate walker;
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
use uucore::fs::display_permissions_unix;
#[cfg(not(windows))]
use uucore::mode;
use walker::Walker;
use walkdir::WalkDir;
const NAME: &str = "chmod";
static SUMMARY: &str = "Change the mode of each FILE to MODE.
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Change the mode of each FILE to MODE.
With --reference, change the mode of each FILE to that of RFILE.";
static LONG_HELP: &str = "
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.
";
mod options {
pub const CHANGES: &str = "changes";
pub const QUIET: &str = "quiet"; // visible_alias("silent")
pub const VERBOSE: &str = "verbose";
pub const NO_PRESERVE_ROOT: &str = "no-preserve-root";
pub const PRESERVE_ROOT: &str = "preserve-root";
pub const REFERENCE: &str = "RFILE";
pub const RECURSIVE: &str = "recursive";
pub const MODE: &str = "MODE";
pub const FILE: &str = "FILE";
}
fn get_usage() -> String {
format!(
"{0} [OPTION]... MODE[,MODE]... FILE...
or: {0} [OPTION]... OCTAL-MODE FILE...
or: {0} [OPTION]... --reference=RFILE FILE...",
executable!()
)
}
fn get_long_usage() -> String {
String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.")
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let mut args = args.collect_str();
let syntax = format!(
"[OPTION]... MODE[,MODE]... FILE...
{0} [OPTION]... OCTAL-MODE FILE...
{0} [OPTION]... --reference=RFILE FILE...",
NAME
);
let mut opts = app!(&syntax, SUMMARY, LONG_HELP);
opts.optflag(
"c",
"changes",
"like verbose but report only when a change is made",
)
// TODO: support --silent (can be done using clap)
.optflag("f", "quiet", "suppress most error messages")
.optflag(
"v",
"verbose",
"output a diagnostic for every file processed",
)
.optflag(
"",
"no-preserve-root",
"do not treat '/' specially (the default)",
)
.optflag("", "preserve-root", "fail to operate recursively on '/'")
.optopt(
"",
"reference",
"use RFILE's mode instead of MODE values",
"RFILE",
)
.optflag("R", "recursive", "change files and directories recursively");
// Before we can parse 'args' with clap (and previously getopts),
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
let mode_had_minus_prefix = strip_minus_from_mode(&mut args);
// sanitize input for - at beginning (e.g. chmod -x test_file). Remove
// the option and save it for later, after parsing is finished.
let negative_option = sanitize_input(&mut args);
let usage = get_usage();
let after_help = get_long_usage();
let mut matches = opts.parse(args);
if matches.free.is_empty() {
show_error!("missing an argument");
show_error!("for help, try '{} --help'", NAME);
return 1;
} else {
let changes = matches.opt_present("changes");
let quiet = matches.opt_present("quiet");
let verbose = matches.opt_present("verbose");
let preserve_root = matches.opt_present("preserve-root");
let recursive = matches.opt_present("recursive");
let fmode = matches
.opt_str("reference")
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.after_help(&after_help[..])
.arg(
Arg::with_name(options::CHANGES)
.long(options::CHANGES)
.short("c")
.help("like verbose but report only when a change is made"),
)
.arg(
Arg::with_name(options::QUIET)
.long(options::QUIET)
.visible_alias("silent")
.short("f")
.help("suppress most error messages"),
)
.arg(
Arg::with_name(options::VERBOSE)
.long(options::VERBOSE)
.short("v")
.help("output a diagnostic for every file processed"),
)
.arg(
Arg::with_name(options::NO_PRESERVE_ROOT)
.long(options::NO_PRESERVE_ROOT)
.help("do not treat '/' specially (the default)"),
)
.arg(
Arg::with_name(options::PRESERVE_ROOT)
.long(options::PRESERVE_ROOT)
.help("fail to operate recursively on '/'"),
)
.arg(
Arg::with_name(options::RECURSIVE)
.long(options::RECURSIVE)
.short("R")
.help("change files and directories recursively"),
)
.arg(
Arg::with_name(options::REFERENCE)
.long("reference")
.takes_value(true)
.help("use RFILE's mode instead of MODE values"),
)
.arg(
Arg::with_name(options::MODE)
.required_unless(options::REFERENCE)
.takes_value(true),
// It would be nice if clap could parse with delimeter, e.g. "g-x,u+x",
// however .multiple(true) cannot be used here because FILE already needs that.
// Only one positional argument with .multiple(true) set is allowed per command
)
.arg(
Arg::with_name(options::FILE)
.required_unless(options::MODE)
.multiple(true),
)
.get_matches_from(args);
let changes = matches.is_present(options::CHANGES);
let quiet = matches.is_present(options::QUIET);
let verbose = matches.is_present(options::VERBOSE);
let preserve_root = matches.is_present(options::PRESERVE_ROOT);
let recursive = matches.is_present(options::RECURSIVE);
let fmode =
matches
.value_of(options::REFERENCE)
.and_then(|ref fref| match fs::metadata(fref) {
Ok(meta) => Some(meta.mode()),
Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err),
});
let cmode = if fmode.is_none() {
// If there was a negative option, now it's a good time to
// use it.
if negative_option.is_some() {
negative_option
} else {
Some(matches.free.remove(0))
}
} else {
None
};
let chmoder = Chmoder {
changes,
quiet,
verbose,
preserve_root,
recursive,
fmode,
cmode,
};
match chmoder.chmod(matches.free) {
Ok(()) => {}
Err(e) => return e,
}
let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
let mut cmode = if mode_had_minus_prefix {
// clap parsing is finished, now put prefix back
Some(format!("-{}", modes))
} else {
Some(modes.to_string())
};
let mut files: Vec<String> = matches
.values_of(options::FILE)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
if fmode.is_some() {
// "--reference" and MODE are mutually exclusive
// if "--reference" was used MODE needs to be interpreted as another FILE
// it wasn't possible to implement this behavior directly with clap
files.push(cmode.unwrap());
cmode = None;
}
let chmoder = Chmoder {
changes,
quiet,
verbose,
preserve_root,
recursive,
fmode,
cmode,
};
match chmoder.chmod(files) {
Ok(()) => {}
Err(e) => return e,
}
0
}
fn sanitize_input(args: &mut Vec<String>) -> Option<String> {
// Iterate 'args' and delete the first occurrence
// of a prefix '-' if it's associated with MODE
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
for i in 0..args.len() {
let first = args[i].chars().next().unwrap();
if first != '-' {
continue;
}
if let Some(second) = args[i].chars().nth(1) {
match second {
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
return Some(args.remove(i));
if args[i].starts_with("-") {
if let Some(second) = args[i].chars().nth(1) {
match second {
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
// TODO: use strip_prefix() once minimum rust version reaches 1.45.0
args[i] = args[i][1..args[i].len()].to_string();
return true;
}
_ => {}
}
_ => {}
}
}
}
None
false
}
struct Chmoder {
@ -150,72 +205,67 @@ impl Chmoder {
for filename in &files {
let filename = &filename[..];
let file = Path::new(filename);
if file.exists() {
if file.is_dir() {
if !self.preserve_root || filename != "/" {
if self.recursive {
let walk_dir = match Walker::new(&file) {
Ok(m) => m,
Err(f) => {
crash!(1, "{}", f.to_string());
}
};
// XXX: here (and elsewhere) we see that this impl will have issues
// with non-UTF-8 filenames. Using OsString won't fix this because
// on Windows OsStrings cannot be built out of non-UTF-8 chars. One
// possible fix is to use CStrings rather than Strings in the args
// to chmod() and chmod_file().
r = self
.chmod(
walk_dir
.filter_map(|x| match x {
Ok(o) => match o.path().into_os_string().to_str() {
Some(s) => Some(s.to_owned()),
None => None,
},
Err(_) => None,
})
.collect(),
)
.and(r);
r = self.chmod_file(&file, filename).and(r);
}
} else {
show_error!("could not change permissions of directory '{}'", filename);
r = Err(1);
if !file.exists() {
if is_symlink(file) {
println!(
"failed to change mode of '{}' from 0000 (---------) to 0000 (---------)",
filename
);
if !self.quiet {
show_error!("cannot operate on dangling symlink '{}'", filename);
}
} else {
r = self.chmod_file(&file, filename).and(r);
show_error!("cannot access '{}': No such file or directory", filename);
}
return Err(1);
}
if self.recursive && self.preserve_root && filename == "/" {
show_error!(
"it is dangerous to operate recursively on '{}'\nuse --no-preserve-root to override this failsafe",
filename
);
return Err(1);
}
if !self.recursive {
r = self.chmod_file(&file).and(r);
} else {
show_error!("no such file or directory '{}'", filename);
r = Err(1);
for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) {
let file = entry.path();
r = self.chmod_file(&file).and(r);
}
}
}
r
}
#[cfg(windows)]
fn chmod_file(&self, file: &Path, name: &str) -> Result<(), i32> {
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
// chmod is useless on Windows
// it doesn't set any permissions at all
// instead it just sets the readonly attribute on the file
Err(0)
}
#[cfg(any(unix, target_os = "redox"))]
fn chmod_file(&self, file: &Path, name: &str) -> Result<(), i32> {
let mut fperm = match fs::metadata(name) {
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
let mut fperm = match fs::metadata(file) {
Ok(meta) => meta.mode() & 0o7777,
Err(err) => {
if !self.quiet {
show_error!("{}", err);
if is_symlink(file) {
if self.verbose {
println!(
"neither symbolic link '{}' nor referent has been changed",
file.display()
);
}
return Ok(());
} else {
show_error!("{}: '{}'", err, file.display());
}
return Err(1);
}
};
match self.fmode {
Some(mode) => self.change_file(fperm, mode, file, name)?,
Some(mode) => self.change_file(fperm, mode, file)?,
None => {
let cmode_unwrapped = self.cmode.clone().unwrap();
for mode in cmode_unwrapped.split(',') {
@ -228,7 +278,7 @@ impl Chmoder {
};
match result {
Ok(mode) => {
self.change_file(fperm, mode, file, name)?;
self.change_file(fperm, mode, file)?;
fperm = mode;
}
Err(f) => {
@ -246,20 +296,18 @@ impl Chmoder {
}
#[cfg(unix)]
fn change_file(&self, fperm: u32, mode: u32, file: &Path, path: &str) -> Result<(), i32> {
fn change_file(&self, fperm: u32, mode: u32, file: &Path) -> Result<(), i32> {
if fperm == mode {
if self.verbose && !self.changes {
show_info!(
"mode of '{}' retained as {:o} ({})",
println!(
"mode of '{}' retained as {:04o} ({})",
file.display(),
fperm,
display_permissions_unix(fperm)
display_permissions_unix(fperm),
);
}
Ok(())
} else if let Err(err) =
fs::set_permissions(Path::new(path), fs::Permissions::from_mode(mode))
{
} else if let Err(err) = fs::set_permissions(file, fs::Permissions::from_mode(mode)) {
if !self.quiet {
show_error!("{}", err);
}
@ -289,3 +337,10 @@ impl Chmoder {
}
}
}
pub fn is_symlink<P: AsRef<Path>>(path: P) -> bool {
match fs::symlink_metadata(path) {
Ok(m) => m.file_type().is_symlink(),
Err(_) => false,
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chown"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chown ~ (uutils) change the ownership of FILE"
@ -15,9 +15,10 @@ edition = "2018"
path = "src/chown.rs"
[dependencies]
clap = "2.33"
glob = "0.3.0"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["entries", "fs"] }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"
[[bin]]

View file

@ -11,94 +11,188 @@
extern crate uucore;
pub use uucore::entries::{self, Group, Locate, Passwd};
use uucore::fs::resolve_relative_path;
use uucore::libc::{self, gid_t, lchown, uid_t};
use uucore::libc::{gid_t, uid_t};
use uucore::perms::{wrap_chown, Verbosity};
use clap::{App, Arg};
extern crate walkdir;
use walkdir::WalkDir;
use std::fs::{self, Metadata};
use std::os::unix::fs::MetadataExt;
use std::io;
use std::io::Result as IOResult;
use std::convert::AsRef;
use std::path::Path;
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
static ABOUT: &str = "change file owner and group";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SYNTAX: &str =
"[OPTION]... [OWNER][:[GROUP]] FILE...\n chown [OPTION]... --reference=RFILE FILE...";
static SUMMARY: &str = "change file owner and group";
pub mod options {
pub mod verbosity {
pub static CHANGES: &str = "changes";
pub static QUIET: &str = "quiet";
pub static SILENT: &str = "silent";
pub static VERBOSE: &str = "verbose";
}
pub mod preserve_root {
pub static PRESERVE: &str = "preserve-root";
pub static NO_PRESERVE: &str = "no-preserve-root";
}
pub mod dereference {
pub static DEREFERENCE: &str = "dereference";
pub static NO_DEREFERENCE: &str = "no-dereference";
}
pub static FROM: &str = "from";
pub static RECURSIVE: &str = "recursive";
pub mod traverse {
pub static TRAVERSE: &str = "H";
pub static NO_TRAVERSE: &str = "P";
pub static EVERY: &str = "L";
}
pub static REFERENCE: &str = "reference";
}
static ARG_OWNER: &str = "owner";
static ARG_FILES: &str = "files";
const FTS_COMFOLLOW: u8 = 1;
const FTS_PHYSICAL: u8 = 1 << 1;
const FTS_LOGICAL: u8 = 1 << 2;
fn get_usage() -> String {
format!(
"{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...",
executable!()
)
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let mut opts = app!(SYNTAX, SUMMARY, "");
opts.optflag("c",
"changes",
"like verbose but report only when a change is made")
.optflag("f", "silent", "")
.optflag("", "quiet", "suppress most error messages")
.optflag("v",
"verbose",
"output a diagnostic for every file processed")
.optflag("", "dereference", "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself")
.optflag("h", "no-dereference", "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)")
let usage = get_usage();
.optopt("", "from", "change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute", "CURRENT_OWNER:CURRENT_GROUP")
.optopt("",
"reference",
"use RFILE's owner and group rather than specifying OWNER:GROUP values",
"RFILE")
.optflag("",
"no-preserve-root",
"do not treat '/' specially (the default)")
.optflag("", "preserve-root", "fail to operate recursively on '/'")
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(options::verbosity::CHANGES)
.short("c")
.long(options::verbosity::CHANGES)
.help("like verbose but report only when a change is made"),
)
.arg(Arg::with_name(options::dereference::DEREFERENCE).long(options::dereference::DEREFERENCE).help(
"affect the referent of each symbolic link (this is the default), rather than the symbolic link itself",
))
.arg(
Arg::with_name(options::dereference::NO_DEREFERENCE)
.short("h")
.long(options::dereference::NO_DEREFERENCE)
.help(
"affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)",
),
)
.arg(
Arg::with_name(options::FROM)
.long(options::FROM)
.help(
"change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute",
)
.value_name("CURRENT_OWNER:CURRENT_GROUP"),
)
.arg(
Arg::with_name(options::preserve_root::PRESERVE)
.long(options::preserve_root::PRESERVE)
.help("fail to operate recursively on '/'"),
)
.arg(
Arg::with_name(options::preserve_root::NO_PRESERVE)
.long(options::preserve_root::NO_PRESERVE)
.help("do not treat '/' specially (the default)"),
)
.arg(
Arg::with_name(options::verbosity::QUIET)
.long(options::verbosity::QUIET)
.help("suppress most error messages"),
)
.arg(
Arg::with_name(options::RECURSIVE)
.short("R")
.long(options::RECURSIVE)
.help("operate on files and directories recursively"),
)
.arg(
Arg::with_name(options::REFERENCE)
.long(options::REFERENCE)
.help("use RFILE's owner and group rather than specifying OWNER:GROUP values")
.value_name("RFILE")
.min_values(1),
)
.arg(Arg::with_name(options::verbosity::SILENT).short("f").long(options::verbosity::SILENT))
.arg(
Arg::with_name(options::traverse::TRAVERSE)
.short(options::traverse::TRAVERSE)
.help("if a command line argument is a symbolic link to a directory, traverse it")
.overrides_with_all(&[options::traverse::EVERY, options::traverse::NO_TRAVERSE]),
)
.arg(
Arg::with_name(options::traverse::EVERY)
.short(options::traverse::EVERY)
.help("traverse every symbolic link to a directory encountered")
.overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::NO_TRAVERSE]),
)
.arg(
Arg::with_name(options::traverse::NO_TRAVERSE)
.short(options::traverse::NO_TRAVERSE)
.help("do not traverse any symbolic links (default)")
.overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]),
)
.arg(
Arg::with_name(options::verbosity::VERBOSE)
.long(options::verbosity::VERBOSE)
.help("output a diagnostic for every file processed"),
)
.arg(
Arg::with_name(ARG_OWNER)
.multiple(false)
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name(ARG_FILES)
.multiple(true)
.takes_value(true)
.required(true)
.min_values(1),
)
.get_matches_from(args);
.optflag("R",
"recursive",
"operate on files and directories recursively")
.optflag("H",
"",
"if a command line argument is a symbolic link to a directory, traverse it")
.optflag("L",
"",
"traverse every symbolic link to a directory encountered")
.optflag("P", "", "do not traverse any symbolic links (default)");
/* First arg is the owner/group */
let owner = matches.value_of(ARG_OWNER).unwrap();
let mut bit_flag = FTS_PHYSICAL;
let mut preserve_root = false;
let mut derefer = -1;
let flags: &[char] = &['H', 'L', 'P'];
for opt in &args {
match opt.as_str() {
// If more than one is specified, only the final one takes effect.
s if s.contains(flags) => {
if let Some(idx) = s.rfind(flags) {
match s.chars().nth(idx).unwrap() {
'H' => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL,
'L' => bit_flag = FTS_LOGICAL,
'P' => bit_flag = FTS_PHYSICAL,
_ => (),
}
}
}
"--no-preserve-root" => preserve_root = false,
"--preserve-root" => preserve_root = true,
"--dereference" => derefer = 1,
"--no-dereference" => derefer = 0,
_ => (),
}
}
/* Then the list of files */
let files: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let matches = opts.parse(args);
let recursive = matches.opt_present("recursive");
let preserve_root = matches.is_present(options::preserve_root::PRESERVE);
let mut derefer = if matches.is_present(options::dereference::NO_DEREFERENCE) {
1
} else {
0
};
let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) {
FTS_COMFOLLOW | FTS_PHYSICAL
} else if matches.is_present(options::traverse::EVERY) {
FTS_LOGICAL
} else {
FTS_PHYSICAL
};
let recursive = matches.is_present(options::RECURSIVE);
if recursive {
if bit_flag == FTS_PHYSICAL {
if derefer == 1 {
@ -111,17 +205,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
bit_flag = FTS_PHYSICAL;
}
let verbosity = if matches.opt_present("changes") {
let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes
} else if matches.opt_present("silent") || matches.opt_present("quiet") {
} else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET)
{
Verbosity::Silent
} else if matches.opt_present("verbose") {
} else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose
} else {
Verbosity::Normal
};
let filter = if let Some(spec) = matches.opt_str("from") {
let filter = if let Some(spec) = matches.value_of(options::FROM) {
match parse_spec(&spec) {
Ok((Some(uid), None)) => IfFrom::User(uid),
Ok((None, Some(gid))) => IfFrom::Group(gid),
@ -136,18 +232,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
IfFrom::All
};
if matches.free.is_empty() {
show_usage_error!("missing operand");
return 1;
} else if matches.free.len() < 2 && !matches.opt_present("reference") {
show_usage_error!("missing operand after {}", matches.free[0]);
return 1;
}
let mut files;
let dest_uid: Option<u32>;
let dest_gid: Option<u32>;
if let Some(file) = matches.opt_str("reference") {
if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) {
Ok(meta) => {
dest_gid = Some(meta.gid());
@ -158,9 +245,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
return 1;
}
}
files = matches.free;
} else {
match parse_spec(&matches.free[0]) {
match parse_spec(&owner) {
Ok((u, g)) => {
dest_uid = u;
dest_gid = g;
@ -170,8 +256,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
return 1;
}
}
files = matches.free;
files.remove(0);
}
let executor = Chowner {
bit_flag,
@ -197,7 +281,7 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
Ok((
Some(match Passwd::locate(args[0]) {
Ok(v) => v.uid(),
_ => return Err(format!("invalid user: {}", spec)),
_ => return Err(format!("invalid user: '{}'", spec)),
}),
None,
))
@ -206,18 +290,18 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
None,
Some(match Group::locate(args[1]) {
Ok(v) => v.gid(),
_ => return Err(format!("invalid group: {}", spec)),
_ => return Err(format!("invalid group: '{}'", spec)),
}),
))
} else if usr_grp {
Ok((
Some(match Passwd::locate(args[0]) {
Ok(v) => v.uid(),
_ => return Err(format!("invalid user: {}", spec)),
_ => return Err(format!("invalid user: '{}'", spec)),
}),
Some(match Group::locate(args[1]) {
Ok(v) => v.gid(),
_ => return Err(format!("invalid group: {}", spec)),
_ => return Err(format!("invalid group: '{}'", spec)),
}),
))
} else {
@ -225,14 +309,6 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
}
}
#[derive(PartialEq, Debug)]
enum Verbosity {
Silent,
Changes,
Verbose,
Normal,
}
enum IfFrom {
All,
User(u32),
@ -270,29 +346,6 @@ impl Chowner {
ret
}
fn chown<P: AsRef<Path>>(
&self,
path: P,
duid: uid_t,
dgid: gid_t,
follow: bool,
) -> IOResult<()> {
let path = path.as_ref();
let s = CString::new(path.as_os_str().as_bytes()).unwrap();
let ret = unsafe {
if follow {
libc::chown(s.as_ptr(), duid, dgid)
} else {
lchown(s.as_ptr(), duid, dgid)
}
};
if ret == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 {
let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL;
let path = root.as_ref();
@ -329,7 +382,27 @@ impl Chowner {
}
let ret = if self.matched(meta.uid(), meta.gid()) {
self.wrap_chown(path, &meta, follow_arg)
match wrap_chown(
path,
&meta,
self.dest_uid,
self.dest_gid,
follow_arg,
self.verbosity.clone(),
) {
Ok(n) => {
if n != "" {
show_info!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_info!("{}", e);
}
1
}
}
} else {
0
};
@ -364,7 +437,27 @@ impl Chowner {
continue;
}
ret = self.wrap_chown(path, &meta, follow);
ret = match wrap_chown(
path,
&meta,
self.dest_uid,
self.dest_gid,
follow,
self.verbosity.clone(),
) {
Ok(n) => {
if n != "" {
show_info!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_info!("{}", e);
}
1
}
}
}
ret
}
@ -392,58 +485,6 @@ impl Chowner {
Some(meta)
}
fn wrap_chown<P: AsRef<Path>>(&self, path: P, meta: &Metadata, follow: bool) -> i32 {
use self::Verbosity::*;
let mut ret = 0;
let dest_uid = self.dest_uid.unwrap_or_else(|| meta.uid());
let dest_gid = self.dest_gid.unwrap_or_else(|| meta.gid());
let path = path.as_ref();
if let Err(e) = self.chown(path, dest_uid, dest_gid, follow) {
match self.verbosity {
Silent => (),
_ => {
show_info!("changing ownership of '{}': {}", path.display(), e);
if self.verbosity == Verbose {
println!(
"failed to change ownership of {} from {}:{} to {}:{}",
path.display(),
entries::uid2usr(meta.uid()).unwrap(),
entries::gid2grp(meta.gid()).unwrap(),
entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap()
);
};
}
}
ret = 1;
} else {
let changed = dest_uid != meta.uid() || dest_gid != meta.gid();
if changed {
match self.verbosity {
Changes | Verbose => {
println!(
"changed ownership of {} from {}:{} to {}:{}",
path.display(),
entries::uid2usr(meta.uid()).unwrap(),
entries::gid2grp(meta.gid()).unwrap(),
entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap()
);
}
_ => (),
};
} else if self.verbosity == Verbose {
println!(
"ownership of {} retained as {}:{}",
path.display(),
entries::uid2usr(dest_uid).unwrap(),
entries::gid2grp(dest_gid).unwrap()
);
}
}
ret
}
#[inline]
fn matched(&self, uid: uid_t, gid: gid_t) -> bool {
match self.filter {

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chroot"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chroot ~ (uutils) run COMMAND under a new root directory"
@ -15,9 +15,9 @@ edition = "2018"
path = "src/chroot.rs"
[dependencies]
getopts = "0.2.18"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["entries"] }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
clap= "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "chroot"

View file

@ -8,65 +8,83 @@
// spell-checker:ignore (ToDO) NEWROOT Userspec pstatus
extern crate getopts;
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::ffi::CString;
use std::io::Error;
use std::path::Path;
use std::process::Command;
use uucore::entries;
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
use std::ffi::CString;
use std::io::Error;
use std::iter::FromIterator;
use std::path::Path;
use std::process::Command;
static VERSION: &str = env!("CARGO_PKG_VERSION");
static NAME: &str = "chroot";
static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT.";
static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]";
static SUMMARY: &str = "Run COMMAND with root directory set to NEWROOT.";
static LONG_HELP: &str = "
If COMMAND is not specified, it defaults to '$(SHELL) -i'.
If $(SHELL) is not set, /bin/sh is used.
";
mod options {
pub const NEWROOT: &str = "newroot";
pub const USER: &str = "user";
pub const GROUP: &str = "group";
pub const GROUPS: &str = "groups";
pub const USERSPEC: &str = "userspec";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optopt(
"u",
"user",
"User (ID or name) to switch before running the program",
"USER",
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(SYNTAX)
.arg(Arg::with_name(options::NEWROOT).hidden(true).required(true))
.arg(
Arg::with_name(options::USER)
.short("u")
.long(options::USER)
.help("User (ID or name) to switch before running the program")
.value_name("USER"),
)
.optopt("g", "group", "Group (ID or name) to switch to", "GROUP")
.optopt(
"G",
"groups",
"Comma-separated list of groups to switch to",
"GROUP1,GROUP2...",
.arg(
Arg::with_name(options::GROUP)
.short("g")
.long(options::GROUP)
.help("Group (ID or name) to switch to")
.value_name("GROUP"),
)
.optopt(
"",
"userspec",
"Colon-separated user and group to switch to. \
Same as -u USER -g GROUP. \
Userspec has higher preference than -u and/or -g",
"USER:GROUP",
.arg(
Arg::with_name(options::GROUPS)
.short("G")
.long(options::GROUPS)
.help("Comma-separated list of groups to switch to")
.value_name("GROUP1,GROUP2..."),
)
.parse(args);
if matches.free.is_empty() {
println!("Missing operand: NEWROOT");
println!("Try `{} --help` for more information.", NAME);
return 1;
}
.arg(
Arg::with_name(options::USERSPEC)
.long(options::USERSPEC)
.help(
"Colon-separated user and group to switch to. \
Same as -u USER -g GROUP. \
Userspec has higher preference than -u and/or -g",
)
.value_name("USER:GROUP"),
)
.get_matches_from(args);
let default_shell: &'static str = "/bin/sh";
let default_option: &'static str = "-i";
let user_shell = std::env::var("SHELL");
let newroot = Path::new(&matches.free[0][..]);
let newroot: &Path = match matches.value_of(options::NEWROOT) {
Some(v) => Path::new(v),
None => crash!(
1,
"Missing operand: NEWROOT\nTry '{} --help' for more information.",
NAME
),
};
if !newroot.is_dir() {
crash!(
1,
@ -75,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
);
}
let command: Vec<&str> = match matches.free.len() {
let command: Vec<&str> = match matches.args.len() {
1 => {
let shell: &str = match user_shell {
Err(_) => default_shell,
@ -83,7 +101,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
};
vec![shell, default_option]
}
_ => matches.free[1..].iter().map(|x| &x[..]).collect(),
_ => {
let mut vector: Vec<&str> = Vec::new();
for (&k, v) in matches.args.iter() {
vector.push(k.clone());
vector.push(&v.vals[0].to_str().unwrap());
}
vector
}
};
set_context(&newroot, &matches);
@ -96,37 +121,30 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if pstatus.success() {
0
} else {
match pstatus.code() {
Some(i) => i,
None => -1,
}
pstatus.code().unwrap_or(-1)
}
}
fn set_context(root: &Path, options: &getopts::Matches) {
let userspec_str = options.opt_str("userspec");
let user_str = options.opt_str("user").unwrap_or_default();
let group_str = options.opt_str("group").unwrap_or_default();
let groups_str = options.opt_str("groups").unwrap_or_default();
fn set_context(root: &Path, options: &clap::ArgMatches) {
let userspec_str = options.value_of(options::USERSPEC);
let user_str = options.value_of(options::USER).unwrap_or_default();
let group_str = options.value_of(options::GROUP).unwrap_or_default();
let groups_str = options.value_of(options::GROUPS).unwrap_or_default();
let userspec = match userspec_str {
Some(ref u) => {
let s: Vec<&str> = u.split(':').collect();
if s.len() != 2 {
if s.len() != 2 || s.iter().any(|&spec| spec == "") {
crash!(1, "invalid userspec: `{}`", u)
};
s
}
None => Vec::new(),
};
let user = if userspec.is_empty() {
&user_str[..]
let (user, group) = if userspec.is_empty() {
(&user_str[..], &group_str[..])
} else {
&userspec[0][..]
};
let group = if userspec.is_empty() {
&group_str[..]
} else {
&userspec[1][..]
(&userspec[0][..], &userspec[1][..])
};
enter_chroot(root);
@ -170,7 +188,7 @@ fn set_main_group(group: &str) {
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
fn set_groups(groups: Vec<libc::gid_t>) -> libc::c_int {
unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) }
}
@ -182,11 +200,13 @@ fn set_groups(groups: Vec<libc::gid_t>) -> libc::c_int {
fn set_groups_from_str(groups: &str) {
if !groups.is_empty() {
let groups_vec: Vec<libc::gid_t> =
FromIterator::from_iter(groups.split(',').map(|x| match entries::grp2gid(x) {
let groups_vec: Vec<libc::gid_t> = groups
.split(',')
.map(|x| match entries::grp2gid(x) {
Ok(g) => g,
_ => crash!(1, "no such group: {}", x),
}));
})
.collect();
let err = set_groups(groups_vec);
if err != 0 {
crash!(1, "cannot set groups: {}", Error::last_os_error())

View file

@ -1,6 +1,6 @@
[package]
name = "uu_cksum"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "cksum ~ (uutils) display CRC and size of input"
@ -15,9 +15,10 @@ edition = "2018"
path = "src/cksum.rs"
[dependencies]
clap = "2.33"
libc = "0.2.42"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "cksum"

View file

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

View file

@ -10,15 +10,107 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs::File;
use std::io::{self, stdin, BufReader, Read};
use std::path::Path;
include!(concat!(env!("OUT_DIR"), "/crc_table.rs"));
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
const CRC_TABLE_LEN: usize = 256;
const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table();
static SYNTAX: &str = "[OPTIONS] [FILE]...";
static SUMMARY: &str = "Print CRC and size for each file";
static LONG_HELP: &str = "";
const VERSION: &str = env!("CARGO_PKG_VERSION");
const NAME: &str = "cksum";
const SYNTAX: &str = "[OPTIONS] [FILE]...";
const SUMMARY: &str = "Print CRC and size for each file";
// this is basically a hack to get "loops" to work on Rust 1.33. Once we update to Rust 1.46 or
// greater, we can just use while loops
macro_rules! unroll {
(256, |$i:ident| $s:expr) => {{
unroll!(@ 32, 0 * 32, $i, $s);
unroll!(@ 32, 1 * 32, $i, $s);
unroll!(@ 32, 2 * 32, $i, $s);
unroll!(@ 32, 3 * 32, $i, $s);
unroll!(@ 32, 4 * 32, $i, $s);
unroll!(@ 32, 5 * 32, $i, $s);
unroll!(@ 32, 6 * 32, $i, $s);
unroll!(@ 32, 7 * 32, $i, $s);
}};
(8, |$i:ident| $s:expr) => {{
unroll!(@ 8, 0, $i, $s);
}};
(@ 32, $start:expr, $i:ident, $s:expr) => {{
unroll!(@ 8, $start + 0 * 8, $i, $s);
unroll!(@ 8, $start + 1 * 8, $i, $s);
unroll!(@ 8, $start + 2 * 8, $i, $s);
unroll!(@ 8, $start + 3 * 8, $i, $s);
}};
(@ 8, $start:expr, $i:ident, $s:expr) => {{
unroll!(@ 4, $start, $i, $s);
unroll!(@ 4, $start + 4, $i, $s);
}};
(@ 4, $start:expr, $i:ident, $s:expr) => {{
unroll!(@ 2, $start, $i, $s);
unroll!(@ 2, $start + 2, $i, $s);
}};
(@ 2, $start:expr, $i:ident, $s:expr) => {{
unroll!(@ 1, $start, $i, $s);
unroll!(@ 1, $start + 1, $i, $s);
}};
(@ 1, $start:expr, $i:ident, $s:expr) => {{
let $i = $start;
let _ = $s;
}};
}
const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] {
let mut table = [0; CRC_TABLE_LEN];
// NOTE: works on Rust 1.46
//let mut i = 0;
//while i < CRC_TABLE_LEN {
// table[i] = crc_entry(i as u8) as u32;
//
// i += 1;
//}
unroll!(256, |i| {
table[i] = crc_entry(i as u8) as u32;
});
table
}
const fn crc_entry(input: u8) -> u32 {
let mut crc = (input as u32) << 24;
// NOTE: this does not work on Rust 1.33, but *does* on 1.46
//let mut i = 0;
//while i < 8 {
// if crc & 0x8000_0000 != 0 {
// crc <<= 1;
// crc ^= 0x04c1_1db7;
// } else {
// crc <<= 1;
// }
//
// i += 1;
//}
unroll!(8, |_i| {
let if_cond = crc & 0x8000_0000;
let if_body = (crc << 1) ^ 0x04c1_1db7;
let else_body = crc << 1;
// NOTE: i feel like this is easier to understand than emulating an if statement in bitwise
// ops
let cond_table = [else_body, if_body];
crc = cond_table[(if_cond != 0) as usize];
});
crc
}
#[inline]
fn crc_update(crc: u32, input: u8) -> u32 {
@ -48,7 +140,20 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
let mut rd: Box<dyn Read> = match fname {
"-" => Box::new(stdin()),
_ => {
file = File::open(&Path::new(fname))?;
let path = &Path::new(fname);
if path.is_dir() {
return Err(std::io::Error::new(
io::ErrorKind::InvalidInput,
"Is a directory",
));
};
if path.metadata().is_err() {
return Err(std::io::Error::new(
io::ErrorKind::NotFound,
"No such file or directory",
));
};
file = File::open(&path)?;
Box::new(BufReader::new(file))
}
};
@ -68,13 +173,27 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
Err(err) => return Err(err),
}
}
//Ok((0 as u32,0 as usize))
}
mod options {
pub static FILE: &str = "file";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str());
let args = args.collect_str();
let files = matches.free;
let matches = App::new(executable!())
.name(NAME)
.version(VERSION)
.about(SUMMARY)
.usage(SYNTAX)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.get_matches_from(args);
let files: Vec<String> = match matches.values_of(options::FILE) {
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
None => vec![],
};
if files.is_empty() {
match cksum("-") {

View file

@ -1,6 +1,6 @@
[package]
name = "uu_comm"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "comm ~ (uutils) compare sorted inputs"
@ -15,10 +15,10 @@ edition = "2018"
path = "src/comm.rs"
[dependencies]
getopts = "0.2.18"
clap = "2.33"
libc = "0.2.42"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "comm"

View file

@ -7,8 +7,6 @@
// spell-checker:ignore (ToDO) delim mkdelim
extern crate getopts;
#[macro_use]
extern crate uucore;
@ -17,21 +15,34 @@ use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Stdin};
use std::path::Path;
static SYNTAX: &str = "[OPTIONS] FILE1 FILE2";
static SUMMARY: &str = "Compare sorted files line by line";
use clap::{App, Arg, ArgMatches};
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "compare two sorted files line by line";
static LONG_HELP: &str = "";
fn mkdelim(col: usize, opts: &getopts::Matches) -> String {
let mut s = String::new();
let delim = match opts.opt_str("output-delimiter") {
Some(d) => d,
None => "\t".to_owned(),
};
mod options {
pub const COLUMN_1: &str = "1";
pub const COLUMN_2: &str = "2";
pub const COLUMN_3: &str = "3";
pub const DELIMITER: &str = "output-delimiter";
pub const DELIMITER_DEFAULT: &str = "\t";
pub const FILE_1: &str = "FILE1";
pub const FILE_2: &str = "FILE2";
}
if col > 1 && !opts.opt_present("1") {
fn get_usage() -> String {
format!("{} [OPTION]... FILE1 FILE2", executable!())
}
fn mkdelim(col: usize, opts: &ArgMatches) -> String {
let mut s = String::new();
let delim = opts.value_of(options::DELIMITER).unwrap();
if col > 1 && !opts.is_present(options::COLUMN_1) {
s.push_str(delim.as_ref());
}
if col > 2 && !opts.opt_present("2") {
if col > 2 && !opts.is_present(options::COLUMN_2) {
s.push_str(delim.as_ref());
}
@ -41,7 +52,7 @@ fn mkdelim(col: usize, opts: &getopts::Matches) -> String {
fn ensure_nl(line: &mut String) {
match line.chars().last() {
Some('\n') => (),
_ => line.push_str("\n"),
_ => line.push('\n'),
}
}
@ -59,7 +70,7 @@ impl LineReader {
}
}
fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
let delim: Vec<String> = (0..4).map(|col| mkdelim(col, opts)).collect();
let ra = &mut String::new();
@ -82,7 +93,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
match ord {
Ordering::Less => {
if !opts.opt_present("1") {
if !opts.is_present(options::COLUMN_1) {
ensure_nl(ra);
print!("{}{}", delim[1], ra);
}
@ -90,7 +101,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
na = a.read_line(ra);
}
Ordering::Greater => {
if !opts.opt_present("2") {
if !opts.is_present(options::COLUMN_2) {
ensure_nl(rb);
print!("{}{}", delim[2], rb);
}
@ -98,7 +109,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
nb = b.read_line(rb);
}
Ordering::Equal => {
if !opts.opt_present("3") {
if !opts.is_present(options::COLUMN_3) {
ensure_nl(ra);
print!("{}{}", delim[3], ra);
}
@ -122,21 +133,42 @@ fn open_file(name: &str) -> io::Result<LineReader> {
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let usage = get_usage();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optflag("1", "", "suppress column 1 (lines uniq to FILE1)")
.optflag("2", "", "suppress column 2 (lines uniq to FILE2)")
.optflag(
"3",
"",
"suppress column 3 (lines that appear in both files)",
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::COLUMN_1)
.short(options::COLUMN_1)
.help("suppress column 1 (lines unique to FILE1)"),
)
.optopt("", "output-delimiter", "separate columns with STR", "STR")
.parse(args);
.arg(
Arg::with_name(options::COLUMN_2)
.short(options::COLUMN_2)
.help("suppress column 2 (lines unique to FILE2)"),
)
.arg(
Arg::with_name(options::COLUMN_3)
.short(options::COLUMN_3)
.help("suppress column 3 (lines that appear in both files)"),
)
.arg(
Arg::with_name(options::DELIMITER)
.long(options::DELIMITER)
.help("separate columns with STR")
.value_name("STR")
.default_value(options::DELIMITER_DEFAULT)
.hide_default_value(true),
)
.arg(Arg::with_name(options::FILE_1).required(true))
.arg(Arg::with_name(options::FILE_2).required(true))
.get_matches_from(args);
let mut f1 = open_file(matches.free[0].as_ref()).unwrap();
let mut f2 = open_file(matches.free[1].as_ref()).unwrap();
let mut f1 = open_file(matches.value_of(options::FILE_1).unwrap()).unwrap();
let mut f2 = open_file(matches.value_of(options::FILE_2).unwrap()).unwrap();
comm(&mut f1, &mut f2, &matches);

View file

@ -1,6 +1,6 @@
[package]
name = "uu_cp"
version = "0.0.1"
version = "0.0.6"
authors = [
"Jordy Dickinson <jordy.dickinson@gmail.com>",
"Joshua S. Miller <jsmiller@uchicago.edu>",
@ -19,13 +19,13 @@ edition = "2018"
path = "src/cp.rs"
[dependencies]
clap = "2.32"
clap = "2.33"
filetime = "0.2"
libc = "0.2.42"
libc = "0.2.85"
quick-error = "1.2.3"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["fs"] }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
walkdir = "2.2.8"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"
[target.'cfg(target_os = "linux")'.dependencies]
ioctl-sys = "0.5.2"

View file

@ -10,22 +10,14 @@
// spell-checker:ignore (ToDO) ficlone linkgs lstat nlink nlinks pathbuf reflink strs xattrs
extern crate clap;
extern crate filetime;
#[cfg(target_os = "linux")]
#[macro_use]
extern crate ioctl_sys;
extern crate libc;
#[macro_use]
extern crate quick_error;
#[macro_use]
extern crate uucore;
extern crate walkdir;
#[cfg(unix)]
extern crate xattr;
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
use winapi::um::fileapi::CreateFileW;
#[cfg(windows)]
@ -43,7 +35,6 @@ use std::ffi::CString;
#[cfg(windows)]
use std::ffi::OsStr;
use std::fs;
#[cfg(target_os = "linux")]
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
@ -121,7 +112,7 @@ macro_rules! or_continue(
})
);
/// Prompts the user yes/no and returns `true` they if successfully
/// Prompts the user yes/no and returns `true` if they successfully
/// answered yes.
macro_rules! prompt_yes(
($($args:tt)+) => ({
@ -216,6 +207,7 @@ pub struct Options {
one_file_system: bool,
overwrite: OverwriteMode,
parents: bool,
strip_trailing_slashes: bool,
reflink: bool,
reflink_mode: ReflinkMode,
preserve_attributes: Vec<Attribute>,
@ -231,11 +223,6 @@ static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
static EXIT_OK: i32 = 0;
static EXIT_ERR: i32 = 1;
/// Prints the version
fn print_version() {
println!("{} {}", executable!(), VERSION);
}
fn get_usage() -> String {
format!(
"{0} [OPTION]... [-T] SOURCE DEST
@ -262,6 +249,7 @@ static OPT_NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs
static OPT_NO_PRESERVE: &str = "no-preserve";
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory";
static OPT_ONE_FILE_SYSTEM: &str = "one-file-system";
static OPT_PARENT: &str = "parent";
static OPT_PARENTS: &str = "parents";
static OPT_PATHS: &str = "paths";
static OPT_PRESERVE: &str = "preserve";
@ -277,7 +265,6 @@ static OPT_SYMBOLIC_LINK: &str = "symbolic-link";
static OPT_TARGET_DIRECTORY: &str = "target-directory";
static OPT_UPDATE: &str = "update";
static OPT_VERBOSE: &str = "verbose";
static OPT_VERSION: &str = "version";
#[cfg(unix)]
static PRESERVABLE_ATTRIBUTES: &[&str] = &[
@ -325,10 +312,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(OPT_NO_TARGET_DIRECTORY)
.conflicts_with(OPT_TARGET_DIRECTORY)
.help("Treat DEST as a regular file and not a directory"))
.arg(Arg::with_name(OPT_VERSION)
.short("V")
.long(OPT_VERSION)
.help("output version information and exit"))
.arg(Arg::with_name(OPT_INTERACTIVE)
.short("i")
.long(OPT_INTERACTIVE)
@ -347,10 +330,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg(Arg::with_name(OPT_RECURSIVE)
.short("r")
.long(OPT_RECURSIVE)
.help("copy directories recursively"))
// --archive sets this option
.help("copy directories recursively"))
.arg(Arg::with_name(OPT_RECURSIVE_ALIAS)
.short("R")
.help("same as -r"))
.arg(Arg::with_name(OPT_STRIP_TRAILING_SLASHES)
.long(OPT_STRIP_TRAILING_SLASHES)
.help("remove any trailing slashes from each SOURCE argument"))
.arg(Arg::with_name(OPT_VERBOSE)
.short("v")
.long(OPT_VERBOSE)
@ -405,7 +392,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.use_delimiter(true)
.possible_values(PRESERVABLE_ATTRIBUTES)
.value_name("ATTR_LIST")
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE, OPT_ARCHIVE])
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE])
// -d sets this option
// --archive sets this option
.help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\
if possible additional attributes: context, links, xattr, all"))
.arg(Arg::with_name(OPT_PRESERVE_DEFAULT_ATTRIBUTES)
@ -419,45 +408,44 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.value_name("ATTR_LIST")
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_ARCHIVE])
.help("don't preserve the specified attributes"))
.arg(Arg::with_name(OPT_PARENTS)
.long(OPT_PARENTS)
.alias(OPT_PARENT)
.help("use full source file name under DIRECTORY"))
.arg(Arg::with_name(OPT_NO_DEREFERENCE)
.short("-P")
.long(OPT_NO_DEREFERENCE)
.conflicts_with(OPT_DEREFERENCE)
// -d sets this option
.help("never follow symbolic links in SOURCE"))
// TODO: implement the following args
.arg(Arg::with_name(OPT_ARCHIVE)
.short("a")
.long(OPT_ARCHIVE)
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE])
.help("NotImplemented: same as -dR --preserve=all"))
.arg(Arg::with_name(OPT_COPY_CONTENTS)
.long(OPT_COPY_CONTENTS)
.conflicts_with(OPT_ATTRIBUTES_ONLY)
.help("NotImplemented: copy contents of special files when recursive"))
.arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
.short("d")
.help("NotImplemented: same as --no-dereference --preserve=links"))
.arg(Arg::with_name(OPT_DEREFERENCE)
.short("L")
.long(OPT_DEREFERENCE)
.conflicts_with(OPT_NO_DEREFERENCE)
.help("NotImplemented: always follow symbolic links in SOURCE"))
.arg(Arg::with_name(OPT_PARENTS)
.long(OPT_PARENTS)
.help("NotImplemented: use full source file name under DIRECTORY"))
.help("always follow symbolic links in SOURCE"))
.arg(Arg::with_name(OPT_ARCHIVE)
.short("a")
.long(OPT_ARCHIVE)
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE])
.help("Same as -dR --preserve=all"))
.arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
.short("d")
.help("same as --no-dereference --preserve=links"))
.arg(Arg::with_name(OPT_ONE_FILE_SYSTEM)
.short("x")
.long(OPT_ONE_FILE_SYSTEM)
.help("stay on this file system"))
// TODO: implement the following args
.arg(Arg::with_name(OPT_COPY_CONTENTS)
.long(OPT_COPY_CONTENTS)
.conflicts_with(OPT_ATTRIBUTES_ONLY)
.help("NotImplemented: copy contents of special files when recursive"))
.arg(Arg::with_name(OPT_SPARSE)
.long(OPT_SPARSE)
.takes_value(true)
.value_name("WHEN")
.help("NotImplemented: control creation of sparse files. See below"))
.arg(Arg::with_name(OPT_STRIP_TRAILING_SLASHES)
.long(OPT_STRIP_TRAILING_SLASHES)
.help("NotImplemented: remove any trailing slashes from each SOURCE argument"))
.arg(Arg::with_name(OPT_ONE_FILE_SYSTEM)
.short("x")
.long(OPT_ONE_FILE_SYSTEM)
.help("NotImplemented: stay on this file system"))
.arg(Arg::with_name(OPT_CONTEXT)
.long(OPT_CONTEXT)
.takes_value(true)
@ -472,14 +460,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.multiple(true))
.get_matches_from(args);
if matches.is_present(OPT_VERSION) {
print_version();
return EXIT_OK;
}
let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches));
let paths: Vec<String> = matches
.values_of("paths")
.values_of(OPT_PATHS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
@ -557,22 +540,30 @@ impl FromStr for Attribute {
return Err(Error::InvalidArgument(format!(
"invalid attribute '{}'",
value
)))
)));
}
})
}
}
fn add_all_attributes() -> Vec<Attribute> {
let mut attr = Vec::new();
#[cfg(unix)]
attr.push(Attribute::Mode);
attr.push(Attribute::Ownership);
attr.push(Attribute::Timestamps);
attr.push(Attribute::Context);
attr.push(Attribute::Xattr);
attr.push(Attribute::Links);
attr
}
impl Options {
fn from_matches(matches: &ArgMatches) -> CopyResult<Options> {
let not_implemented_opts = vec![
OPT_ARCHIVE,
OPT_COPY_CONTENTS,
OPT_NO_DEREFERENCE_PRESERVE_LINKS,
OPT_DEREFERENCE,
OPT_PARENTS,
OPT_SPARSE,
OPT_STRIP_TRAILING_SLASHES,
#[cfg(not(any(windows, unix)))]
OPT_ONE_FILE_SYSTEM,
OPT_CONTEXT,
#[cfg(windows)]
@ -605,13 +596,7 @@ impl Options {
let mut attributes = Vec::new();
for attribute_str in attribute_strs {
if attribute_str == "all" {
#[cfg(unix)]
attributes.push(Attribute::Mode);
attributes.push(Attribute::Ownership);
attributes.push(Attribute::Timestamps);
attributes.push(Attribute::Context);
attributes.push(Attribute::Xattr);
attributes.push(Attribute::Links);
attributes = add_all_attributes();
break;
} else {
attributes.push(Attribute::from_str(attribute_str)?);
@ -620,6 +605,11 @@ impl Options {
attributes
}
}
} else if matches.is_present(OPT_ARCHIVE) {
// --archive is used. Same as --preserve=all
add_all_attributes()
} else if matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) {
vec![Attribute::Links]
} else if matches.is_present(OPT_PRESERVE_DEFAULT_ATTRIBUTES) {
DEFAULT_ATTRIBUTES.to_vec()
} else {
@ -631,13 +621,17 @@ impl Options {
copy_contents: matches.is_present(OPT_COPY_CONTENTS),
copy_mode: CopyMode::from_matches(matches),
dereference: matches.is_present(OPT_DEREFERENCE),
no_dereference: matches.is_present(OPT_NO_DEREFERENCE),
// No dereference is set with -p, -d and --archive
no_dereference: matches.is_present(OPT_NO_DEREFERENCE)
|| matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
|| matches.is_present(OPT_ARCHIVE),
one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM),
overwrite: OverwriteMode::from_matches(matches),
parents: matches.is_present(OPT_PARENTS),
backup_suffix: matches.value_of(OPT_SUFFIX).unwrap().to_string(),
update: matches.is_present(OPT_UPDATE),
verbose: matches.is_present(OPT_VERBOSE),
strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES),
reflink: matches.is_present(OPT_REFLINK),
reflink_mode: {
if let Some(reflink) = matches.value_of(OPT_REFLINK) {
@ -648,7 +642,7 @@ impl Options {
return Err(Error::InvalidArgument(format!(
"invalid argument '{}' for \'reflink\'",
value
)))
)));
}
}
} else {
@ -695,7 +689,7 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
return Err(format!("extra operand {:?}", paths[2]).into());
}
let (sources, target) = match options.target_dir {
let (mut sources, target) = match options.target_dir {
Some(ref target) => {
// All path args are sources, and the target dir was
// specified separately
@ -709,6 +703,12 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
}
};
if options.strip_trailing_slashes {
for source in sources.iter_mut() {
*source = source.components().as_path().to_owned()
}
}
Ok((sources, target))
}
@ -812,13 +812,19 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()
let dest = construct_dest_path(source, target, &target_type, options)?;
preserve_hardlinks(&mut hard_links, source, dest, &mut found_hard_link).unwrap();
}
if !found_hard_link {
if let Err(error) = copy_source(source, target, &target_type, options) {
show_error!("{}", error);
match error {
Error::Skipped(_) => (),
_ => non_fatal_errors = true,
// When using --no-clobber, we don't want to show
// an error message
Error::NotAllFilesCopied => (),
Error::Skipped(_) => {
show_error!("{}", error);
}
_ => {
show_error!("{}", error);
non_fatal_errors = true
}
}
}
}
@ -846,9 +852,17 @@ fn construct_dest_path(
.into());
}
if options.parents && !target.is_dir() {
return Err("with --parents, the destination must be a directory".into());
}
Ok(match *target_type {
TargetType::Directory => {
let root = source_path.parent().unwrap_or(source_path);
let root = if options.parents {
Path::new("")
} else {
source_path.parent().unwrap_or(source_path)
};
localize_to_target(root, source_path, target)?
}
TargetType::File => target.to_path_buf(),
@ -924,10 +938,10 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
#[cfg(any(windows, target_os = "redox"))]
let mut hard_links: Vec<(String, u64)> = vec![];
for path in WalkDir::new(root) {
for path in WalkDir::new(root).same_file_system(options.one_file_system) {
let p = or_continue!(path);
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
let path = if options.no_dereference && is_symlink {
let path = if (options.no_dereference || options.dereference) && is_symlink {
// we are dealing with a symlink. Don't follow it
match env::current_dir() {
Ok(cwd) => cwd.join(resolve_relative_path(p.path())),
@ -941,7 +955,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
Some(parent) => {
#[cfg(windows)]
{
// On Windows, some pathes are starting with \\?
// On Windows, some paths are starting with \\?
// but not always, so, make sure that we are consistent for strip_prefix
// See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file for more info
let parent_can = adjust_canonicalization(parent);
@ -968,7 +982,19 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
let dest = local_to_target.as_path().to_path_buf();
preserve_hardlinks(&mut hard_links, &source, dest, &mut found_hard_link).unwrap();
if !found_hard_link {
copy_file(path.as_path(), local_to_target.as_path(), options)?;
match copy_file(path.as_path(), local_to_target.as_path(), options) {
Ok(_) => Ok(()),
Err(err) => {
if fs::symlink_metadata(&source)?.file_type().is_symlink() {
// silent the error with a symlink
// In case we do --archive, we might copy the symlink
// before the file itself
Ok(())
} else {
Err(err)
}
}
}?;
}
} else {
copy_file(path.as_path(), local_to_target.as_path(), options)?;
@ -982,11 +1008,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
impl OverwriteMode {
fn verify(&self, path: &Path) -> CopyResult<()> {
match *self {
OverwriteMode::NoClobber => Err(Error::Skipped(format!(
"Not overwriting {} because of option '{}'",
path.display(),
OPT_NO_CLOBBER
))),
OverwriteMode::NoClobber => Err(Error::NotAllFilesCopied),
OverwriteMode::Interactive(_) => {
if prompt_yes!("{}: overwrite {}? ", executable!(), path.display()) {
Ok(())
@ -1041,12 +1063,16 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
}
}
};
Ok(())
}
#[cfg(not(windows))]
fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
Ok(std::os::unix::fs::symlink(source, dest).context(context)?)
match std::os::unix::fs::symlink(source, dest).context(context) {
Ok(_) => Ok(()),
Err(_) => Ok(()),
}
}
#[cfg(windows)]
@ -1159,11 +1185,9 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
.unwrap();
}
};
for attribute in &options.preserve_attributes {
copy_attribute(source, dest, attribute)?;
}
Ok(())
}
@ -1224,7 +1248,16 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
dest.into()
};
symlink_file(&link, &dest, &*context_for(&link, &dest))?;
} else if source.to_string_lossy() == "/dev/null" {
/* workaround a limitation of fs::copy
* https://github.com/rust-lang/rust/issues/79390
*/
File::create(dest)?;
} else {
if options.parents {
let parent = dest.parent().unwrap_or(dest);
fs::create_dir_all(parent)?;
}
fs::copy(source, dest).context(&*context_for(source, dest))?;
}

27
src/uu/csplit/Cargo.toml Normal file
View file

@ -0,0 +1,27 @@
[package]
name = "uu_csplit"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output"
homepage = "https://github.com/uutils/coreutils"
repository = "https://github.com/uutils/coreutils/tree/master/src/uu/ls"
keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"]
edition = "2018"
[lib]
path = "src/csplit.rs"
[dependencies]
clap = "2.33"
thiserror = "1.0"
regex = "1.0.0"
glob = "0.2.11"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "csplit"
path = "src/main.rs"

798
src/uu/csplit/src/csplit.rs Normal file
View file

@ -0,0 +1,798 @@
#![crate_name = "uu_csplit"]
#[macro_use]
extern crate uucore;
use clap::{App, Arg, ArgMatches};
use regex::Regex;
use std::cmp::Ordering;
use std::io::{self, BufReader};
use std::{
fs::{remove_file, File},
io::{BufRead, BufWriter, Write},
};
mod csplit_error;
mod patterns;
mod splitname;
use crate::csplit_error::CsplitError;
use crate::splitname::SplitName;
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "split a file into sections determined by context lines";
static LONG_HELP: &str = "Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output.";
mod options {
pub const SUFFIX_FORMAT: &str = "suffix-format";
pub const SUPPRESS_MATCHED: &str = "suppress-matched";
pub const DIGITS: &str = "digits";
pub const PREFIX: &str = "prefix";
pub const KEEP_FILES: &str = "keep-files";
pub const QUIET: &str = "quiet";
pub const ELIDE_EMPTY_FILES: &str = "elide-empty-files";
pub const FILE: &str = "file";
pub const PATTERN: &str = "pattern";
}
fn get_usage() -> String {
format!("{0} [OPTION]... FILE PATTERN...", executable!())
}
/// Command line options for csplit.
pub struct CsplitOptions {
split_name: crate::SplitName,
keep_files: bool,
quiet: bool,
elide_empty_files: bool,
suppress_matched: bool,
}
impl CsplitOptions {
fn new(matches: &ArgMatches) -> CsplitOptions {
let keep_files = matches.is_present(options::KEEP_FILES);
let quiet = matches.is_present(options::QUIET);
let elide_empty_files = matches.is_present(options::ELIDE_EMPTY_FILES);
let suppress_matched = matches.is_present(options::SUPPRESS_MATCHED);
CsplitOptions {
split_name: crash_if_err!(
1,
SplitName::new(
matches.value_of(options::PREFIX).map(str::to_string),
matches.value_of(options::SUFFIX_FORMAT).map(str::to_string),
matches.value_of(options::DIGITS).map(str::to_string)
)
),
keep_files,
quiet,
elide_empty_files,
suppress_matched,
}
}
}
/// Splits a file into severals according to the command line patterns.
///
/// # Errors
///
/// - [`io::Error`] if there is some problem reading/writing from/to a file.
/// - [`::CsplitError::LineOutOfRange`] if the linenum pattern is larger than the number of input
/// lines.
/// - [`::CsplitError::LineOutOfRangeOnRepetition`], like previous but after applying the pattern
/// more than once.
/// - [`::CsplitError::MatchNotFound`] if no line matched a regular expression.
/// - [`::CsplitError::MatchNotFoundOnRepetition`], like previous but after applying the pattern
/// more than once.
pub fn csplit<T>(
options: &CsplitOptions,
patterns: Vec<patterns::Pattern>,
input: T,
) -> Result<(), CsplitError>
where
T: BufRead,
{
let mut input_iter = InputSplitter::new(input.lines().enumerate());
let mut split_writer = SplitWriter::new(&options);
let ret = do_csplit(&mut split_writer, patterns, &mut input_iter);
// consume the rest
input_iter.rewind_buffer();
if let Some((_, line)) = input_iter.next() {
split_writer.new_writer()?;
split_writer.writeln(line?)?;
for (_, line) in input_iter {
split_writer.writeln(line?)?;
}
split_writer.finish_split();
}
// delete files on error by default
if ret.is_err() && !options.keep_files {
split_writer.delete_all_splits()?;
}
ret
}
fn do_csplit<I>(
split_writer: &mut SplitWriter,
patterns: Vec<patterns::Pattern>,
input_iter: &mut InputSplitter<I>,
) -> Result<(), CsplitError>
where
I: Iterator<Item = (usize, io::Result<String>)>,
{
// split the file based on patterns
for pattern in patterns.into_iter() {
let pattern_as_str = pattern.to_string();
#[allow(clippy::match_like_matches_macro)]
let is_skip = if let patterns::Pattern::SkipToMatch(_, _, _) = pattern {
true
} else {
false
};
match pattern {
patterns::Pattern::UpToLine(n, ex) => {
let mut up_to_line = n;
for (_, ith) in ex.iter() {
split_writer.new_writer()?;
match split_writer.do_to_line(&pattern_as_str, up_to_line, input_iter) {
// the error happened when applying the pattern more than once
Err(CsplitError::LineOutOfRange(_)) if ith != 1 => {
return Err(CsplitError::LineOutOfRangeOnRepetition(
pattern_as_str.to_string(),
ith - 1,
));
}
Err(err) => return Err(err),
// continue the splitting process
Ok(()) => (),
}
up_to_line += n;
}
}
patterns::Pattern::UpToMatch(regex, offset, ex)
| patterns::Pattern::SkipToMatch(regex, offset, ex) => {
for (max, ith) in ex.iter() {
if is_skip {
// when skipping a part of the input, no writer is created
split_writer.as_dev_null();
} else {
split_writer.new_writer()?;
}
match (
split_writer.do_to_match(&pattern_as_str, &regex, offset, input_iter),
max,
) {
// in case of ::pattern::ExecutePattern::Always, then it's fine not to find a
// matching line
(Err(CsplitError::MatchNotFound(_)), None) => {
return Ok(());
}
// the error happened when applying the pattern more than once
(Err(CsplitError::MatchNotFound(_)), Some(m)) if m != 1 && ith != 1 => {
return Err(CsplitError::MatchNotFoundOnRepetition(
pattern_as_str.to_string(),
ith - 1,
));
}
(Err(err), _) => return Err(err),
// continue the splitting process
(Ok(()), _) => (),
};
}
}
};
}
Ok(())
}
/// Write a portion of the input file into a split which filename is based on an incrementing
/// counter.
struct SplitWriter<'a> {
/// the options set through the command line
options: &'a CsplitOptions,
/// a split counter
counter: usize,
/// the writer to the current split
current_writer: Option<BufWriter<File>>,
/// the size in bytes of the current split
size: usize,
/// flag to indicate that no content should be written to a split
dev_null: bool,
}
impl<'a> Drop for SplitWriter<'a> {
fn drop(&mut self) {
if self.options.elide_empty_files && self.size == 0 {
let file_name = self.options.split_name.get(self.counter);
remove_file(file_name).expect("Failed to elide split");
}
}
}
impl<'a> SplitWriter<'a> {
fn new(options: &CsplitOptions) -> SplitWriter {
SplitWriter {
options,
counter: 0,
current_writer: None,
size: 0,
dev_null: false,
}
}
/// Creates a new split and returns its filename.
///
/// # Errors
///
/// The creation of the split file may fail with some [`io::Error`].
fn new_writer(&mut self) -> io::Result<()> {
let file_name = self.options.split_name.get(self.counter);
let file = File::create(&file_name)?;
self.current_writer = Some(BufWriter::new(file));
self.counter += 1;
self.size = 0;
self.dev_null = false;
Ok(())
}
/// The current split will not keep any of the read input lines.
fn as_dev_null(&mut self) {
self.dev_null = true;
}
/// Writes the line to the current split, appending a newline character.
/// If [`dev_null`] is true, then the line is discarded.
///
/// # Errors
///
/// Some [`io::Error`] may occur when attempting to write the line.
fn writeln(&mut self, line: String) -> io::Result<()> {
if !self.dev_null {
match self.current_writer {
Some(ref mut current_writer) => {
let bytes = line.as_bytes();
current_writer.write_all(bytes)?;
current_writer.write_all(b"\n")?;
self.size += bytes.len() + 1;
}
None => panic!("trying to write to a split that was not created"),
}
}
Ok(())
}
/// Perform some operations after completing a split, i.e., either remove it
/// if the [`::ELIDE_EMPTY_FILES_OPT`] option is enabled, or print how much bytes were written
/// to it if [`::QUIET_OPT`] is disabled.
///
/// # Errors
///
/// Some [`io::Error`] if the split could not be removed in case it should be elided.
fn finish_split(&mut self) {
if !self.dev_null {
if self.options.elide_empty_files && self.size == 0 {
self.counter -= 1;
} else if !self.options.quiet {
println!("{}", self.size);
}
}
}
/// Removes all the split files that were created.
///
/// # Errors
///
/// Returns an [`io::Error`] if there was a problem removing a split.
fn delete_all_splits(&self) -> io::Result<()> {
let mut ret = Ok(());
for ith in 0..self.counter {
let file_name = self.options.split_name.get(ith);
if let Err(err) = remove_file(file_name) {
ret = Err(err);
}
}
ret
}
/// Split the input stream up to the line number `n`.
///
/// If the line number `n` is smaller than the current position in the input, then an empty
/// split is created.
///
/// # Errors
///
/// In addition to errors reading/writing from/to a file, if the line number
/// `n` is greater than the total available lines, then a
/// [`::CsplitError::LineOutOfRange`] error is returned.
fn do_to_line<I>(
&mut self,
pattern_as_str: &str,
n: usize,
input_iter: &mut InputSplitter<I>,
) -> Result<(), CsplitError>
where
I: Iterator<Item = (usize, io::Result<String>)>,
{
input_iter.rewind_buffer();
input_iter.set_size_of_buffer(1);
let mut ret = Err(CsplitError::LineOutOfRange(pattern_as_str.to_string()));
while let Some((ln, line)) = input_iter.next() {
let l = line?;
match n.cmp(&(&ln + 1)) {
Ordering::Less => {
if input_iter.add_line_to_buffer(ln, l).is_some() {
panic!("the buffer is big enough to contain 1 line");
}
ret = Ok(());
break;
}
Ordering::Equal => {
if !self.options.suppress_matched
&& input_iter.add_line_to_buffer(ln, l).is_some()
{
panic!("the buffer is big enough to contain 1 line");
}
ret = Ok(());
break;
}
Ordering::Greater => (),
}
self.writeln(l)?;
}
self.finish_split();
ret
}
/// Read lines up to the line matching a [`Regex`]. With a non-zero offset,
/// the block of relevant lines can be extended (if positive), or reduced
/// (if negative).
///
/// # Errors
///
/// In addition to errors reading/writing from/to a file, the following errors may be returned:
/// - if no line matched, an [`::CsplitError::MatchNotFound`].
/// - if there are not enough lines to accommodate the offset, an
/// [`::CsplitError::LineOutOfRange`].
fn do_to_match<I>(
&mut self,
pattern_as_str: &str,
regex: &Regex,
mut offset: i32,
input_iter: &mut InputSplitter<I>,
) -> Result<(), CsplitError>
where
I: Iterator<Item = (usize, io::Result<String>)>,
{
if offset >= 0 {
// The offset is zero or positive, no need for a buffer on the lines read.
// NOTE: drain the buffer of input_iter, no match should be done within.
for line in input_iter.drain_buffer() {
self.writeln(line)?;
}
// retain the matching line
input_iter.set_size_of_buffer(1);
while let Some((ln, line)) = input_iter.next() {
let l = line?;
if regex.is_match(&l) {
match (self.options.suppress_matched, offset) {
// no offset, add the line to the next split
(false, 0) => {
if input_iter.add_line_to_buffer(ln, l).is_some() {
panic!("the buffer is big enough to contain 1 line");
}
}
// a positive offset, some more lines need to be added to the current split
(false, _) => self.writeln(l)?,
_ => (),
};
offset -= 1;
// write the extra lines required by the offset
while offset > 0 {
match input_iter.next() {
Some((_, line)) => {
self.writeln(line?)?;
}
None => {
self.finish_split();
return Err(CsplitError::LineOutOfRange(
pattern_as_str.to_string(),
));
}
};
offset -= 1;
}
self.finish_split();
return Ok(());
}
self.writeln(l)?;
}
} else {
// With a negative offset we use a buffer to keep the lines within the offset.
// NOTE: do not drain the buffer of input_iter, in case of an LineOutOfRange error
// but do not rewind it either since no match should be done within.
// The consequence is that the buffer may already be full with lines from a previous
// split, which is taken care of when calling `shrink_buffer_to_size`.
let offset_usize = -offset as usize;
input_iter.set_size_of_buffer(offset_usize);
while let Some((ln, line)) = input_iter.next() {
let l = line?;
if regex.is_match(&l) {
for line in input_iter.shrink_buffer_to_size() {
self.writeln(line)?;
}
if !self.options.suppress_matched {
// add 1 to the buffer size to make place for the matched line
input_iter.set_size_of_buffer(offset_usize + 1);
if input_iter.add_line_to_buffer(ln, l).is_some() {
panic!("should be big enough to hold every lines");
}
}
self.finish_split();
if input_iter.buffer_len() < offset_usize {
return Err(CsplitError::LineOutOfRange(pattern_as_str.to_string()));
}
return Ok(());
}
if let Some(line) = input_iter.add_line_to_buffer(ln, l) {
self.writeln(line)?;
}
}
// no match, drain the buffer into the current split
for line in input_iter.drain_buffer() {
self.writeln(line)?;
}
}
self.finish_split();
Err(CsplitError::MatchNotFound(pattern_as_str.to_string()))
}
}
/// An iterator which can output items from a buffer filled externally.
/// This is used to pass matching lines to the next split and to support patterns with a negative offset.
struct InputSplitter<I>
where
I: Iterator<Item = (usize, io::Result<String>)>,
{
iter: I,
buffer: Vec<<I as Iterator>::Item>,
/// the number of elements the buffer may hold
size: usize,
/// flag to indicate content off the buffer should be returned instead of off the wrapped
/// iterator
rewind: bool,
}
impl<I> InputSplitter<I>
where
I: Iterator<Item = (usize, io::Result<String>)>,
{
fn new(iter: I) -> InputSplitter<I> {
InputSplitter {
iter,
buffer: Vec::new(),
rewind: false,
size: 1,
}
}
/// Rewind the iteration by outputting the buffer's content.
fn rewind_buffer(&mut self) {
self.rewind = true;
}
/// Shrink the buffer so that its length is equal to the set size, returning an iterator for
/// the elements that were too much.
fn shrink_buffer_to_size(&mut self) -> impl Iterator<Item = String> + '_ {
let mut shrink_offset = 0;
if self.buffer.len() > self.size {
shrink_offset = self.buffer.len() - self.size;
}
self.buffer
.drain(..shrink_offset)
.map(|(_, line)| line.unwrap())
}
/// Drain the content of the buffer.
fn drain_buffer(&mut self) -> impl Iterator<Item = String> + '_ {
self.buffer.drain(..).map(|(_, line)| line.unwrap())
}
/// Set the maximum number of lines to keep.
fn set_size_of_buffer(&mut self, size: usize) {
self.size = size;
}
/// Add a line to the buffer. If the buffer has [`size`] elements, then its head is removed and
/// the new line is pushed to the buffer. The removed head is then available in the returned
/// option.
fn add_line_to_buffer(&mut self, ln: usize, line: String) -> Option<String> {
if self.rewind {
self.buffer.insert(0, (ln, Ok(line)));
None
} else if self.buffer.len() >= self.size {
let (_, head_line) = self.buffer.remove(0);
self.buffer.push((ln, Ok(line)));
Some(head_line.unwrap())
} else {
self.buffer.push((ln, Ok(line)));
None
}
}
/// Returns the number of lines stored in the buffer
fn buffer_len(&self) -> usize {
self.buffer.len()
}
}
impl<I> Iterator for InputSplitter<I>
where
I: Iterator<Item = (usize, io::Result<String>)>,
{
type Item = <I as Iterator>::Item;
fn next(&mut self) -> Option<Self::Item> {
if self.rewind {
if !self.buffer.is_empty() {
return Some(self.buffer.remove(0));
}
self.rewind = false;
}
self.iter.next()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn input_splitter() {
let input = vec![
Ok(String::from("aaa")),
Ok(String::from("bbb")),
Ok(String::from("ccc")),
Ok(String::from("ddd")),
];
let mut input_splitter = InputSplitter::new(input.into_iter().enumerate());
input_splitter.set_size_of_buffer(2);
assert_eq!(input_splitter.buffer_len(), 0);
match input_splitter.next() {
Some((0, Ok(line))) => {
assert_eq!(line, String::from("aaa"));
assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
assert_eq!(input_splitter.buffer_len(), 1);
}
item @ _ => panic!("wrong item: {:?}", item),
};
match input_splitter.next() {
Some((1, Ok(line))) => {
assert_eq!(line, String::from("bbb"));
assert_eq!(input_splitter.add_line_to_buffer(1, line), None);
assert_eq!(input_splitter.buffer_len(), 2);
}
item @ _ => panic!("wrong item: {:?}", item),
};
match input_splitter.next() {
Some((2, Ok(line))) => {
assert_eq!(line, String::from("ccc"));
assert_eq!(
input_splitter.add_line_to_buffer(2, line),
Some(String::from("aaa"))
);
assert_eq!(input_splitter.buffer_len(), 2);
}
item @ _ => panic!("wrong item: {:?}", item),
};
input_splitter.rewind_buffer();
match input_splitter.next() {
Some((1, Ok(line))) => {
assert_eq!(line, String::from("bbb"));
assert_eq!(input_splitter.buffer_len(), 1);
}
item @ _ => panic!("wrong item: {:?}", item),
};
match input_splitter.next() {
Some((2, Ok(line))) => {
assert_eq!(line, String::from("ccc"));
assert_eq!(input_splitter.buffer_len(), 0);
}
item @ _ => panic!("wrong item: {:?}", item),
};
match input_splitter.next() {
Some((3, Ok(line))) => {
assert_eq!(line, String::from("ddd"));
assert_eq!(input_splitter.buffer_len(), 0);
}
item @ _ => panic!("wrong item: {:?}", item),
};
assert!(input_splitter.next().is_none());
}
#[test]
fn input_splitter_interrupt_rewind() {
let input = vec![
Ok(String::from("aaa")),
Ok(String::from("bbb")),
Ok(String::from("ccc")),
Ok(String::from("ddd")),
];
let mut input_splitter = InputSplitter::new(input.into_iter().enumerate());
input_splitter.set_size_of_buffer(3);
assert_eq!(input_splitter.buffer_len(), 0);
match input_splitter.next() {
Some((0, Ok(line))) => {
assert_eq!(line, String::from("aaa"));
assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
assert_eq!(input_splitter.buffer_len(), 1);
}
item @ _ => panic!("wrong item: {:?}", item),
};
match input_splitter.next() {
Some((1, Ok(line))) => {
assert_eq!(line, String::from("bbb"));
assert_eq!(input_splitter.add_line_to_buffer(1, line), None);
assert_eq!(input_splitter.buffer_len(), 2);
}
item @ _ => panic!("wrong item: {:?}", item),
};
match input_splitter.next() {
Some((2, Ok(line))) => {
assert_eq!(line, String::from("ccc"));
assert_eq!(input_splitter.add_line_to_buffer(2, line), None);
assert_eq!(input_splitter.buffer_len(), 3);
}
item @ _ => panic!("wrong item: {:?}", item),
};
input_splitter.rewind_buffer();
match input_splitter.next() {
Some((0, Ok(line))) => {
assert_eq!(line, String::from("aaa"));
assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
assert_eq!(input_splitter.buffer_len(), 3);
}
item @ _ => panic!("wrong item: {:?}", item),
};
match input_splitter.next() {
Some((0, Ok(line))) => {
assert_eq!(line, String::from("aaa"));
assert_eq!(input_splitter.buffer_len(), 2);
}
item @ _ => panic!("wrong item: {:?}", item),
};
match input_splitter.next() {
Some((1, Ok(line))) => {
assert_eq!(line, String::from("bbb"));
assert_eq!(input_splitter.buffer_len(), 1);
}
item @ _ => panic!("wrong item: {:?}", item),
};
match input_splitter.next() {
Some((2, Ok(line))) => {
assert_eq!(line, String::from("ccc"));
assert_eq!(input_splitter.buffer_len(), 0);
}
item @ _ => panic!("wrong item: {:?}", item),
};
match input_splitter.next() {
Some((3, Ok(line))) => {
assert_eq!(line, String::from("ddd"));
assert_eq!(input_splitter.buffer_len(), 0);
}
item @ _ => panic!("wrong item: {:?}", item),
};
assert!(input_splitter.next().is_none());
}
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let args = args.collect_str();
let matches = App::new(executable!())
.version(VERSION)
.about(SUMMARY)
.usage(&usage[..])
.arg(
Arg::with_name(options::SUFFIX_FORMAT)
.short("b")
.long(options::SUFFIX_FORMAT)
.value_name("FORMAT")
.help("use sprintf FORMAT instead of %02d"),
)
.arg(
Arg::with_name(options::PREFIX)
.short("f")
.long(options::PREFIX)
.value_name("PREFIX")
.help("use PREFIX instead of 'xx'"),
)
.arg(
Arg::with_name(options::KEEP_FILES)
.short("k")
.long(options::KEEP_FILES)
.help("do not remove output files on errors"),
)
.arg(
Arg::with_name(options::SUPPRESS_MATCHED)
.long(options::SUPPRESS_MATCHED)
.help("suppress the lines matching PATTERN"),
)
.arg(
Arg::with_name(options::DIGITS)
.short("n")
.long(options::DIGITS)
.value_name("DIGITS")
.help("use specified number of digits instead of 2"),
)
.arg(
Arg::with_name(options::QUIET)
.short("s")
.long(options::QUIET)
.visible_alias("silent")
.help("do not print counts of output file sizes"),
)
.arg(
Arg::with_name(options::ELIDE_EMPTY_FILES)
.short("z")
.long(options::ELIDE_EMPTY_FILES)
.help("remove empty output files"),
)
.arg(Arg::with_name(options::FILE).hidden(true).required(true))
.arg(
Arg::with_name(options::PATTERN)
.hidden(true)
.multiple(true)
.required(true),
)
.after_help(LONG_HELP)
.get_matches_from(args);
// get the file to split
let file_name = matches.value_of(options::FILE).unwrap();
// get the patterns to split on
let patterns: Vec<String> = matches
.values_of(options::PATTERN)
.unwrap()
.map(str::to_string)
.collect();
let patterns = return_if_err!(1, patterns::get_patterns(&patterns[..]));
let options = CsplitOptions::new(&matches);
if file_name == "-" {
let stdin = io::stdin();
crash_if_err!(1, csplit(&options, patterns, stdin.lock()));
} else {
let file = return_if_err!(1, File::open(file_name));
let file_metadata = return_if_err!(1, file.metadata());
if !file_metadata.is_file() {
crash!(1, "'{}' is not a regular file", file_name);
}
crash_if_err!(1, csplit(&options, patterns, BufReader::new(file)));
};
0
}

View file

@ -0,0 +1,35 @@
use std::io;
use thiserror::Error;
/// Errors thrown by the csplit command
#[derive(Debug, Error)]
pub enum CsplitError {
#[error("IO error: {}", _0)]
IoError(io::Error),
#[error("'{}': line number out of range", _0)]
LineOutOfRange(String),
#[error("'{}': line number out of range on repetition {}", _0, _1)]
LineOutOfRangeOnRepetition(String, usize),
#[error("'{}': match not found", _0)]
MatchNotFound(String),
#[error("'{}': match not found on repetition {}", _0, _1)]
MatchNotFoundOnRepetition(String, usize),
#[error("line number must be greater than zero")]
LineNumberIsZero,
#[error("line number '{}' is smaller than preceding line number, {}", _0, _1)]
LineNumberSmallerThanPrevious(usize, usize),
#[error("invalid pattern: {}", _0)]
InvalidPattern(String),
#[error("invalid number: '{}'", _0)]
InvalidNumber(String),
#[error("incorrect conversion specification in suffix")]
SuffixFormatIncorrect,
#[error("too many % conversion specifications in suffix")]
SuffixFormatTooManyPercents,
}
impl From<io::Error> for CsplitError {
fn from(error: io::Error) -> Self {
CsplitError::IoError(error)
}
}

View file

@ -0,0 +1 @@
uucore_procs::main!(uu_csplit); // spell-checker:ignore procs uucore

View file

@ -0,0 +1,355 @@
use crate::csplit_error::CsplitError;
use regex::Regex;
/// The definition of a pattern to match on a line.
#[derive(Debug)]
pub enum Pattern {
/// Copy the file's content to a split up to, not including, the given line number. The number
/// of times the pattern is executed is detailed in [`ExecutePattern`].
UpToLine(usize, ExecutePattern),
/// Copy the file's content to a split up to, not including, the line matching the regex. The
/// integer is an offset relative to the matched line of what to include (if positive) or
/// to exclude (if negative). The number of times the pattern is executed is detailed in
/// [`ExecutePattern`].
UpToMatch(Regex, i32, ExecutePattern),
/// Skip the file's content up to, not including, the line matching the regex. The integer
/// is an offset relative to the matched line of what to include (if positive) or to exclude
/// (if negative). The number of times the pattern is executed is detailed in [`ExecutePattern`].
SkipToMatch(Regex, i32, ExecutePattern),
}
impl ToString for Pattern {
fn to_string(&self) -> String {
match self {
Pattern::UpToLine(n, _) => n.to_string(),
Pattern::UpToMatch(regex, 0, _) => format!("/{}/", regex.as_str()),
Pattern::UpToMatch(regex, offset, _) => format!("/{}/{:+}", regex.as_str(), offset),
Pattern::SkipToMatch(regex, 0, _) => format!("%{}%", regex.as_str()),
Pattern::SkipToMatch(regex, offset, _) => format!("%{}%{:+}", regex.as_str(), offset),
}
}
}
/// The number of times a pattern can be used.
#[derive(Debug)]
pub enum ExecutePattern {
/// Execute the pattern as many times as possible
Always,
/// Execute the pattern a fixed number of times
Times(usize),
}
impl ExecutePattern {
pub fn iter(&self) -> ExecutePatternIter {
match self {
ExecutePattern::Times(n) => ExecutePatternIter::new(Some(*n)),
ExecutePattern::Always => ExecutePatternIter::new(None),
}
}
}
pub struct ExecutePatternIter {
max: Option<usize>,
cur: usize,
}
impl ExecutePatternIter {
fn new(max: Option<usize>) -> ExecutePatternIter {
ExecutePatternIter { max, cur: 0 }
}
}
impl Iterator for ExecutePatternIter {
type Item = (Option<usize>, usize);
fn next(&mut self) -> Option<(Option<usize>, usize)> {
match self.max {
// iterate until m is reached
Some(m) => {
if self.cur == m {
None
} else {
self.cur += 1;
Some((self.max, self.cur))
}
}
// no limit, just increment a counter
None => {
self.cur += 1;
Some((None, self.cur))
}
}
}
}
/// Parses the definitions of patterns given on the command line into a list of [`Pattern`]s.
///
/// # Errors
///
/// If a pattern is incorrect, a [`::CsplitError::InvalidPattern`] error is returned, which may be
/// due to, e.g.,:
/// - an invalid regular expression;
/// - an invalid number for, e.g., the offset.
pub fn get_patterns(args: &[String]) -> Result<Vec<Pattern>, CsplitError> {
let patterns = extract_patterns(args)?;
validate_line_numbers(&patterns)?;
Ok(patterns)
}
fn extract_patterns(args: &[String]) -> Result<Vec<Pattern>, CsplitError> {
let mut patterns = Vec::with_capacity(args.len());
let to_match_reg =
Regex::new(r"^(/(?P<UPTO>.+)/|%(?P<SKIPTO>.+)%)(?P<OFFSET>[\+-]\d+)?$").unwrap();
let execute_ntimes_reg = Regex::new(r"^\{(?P<TIMES>\d+)|\*\}$").unwrap();
let mut iter = args.iter().peekable();
while let Some(arg) = iter.next() {
// get the number of times a pattern is repeated, which is at least once plus whatever is
// in the quantifier.
let execute_ntimes = match iter.peek() {
None => ExecutePattern::Times(1),
Some(&next_item) => {
match execute_ntimes_reg.captures(next_item) {
None => ExecutePattern::Times(1),
Some(r) => {
// skip the next item
iter.next();
if let Some(times) = r.name("TIMES") {
ExecutePattern::Times(times.as_str().parse::<usize>().unwrap() + 1)
} else {
ExecutePattern::Always
}
}
}
}
};
// get the pattern definition
if let Some(captures) = to_match_reg.captures(arg) {
let offset = match captures.name("OFFSET") {
None => 0,
Some(m) => m.as_str().parse().unwrap(),
};
if let Some(up_to_match) = captures.name("UPTO") {
let pattern = match Regex::new(up_to_match.as_str()) {
Err(_) => {
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
Ok(reg) => reg,
};
patterns.push(Pattern::UpToMatch(pattern, offset, execute_ntimes));
} else if let Some(skip_to_match) = captures.name("SKIPTO") {
let pattern = match Regex::new(skip_to_match.as_str()) {
Err(_) => {
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
Ok(reg) => reg,
};
patterns.push(Pattern::SkipToMatch(pattern, offset, execute_ntimes));
}
} else if let Ok(line_number) = arg.parse::<usize>() {
patterns.push(Pattern::UpToLine(line_number, execute_ntimes));
} else {
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
}
Ok(patterns)
}
/// Asserts the line numbers are in increasing order, starting at 1.
fn validate_line_numbers(patterns: &[Pattern]) -> Result<(), CsplitError> {
patterns
.iter()
.filter_map(|pattern| match pattern {
Pattern::UpToLine(line_number, _) => Some(line_number),
_ => None,
})
.try_fold(0, |prev_ln, &current_ln| match (prev_ln, current_ln) {
// a line number cannot be zero
(_, 0) => Err(CsplitError::LineNumberIsZero),
// two consecutifs numbers should not be equal
(n, m) if n == m => {
show_warning!("line number '{}' is the same as preceding line number", n);
Ok(n)
}
// a number cannot be greater than the one that follows
(n, m) if n > m => Err(CsplitError::LineNumberSmallerThanPrevious(m, n)),
(_, m) => Ok(m),
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bad_pattern() {
let input = vec!["bad".to_string()];
assert!(get_patterns(input.as_slice()).is_err());
}
#[test]
fn up_to_line_pattern() {
let input: Vec<String> = vec!["24", "42", "{*}", "50", "{4}"]
.into_iter()
.map(|v| v.to_string())
.collect();
let patterns = get_patterns(input.as_slice()).unwrap();
assert_eq!(patterns.len(), 3);
match patterns.get(0) {
Some(Pattern::UpToLine(24, ExecutePattern::Times(1))) => (),
_ => panic!("expected UpToLine pattern"),
};
match patterns.get(1) {
Some(Pattern::UpToLine(42, ExecutePattern::Always)) => (),
_ => panic!("expected UpToLine pattern"),
};
match patterns.get(2) {
Some(Pattern::UpToLine(50, ExecutePattern::Times(5))) => (),
_ => panic!("expected UpToLine pattern"),
};
}
#[test]
fn up_to_match_pattern() {
let input: Vec<String> = vec![
"/test1.*end$/",
"/test2.*end$/",
"{*}",
"/test3.*end$/",
"{4}",
"/test4.*end$/+3",
"/test5.*end$/-3",
]
.into_iter()
.map(|v| v.to_string())
.collect();
let patterns = get_patterns(input.as_slice()).unwrap();
assert_eq!(patterns.len(), 5);
match patterns.get(0) {
Some(Pattern::UpToMatch(reg, 0, ExecutePattern::Times(1))) => {
let parsed_reg = format!("{}", reg);
assert_eq!(parsed_reg, "test1.*end$");
}
_ => panic!("expected UpToMatch pattern"),
};
match patterns.get(1) {
Some(Pattern::UpToMatch(reg, 0, ExecutePattern::Always)) => {
let parsed_reg = format!("{}", reg);
assert_eq!(parsed_reg, "test2.*end$");
}
_ => panic!("expected UpToMatch pattern"),
};
match patterns.get(2) {
Some(Pattern::UpToMatch(reg, 0, ExecutePattern::Times(5))) => {
let parsed_reg = format!("{}", reg);
assert_eq!(parsed_reg, "test3.*end$");
}
_ => panic!("expected UpToMatch pattern"),
};
match patterns.get(3) {
Some(Pattern::UpToMatch(reg, 3, ExecutePattern::Times(1))) => {
let parsed_reg = format!("{}", reg);
assert_eq!(parsed_reg, "test4.*end$");
}
_ => panic!("expected UpToMatch pattern"),
};
match patterns.get(4) {
Some(Pattern::UpToMatch(reg, -3, ExecutePattern::Times(1))) => {
let parsed_reg = format!("{}", reg);
assert_eq!(parsed_reg, "test5.*end$");
}
_ => panic!("expected UpToMatch pattern"),
};
}
#[test]
fn skip_to_match_pattern() {
let input: Vec<String> = vec![
"%test1.*end$%",
"%test2.*end$%",
"{*}",
"%test3.*end$%",
"{4}",
"%test4.*end$%+3",
"%test5.*end$%-3",
]
.into_iter()
.map(|v| v.to_string())
.collect();
let patterns = get_patterns(input.as_slice()).unwrap();
assert_eq!(patterns.len(), 5);
match patterns.get(0) {
Some(Pattern::SkipToMatch(reg, 0, ExecutePattern::Times(1))) => {
let parsed_reg = format!("{}", reg);
assert_eq!(parsed_reg, "test1.*end$");
}
_ => panic!("expected SkipToMatch pattern"),
};
match patterns.get(1) {
Some(Pattern::SkipToMatch(reg, 0, ExecutePattern::Always)) => {
let parsed_reg = format!("{}", reg);
assert_eq!(parsed_reg, "test2.*end$");
}
_ => panic!("expected SkipToMatch pattern"),
};
match patterns.get(2) {
Some(Pattern::SkipToMatch(reg, 0, ExecutePattern::Times(5))) => {
let parsed_reg = format!("{}", reg);
assert_eq!(parsed_reg, "test3.*end$");
}
_ => panic!("expected SkipToMatch pattern"),
};
match patterns.get(3) {
Some(Pattern::SkipToMatch(reg, 3, ExecutePattern::Times(1))) => {
let parsed_reg = format!("{}", reg);
assert_eq!(parsed_reg, "test4.*end$");
}
_ => panic!("expected SkipToMatch pattern"),
};
match patterns.get(4) {
Some(Pattern::SkipToMatch(reg, -3, ExecutePattern::Times(1))) => {
let parsed_reg = format!("{}", reg);
assert_eq!(parsed_reg, "test5.*end$");
}
_ => panic!("expected SkipToMatch pattern"),
};
}
#[test]
fn line_number_zero() {
let patterns = vec![Pattern::UpToLine(0, ExecutePattern::Times(1))];
match validate_line_numbers(&patterns) {
Err(CsplitError::LineNumberIsZero) => (),
_ => panic!("expected LineNumberIsZero error"),
}
}
#[test]
fn line_number_smaller_than_previous() {
let input: Vec<String> = vec!["10".to_string(), "5".to_string()];
match get_patterns(input.as_slice()) {
Err(CsplitError::LineNumberSmallerThanPrevious(5, 10)) => (),
_ => panic!("expected LineNumberSmallerThanPrevious error"),
}
}
#[test]
fn line_number_smaller_than_previous_separate() {
let input: Vec<String> = vec!["10".to_string(), "/20/".to_string(), "5".to_string()];
match get_patterns(input.as_slice()) {
Err(CsplitError::LineNumberSmallerThanPrevious(5, 10)) => (),
_ => panic!("expected LineNumberSmallerThanPrevious error"),
}
}
#[test]
fn line_number_zero_separate() {
let input: Vec<String> = vec!["10".to_string(), "/20/".to_string(), "0".to_string()];
match get_patterns(input.as_slice()) {
Err(CsplitError::LineNumberIsZero) => (),
_ => panic!("expected LineNumberIsZero error"),
}
}
}

View file

@ -0,0 +1,395 @@
use regex::Regex;
use crate::csplit_error::CsplitError;
/// Computes the filename of a split, taking into consideration a possible user-defined suffix
/// format.
pub struct SplitName {
fn_split_name: Box<dyn Fn(usize) -> String>,
}
impl SplitName {
/// Creates a new SplitName with the given user-defined options:
/// - `prefix_opt` specifies a prefix for all splits.
/// - `format_opt` specifies a custom format for the suffix part of the filename, using the
/// `sprintf` format notation.
/// - `n_digits_opt` defines the width of the split number.
///
/// # Caveats
///
/// If `prefix_opt` and `format_opt` are defined, and the `format_opt` has some string appearing
/// before the conversion pattern (e.g., "here-%05d"), then it is appended to the passed prefix
/// via `prefix_opt`.
///
/// If `n_digits_opt` and `format_opt` are defined, then width defined in `format_opt` is
/// taken.
pub fn new(
prefix_opt: Option<String>,
format_opt: Option<String>,
n_digits_opt: Option<String>,
) -> Result<SplitName, CsplitError> {
// get the prefix
let prefix = prefix_opt.unwrap_or_else(|| "xx".to_string());
// the width for the split offset
let n_digits = match n_digits_opt {
None => 2,
Some(opt) => match opt.parse::<usize>() {
Ok(digits) => digits,
Err(_) => return Err(CsplitError::InvalidNumber(opt)),
},
};
// translate the custom format into a function
let fn_split_name: Box<dyn Fn(usize) -> String> = match format_opt {
None => Box::new(move |n: usize| -> String {
format!("{}{:0width$}", prefix, n, width = n_digits)
}),
Some(custom) => {
let spec =
Regex::new(r"(?P<ALL>%(?P<FLAG>[0#-])(?P<WIDTH>\d+)?(?P<TYPE>[diuoxX]))")
.unwrap();
let mut captures_iter = spec.captures_iter(&custom);
let custom_fn: Box<dyn Fn(usize) -> String> = match captures_iter.next() {
Some(captures) => {
let all = captures.name("ALL").unwrap();
let before = custom[0..all.start()].to_owned();
let after = custom[all.end()..].to_owned();
let n_digits = match captures.name("WIDTH") {
None => 0,
Some(m) => m.as_str().parse::<usize>().unwrap(),
};
match (captures.name("FLAG"), captures.name("TYPE")) {
(Some(ref f), Some(ref t)) => {
match (f.as_str(), t.as_str()) {
/*
* zero padding
*/
// decimal
("0", "d") | ("0", "i") | ("0", "u") => {
Box::new(move |n: usize| -> String {
format!(
"{}{}{:0width$}{}",
prefix,
before,
n,
after,
width = n_digits
)
})
}
// octal
("0", "o") => Box::new(move |n: usize| -> String {
format!(
"{}{}{:0width$o}{}",
prefix,
before,
n,
after,
width = n_digits
)
}),
// lower hexadecimal
("0", "x") => Box::new(move |n: usize| -> String {
format!(
"{}{}{:0width$x}{}",
prefix,
before,
n,
after,
width = n_digits
)
}),
// upper hexadecimal
("0", "X") => Box::new(move |n: usize| -> String {
format!(
"{}{}{:0width$X}{}",
prefix,
before,
n,
after,
width = n_digits
)
}),
/*
* Alternate form
*/
// octal
("#", "o") => Box::new(move |n: usize| -> String {
format!(
"{}{}{:>#width$o}{}",
prefix,
before,
n,
after,
width = n_digits
)
}),
// lower hexadecimal
("#", "x") => Box::new(move |n: usize| -> String {
format!(
"{}{}{:>#width$x}{}",
prefix,
before,
n,
after,
width = n_digits
)
}),
// upper hexadecimal
("#", "X") => Box::new(move |n: usize| -> String {
format!(
"{}{}{:>#width$X}{}",
prefix,
before,
n,
after,
width = n_digits
)
}),
/*
* Left adjusted
*/
// decimal
("-", "d") | ("-", "i") | ("-", "u") => {
Box::new(move |n: usize| -> String {
format!(
"{}{}{:<#width$}{}",
prefix,
before,
n,
after,
width = n_digits
)
})
}
// octal
("-", "o") => Box::new(move |n: usize| -> String {
format!(
"{}{}{:<#width$o}{}",
prefix,
before,
n,
after,
width = n_digits
)
}),
// lower hexadecimal
("-", "x") => Box::new(move |n: usize| -> String {
format!(
"{}{}{:<#width$x}{}",
prefix,
before,
n,
after,
width = n_digits
)
}),
// upper hexadecimal
("-", "X") => Box::new(move |n: usize| -> String {
format!(
"{}{}{:<#width$X}{}",
prefix,
before,
n,
after,
width = n_digits
)
}),
_ => return Err(CsplitError::SuffixFormatIncorrect),
}
}
_ => return Err(CsplitError::SuffixFormatIncorrect),
}
}
None => return Err(CsplitError::SuffixFormatIncorrect),
};
// there cannot be more than one format pattern
if captures_iter.next().is_some() {
return Err(CsplitError::SuffixFormatTooManyPercents);
}
custom_fn
}
};
Ok(SplitName { fn_split_name })
}
/// Returns the filename of the i-th split.
pub fn get(&self, n: usize) -> String {
(self.fn_split_name)(n)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalid_number() {
let split_name = SplitName::new(None, None, Some(String::from("bad")));
match split_name {
Err(CsplitError::InvalidNumber(_)) => (),
_ => panic!("should fail with InvalidNumber"),
};
}
#[test]
fn invalid_suffix_format1() {
let split_name = SplitName::new(None, Some(String::from("no conversion string")), None);
match split_name {
Err(CsplitError::SuffixFormatIncorrect) => (),
_ => panic!("should fail with SuffixFormatIncorrect"),
};
}
#[test]
fn invalid_suffix_format2() {
let split_name = SplitName::new(None, Some(String::from("%042a")), None);
match split_name {
Err(CsplitError::SuffixFormatIncorrect) => (),
_ => panic!("should fail with SuffixFormatIncorrect"),
};
}
#[test]
fn default_formatter() {
let split_name = SplitName::new(None, None, None).unwrap();
assert_eq!(split_name.get(2), "xx02");
}
#[test]
fn default_formatter_with_prefix() {
let split_name = SplitName::new(Some(String::from("aaa")), None, None).unwrap();
assert_eq!(split_name.get(2), "aaa02");
}
#[test]
fn default_formatter_with_width() {
let split_name = SplitName::new(None, None, Some(String::from("5"))).unwrap();
assert_eq!(split_name.get(2), "xx00002");
}
#[test]
fn zero_padding_decimal1() {
let split_name = SplitName::new(None, Some(String::from("cst-%03d-")), None).unwrap();
assert_eq!(split_name.get(2), "xxcst-002-");
}
#[test]
fn zero_padding_decimal2() {
let split_name = SplitName::new(
Some(String::from("pre-")),
Some(String::from("cst-%03d-post")),
None,
)
.unwrap();
assert_eq!(split_name.get(2), "pre-cst-002-post");
}
#[test]
fn zero_padding_decimal3() {
let split_name = SplitName::new(
None,
Some(String::from("cst-%03d-")),
Some(String::from("42")),
)
.unwrap();
assert_eq!(split_name.get(2), "xxcst-002-");
}
#[test]
fn zero_padding_decimal4() {
let split_name = SplitName::new(None, Some(String::from("cst-%03i-")), None).unwrap();
assert_eq!(split_name.get(2), "xxcst-002-");
}
#[test]
fn zero_padding_decimal5() {
let split_name = SplitName::new(None, Some(String::from("cst-%03u-")), None).unwrap();
assert_eq!(split_name.get(2), "xxcst-002-");
}
#[test]
fn zero_padding_octal() {
let split_name = SplitName::new(None, Some(String::from("cst-%03o-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-052-");
}
#[test]
fn zero_padding_lower_hexa() {
let split_name = SplitName::new(None, Some(String::from("cst-%03x-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-02a-");
}
#[test]
fn zero_padding_upper_hexa() {
let split_name = SplitName::new(None, Some(String::from("cst-%03X-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-02A-");
}
#[test]
fn alternate_form_octal() {
let split_name = SplitName::new(None, Some(String::from("cst-%#10o-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst- 0o52-");
}
#[test]
fn alternate_form_lower_hexa() {
let split_name = SplitName::new(None, Some(String::from("cst-%#10x-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst- 0x2a-");
}
#[test]
fn alternate_form_upper_hexa() {
let split_name = SplitName::new(None, Some(String::from("cst-%#10X-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst- 0x2A-");
}
#[test]
fn left_adjusted_decimal1() {
let split_name = SplitName::new(None, Some(String::from("cst-%-10d-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-42 -");
}
#[test]
fn left_adjusted_decimal2() {
let split_name = SplitName::new(None, Some(String::from("cst-%-10i-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-42 -");
}
#[test]
fn left_adjusted_decimal3() {
let split_name = SplitName::new(None, Some(String::from("cst-%-10u-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-42 -");
}
#[test]
fn left_adjusted_octal() {
let split_name = SplitName::new(None, Some(String::from("cst-%-10o-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-0o52 -");
}
#[test]
fn left_adjusted_lower_hexa() {
let split_name = SplitName::new(None, Some(String::from("cst-%-10x-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-0x2a -");
}
#[test]
fn left_adjusted_upper_hexa() {
let split_name = SplitName::new(None, Some(String::from("cst-%-10X-")), None).unwrap();
assert_eq!(split_name.get(42), "xxcst-0x2A -");
}
#[test]
fn too_many_percent() {
let split_name = SplitName::new(None, Some(String::from("%02d-%-3x")), None);
match split_name {
Err(CsplitError::SuffixFormatTooManyPercents) => (),
_ => panic!("should fail with SuffixFormatTooManyPercents"),
};
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_cut"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "cut ~ (uutils) display byte/field columns of input lines"
@ -15,8 +15,9 @@ edition = "2018"
path = "src/cut.rs"
[dependencies]
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "cut"

View file

@ -107,7 +107,7 @@ impl<R: Read> self::Bytes::Select for ByteReader<R> {
Comp,
Part,
Newl,
};
}
use self::Bytes::Selected::*;

View file

@ -10,17 +10,19 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, Read, Stdout, Write};
use std::path::Path;
use self::ranges::Range;
use self::searcher::Searcher;
use uucore::ranges::Range;
mod buffer;
mod ranges;
mod searcher;
static NAME: &str = "cut";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SYNTAX: &str =
"[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+";
static SUMMARY: &str =
@ -125,7 +127,7 @@ enum Mode {
fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
if complement {
Range::from_list(list).map(|r| ranges::complement(&r))
Range::from_list(list).map(|r| uucore::ranges::complement(&r))
} else {
Range::from_list(list)
}
@ -399,8 +401,13 @@ fn cut_files(mut filenames: Vec<String>, mode: Mode) -> i32 {
} else {
let path = Path::new(&filename[..]);
if !path.exists() {
show_error!("{}", msg_args_nonexistent_file!(filename));
if path.is_dir() {
show_error!("{}: Is a directory", filename);
continue;
}
if !path.metadata().is_ok() {
show_error!("{}: No such file or directory", filename);
continue;
}
@ -423,34 +430,123 @@ fn cut_files(mut filenames: Vec<String>, mode: Mode) -> i32 {
exit_code
}
mod options {
pub const BYTES: &str = "bytes";
pub const CHARACTERS: &str = "characters";
pub const DELIMITER: &str = "delimiter";
pub const FIELDS: &str = "fields";
pub const ZERO_TERMINATED: &str = "zero-terminated";
pub const ONLY_DELIMITED: &str = "only-delimited";
pub const OUTPUT_DELIMITER: &str = "output-delimiter";
pub const COMPLEMENT: &str = "complement";
pub const FILE: &str = "file";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optopt("b", "bytes", "filter byte columns from the input source", "sequence")
.optopt("c", "characters", "alias for character mode", "sequence")
.optopt("d", "delimiter", "specify the delimiter character that separates fields in the input source. Defaults to Tab.", "delimiter")
.optopt("f", "fields", "filter field columns from the input source", "sequence")
.optflag("n", "", "legacy option - has no effect.")
.optflag("", "complement", "invert the filter - instead of displaying only the filtered columns, display all but those columns")
.optflag("s", "only-delimited", "in field mode, only print lines which contain the delimiter")
.optflag("z", "zero-terminated", "instead of filtering columns based on line, filter columns based on \\0 (NULL character)")
.optopt("", "output-delimiter", "in field mode, replace the delimiter in output lines with this option's argument", "new delimiter")
.parse(args);
let complement = matches.opt_present("complement");
let matches = App::new(executable!())
.name(NAME)
.version(VERSION)
.usage(SYNTAX)
.about(SUMMARY)
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::BYTES)
.short("b")
.long(options::BYTES)
.takes_value(true)
.help("filter byte columns from the input source")
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(1),
)
.arg(
Arg::with_name(options::CHARACTERS)
.short("c")
.long(options::CHARACTERS)
.help("alias for character mode")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(2),
)
.arg(
Arg::with_name(options::DELIMITER)
.short("d")
.long(options::DELIMITER)
.help("specify the delimiter character that separates fields in the input source. Defaults to Tab.")
.takes_value(true)
.value_name("DELIM")
.display_order(3),
)
.arg(
Arg::with_name(options::FIELDS)
.short("f")
.long(options::FIELDS)
.help("filter field columns from the input source")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.display_order(4),
)
.arg(
Arg::with_name(options::COMPLEMENT)
.long(options::COMPLEMENT)
.help("invert the filter - instead of displaying only the filtered columns, display all but those columns")
.takes_value(false)
.display_order(5),
)
.arg(
Arg::with_name(options::ONLY_DELIMITED)
.short("s")
.long(options::ONLY_DELIMITED)
.help("in field mode, only print lines which contain the delimiter")
.takes_value(false)
.display_order(6),
)
.arg(
Arg::with_name(options::ZERO_TERMINATED)
.short("z")
.long(options::ZERO_TERMINATED)
.help("instead of filtering columns based on line, filter columns based on \\0 (NULL character)")
.takes_value(false)
.display_order(8),
)
.arg(
Arg::with_name(options::OUTPUT_DELIMITER)
.long(options::OUTPUT_DELIMITER)
.help("in field mode, replace the delimiter in output lines with this option's argument")
.takes_value(true)
.value_name("NEW_DELIM")
.display_order(7),
)
.arg(
Arg::with_name(options::FILE)
.hidden(true)
.multiple(true)
)
.get_matches_from(args);
let complement = matches.is_present(options::COMPLEMENT);
let mode_parse = match (
matches.opt_str("bytes"),
matches.opt_str("characters"),
matches.opt_str("fields"),
matches.value_of(options::BYTES),
matches.value_of(options::CHARACTERS),
matches.value_of(options::FIELDS),
) {
(Some(byte_ranges), None, None) => {
list_to_ranges(&byte_ranges[..], complement).map(|ranges| {
Mode::Bytes(
ranges,
Options {
out_delim: matches.opt_str("output-delimiter"),
zero_terminated: matches.opt_present("zero-terminated"),
out_delim: Some(
matches
.value_of(options::OUTPUT_DELIMITER)
.unwrap_or_default()
.to_owned(),
),
zero_terminated: matches.is_present(options::ZERO_TERMINATED),
},
)
})
@ -460,29 +556,34 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Mode::Characters(
ranges,
Options {
out_delim: matches.opt_str("output-delimiter"),
zero_terminated: matches.opt_present("zero-terminated"),
out_delim: Some(
matches
.value_of(options::OUTPUT_DELIMITER)
.unwrap_or_default()
.to_owned(),
),
zero_terminated: matches.is_present(options::ZERO_TERMINATED),
},
)
})
}
(None, None, Some(field_ranges)) => {
list_to_ranges(&field_ranges[..], complement).and_then(|ranges| {
let out_delim = match matches.opt_str("output-delimiter") {
let out_delim = match matches.value_of(options::OUTPUT_DELIMITER) {
Some(s) => {
if s.is_empty() {
Some("\0".to_owned())
} else {
Some(s)
Some(s.to_owned())
}
}
None => None,
};
let only_delimited = matches.opt_present("only-delimited");
let zero_terminated = matches.opt_present("zero-terminated");
let only_delimited = matches.is_present(options::ONLY_DELIMITED);
let zero_terminated = matches.is_present(options::ZERO_TERMINATED);
match matches.opt_str("delimiter") {
match matches.value_of(options::DELIMITER) {
Some(delim) => {
if delim.chars().count() > 1 {
Err(msg_opt_invalid_should_be!(
@ -495,7 +596,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let delim = if delim.is_empty() {
"\0".to_owned()
} else {
delim
delim.to_owned()
};
Ok(Mode::Fields(
@ -534,10 +635,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let mode_parse = match mode_parse {
Err(_) => mode_parse,
Ok(mode) => match mode {
Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("delimiter") => Err(
msg_opt_only_usable_if!("printing a sequence of fields", "--delimiter", "-d"),
),
Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("only-delimited") => {
Mode::Bytes(_, _) | Mode::Characters(_, _)
if matches.is_present(options::DELIMITER) =>
{
Err(msg_opt_only_usable_if!(
"printing a sequence of fields",
"--delimiter",
"-d"
))
}
Mode::Bytes(_, _) | Mode::Characters(_, _)
if matches.is_present(options::ONLY_DELIMITED) =>
{
Err(msg_opt_only_usable_if!(
"printing a sequence of fields",
"--only-delimited",
@ -548,8 +657,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
},
};
let files: Vec<String> = matches
.values_of(options::FILE)
.unwrap_or_default()
.map(str::to_owned)
.collect();
match mode_parse {
Ok(mode) => cut_files(matches.free, mode),
Ok(mode) => cut_files(files, mode),
Err(err_msg) => {
show_error!("{}", err_msg);
1

View file

@ -1,6 +1,6 @@
[package]
name = "uu_date"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "date ~ (uutils) display or set the current time"
@ -16,9 +16,15 @@ path = "src/date.rs"
[dependencies]
chrono = "0.4.4"
clap = "2.32"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(unix)'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["minwinbase", "sysinfoapi", "minwindef"] }
[[bin]]
name = "date"

View file

@ -9,19 +9,23 @@
// spell-checker:ignore (format) MMDDhhmm
// spell-checker:ignore (ToDO) DATEFILE
extern crate chrono;
extern crate clap;
#[macro_use]
extern crate uucore;
use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
#[cfg(windows)]
use chrono::{Datelike, Timelike};
use clap::{App, Arg};
use chrono::offset::Utc;
use chrono::{DateTime, FixedOffset, Local, Offset};
#[cfg(all(unix, not(target_os = "macos")))]
use libc::{clock_settime, timespec, CLOCK_REALTIME};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
#[cfg(windows)]
use winapi::{
shared::minwindef::WORD,
um::{minwinbase::SYSTEMTIME, sysinfoapi::SetSystemTime},
};
// Options
const DATE: &str = "date";
@ -65,6 +69,11 @@ static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format.
for date and time to the indicated precision.
Example: 2006-08-14 02:34:56-06:00";
#[cfg(not(target_os = "macos"))]
static OPT_SET_HELP_STRING: &str = "set time described by STRING";
#[cfg(target_os = "macos")]
static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)";
/// Settings for this program, parsed from the command line
struct Settings {
utc: bool,
@ -189,7 +198,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("s")
.long(OPT_SET)
.takes_value(true)
.help("set time described by STRING"),
.help(OPT_SET_HELP_STRING),
)
.arg(
Arg::with_name(OPT_UNIVERSAL)
@ -225,18 +234,31 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
DateSource::Now
};
let set_to = match matches.value_of(OPT_SET).map(parse_date) {
None => None,
Some(Err((input, _err))) => {
eprintln!("date: invalid date '{}'", input);
return 1;
}
Some(Ok(date)) => Some(date),
};
let settings = Settings {
utc: matches.is_present(OPT_UNIVERSAL),
format,
date_source,
// TODO: Handle this option:
set_to: None,
set_to,
};
if let Some(_time) = settings.set_to {
unimplemented!();
// Probably need to use this syscall:
// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html
if let Some(date) = settings.set_to {
// All set time functions expect UTC datetimes.
let date: DateTime<Utc> = if settings.utc {
date.with_timezone(&Utc)
} else {
date.into()
};
return set_system_datetime(date);
} else {
// Declare a file here because it needs to outlive the `dates` iterator.
let file: File;
@ -250,15 +272,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
now.with_timezone(now.offset())
};
/// Parse a `String` into a `DateTime`.
/// If it fails, return a tuple of the `String` along with its `ParseError`.
fn parse_date(
s: String,
) -> Result<DateTime<FixedOffset>, (String, chrono::format::ParseError)> {
// TODO: The GNU date command can parse a wide variety of inputs.
s.parse().map_err(|e| (s, e))
}
// Iterate over all dates - whether it's a single date or a file.
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
DateSource::Custom(ref input) => {
@ -317,3 +330,76 @@ fn make_format_string(settings: &Settings) -> &str {
Format::Default => "%c",
}
}
/// Parse a `String` into a `DateTime`.
/// If it fails, return a tuple of the `String` along with its `ParseError`.
fn parse_date<S: AsRef<str> + Clone>(
s: S,
) -> Result<DateTime<FixedOffset>, (String, chrono::format::ParseError)> {
// TODO: The GNU date command can parse a wide variety of inputs.
s.as_ref().parse().map_err(|e| (s.as_ref().into(), e))
}
#[cfg(not(any(unix, windows)))]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
unimplemented!("setting date not implemented (unsupported target)");
}
#[cfg(target_os = "macos")]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
eprintln!("date: setting the date is not supported by macOS");
return 1;
}
#[cfg(all(unix, not(target_os = "macos")))]
/// System call to set date (unix).
/// See here for more:
/// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html
/// https://linux.die.net/man/3/clock_settime
/// https://www.gnu.org/software/libc/manual/html_node/Time-Types.html
fn set_system_datetime(date: DateTime<Utc>) -> i32 {
let timespec = timespec {
tv_sec: date.timestamp() as _,
tv_nsec: date.timestamp_subsec_nanos() as _,
};
let result = unsafe { clock_settime(CLOCK_REALTIME, &timespec) };
if result != 0 {
let error = std::io::Error::last_os_error();
eprintln!("date: cannot set date: {}", error);
error.raw_os_error().unwrap()
} else {
0
}
}
#[cfg(windows)]
/// System call to set date (Windows).
/// See here for more:
/// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-setsystemtime
/// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime
fn set_system_datetime(date: DateTime<Utc>) -> i32 {
let system_time = SYSTEMTIME {
wYear: date.year() as WORD,
wMonth: date.month() as WORD,
// Ignored
wDayOfWeek: 0,
wDay: date.day() as WORD,
wHour: date.hour() as WORD,
wMinute: date.minute() as WORD,
wSecond: date.second() as WORD,
// TODO: be careful of leap seconds - valid range is [0, 999] - how to handle?
wMilliseconds: ((date.nanosecond() / 1_000_000) % 1000) as WORD,
};
let result = unsafe { SetSystemTime(&system_time) };
if result == 0 {
let error = std::io::Error::last_os_error();
eprintln!("date: cannot set date: {}", error);
error.raw_os_error().unwrap()
} else {
0
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_df"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "df ~ (uutils) display file system information"
@ -15,11 +15,11 @@ edition = "2018"
path = "src/df.rs"
[dependencies]
clap = "2.32"
clap = "2.33"
libc = "0.2"
number_prefix = "0.2"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
number_prefix = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }

View file

@ -9,17 +9,11 @@
// spell-checker:ignore (ToDO) mountinfo mtab BLOCKSIZE getmntinfo fobj mptr noatime Iused overmounted
// spell-checker:ignore (libc/fs) asyncreads asyncwrites autofs bavail bfree bsize charspare cifs debugfs devfs devpts ffree frsize fsid fstypename fusectl inode inodes iosize kernfs mntbufp mntfromname mntonname mqueue namemax pipefs smbfs statfs statvfs subfs syncreads syncwrites sysfs wcslen
extern crate clap;
extern crate libc;
extern crate number_prefix;
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
use winapi::um::errhandlingapi::GetLastError;
#[cfg(windows)]
@ -28,7 +22,7 @@ use winapi::um::fileapi::{
GetVolumePathNamesForVolumeNameW, QueryDosDeviceW,
};
use number_prefix::{binary_prefix, decimal_prefix, PrefixNames, Prefixed, Standalone};
use number_prefix::NumberPrefix;
use std::cell::Cell;
use std::collections::HashMap;
use std::collections::HashSet;
@ -38,15 +32,15 @@ use std::ffi::CString;
#[cfg(unix)]
use std::mem;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use libc::c_int;
#[cfg(target_os = "macos")]
#[cfg(target_vendor = "apple")]
use libc::statfs;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::ffi::CStr;
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "windows"))]
#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "windows"))]
use std::ptr;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::slice;
#[cfg(target_os = "freebsd")]
@ -106,7 +100,6 @@ static OPT_SYNC: &str = "sync";
static OPT_TYPE: &str = "type";
static OPT_PRINT_TYPE: &str = "print-type";
static OPT_EXCLUDE_TYPE: &str = "exclude-type";
static OPT_VERSION: &str = "version";
static MOUNT_OPT_BIND: &str = "bind";
@ -144,7 +137,7 @@ struct MountInfo {
#[cfg(all(
target_os = "freebsd",
not(all(target_os = "macos", target_arch = "x86_64"))
not(all(target_vendor = "apple", target_arch = "x86_64"))
))]
#[repr(C)]
#[derive(Copy, Clone)]
@ -216,20 +209,20 @@ fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!())
}
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
extern "C" {
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
#[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
#[link_name = "getmntinfo$INODE64"]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
#[cfg(all(
target_os = "freebsd",
not(all(target_os = "macos", target_arch = "x86_64"))
#[cfg(any(
all(target_os = "freebsd"),
all(target_vendor = "apple", target_arch = "aarch64")
))]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
}
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
impl From<statfs> for MountInfo {
fn from(statfs: statfs) -> Self {
let mut info = MountInfo {
@ -592,7 +585,7 @@ fn read_fs_list() -> Vec<MountInfo> {
})
.collect::<Vec<_>>()
}
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
{
let mut mptr: *mut statfs = ptr::null_mut();
let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) };
@ -676,7 +669,7 @@ fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Ve
#[allow(clippy::map_entry)]
{
if acc.contains_key(&id) {
let seen = acc.get(&id).unwrap().replace(mi.clone());
let seen = acc[&id].replace(mi.clone());
let target_nearer_root = seen.mount_dir.len() > mi.mount_dir.len();
// With bind mounts, prefer items nearer the root of the source
let source_below_root = !seen.mount_root.is_empty()
@ -694,7 +687,7 @@ fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Ve
environments for example. */
|| seen.mount_dir != mi.mount_dir)
{
acc.get(&id).unwrap().replace(seen);
acc[&id].replace(seen);
}
} else {
acc.insert(id, Cell::new(mi));
@ -717,14 +710,14 @@ fn human_readable(value: u64, base: i64) -> String {
// ref: [Binary prefix](https://en.wikipedia.org/wiki/Binary_prefix) @@ <https://archive.is/cnwmF>
// ref: [SI/metric prefix](https://en.wikipedia.org/wiki/Metric_prefix) @@ <https://archive.is/QIuLj>
1000 => match decimal_prefix(value as f64) {
Standalone(bytes) => bytes.to_string(),
Prefixed(prefix, bytes) => format!("{:.1}{}", bytes, prefix.symbol()),
1000 => match NumberPrefix::decimal(value as f64) {
NumberPrefix::Standalone(bytes) => bytes.to_string(),
NumberPrefix::Prefixed(prefix, bytes) => format!("{:.1}{}", bytes, prefix.symbol()),
},
1024 => match binary_prefix(value as f64) {
Standalone(bytes) => bytes.to_string(),
Prefixed(prefix, bytes) => format!("{:.1}{}", bytes, prefix.symbol()),
1024 => match NumberPrefix::binary(value as f64) {
NumberPrefix::Standalone(bytes) => bytes.to_string(),
NumberPrefix::Prefixed(prefix, bytes) => format!("{:.1}{}", bytes, prefix.symbol()),
},
_ => crash!(EXIT_ERR, "Internal error: Unknown base value {}", base),
@ -760,7 +753,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(true)
.help(
"scale sizes by SIZE before printing them; e.g.\
'-BM' prints sizes in units of 1,048,576 bytes",
'-BM' prints sizes in units of 1,048,576 bytes",
),
)
.arg(
@ -817,7 +810,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.use_delimiter(true)
.help(
"use the output format defined by FIELD_LIST,\
or print all fields if FIELD_LIST is omitted.",
or print all fields if FIELD_LIST is omitted.",
),
)
.arg(
@ -854,21 +847,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.use_delimiter(true)
.help("limit listing to file systems not of type TYPE"),
)
.arg(
Arg::with_name(OPT_VERSION)
.short("v")
.long("version")
.help("output version information and exit"),
)
.arg(Arg::with_name(OPT_PATHS).multiple(true))
.help("Filesystem(s) to list")
.get_matches_from(args);
if matches.is_present(OPT_VERSION) {
println!("{} {}", executable!(), VERSION);
return EXIT_OK;
}
let paths: Vec<String> = matches
.values_of(OPT_PATHS)
.map(|v| v.map(ToString::to_string).collect())

View file

@ -1,6 +1,6 @@
[package]
name = "uu_dircolors"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "dircolors ~ (uutils) display commands to set LS_COLORS"
@ -16,8 +16,8 @@ path = "src/dircolors.rs"
[dependencies]
glob = "0.3.0"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "dircolors"

View file

@ -7,8 +7,6 @@
// spell-checker:ignore (ToDO) clrtoeol dircolors eightbit endcode fnmatch leftcode multihardlink rightcode setenv sgid suid
extern crate glob;
#[macro_use]
extern crate uucore;

View file

@ -1,6 +1,6 @@
[package]
name = "uu_dirname"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "dirname ~ (uutils) display parent directory of PATHNAME"
@ -15,9 +15,10 @@ edition = "2018"
path = "src/dirname.rs"
[dependencies]
clap = "2.33"
libc = "0.2.42"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "dirname"

View file

@ -8,32 +8,57 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::path::Path;
static NAME: &str = "dirname";
static SYNTAX: &str = "[OPTION] NAME...";
static SUMMARY: &str = "strip last component from file name";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static LONG_HELP: &str = "
Output each NAME with its last non-slash component and trailing slashes
removed; if NAME contains no /'s, output '.' (meaning the current
directory).
";
mod options {
pub const ZERO: &str = "zero";
pub const DIR: &str = "dir";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optflag("z", "zero", "separate output with NUL rather than newline")
.parse(args);
let matches = App::new(executable!())
.name(NAME)
.usage(SYNTAX)
.about(SUMMARY)
.after_help(LONG_HELP)
.version(VERSION)
.arg(
Arg::with_name(options::ZERO)
.short(options::ZERO)
.short("z")
.takes_value(false)
.help("separate output with NUL rather than newline"),
)
.arg(Arg::with_name(options::DIR).hidden(true).multiple(true))
.get_matches_from(args);
let separator = if matches.opt_present("zero") {
let separator = if matches.is_present(options::ZERO) {
"\0"
} else {
"\n"
};
if !matches.free.is_empty() {
for path in &matches.free {
let dirnames: Vec<String> = matches
.values_of(options::DIR)
.unwrap_or_default()
.map(str::to_owned)
.collect();
if !dirnames.is_empty() {
for path in dirnames.iter() {
let p = Path::new(path);
match p.parent() {
Some(d) => {
@ -54,8 +79,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
print!("{}", separator);
}
} else {
println!("{0}: missing operand", NAME);
println!("Try '{0} --help' for more information.", NAME);
show_usage_error!("missing operand");
return 1;
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_du"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "du ~ (uutils) display disk usage"
@ -16,8 +16,9 @@ path = "src/du.rs"
[dependencies]
time = "0.1.40"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
winapi = { version="0.3", features=[] }
[[bin]]
name = "du"

View file

@ -7,8 +7,6 @@
// spell-checker:ignore (ToDO) BLOCKSIZE inode inodes ment strs
extern crate time;
#[macro_use]
extern crate uucore;
@ -17,9 +15,24 @@ use std::env;
use std::fs;
use std::io::{stderr, Result, Write};
use std::iter;
#[cfg(not(windows))]
use std::os::unix::fs::MetadataExt;
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
#[cfg(windows)]
use std::os::windows::io::AsRawHandle;
use std::path::PathBuf;
use time::Timespec;
#[cfg(windows)]
use winapi::shared::minwindef::{DWORD, LPVOID};
#[cfg(windows)]
use winapi::um::fileapi::{FILE_ID_INFO, FILE_STANDARD_INFO};
#[cfg(windows)]
use winapi::um::minwinbase::{FileIdInfo, FileStandardInfo};
#[cfg(windows)]
use winapi::um::winbase::GetFileInformationByHandleEx;
#[cfg(windows)]
use winapi::um::winnt::{FILE_ID_128, ULONGLONG};
const NAME: &str = "du";
const SUMMARY: &str = "estimate file space usage";
@ -45,12 +58,18 @@ struct Options {
separate_dirs: bool,
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
struct FileInfo {
file_id: u128,
dev_id: u64,
}
struct Stat {
path: PathBuf,
is_dir: bool,
size: u64,
blocks: u64,
inode: u64,
inode: Option<FileInfo>,
created: u64,
accessed: u64,
modified: u64,
@ -59,19 +78,114 @@ struct Stat {
impl Stat {
fn new(path: PathBuf) -> Result<Stat> {
let metadata = fs::symlink_metadata(&path)?;
Ok(Stat {
#[cfg(not(windows))]
let file_info = FileInfo {
file_id: metadata.ino() as u128,
dev_id: metadata.dev(),
};
#[cfg(not(windows))]
return Ok(Stat {
path,
is_dir: metadata.is_dir(),
size: metadata.len(),
blocks: metadata.blocks() as u64,
inode: metadata.ino() as u64,
inode: Some(file_info),
created: metadata.mtime() as u64,
accessed: metadata.atime() as u64,
modified: metadata.mtime() as u64,
});
#[cfg(windows)]
let size_on_disk = get_size_on_disk(&path);
#[cfg(windows)]
let file_info = get_file_info(&path);
#[cfg(windows)]
Ok(Stat {
path,
is_dir: metadata.is_dir(),
size: metadata.len(),
blocks: size_on_disk / 1024 * 2,
inode: file_info,
created: windows_time_to_unix_time(metadata.creation_time()),
accessed: windows_time_to_unix_time(metadata.last_access_time()),
modified: windows_time_to_unix_time(metadata.last_write_time()),
})
}
}
#[cfg(windows)]
// https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.creation_time
// "The returned 64-bit value [...] which represents the number of 100-nanosecond intervals since January 1, 1601 (UTC)."
fn windows_time_to_unix_time(win_time: u64) -> u64 {
win_time / 10_000 - 11_644_473_600_000
}
#[cfg(windows)]
fn get_size_on_disk(path: &PathBuf) -> u64 {
let mut size_on_disk = 0;
// bind file so it stays in scope until end of function
// if it goes out of scope the handle below becomes invalid
let file = match fs::File::open(path) {
Ok(file) => file,
Err(_) => return size_on_disk, // opening directories will fail
};
let handle = file.as_raw_handle();
unsafe {
let mut file_info: FILE_STANDARD_INFO = core::mem::zeroed();
let file_info_ptr: *mut FILE_STANDARD_INFO = &mut file_info;
let success = GetFileInformationByHandleEx(
handle,
FileStandardInfo,
file_info_ptr as LPVOID,
std::mem::size_of::<FILE_STANDARD_INFO>() as DWORD,
);
if success != 0 {
size_on_disk = *file_info.AllocationSize.QuadPart() as u64;
}
}
size_on_disk
}
#[cfg(windows)]
fn get_file_info(path: &PathBuf) -> Option<FileInfo> {
let mut result = None;
let file = match fs::File::open(path) {
Ok(file) => file,
Err(_) => return result,
};
let handle = file.as_raw_handle();
unsafe {
let mut file_info: FILE_ID_INFO = core::mem::zeroed();
let file_info_ptr: *mut FILE_ID_INFO = &mut file_info;
let success = GetFileInformationByHandleEx(
handle,
FileIdInfo,
file_info_ptr as LPVOID,
std::mem::size_of::<FILE_ID_INFO>() as DWORD,
);
if success != 0 {
result = Some(FileInfo {
file_id: std::mem::transmute::<FILE_ID_128, u128>(file_info.FileId),
dev_id: std::mem::transmute::<ULONGLONG, u64>(file_info.VolumeSerialNumber),
});
}
}
result
}
fn unit_string_to_number(s: &str) -> Option<u64> {
let mut offset = 0;
let mut s_chars = s.chars().rev();
@ -139,7 +253,7 @@ fn du(
mut my_stat: Stat,
options: &Options,
depth: usize,
inodes: &mut HashSet<u64>,
inodes: &mut HashSet<FileInfo>,
) -> Box<dyn DoubleEndedIterator<Item = Stat>> {
let mut stats = vec![];
let mut futures = vec![];
@ -166,10 +280,13 @@ fn du(
if this_stat.is_dir {
futures.push(du(this_stat, options, depth + 1, inodes));
} else {
if inodes.contains(&this_stat.inode) {
continue;
if this_stat.inode.is_some() {
let inode = this_stat.inode.unwrap();
if inodes.contains(&inode) {
continue;
}
inodes.insert(inode);
}
inodes.insert(this_stat.inode);
my_stat.size += this_stat.size;
my_stat.blocks += this_stat.blocks;
if options.all {
@ -202,6 +319,9 @@ fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String {
return format!("{:.1}{}", (size as f64) / (limit as f64), unit);
}
}
if size == 0 {
return format!("0");
}
format!("{}B", size)
}
@ -420,7 +540,7 @@ Try '{} --help' for more information.",
let path = PathBuf::from(&path_str);
match Stat::new(path) {
Ok(stat) => {
let mut inodes: HashSet<u64> = HashSet::new();
let mut inodes: HashSet<FileInfo> = HashSet::new();
let iter = du(stat, &options, 0, &mut inodes);
let (_, len) = iter.size_hint();

View file

@ -1,6 +1,6 @@
[package]
name = "uu_echo"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "echo ~ (uutils) display TEXT"
@ -15,8 +15,9 @@ edition = "2018"
path = "src/echo.rs"
[dependencies]
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "echo"

View file

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

View file

@ -1,6 +1,6 @@
[package]
name = "uu_env"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND"
@ -18,8 +18,8 @@ path = "src/env.rs"
clap = "2.33"
libc = "0.2.42"
rust-ini = "0.13.0"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "env"

View file

@ -1,6 +1,6 @@
[package]
name = "uu_expand"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "expand ~ (uutils) convert input tabs to spaces"
@ -15,10 +15,10 @@ edition = "2018"
path = "src/expand.rs"
[dependencies]
getopts = "0.2.18"
clap = "2.33"
unicode-width = "0.1.5"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "expand"

View file

@ -9,30 +9,39 @@
// spell-checker:ignore (ToDO) ctype cwidth iflag nbytes nspaces nums tspaces uflag
extern crate getopts;
extern crate unicode_width;
#[macro_use]
extern crate uucore;
use clap::{App, Arg, ArgMatches};
use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
use std::iter::repeat;
use std::str::from_utf8;
use unicode_width::UnicodeWidthChar;
static SYNTAX: &str = "[OPTION]... [FILE]...";
static SUMMARY: &str = "Convert tabs in each FILE to spaces, writing to standard output.
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output.
With no FILE, or when FILE is -, read standard input.";
pub mod options {
pub static TABS: &str = "tabs";
pub static INITIAL: &str = "initial";
pub static NO_UTF8: &str = "no-utf8";
pub static FILES: &str = "FILES";
}
static LONG_HELP: &str = "";
static DEFAULT_TABSTOP: usize = 8;
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!())
}
fn tabstops_parse(s: String) -> Vec<usize> {
let words = s.split(',').collect::<Vec<&str>>();
let words = s.split(',');
let nums = words
.into_iter()
.map(|sn| {
sn.parse::<usize>()
.unwrap_or_else(|_| crash!(1, "{}\n", "tab size contains invalid character(s)"))
@ -62,14 +71,14 @@ struct Options {
}
impl Options {
fn new(matches: getopts::Matches) -> Options {
let tabstops = match matches.opt_str("t") {
fn new(matches: &ArgMatches) -> Options {
let tabstops = match matches.value_of(options::TABS) {
Some(s) => tabstops_parse(s.to_string()),
None => vec![DEFAULT_TABSTOP],
Some(s) => tabstops_parse(s),
};
let iflag = matches.opt_present("i");
let uflag = !matches.opt_present("U");
let iflag = matches.is_present(options::INITIAL);
let uflag = !matches.is_present(options::NO_UTF8);
// avoid allocations when dumping out long sequences of spaces
// by precomputing the longest string of spaces we will ever need
@ -84,10 +93,9 @@ impl Options {
.unwrap(); // length of tabstops is guaranteed >= 1
let tspaces = repeat(' ').take(nspaces).collect();
let files = if matches.free.is_empty() {
vec!["-".to_owned()]
} else {
matches.free
let files: Vec<String> = match matches.values_of(options::FILES) {
Some(s) => s.map(|v| v.to_string()).collect(),
None => vec!["-".to_owned()],
};
Options {
@ -101,31 +109,40 @@ impl Options {
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optflag("i", "initial", "do not convert tabs after non blanks")
.optopt(
"t",
"tabs",
"have tabs NUMBER characters apart, not 8",
"NUMBER",
let usage = get_usage();
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::INITIAL)
.long(options::INITIAL)
.short("i")
.help("do not convert tabs after non blanks"),
)
.optopt(
"t",
"tabs",
"use comma separated list of explicit tab positions",
"LIST",
.arg(
Arg::with_name(options::TABS)
.long(options::TABS)
.short("t")
.value_name("N, LIST")
.takes_value(true)
.help("have tabs N characters apart, not 8 or use comma separated list of explicit tab positions"),
)
.optflag(
"U",
"no-utf8",
"interpret input file as 8-bit ASCII rather than UTF-8",
.arg(
Arg::with_name(options::NO_UTF8)
.long(options::NO_UTF8)
.short("U")
.help("interpret input file as 8-bit ASCII rather than UTF-8"),
).arg(
Arg::with_name(options::FILES)
.multiple(true)
.hidden(true)
.takes_value(true)
)
.parse(args);
expand(Options::new(matches));
.get_matches_from(args);
expand(Options::new(&matches));
0
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_expr"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "expr ~ (uutils) display the value of EXPRESSION"
@ -17,8 +17,8 @@ path = "src/expr.rs"
[dependencies]
libc = "0.2.42"
onig = "~4.3.2"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "expr"

View file

@ -5,7 +5,6 @@
//* For the full copyright and license information, please view the LICENSE
//* file that was distributed with this source code.
extern crate onig;
#[macro_use]
extern crate uucore;
@ -41,7 +40,7 @@ fn process_expr(token_strings: &[String]) -> Result<String, String> {
fn print_expr_ok(expr_result: &str) -> i32 {
println!("{}", expr_result);
if expr_result == "0" || expr_result == "" {
if expr_result == "0" || expr_result.is_empty() {
1
} else {
0

View file

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

View file

@ -63,6 +63,8 @@ impl Token {
}
}
fn is_a_close_paren(&self) -> bool {
#[allow(clippy::match_like_matches_macro)]
// `matches!(...)` macro not stabilized until rust v1.42
match *self {
Token::ParClose => true,
_ => false,

View file

@ -1,6 +1,6 @@
[package]
name = "uu_factor"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "factor ~ (uutils) display the prime factors of each NUMBER"
@ -12,14 +12,15 @@ categories = ["command-line-utilities"]
edition = "2018"
[build-dependencies]
num-traits = "0.2" # used in src/numerics.rs, which is included by build.rs
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
[dependencies]
num-traits = "0.2"
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
rand = { version="0.7", features=["small_rng"] }
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
smallvec = { version="0.6.14, < 1.0" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[dev-dependencies]
criterion = "0.3"

View file

@ -29,6 +29,7 @@ use miller_rabin::is_prime;
#[path = "src/numeric/modular_inverse.rs"]
mod modular_inverse;
#[allow(unused_imports)] // imports there are used, but invisible from build.rs
#[path = "src/numeric/traits.rs"]
mod traits;
use modular_inverse::modular_inverse;

View file

@ -7,7 +7,7 @@
// spell-checker:ignore (ToDO) filts, minidx, minkey paridx
use std::iter::{Chain, Cycle, Map};
use std::iter::{Chain, Copied, Cycle};
use std::slice::Iter;
/// A lazy Sieve of Eratosthenes.
@ -68,27 +68,17 @@ impl Sieve {
#[allow(dead_code)]
#[inline]
pub fn primes() -> PrimeSieve {
#[allow(clippy::trivially_copy_pass_by_ref)]
fn deref(x: &u64) -> u64 {
*x
}
let deref = deref as fn(&u64) -> u64;
INIT_PRIMES.iter().map(deref).chain(Sieve::new())
INIT_PRIMES.iter().copied().chain(Sieve::new())
}
#[allow(dead_code)]
#[inline]
pub fn odd_primes() -> PrimeSieve {
#[allow(clippy::trivially_copy_pass_by_ref)]
fn deref(x: &u64) -> u64 {
*x
}
let deref = deref as fn(&u64) -> u64;
(&INIT_PRIMES[1..]).iter().map(deref).chain(Sieve::new())
(&INIT_PRIMES[1..]).iter().copied().chain(Sieve::new())
}
}
pub type PrimeSieve = Chain<Map<Iter<'static, u64>, fn(&u64) -> u64>, Sieve>;
pub type PrimeSieve = Chain<Copied<Iter<'static, u64>>, Sieve>;
/// An iterator that generates an infinite list of numbers that are
/// not divisible by any of 2, 3, 5, or 7.

View file

@ -5,8 +5,7 @@
// * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code.
extern crate rand;
use smallvec::SmallVec;
use std::cell::RefCell;
use std::fmt;
@ -16,11 +15,16 @@ use crate::{miller_rabin, rho, table};
type Exponent = u8;
#[derive(Clone, Debug)]
struct Decomposition(Vec<(u64, Exponent)>);
struct Decomposition(SmallVec<[(u64, Exponent); NUM_FACTORS_INLINE]>);
// The number of factors to inline directly into a `Decomposition` object.
// As a consequence of the ErdősKac theorem, the average number of prime factors
// of integers < 10²⁵ ≃ 2⁸³ is 4, so we can use a slightly higher value.
const NUM_FACTORS_INLINE: usize = 5;
impl Decomposition {
fn one() -> Decomposition {
Decomposition(Vec::new())
Decomposition(SmallVec::new())
}
fn add(&mut self, factor: u64, exp: Exponent) {
@ -141,10 +145,10 @@ pub fn factor(mut n: u64) -> Factors {
return factors;
}
let z = n.trailing_zeros();
if z > 0 {
factors.add(2, z as Exponent);
n >>= z;
let n_zeros = n.trailing_zeros();
if n_zeros > 0 {
factors.add(2, n_zeros as Exponent);
n >>= n_zeros;
}
if n == 1 {
@ -162,8 +166,20 @@ pub fn factor(mut n: u64) -> Factors {
#[cfg(test)]
mod tests {
use super::{factor, Factors};
use super::{factor, Decomposition, Exponent, Factors};
use quickcheck::quickcheck;
use smallvec::smallvec;
use std::cell::RefCell;
#[test]
fn factor_2044854919485649() {
let f = Factors(RefCell::new(Decomposition(smallvec![
(503, 1),
(2423, 1),
(40961, 2)
])));
assert_eq!(factor(f.product()), f);
}
#[test]
fn factor_recombines_small() {
@ -182,7 +198,7 @@ mod tests {
#[test]
fn factor_recombines_strong_pseudoprime() {
// This is a strong pseudoprime (wrt. miller_rabin::BASIS)
// and triggered a bug in rho::factor's codepath handling
// and triggered a bug in rho::factor's code path handling
// miller_rabbin::Result::Composite
let pseudoprime = 17179869183;
for _ in 0..20 {
@ -197,9 +213,15 @@ mod tests {
i == 0 || factor(i).product() == i
}
fn recombines_factors(f: Factors) -> bool {
fn recombines_factors(f: Factors) -> () {
assert_eq!(factor(f.product()), f);
true
}
fn exponentiate_factors(f: Factors, e: Exponent) -> () {
if e == 0 { return; }
if let Some(fe) = f.product().checked_pow(e.into()) {
assert_eq!(factor(fe), f ^ e);
}
}
}
}
@ -213,7 +235,7 @@ impl quickcheck::Arbitrary for Factors {
let mut n = u64::MAX;
// Adam Kalai's algorithm for generating uniformly-distributed
// integers and their factorisation.
// integers and their factorization.
//
// See Generating Random Factored Numbers, Easily, J. Cryptology (2003)
'attempt: loop {
@ -234,3 +256,19 @@ impl quickcheck::Arbitrary for Factors {
}
}
}
#[cfg(test)]
impl std::ops::BitXor<Exponent> for Factors {
type Output = Self;
fn bitxor(self, rhs: Exponent) -> Factors {
debug_assert_ne!(rhs, 0);
let mut r = Factors::one();
for (p, e) in self.0.borrow().0.iter() {
r.add(*p, rhs * e);
}
debug_assert_eq!(r.product(), self.product().pow(rhs.into()));
return r;
}
}

View file

@ -76,10 +76,13 @@ mod tests {
gcd(0, a) == a
}
fn divisor(a: u64, b: u64) -> bool {
// Test that gcd(a, b) divides a and b
fn divisor(a: u64, b: u64) -> () {
// Test that gcd(a, b) divides a and b, unless a == b == 0
if a == 0 && b == 0 { return; }
let g = gcd(a, b);
(g != 0 && a % g == 0 && b % g == 0) || (g == 0 && a == 0 && b == 0)
assert_eq!(a % g, 0);
assert_eq!(b % g, 0);
}
fn commutative(a: u64, b: u64) -> bool {

View file

@ -9,7 +9,6 @@ mod gcd;
pub use gcd::gcd;
pub(crate) mod traits;
use traits::{DoubleInt, Int, OverflowingAdd};
mod modular_inverse;
pub(crate) use modular_inverse::modular_inverse;

View file

@ -7,7 +7,8 @@
// * that was distributed with this source code.
use super::*;
use num_traits::identities::{One, Zero};
use traits::{DoubleInt, Int, One, OverflowingAdd, Zero};
pub(crate) trait Arithmetic: Copy + Sized {
// The type of integers mod m, in some opaque representation
@ -72,7 +73,7 @@ impl<T: DoubleInt> Montgomery<T> {
let Montgomery { a, n } = self;
let m = T::from_double_width(x).wrapping_mul(a);
let nm = (n.as_double_width()) * (m.as_double_width());
let (xnm, overflow) = x.overflowing_add_(nm); // x + n*m
let (xnm, overflow) = x.overflowing_add(&nm); // x + n*m
debug_assert_eq!(
xnm % (T::DoubleWidth::one() << T::zero().count_zeros() as usize),
T::DoubleWidth::zero()
@ -128,7 +129,7 @@ impl<T: DoubleInt> Arithmetic for Montgomery<T> {
}
fn add(&self, a: Self::ModInt, b: Self::ModInt) -> Self::ModInt {
let (r, overflow) = a.overflowing_add_(b);
let (r, overflow) = a.overflowing_add(&b);
// In case of overflow, a+b = 2⁶⁴ + r = (2⁶⁴ - n) + r (working mod n)
let r = if !overflow {

View file

@ -6,31 +6,16 @@
// * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code.
pub(crate) use num_traits::{
identities::{One, Zero},
ops::overflowing::OverflowingAdd,
};
use num_traits::{
int::PrimInt,
ops::wrapping::{WrappingMul, WrappingNeg, WrappingSub},
};
use std::fmt::{Debug, Display};
// NOTE: Trait can be removed once num-traits adds a similar one;
// see https://github.com/rust-num/num-traits/issues/168
pub(crate) trait OverflowingAdd: Sized {
fn overflowing_add_(self, n: Self) -> (Self, bool);
}
macro_rules! overflowing {
($x:ty) => {
impl OverflowingAdd for $x {
fn overflowing_add_(self, n: Self) -> (Self, bool) {
self.overflowing_add(n)
}
}
};
}
overflowing!(u32);
overflowing!(u64);
overflowing!(u128);
pub(crate) trait Int:
Display + Debug + PrimInt + OverflowingAdd + WrappingNeg + WrappingSub + WrappingMul
{

View file

@ -1,6 +1,6 @@
[package]
name = "uu_false"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "false ~ (uutils) do nothing and fail"
@ -15,8 +15,8 @@ edition = "2018"
path = "src/false.rs"
[dependencies]
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "false"

View file

@ -1,6 +1,6 @@
[package]
name = "uu_fmt"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "fmt ~ (uutils) reformat each paragraph of input"
@ -15,10 +15,11 @@ edition = "2018"
path = "src/fmt.rs"
[dependencies]
clap = "2.33"
libc = "0.2.42"
unicode-width = "0.1.5"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "fmt"

View file

@ -7,11 +7,10 @@
// spell-checker:ignore (ToDO) PSKIP linebreak ostream parasplit tabwidth xanti xprefix
extern crate unicode_width;
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::cmp;
use std::fs::File;
use std::io::{stdin, stdout, Write};
@ -32,10 +31,29 @@ macro_rules! silent_unwrap(
mod linebreak;
mod parasplit;
// program's NAME and VERSION are used for -V and -h
static SYNTAX: &str = "[OPTION]... [FILE]...";
static SUMMARY: &str = "Reformat paragraphs from input files (or stdin) to stdout.";
static LONG_HELP: &str = "";
static ABOUT: &str = "Reformat paragraphs from input files (or stdin) to stdout.";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static MAX_WIDTH: usize = 2500;
static OPT_CROWN_MARGIN: &str = "crown-margin";
static OPT_TAGGED_PARAGRAPH: &str = "tagged-paragraph";
static OPT_PRESERVE_HEADERS: &str = "preserve-headers";
static OPT_SPLIT_ONLY: &str = "split-only";
static OPT_UNIFORM_SPACING: &str = "uniform-spacing";
static OPT_PREFIX: &str = "prefix";
static OPT_SKIP_PREFIX: &str = "skip-prefix";
static OPT_EXACT_PREFIX: &str = "exact-prefix";
static OPT_EXACT_SKIP_PREFIX: &str = "exact-skip-prefix";
static OPT_WIDTH: &str = "width";
static OPT_GOAL: &str = "goal";
static OPT_QUICK: &str = "quick";
static OPT_TAB_WIDTH: &str = "tab-width";
static ARG_FILES: &str = "files";
fn get_usage() -> String {
format!("{} [OPTION]... [FILE]...", executable!())
}
pub type FileOrStdReader = BufReader<Box<dyn Read + 'static>>;
pub struct FmtOptions {
@ -58,23 +76,136 @@ pub struct FmtOptions {
#[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let usage = get_usage();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optflag("c", "crown-margin", "First and second line of paragraph may have different indentations, in which case the first line's indentation is preserved, and each subsequent line's indentation matches the second line.")
.optflag("t", "tagged-paragraph", "Like -c, except that the first and second line of a paragraph *must* have different indentation or they are treated as separate paragraphs.")
.optflag("m", "preserve-headers", "Attempt to detect and preserve mail headers in the input. Be careful when combining this flag with -p.")
.optflag("s", "split-only", "Split lines only, do not reflow.")
.optflag("u", "uniform-spacing", "Insert exactly one space between words, and two between sentences. Sentence breaks in the input are detected as [?!.] followed by two spaces or a newline; other punctuation is not interpreted as a sentence break.")
.optopt("p", "prefix", "Reformat only lines beginning with PREFIX, reattaching PREFIX to reformatted lines. Unless -x is specified, leading whitespace will be ignored when matching PREFIX.", "PREFIX")
.optopt("P", "skip-prefix", "Do not reformat lines beginning with PSKIP. Unless -X is specified, leading whitespace will be ignored when matching PSKIP", "PSKIP")
.optflag("x", "exact-prefix", "PREFIX must match at the beginning of the line with no preceding whitespace.")
.optflag("X", "exact-skip-prefix", "PSKIP must match at the beginning of the line with no preceding whitespace.")
.optopt("w", "width", "Fill output lines up to a maximum of WIDTH columns, default 79.", "WIDTH")
.optopt("g", "goal", "Goal width, default ~0.94*WIDTH. Must be less than WIDTH.", "GOAL")
.optflag("q", "quick", "Break lines more quickly at the expense of a potentially more ragged appearance.")
.optopt("T", "tab-width", "Treat tabs as TABWIDTH spaces for determining line length, default 8. Note that this is used only for calculating line lengths; tabs are preserved in the output.", "TABWIDTH")
.parse(args);
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(OPT_CROWN_MARGIN)
.short("c")
.long(OPT_CROWN_MARGIN)
.help(
"First and second line of paragraph
may have different indentations, in which
case the first line's indentation is preserved,
and each subsequent line's indentation matches the second line.",
),
)
.arg(
Arg::with_name(OPT_TAGGED_PARAGRAPH)
.short("t")
.long("tagged-paragraph")
.help(
"Like -c, except that the first and second line of a paragraph *must*
have different indentation or they are treated as separate paragraphs.",
),
)
.arg(
Arg::with_name(OPT_PRESERVE_HEADERS)
.short("m")
.long("preserve-headers")
.help(
"Attempt to detect and preserve mail headers in the input.
Be careful when combining this flag with -p.",
),
)
.arg(
Arg::with_name(OPT_SPLIT_ONLY)
.short("s")
.long("split-only")
.help("Split lines only, do not reflow."),
)
.arg(
Arg::with_name(OPT_UNIFORM_SPACING)
.short("u")
.long("uniform-spacing")
.help(
"Insert exactly one
space between words, and two between sentences.
Sentence breaks in the input are detected as [?!.]
followed by two spaces or a newline; other punctuation
is not interpreted as a sentence break.",
),
)
.arg(
Arg::with_name(OPT_PREFIX)
.short("p")
.long("prefix")
.help(
"Reformat only lines
beginning with PREFIX, reattaching PREFIX to reformatted lines.
Unless -x is specified, leading whitespace will be ignored
when matching PREFIX.",
)
.value_name("PREFIX"),
)
.arg(
Arg::with_name(OPT_SKIP_PREFIX)
.short("P")
.long("skip-prefix")
.help(
"Do not reformat lines
beginning with PSKIP. Unless -X is specified, leading whitespace
will be ignored when matching PSKIP",
)
.value_name("PSKIP"),
)
.arg(
Arg::with_name(OPT_EXACT_PREFIX)
.short("x")
.long("exact-prefix")
.help(
"PREFIX must match at the
beginning of the line with no preceding whitespace.",
),
)
.arg(
Arg::with_name(OPT_EXACT_SKIP_PREFIX)
.short("X")
.long("exact-skip-prefix")
.help(
"PSKIP must match at the
beginning of the line with no preceding whitespace.",
),
)
.arg(
Arg::with_name(OPT_WIDTH)
.short("w")
.long("width")
.help("Fill output lines up to a maximum of WIDTH columns, default 79.")
.value_name("WIDTH"),
)
.arg(
Arg::with_name(OPT_GOAL)
.short("g")
.long("goal")
.help("Goal width, default ~0.94*WIDTH. Must be less than WIDTH.")
.value_name("GOAL"),
)
.arg(Arg::with_name(OPT_QUICK).short("q").long("quick").help(
"Break lines more quickly at the
expense of a potentially more ragged appearance.",
))
.arg(
Arg::with_name(OPT_TAB_WIDTH)
.short("T")
.long("tab-width")
.help(
"Treat tabs as TABWIDTH spaces for
determining line length, default 8. Note that this is used only for
calculating line lengths; tabs are preserved in the output.",
)
.value_name("TABWIDTH"),
)
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
.get_matches_from(args);
let mut files: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let mut fmt_opts = FmtOptions {
crown: false,
@ -94,69 +225,64 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
tabwidth: 8,
};
if matches.opt_present("t") {
fmt_opts.tagged = true;
}
if matches.opt_present("c") {
fmt_opts.tagged = matches.is_present(OPT_TAGGED_PARAGRAPH);
if matches.is_present(OPT_CROWN_MARGIN) {
fmt_opts.crown = true;
fmt_opts.tagged = false;
}
if matches.opt_present("m") {
fmt_opts.mail = true;
}
if matches.opt_present("u") {
fmt_opts.uniform = true;
}
if matches.opt_present("q") {
fmt_opts.quick = true;
}
if matches.opt_present("s") {
fmt_opts.mail = matches.is_present(OPT_PRESERVE_HEADERS);
fmt_opts.uniform = matches.is_present(OPT_UNIFORM_SPACING);
fmt_opts.quick = matches.is_present(OPT_QUICK);
if matches.is_present(OPT_SPLIT_ONLY) {
fmt_opts.split_only = true;
fmt_opts.crown = false;
fmt_opts.tagged = false;
}
if matches.opt_present("x") {
fmt_opts.xprefix = true;
}
if matches.opt_present("X") {
fmt_opts.xanti_prefix = true;
}
fmt_opts.xprefix = matches.is_present(OPT_EXACT_PREFIX);
fmt_opts.xanti_prefix = matches.is_present(OPT_SKIP_PREFIX);
if let Some(s) = matches.opt_str("p") {
if let Some(s) = matches.value_of(OPT_PREFIX).map(String::from) {
fmt_opts.prefix = s;
fmt_opts.use_prefix = true;
};
if let Some(s) = matches.opt_str("P") {
if let Some(s) = matches.value_of(OPT_SKIP_PREFIX).map(String::from) {
fmt_opts.anti_prefix = s;
fmt_opts.use_anti_prefix = true;
};
if let Some(s) = matches.opt_str("w") {
if let Some(s) = matches.value_of(OPT_WIDTH) {
fmt_opts.width = match s.parse::<usize>() {
Ok(t) => t,
Err(e) => {
crash!(1, "Invalid WIDTH specification: `{}': {}", s, e);
}
};
if fmt_opts.width > MAX_WIDTH {
crash!(
1,
"invalid width: '{}': Numerical result out of range",
fmt_opts.width
);
}
fmt_opts.goal = cmp::min(fmt_opts.width * 94 / 100, fmt_opts.width - 3);
};
if let Some(s) = matches.opt_str("g") {
if let Some(s) = matches.value_of(OPT_GOAL) {
fmt_opts.goal = match s.parse::<usize>() {
Ok(t) => t,
Err(e) => {
crash!(1, "Invalid GOAL specification: `{}': {}", s, e);
}
};
if !matches.opt_present("w") {
if !matches.is_present(OPT_WIDTH) {
fmt_opts.width = cmp::max(fmt_opts.goal * 100 / 94, fmt_opts.goal + 3);
} else if fmt_opts.goal > fmt_opts.width {
crash!(1, "GOAL cannot be greater than WIDTH.");
}
};
if let Some(s) = matches.opt_str("T") {
if let Some(s) = matches.value_of(OPT_TAB_WIDTH) {
fmt_opts.tabwidth = match s.parse::<usize>() {
Ok(t) => t,
Err(e) => {
@ -172,7 +298,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// immutable now
let fmt_opts = fmt_opts;
let mut files = matches.free;
if files.is_empty() {
files.push("-".to_owned());
}

View file

@ -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,

View file

@ -264,6 +264,8 @@ impl<'a> ParagraphStream<'a> {
return false;
}
#[allow(clippy::match_like_matches_macro)]
// `matches!(...)` macro not stabilized until rust v1.42
l_slice[..colon_posn].chars().all(|x| match x as usize {
y if y < 33 || y > 126 => false,
_ => true,
@ -539,6 +541,8 @@ impl<'a> WordSplit<'a> {
}
fn is_punctuation(c: char) -> bool {
#[allow(clippy::match_like_matches_macro)]
// `matches!(...)` macro not stabilized until rust v1.42
match c {
'!' | '.' | '?' => true,
_ => false,

View file

@ -1,6 +1,6 @@
[package]
name = "uu_fold"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "fold ~ (uutils) wrap each line of input"
@ -15,8 +15,8 @@ edition = "2018"
path = "src/fold.rs"
[dependencies]
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "fold"

View file

@ -14,6 +14,8 @@ use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path;
const TAB_WIDTH: usize = 8;
static SYNTAX: &str = "[OPTION]... [FILE]...";
static SUMMARY: &str = "Writes each file (or standard input if no files are given)
to standard output whilst breaking long lines";
@ -79,7 +81,6 @@ fn handle_obsolete(args: &[String]) -> (Vec<String>, Option<String>) {
(args.to_vec(), None)
}
#[inline]
fn fold(filenames: Vec<String>, bytes: bool, spaces: bool, width: usize) {
for filename in &filenames {
let filename: &str = &filename;
@ -92,124 +93,176 @@ fn fold(filenames: Vec<String>, bytes: bool, spaces: bool, width: usize) {
file_buf = safe_unwrap!(File::open(Path::new(filename)));
&mut file_buf as &mut dyn Read
});
fold_file(buffer, bytes, spaces, width);
if bytes {
fold_file_bytewise(buffer, spaces, width);
} else {
fold_file(buffer, spaces, width);
}
}
}
#[inline]
fn fold_file<T: Read>(mut file: BufReader<T>, bytes: bool, spaces: bool, width: usize) {
/// Fold `file` to fit `width` (number of columns), counting all characters as
/// one column.
///
/// This function handles folding for the `-b`/`--bytes` option, counting
/// tab, backspace, and carriage return as occupying one column, identically
/// to all other characters in the stream.
///
/// If `spaces` is `true`, attempt to break lines at whitespace boundaries.
fn fold_file_bytewise<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) {
let mut line = String::new();
while safe_unwrap!(file.read_line(&mut line)) > 0 {
if bytes {
let len = line.len();
let mut i = 0;
while i < len {
let width = if len - i >= width { width } else { len - i };
let slice = {
let slice = &line[i..i + width];
if spaces && i + width < len {
match slice.rfind(char::is_whitespace) {
Some(m) => &slice[..=m],
None => slice,
}
} else {
slice
loop {
if let Ok(0) = file.read_line(&mut line) {
break;
}
if line == "\n" {
println!();
line.truncate(0);
continue;
}
let len = line.len();
let mut i = 0;
while i < len {
let width = if len - i >= width { width } else { len - i };
let slice = {
let slice = &line[i..i + width];
if spaces && i + width < len {
match slice.rfind(char::is_whitespace) {
Some(m) => &slice[..=m],
None => slice,
}
};
print!("{}", slice);
i += slice.len();
} else {
slice
}
};
// Don't duplicate trailing newlines: if the slice is "\n", the
// previous iteration folded just before the end of the line and
// has already printed this newline.
if slice == "\n" {
break;
}
} else {
let mut len = line.chars().count();
let newline = line.ends_with('\n');
if newline {
if len == 1 {
println!();
i += slice.len();
let at_eol = i >= len;
if at_eol {
print!("{}", slice);
} else {
println!("{}", slice);
}
}
line.truncate(0);
}
}
/// Fold `file` to fit `width` (number of columns).
///
/// By default `fold` treats tab, backspace, and carriage return specially:
/// tab characters count as 8 columns, backspace decreases the
/// column count, and carriage return resets the column count to 0.
///
/// If `spaces` is `true`, attempt to break lines at whitespace boundaries.
#[allow(unused_assignments)]
fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) {
let mut line = String::new();
let mut output = String::new();
let mut col_count = 0;
let mut char_count = 0;
let mut last_space = None;
/// Print the output line, resetting the column and character counts.
///
/// If `spaces` is `true`, print the output line up to the last
/// encountered whitespace character (inclusive) and set the remaining
/// characters as the start of the next line.
macro_rules! emit_output {
() => {
let consume = match last_space {
Some(i) => i + 1,
None => output.len(),
};
println!("{}", &output[..consume]);
output.replace_range(..consume, "");
char_count = output.len();
// we know there are no tabs left in output, so each char counts
// as 1 column
col_count = char_count;
last_space = None;
};
}
loop {
if let Ok(0) = file.read_line(&mut line) {
break;
}
for ch in line.chars() {
if ch == '\n' {
// make sure to _not_ split output at whitespace, since we
// know the entire output will fit
last_space = None;
emit_output!();
break;
}
if col_count >= width {
emit_output!();
}
match ch {
'\t' => {
let next_tab_stop = col_count + TAB_WIDTH - col_count % TAB_WIDTH;
if next_tab_stop > width && !output.is_empty() {
emit_output!();
}
col_count = next_tab_stop;
last_space = if spaces { Some(char_count) } else { None };
}
'\x08' => {
// FIXME: does not match GNU's handling of backspace
if col_count > 0 {
col_count -= 1;
char_count -= 1;
output.truncate(char_count);
}
continue;
}
len -= 1;
line.truncate(len);
}
let mut output = String::new();
let mut count = 0;
for (i, ch) in line.chars().enumerate() {
if count >= width {
let (val, ncount) = {
let slice = &output[..];
let (out, val, ncount) = if spaces && i + 1 < len {
match rfind_whitespace(slice) {
Some(m) => {
let routput = &slice[m + 1..slice.chars().count()];
let ncount = routput.chars().fold(0, |out, ch: char| {
out + match ch {
'\t' => 8,
'\x08' => {
if out > 0 {
!0
} else {
0
}
}
'\r' => return 0,
_ => 1,
}
});
(&slice[0..=m], routput, ncount)
}
None => (slice, "", 0),
}
} else {
(slice, "", 0)
};
println!("{}", out);
(val.to_owned(), ncount)
};
output = val;
count = ncount;
'\r' => {
// FIXME: does not match GNU's handling of carriage return
output.truncate(0);
col_count = 0;
char_count = 0;
continue;
}
match ch {
'\t' => {
count += 8;
if count > width {
println!("{}", output);
output.truncate(0);
count = 8;
}
}
'\x08' => {
if count > 0 {
count -= 1;
let len = output.len() - 1;
output.truncate(len);
}
continue;
}
'\r' => {
output.truncate(0);
count = 0;
continue;
}
_ => count += 1,
};
output.push(ch);
}
if count > 0 {
if newline {
println!("{}", output);
} else {
print!("{}", output);
_ if spaces && ch.is_whitespace() => {
last_space = Some(char_count);
col_count += 1
}
}
}
}
}
_ => col_count += 1,
};
#[inline]
fn rfind_whitespace(slice: &str) -> Option<usize> {
for (i, ch) in slice.chars().rev().enumerate() {
if ch.is_whitespace() {
return Some(slice.chars().count() - (i + 1));
output.push(ch);
char_count += 1;
}
if col_count > 0 {
print!("{}", output);
output.truncate(0);
}
line.truncate(0);
}
None
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_groups"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "groups ~ (uutils) display group memberships for USERNAME"
@ -15,9 +15,9 @@ edition = "2018"
path = "src/groups.rs"
[dependencies]
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["entries"] }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
clap = "2.32"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
clap = "2.33"
[[bin]]
name = "groups"

View file

@ -12,7 +12,6 @@
extern crate uucore;
use uucore::entries::{get_groups, gid2grp, Locate, Passwd};
extern crate clap;
use clap::{App, Arg};
static VERSION: &str = env!("CARGO_PKG_VERSION");

View file

@ -1,6 +1,6 @@
[package]
name = "uu_hashsum"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "hashsum ~ (uutils) display or check input digests"
@ -16,7 +16,7 @@ path = "src/hashsum.rs"
[dependencies]
digest = "0.6.2"
clap = "2"
clap = "2.33"
hex = "0.2.0"
libc = "0.2.42"
md5 = "0.3.5"
@ -25,8 +25,9 @@ regex-syntax = "0.6.7"
sha1 = "0.6.0"
sha2 = "0.6.0"
sha3 = "0.6.0"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
blake2-rfc = "0.2.18"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "hashsum"

View file

@ -1,3 +1,4 @@
extern crate blake2_rfc;
extern crate digest;
extern crate md5;
extern crate sha1;
@ -48,6 +49,29 @@ impl Digest for md5::Context {
}
}
impl Digest for blake2_rfc::blake2b::Blake2b {
fn new() -> Self {
blake2_rfc::blake2b::Blake2b::new(64)
}
fn input(&mut self, input: &[u8]) {
self.update(input);
}
fn result(&mut self, out: &mut [u8]) {
let hash_result = &self.clone().finalize();
out.copy_from_slice(&hash_result.as_bytes());
}
fn reset(&mut self) {
*self = blake2_rfc::blake2b::Blake2b::new(64);
}
fn output_bits(&self) -> usize {
512
}
}
impl Digest for sha1::Sha1 {
fn new() -> Self {
sha1::Sha1::new()

View file

@ -11,13 +11,6 @@
#[macro_use]
extern crate clap;
extern crate hex;
extern crate md5;
extern crate regex;
extern crate regex_syntax;
extern crate sha1;
extern crate sha2;
extern crate sha3;
#[macro_use]
extern crate uucore;
@ -26,6 +19,7 @@ mod digest;
use self::digest::Digest;
use blake2_rfc::blake2b::Blake2b;
use clap::{App, Arg, ArgMatches};
use hex::ToHex;
use md5::Context as Md5;
@ -57,10 +51,12 @@ struct Options {
}
fn is_custom_binary(program: &str) -> bool {
#[allow(clippy::match_like_matches_macro)]
// `matches!(...)` macro not stabilized until rust v1.42
match program {
"md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum"
| "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum"
| "shake128sum" | "shake256sum" => true,
| "shake128sum" | "shake256sum" | "b2sum" => true,
_ => false,
}
}
@ -80,6 +76,7 @@ fn detect_algo<'a>(
"sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box<dyn Digest>, 256),
"sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box<dyn Digest>, 384),
"sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512),
"b2sum" => ("BLAKE2", Box::new(Blake2b::new(64)) as Box<dyn Digest>, 512),
"sha3sum" => match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) {
Ok(224) => (
@ -180,6 +177,9 @@ fn detect_algo<'a>(
if matches.is_present("sha512") {
set_or_crash("SHA512", Box::new(Sha512::new()), 512)
}
if matches.is_present("b2sum") {
set_or_crash("BLAKE2", Box::new(Blake2b::new(64)), 512)
}
if matches.is_present("sha3") {
match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) {
@ -381,6 +381,7 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 {
"shake256",
"work with SHAKE256 using BITS for the output size",
),
("b2sum", "work with BLAKE2"),
];
for (name, desc) in algos {

View file

@ -1,6 +1,6 @@
[package]
name = "uu_head"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "head ~ (uutils) display the first lines of input"
@ -15,9 +15,9 @@ edition = "2018"
path = "src/head.rs"
[dependencies]
libc = "0.2.42"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "head"

View file

@ -1,223 +1,642 @@
// * This file is part of the uutils coreutils package.
// *
// * (c) Alan Andrade <alan.andradec@gmail.com>
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// *
// * Synced with: https://raw.github.com/avsm/src/master/usr.bin/head/head.c
use clap::{App, Arg};
use std::convert::TryFrom;
use std::ffi::OsString;
use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
use uucore::{crash, executable, show_error};
#[macro_use]
extern crate uucore;
const EXIT_FAILURE: i32 = 1;
const EXIT_SUCCESS: i32 = 0;
const BUF_SIZE: usize = 65536;
use std::collections::VecDeque;
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path;
use std::str::from_utf8;
const VERSION: &str = env!("CARGO_PKG_VERSION");
const ABOUT: &str = "\
Print the first 10 lines of each FILE to standard output.\n\
With more than one FILE, precede each with a header giving the file name.\n\
\n\
With no FILE, or when FILE is -, read standard input.\n\
\n\
Mandatory arguments to long flags are mandatory for short flags too.\
";
const USAGE: &str = "head [FLAG]... [FILE]...";
static SYNTAX: &str = "";
static SUMMARY: &str = "";
static LONG_HELP: &str = "";
mod options {
pub const BYTES_NAME: &str = "BYTES";
pub const LINES_NAME: &str = "LINES";
pub const QUIET_NAME: &str = "QUIET";
pub const VERBOSE_NAME: &str = "VERBOSE";
pub const ZERO_NAME: &str = "ZERO";
pub const FILES_NAME: &str = "FILE";
}
mod parse;
mod split;
enum FilterMode {
Bytes(usize),
fn app<'a>() -> App<'a, 'a> {
App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(USAGE)
.arg(
Arg::with_name(options::BYTES_NAME)
.short("c")
.long("bytes")
.value_name("[-]NUM")
.takes_value(true)
.help(
"\
print the first NUM bytes of each file;\n\
with the leading '-', print all but the last\n\
NUM bytes of each file\
",
)
.overrides_with_all(&[options::BYTES_NAME, options::LINES_NAME])
.allow_hyphen_values(true),
)
.arg(
Arg::with_name(options::LINES_NAME)
.short("n")
.long("lines")
.value_name("[-]NUM")
.takes_value(true)
.help(
"\
print the first NUM lines instead of the first 10;\n\
with the leading '-', print all but the last\n\
NUM lines of each file\
",
)
.overrides_with_all(&[options::LINES_NAME, options::BYTES_NAME])
.allow_hyphen_values(true),
)
.arg(
Arg::with_name(options::QUIET_NAME)
.short("q")
.long("--quiet")
.visible_alias("silent")
.help("never print headers giving file names")
.overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]),
)
.arg(
Arg::with_name(options::VERBOSE_NAME)
.short("v")
.long("verbose")
.help("always print headers giving file names")
.overrides_with_all(&[options::QUIET_NAME, options::VERBOSE_NAME]),
)
.arg(
Arg::with_name(options::ZERO_NAME)
.short("z")
.long("zero-terminated")
.help("line delimiter is NUL, not newline")
.overrides_with(options::ZERO_NAME),
)
.arg(Arg::with_name(options::FILES_NAME).multiple(true))
}
#[derive(PartialEq, Debug, Clone, Copy)]
enum Modes {
Lines(usize),
NLines(usize),
Bytes(usize),
}
struct Settings {
mode: FilterMode,
verbose: bool,
fn parse_mode<F>(src: &str, closure: F) -> Result<(Modes, bool), String>
where
F: FnOnce(usize) -> Modes,
{
match parse::parse_num(src) {
Ok((n, last)) => Ok((closure(n), last)),
Err(reason) => match reason {
parse::ParseError::Syntax => Err(format!("'{}'", src)),
parse::ParseError::Overflow => {
Err(format!("'{}': Value too large for defined datatype", src))
}
},
}
}
impl Default for Settings {
fn default() -> Settings {
Settings {
mode: FilterMode::Lines(10),
verbose: false,
fn arg_iterate<'a>(
mut args: impl uucore::Args + 'a,
) -> Result<Box<dyn Iterator<Item = OsString> + 'a>, String> {
// argv[0] is always present
let first = args.next().unwrap();
if let Some(second) = args.next() {
if let Some(s) = second.to_str() {
match parse::parse_obsolete(s) {
Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
Some(Err(e)) => match e {
parse::ParseError::Syntax => Err(format!("bad argument format: '{}'", s)),
parse::ParseError::Overflow => Err(format!(
"invalid argument: '{}' Value too large for defined datatype",
s
)),
},
None => Ok(Box::new(vec![first, second].into_iter().chain(args))),
}
} else {
Err("bad argument encoding".to_owned())
}
} else {
Ok(Box::new(vec![first].into_iter()))
}
}
#[derive(Debug, PartialEq)]
struct HeadOptions {
pub quiet: bool,
pub verbose: bool,
pub zeroed: bool,
pub all_but_last: bool,
pub mode: Modes,
pub files: Vec<String>,
}
impl HeadOptions {
pub fn new() -> HeadOptions {
HeadOptions {
quiet: false,
verbose: false,
zeroed: false,
all_but_last: false,
mode: Modes::Lines(10),
files: Vec::new(),
}
}
///Construct options from matches
pub fn get_from(args: impl uucore::Args) -> Result<Self, String> {
let matches = app().get_matches_from(arg_iterate(args)?);
let mut options = HeadOptions::new();
options.quiet = matches.is_present(options::QUIET_NAME);
options.verbose = matches.is_present(options::VERBOSE_NAME);
options.zeroed = matches.is_present(options::ZERO_NAME);
let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) {
match parse_mode(v, Modes::Bytes) {
Ok(v) => v,
Err(err) => {
return Err(format!("invalid number of bytes: {}", err));
}
}
} else if let Some(v) = matches.value_of(options::LINES_NAME) {
match parse_mode(v, Modes::Lines) {
Ok(v) => v,
Err(err) => {
return Err(format!("invalid number of lines: {}", err));
}
}
} else {
(Modes::Lines(10), false)
};
options.mode = mode_and_from_end.0;
options.all_but_last = mode_and_from_end.1;
options.files = match matches.values_of(options::FILES_NAME) {
Some(v) => v.map(|s| s.to_owned()).collect(),
None => vec!["-".to_owned()],
};
//println!("{:#?}", options);
Ok(options)
}
}
// to make clippy shut up
impl Default for HeadOptions {
fn default() -> Self {
Self::new()
}
}
fn rbuf_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> {
if n == 0 {
return Ok(());
}
let mut readbuf = [0u8; BUF_SIZE];
let mut i = 0usize;
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
loop {
let read = loop {
match input.read(&mut readbuf) {
Ok(n) => break n,
Err(e) => match e.kind() {
ErrorKind::Interrupted => {}
_ => return Err(e),
},
}
};
if read == 0 {
// might be unexpected if
// we haven't read `n` bytes
// but this mirrors GNU's behavior
return Ok(());
}
stdout.write_all(&readbuf[..read.min(n - i)])?;
i += read.min(n - i);
if i == n {
return Ok(());
}
}
}
fn rbuf_n_lines(input: &mut impl std::io::BufRead, n: usize, zero: bool) -> std::io::Result<()> {
if n == 0 {
return Ok(());
}
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
let mut lines = 0usize;
split::walk_lines(input, zero, |e| match e {
split::Event::Data(dat) => {
stdout.write_all(dat)?;
Ok(true)
}
split::Event::Line => {
lines += 1;
if lines == n {
Ok(false)
} else {
Ok(true)
}
}
})
}
fn rbuf_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> {
if n == 0 {
//prints everything
return rbuf_n_bytes(input, std::usize::MAX);
}
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
let mut ringbuf = vec![0u8; n];
// first we fill the ring buffer
if let Err(e) = input.read_exact(&mut ringbuf) {
if e.kind() == ErrorKind::UnexpectedEof {
return Ok(());
} else {
return Err(e);
}
}
let mut buffer = [0u8; BUF_SIZE];
loop {
let read = loop {
match input.read(&mut buffer) {
Ok(n) => break n,
Err(e) => match e.kind() {
ErrorKind::Interrupted => {}
_ => return Err(e),
},
}
};
if read == 0 {
return Ok(());
} else if read >= n {
stdout.write_all(&ringbuf)?;
stdout.write_all(&buffer[..read - n])?;
for i in 0..n {
ringbuf[i] = buffer[read - n + i];
}
} else {
stdout.write_all(&ringbuf[..read])?;
for i in 0..n - read {
ringbuf[i] = ringbuf[read + i];
}
ringbuf[n - read..].copy_from_slice(&buffer[..read]);
}
}
}
fn rbuf_but_last_n_lines(
input: &mut impl std::io::BufRead,
n: usize,
zero: bool,
) -> std::io::Result<()> {
if n == 0 {
//prints everything
return rbuf_n_bytes(input, std::usize::MAX);
}
let mut ringbuf = vec![Vec::new(); n];
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
let mut line = Vec::new();
let mut lines = 0usize;
split::walk_lines(input, zero, |e| match e {
split::Event::Data(dat) => {
line.extend_from_slice(dat);
Ok(true)
}
split::Event::Line => {
if lines < n {
ringbuf[lines] = std::mem::replace(&mut line, Vec::new());
lines += 1;
} else {
stdout.write_all(&ringbuf[0])?;
ringbuf.rotate_left(1);
ringbuf[n - 1] = std::mem::replace(&mut line, Vec::new());
}
Ok(true)
}
})
}
fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {
assert!(options.all_but_last);
let size = input.seek(SeekFrom::End(0))?;
let size = usize::try_from(size).unwrap();
match options.mode {
Modes::Bytes(n) => {
if n >= size {
return Ok(());
} else {
input.seek(SeekFrom::Start(0))?;
rbuf_n_bytes(
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
size - n,
)?;
}
}
Modes::Lines(n) => {
let mut buffer = [0u8; BUF_SIZE];
let buffer = &mut buffer[..BUF_SIZE.min(size)];
let mut i = 0usize;
let mut lines = 0usize;
let found = 'o: loop {
// the casts here are ok, `buffer.len()` should never be above a few k
input.seek(SeekFrom::Current(
-((buffer.len() as i64).min((size - i) as i64)),
))?;
input.read_exact(buffer)?;
for byte in buffer.iter().rev() {
match byte {
b'\n' if !options.zeroed => {
lines += 1;
}
0u8 if options.zeroed => {
lines += 1;
}
_ => {}
}
// if it were just `n`,
if lines == n + 1 {
break 'o i;
}
i += 1;
}
if size - i == 0 {
return Ok(());
}
};
input.seek(SeekFrom::Start(0))?;
rbuf_n_bytes(
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
size - found,
)?;
}
}
Ok(())
}
fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {
if options.all_but_last {
head_backwards_file(input, options)
} else {
match options.mode {
Modes::Bytes(n) => {
rbuf_n_bytes(&mut std::io::BufReader::with_capacity(BUF_SIZE, input), n)
}
Modes::Lines(n) => rbuf_n_lines(
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
n,
options.zeroed,
),
}
}
}
fn uu_head(options: &HeadOptions) {
let mut first = true;
for fname in &options.files {
let res = match fname.as_str() {
"-" => {
if options.verbose {
if !first {
println!();
}
println!("==> standard input <==")
}
let stdin = std::io::stdin();
let mut stdin = stdin.lock();
match options.mode {
Modes::Bytes(n) => {
if options.all_but_last {
rbuf_but_last_n_bytes(&mut stdin, n)
} else {
rbuf_n_bytes(&mut stdin, n)
}
}
Modes::Lines(n) => {
if options.all_but_last {
rbuf_but_last_n_lines(&mut stdin, n, options.zeroed)
} else {
rbuf_n_lines(&mut stdin, n, options.zeroed)
}
}
}
}
name => {
let mut file = match std::fs::File::open(name) {
Ok(f) => f,
Err(err) => match err.kind() {
ErrorKind::NotFound => {
crash!(
EXIT_FAILURE,
"head: cannot open '{}' for reading: No such file or directory",
name
);
}
ErrorKind::PermissionDenied => {
crash!(
EXIT_FAILURE,
"head: cannot open '{}' for reading: Permission denied",
name
);
}
_ => {
crash!(
EXIT_FAILURE,
"head: cannot open '{}' for reading: {}",
name,
err
);
}
},
};
if (options.files.len() > 1 && !options.quiet) || options.verbose {
println!("==> {} <==", name)
}
head_file(&mut file, options)
}
};
if res.is_err() {
if fname.as_str() == "-" {
crash!(
EXIT_FAILURE,
"head: error reading standard input: Input/output error"
);
} else {
crash!(
EXIT_FAILURE,
"head: error reading {}: Input/output error",
fname
);
}
}
first = false;
}
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let mut settings: Settings = Default::default();
// handle obsolete -number syntax
let new_args = match obsolete(&args[0..]) {
(args, Some(n)) => {
settings.mode = FilterMode::Lines(n);
args
}
(args, None) => args,
};
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optopt(
"c",
"bytes",
"Print the first K bytes. With the leading '-', print all but the last K bytes",
"[-]K",
)
.optopt(
"n",
"lines",
"Print the first K lines. With the leading '-', print all but the last K lines",
"[-]K",
)
.optflag("q", "quiet", "never print headers giving file names")
.optflag("v", "verbose", "always print headers giving file names")
.optflag("h", "help", "display this help and exit")
.optflag("V", "version", "output version information and exit")
.parse(new_args);
let use_bytes = matches.opt_present("c");
// TODO: suffixes (e.g. b, kB, etc.)
match matches.opt_str("n") {
Some(n) => {
if use_bytes {
show_error!("cannot specify both --bytes and --lines.");
return 1;
}
match n.parse::<isize>() {
Ok(m) => {
settings.mode = if m < 0 {
let m: usize = m.abs() as usize;
FilterMode::NLines(m)
} else {
let m: usize = m.abs() as usize;
FilterMode::Lines(m)
}
}
Err(e) => {
show_error!("invalid line count '{}': {}", n, e);
return 1;
}
}
}
None => {
if let Some(count) = matches.opt_str("c") {
match count.parse::<usize>() {
Ok(m) => settings.mode = FilterMode::Bytes(m),
Err(e) => {
show_error!("invalid byte count '{}': {}", count, e);
return 1;
}
}
}
let args = match HeadOptions::get_from(args) {
Ok(o) => o,
Err(s) => {
crash!(EXIT_FAILURE, "head: {}", s);
}
};
uu_head(&args);
let quiet = matches.opt_present("q");
let verbose = matches.opt_present("v");
let files = matches.free;
// GNU implementation allows multiple declarations of "-q" and "-v" with the
// last flag winning. This can't be simulated with the getopts cargo unless
// we manually parse the arguments. Given the declaration of both flags,
// verbose mode always wins. This is a potential future improvement.
if files.len() > 1 && !quiet && !verbose {
settings.verbose = true;
}
if quiet {
settings.verbose = false;
}
if verbose {
settings.verbose = true;
}
if files.is_empty() {
let mut buffer = BufReader::new(stdin());
head(&mut buffer, &settings);
} else {
let mut first_time = true;
for file in &files {
if settings.verbose {
if !first_time {
println!();
}
println!("==> {} <==", file);
}
first_time = false;
let path = Path::new(file);
let reader = File::open(&path).unwrap();
let mut buffer = BufReader::new(reader);
if !head(&mut buffer, &settings) {
break;
}
}
}
0
EXIT_SUCCESS
}
// It searches for an option in the form of -123123
//
// In case is found, the options vector will get rid of that object so that
// getopts works correctly.
fn obsolete(options: &[String]) -> (Vec<String>, Option<usize>) {
let mut options: Vec<String> = options.to_vec();
let mut a = 1;
let b = options.len();
#[cfg(test)]
mod tests {
use std::ffi::OsString;
while a < b {
let previous = options[a - 1].clone();
let current = options[a].clone();
let current = current.as_bytes();
if previous != "-n" && current.len() > 1 && current[0] == b'-' {
let len = current.len();
for pos in 1..len {
// Ensure that the argument is only made out of digits
if !(current[pos] as char).is_numeric() {
break;
}
// If this is the last number
if pos == len - 1 {
options.remove(a);
let number: Option<usize> =
from_utf8(&current[1..len]).unwrap().parse::<usize>().ok();
return (options, Some(number.unwrap()));
}
}
}
a += 1;
use super::*;
fn options(args: &str) -> Result<HeadOptions, String> {
let combined = "head ".to_owned() + args;
let args = combined.split_whitespace();
HeadOptions::get_from(args.map(|s| OsString::from(s)))
}
#[test]
fn test_args_modes() {
let args = options("-n -10M -vz").unwrap();
assert!(args.zeroed);
assert!(args.verbose);
assert!(args.all_but_last);
assert_eq!(args.mode, Modes::Lines(10 * 1024 * 1024));
}
#[test]
fn test_gnu_compatibility() {
let args = options("-n 1 -c 1 -n 5 -c kiB -vqvqv").unwrap();
assert!(args.mode == Modes::Bytes(1024));
assert!(args.verbose);
assert_eq!(options("-5").unwrap().mode, Modes::Lines(5));
assert_eq!(options("-2b").unwrap().mode, Modes::Bytes(1024));
assert_eq!(options("-5 -c 1").unwrap().mode, Modes::Bytes(1));
}
#[test]
fn all_args_test() {
assert!(options("--silent").unwrap().quiet);
assert!(options("--quiet").unwrap().quiet);
assert!(options("-q").unwrap().quiet);
assert!(options("--verbose").unwrap().verbose);
assert!(options("-v").unwrap().verbose);
assert!(options("--zero-terminated").unwrap().zeroed);
assert!(options("-z").unwrap().zeroed);
assert_eq!(options("--lines 15").unwrap().mode, Modes::Lines(15));
assert_eq!(options("-n 15").unwrap().mode, Modes::Lines(15));
assert_eq!(options("--bytes 15").unwrap().mode, Modes::Bytes(15));
assert_eq!(options("-c 15").unwrap().mode, Modes::Bytes(15));
}
#[test]
fn test_options_errors() {
assert!(options("-n IsThisTheRealLife?").is_err());
assert!(options("-c IsThisJustFantasy").is_err());
}
#[test]
fn test_options_correct_defaults() {
let opts = HeadOptions::new();
let opts2: HeadOptions = Default::default();
(options, None)
}
assert_eq!(opts, opts2);
// TODO: handle errors on read
fn head<T: Read>(reader: &mut BufReader<T>, settings: &Settings) -> bool {
match settings.mode {
FilterMode::Bytes(count) => {
for byte in reader.bytes().take(count) {
print!("{}", byte.unwrap() as char);
}
}
FilterMode::Lines(count) => {
for line in reader.lines().take(count) {
println!("{}", line.unwrap());
}
}
FilterMode::NLines(count) => {
let mut vector: VecDeque<String> = VecDeque::new();
for line in reader.lines() {
vector.push_back(line.unwrap());
if vector.len() <= count {
continue;
}
println!("{}", vector.pop_front().unwrap());
assert!(opts.verbose == false);
assert!(opts.quiet == false);
assert!(opts.zeroed == false);
assert!(opts.all_but_last == false);
assert_eq!(opts.mode, Modes::Lines(10));
assert!(opts.files.is_empty());
}
#[test]
fn test_parse_mode() {
assert_eq!(
parse_mode("123", Modes::Lines),
Ok((Modes::Lines(123), false))
);
assert_eq!(
parse_mode("-456", Modes::Bytes),
Ok((Modes::Bytes(456), true))
);
assert!(parse_mode("Nonsensical Nonsense", Modes::Bytes).is_err());
#[cfg(target_pointer_width = "64")]
assert!(parse_mode("1Y", Modes::Lines).is_err());
#[cfg(target_pointer_width = "32")]
assert!(parse_mode("1T", Modes::Bytes).is_err());
}
fn arg_outputs(src: &str) -> Result<String, String> {
let split = src.split_whitespace().map(|x| OsString::from(x));
match arg_iterate(split) {
Ok(args) => {
let vec = args
.map(|s| s.to_str().unwrap().to_owned())
.collect::<Vec<_>>();
Ok(vec.join(" "))
}
Err(e) => Err(e),
}
}
true
#[test]
fn test_arg_iterate() {
// test that normal args remain unchanged
assert_eq!(
arg_outputs("head -n -5 -zv"),
Ok("head -n -5 -zv".to_owned())
);
// tests that nonsensical args are unchanged
assert_eq!(
arg_outputs("head -to_be_or_not_to_be,..."),
Ok("head -to_be_or_not_to_be,...".to_owned())
);
//test that the obsolete syntax is unrolled
assert_eq!(
arg_outputs("head -123qvqvqzc"),
Ok("head -q -z -c 123".to_owned())
);
//test that bad obsoletes are an error
assert!(arg_outputs("head -123FooBar").is_err());
//test overflow
assert!(arg_outputs("head -100000000000000000000000000000000000000000").is_err());
//test that empty args remain unchanged
assert_eq!(arg_outputs("head"), Ok("head".to_owned()));
}
#[test]
#[cfg(linux)]
fn test_arg_iterate_bad_encoding() {
let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") };
// this arises from a conversion from OsString to &str
assert!(
arg_iterate(vec![OsString::from("head"), OsString::from(invalid)].into_iter()).is_err()
);
}
#[test]
fn rbuf_early_exit() {
let mut empty = std::io::BufReader::new(std::io::Cursor::new(Vec::new()));
assert!(rbuf_n_bytes(&mut empty, 0).is_ok());
assert!(rbuf_n_lines(&mut empty, 0, false).is_ok());
}
}

282
src/uu/head/src/parse.rs Normal file
View file

@ -0,0 +1,282 @@
use std::convert::TryFrom;
use std::ffi::OsString;
#[derive(PartialEq, Debug)]
pub enum ParseError {
Syntax,
Overflow,
}
/// Parses obsolete syntax
/// head -NUM[kmzv]
pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>, ParseError>> {
let mut chars = src.char_indices();
if let Some((_, '-')) = chars.next() {
let mut num_end = 0usize;
let mut has_num = false;
let mut last_char = 0 as char;
while let Some((n, c)) = chars.next() {
if c.is_numeric() {
has_num = true;
num_end = n;
} else {
last_char = c;
break;
}
}
if has_num {
match src[1..=num_end].parse::<usize>() {
Ok(num) => {
let mut quiet = false;
let mut verbose = false;
let mut zero_terminated = false;
let mut multiplier = None;
let mut c = last_char;
loop {
// not that here, we only match lower case 'k', 'c', and 'm'
match c {
// we want to preserve order
// this also saves us 1 heap allocation
'q' => {
quiet = true;
verbose = false
}
'v' => {
verbose = true;
quiet = false
}
'z' => zero_terminated = true,
'c' => multiplier = Some(1),
'b' => multiplier = Some(512),
'k' => multiplier = Some(1024),
'm' => multiplier = Some(1024 * 1024),
'\0' => {}
_ => return Some(Err(ParseError::Syntax)),
}
if let Some((_, next)) = chars.next() {
c = next
} else {
break;
}
}
let mut options = Vec::new();
if quiet {
options.push(OsString::from("-q"))
}
if verbose {
options.push(OsString::from("-v"))
}
if zero_terminated {
options.push(OsString::from("-z"))
}
if let Some(n) = multiplier {
options.push(OsString::from("-c"));
let num = match num.checked_mul(n) {
Some(n) => n,
None => return Some(Err(ParseError::Overflow)),
};
options.push(OsString::from(format!("{}", num)));
} else {
options.push(OsString::from("-n"));
options.push(OsString::from(format!("{}", num)));
}
Some(Ok(options.into_iter()))
}
Err(_) => Some(Err(ParseError::Overflow)),
}
} else {
None
}
} else {
None
}
}
/// Parses an -c or -n argument,
/// the bool specifies whether to read from the end
pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> {
let mut num_start = 0;
let mut chars = src.char_indices();
let (mut chars, all_but_last) = match chars.next() {
Some((_, c)) => {
if c == '-' {
num_start += 1;
(chars, true)
} else {
(src.char_indices(), false)
}
}
None => return Err(ParseError::Syntax),
};
let mut num_end = 0usize;
let mut last_char = 0 as char;
let mut num_count = 0usize;
while let Some((n, c)) = chars.next() {
if c.is_numeric() {
num_end = n;
num_count += 1;
} else {
last_char = c;
break;
}
}
let num = if num_count > 0 {
match src[num_start..=num_end].parse::<usize>() {
Ok(n) => Some(n),
Err(_) => return Err(ParseError::Overflow),
}
} else {
None
};
if last_char == 0 as char {
if let Some(n) = num {
Ok((n, all_but_last))
} else {
Err(ParseError::Syntax)
}
} else {
let base: u128 = match chars.next() {
Some((_, c)) => {
let b = match c {
'B' if last_char != 'b' => 1000,
'i' if last_char != 'b' => {
if let Some((_, 'B')) = chars.next() {
1024
} else {
return Err(ParseError::Syntax);
}
}
_ => return Err(ParseError::Syntax),
};
if chars.next().is_some() {
return Err(ParseError::Syntax);
} else {
b
}
}
None => 1024,
};
let mul = match last_char.to_lowercase().next().unwrap() {
'b' => 512,
'k' => base.pow(1),
'm' => base.pow(2),
'g' => base.pow(3),
't' => base.pow(4),
'p' => base.pow(5),
'e' => base.pow(6),
'z' => base.pow(7),
'y' => base.pow(8),
_ => return Err(ParseError::Syntax),
};
let mul = match usize::try_from(mul) {
Ok(n) => n,
Err(_) => return Err(ParseError::Overflow),
};
match num.unwrap_or(1).checked_mul(mul) {
Some(n) => Ok((n, all_but_last)),
None => Err(ParseError::Overflow),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn obsolete(src: &str) -> Option<Result<Vec<String>, ParseError>> {
let r = parse_obsolete(src);
match r {
Some(s) => match s {
Ok(v) => Some(Ok(v.map(|s| s.to_str().unwrap().to_owned()).collect())),
Err(e) => Some(Err(e)),
},
None => None,
}
}
fn obsolete_result(src: &[&str]) -> Option<Result<Vec<String>, ParseError>> {
Some(Ok(src.iter().map(|s| s.to_string()).collect()))
}
#[test]
#[cfg(not(target_pointer_width = "128"))]
fn test_parse_overflow_x64() {
assert_eq!(parse_num("1Y"), Err(ParseError::Overflow));
assert_eq!(parse_num("1Z"), Err(ParseError::Overflow));
assert_eq!(parse_num("100E"), Err(ParseError::Overflow));
assert_eq!(parse_num("100000P"), Err(ParseError::Overflow));
assert_eq!(parse_num("1000000000T"), Err(ParseError::Overflow));
assert_eq!(
parse_num("10000000000000000000000"),
Err(ParseError::Overflow)
);
}
#[test]
#[cfg(target_pointer_width = "32")]
fn test_parse_overflow_x32() {
assert_eq!(parse_num("1T"), Err(ParseError::Overflow));
assert_eq!(parse_num("1000G"), Err(ParseError::Overflow));
}
#[test]
fn test_parse_bad_syntax() {
assert_eq!(parse_num("5MiB nonsense"), Err(ParseError::Syntax));
assert_eq!(parse_num("Nonsense string"), Err(ParseError::Syntax));
assert_eq!(parse_num("5mib"), Err(ParseError::Syntax));
assert_eq!(parse_num("biB"), Err(ParseError::Syntax));
assert_eq!(parse_num("-"), Err(ParseError::Syntax));
assert_eq!(parse_num(""), Err(ParseError::Syntax));
}
#[test]
fn test_parse_numbers() {
assert_eq!(parse_num("k"), Ok((1024, false)));
assert_eq!(parse_num("MiB"), Ok((1024 * 1024, false)));
assert_eq!(parse_num("-5"), Ok((5, true)));
assert_eq!(parse_num("b"), Ok((512, false)));
assert_eq!(parse_num("-2GiB"), Ok((2 * 1024 * 1024 * 1024, true)));
assert_eq!(parse_num("5M"), Ok((5 * 1024 * 1024, false)));
assert_eq!(parse_num("5MB"), Ok((5 * 1000 * 1000, false)));
}
#[test]
fn test_parse_numbers_obsolete() {
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));
assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "5242880"]));
assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "1024"]));
assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "1024"]));
assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "1024"]));
assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "1"]));
assert_eq!(
obsolete("-1vzqvq"),
obsolete_result(&["-q", "-z", "-n", "1"])
);
assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "1"]));
assert_eq!(
obsolete("-105kzm"),
obsolete_result(&["-z", "-c", "110100480"])
);
}
#[test]
fn test_parse_errors_obsolete() {
assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax)));
assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax)));
}
#[test]
fn test_parse_obsolete_nomatch() {
assert_eq!(obsolete("-k"), None);
assert_eq!(obsolete("asd"), None);
}
#[test]
#[cfg(target_pointer_width = "64")]
fn test_parse_obsolete_overflow_x64() {
assert_eq!(
obsolete("-1000000000000000m"),
Some(Err(ParseError::Overflow))
);
assert_eq!(
obsolete("-10000000000000000000000"),
Some(Err(ParseError::Overflow))
);
}
#[test]
#[cfg(target_pointer_width = "32")]
fn test_parse_obsolete_overflow_x32() {
assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow)));
assert_eq!(obsolete("-42949672k"), Some(Err(ParseError::Overflow)));
}
}

60
src/uu/head/src/split.rs Normal file
View file

@ -0,0 +1,60 @@
#[derive(Debug)]
pub enum Event<'a> {
Data(&'a [u8]),
Line,
}
/// Loops over the lines read from a BufRead.
/// # Arguments
/// * `input` the ReadBuf to read from
/// * `zero` whether to use 0u8 as a line delimiter
/// * `on_event` a closure receiving some bytes read in a slice, or
/// event signalling a line was just read.
/// this is guaranteed to be signalled *directly* after the
/// slice containing the (CR on win)LF / 0 is passed
///
/// Return whether to continue
pub fn walk_lines<F>(
input: &mut impl std::io::BufRead,
zero: bool,
mut on_event: F,
) -> std::io::Result<()>
where
F: FnMut(Event) -> std::io::Result<bool>,
{
let mut buffer = [0u8; super::BUF_SIZE];
loop {
let read = loop {
match input.read(&mut buffer) {
Ok(n) => break n,
Err(e) => match e.kind() {
std::io::ErrorKind::Interrupted => {}
_ => return Err(e),
},
}
};
if read == 0 {
return Ok(());
}
let mut base = 0usize;
for (i, byte) in buffer[..read].iter().enumerate() {
match byte {
b'\n' if !zero => {
on_event(Event::Data(&buffer[base..=i]))?;
base = i + 1;
if !on_event(Event::Line)? {
return Ok(());
}
}
0u8 if zero => {
on_event(Event::Data(&buffer[base..=i]))?;
base = i + 1;
if !on_event(Event::Line)? {
return Ok(());
}
}
_ => {}
}
}
on_event(Event::Data(&buffer[base..read]))?;
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_hostid"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "hostid ~ (uutils) display the numeric identifier of the current host"
@ -16,8 +16,8 @@ path = "src/hostid.rs"
[dependencies]
libc = "0.2.42"
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]
name = "hostid"

View file

@ -7,8 +7,6 @@
// spell-checker:ignore (ToDO) gethostid
extern crate libc;
#[macro_use]
extern crate uucore;

View file

@ -1,6 +1,6 @@
[package]
name = "uu_hostname"
version = "0.0.1"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "hostname ~ (uutils) display or set the host name of the current host"
@ -15,11 +15,11 @@ edition = "2018"
path = "src/hostname.rs"
[dependencies]
clap = "2.32"
clap = "2.33"
libc = "0.2.42"
hostname = { version = "0.3", features = ["set"] }
uucore = { version="0.0.4", package="uucore", git="https://github.com/uutils/uucore.git", branch="canary", features=["wide"] }
uucore_procs = { version="0.0.4", package="uucore_procs", git="https://github.com/uutils/uucore.git", branch="canary" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["wide"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
winapi = { version="0.3", features=["sysinfoapi", "winsock2"] }
[[bin]]

View file

@ -7,12 +7,6 @@
// spell-checker:ignore (ToDO) MAKEWORD addrs hashset
extern crate clap;
extern crate hostname;
extern crate libc;
#[cfg(windows)]
extern crate winapi;
#[macro_use]
extern crate uucore;
@ -84,7 +78,7 @@ fn execute(args: impl uucore::Args) -> i32 {
)
.arg(Arg::with_name(OPT_SHORT).short("s").long("short").help(
"Display the short hostname (the portion before the first dot) if \
possible",
possible",
))
.arg(Arg::with_name(OPT_HOST))
.get_matches_from(args);
@ -123,7 +117,7 @@ fn display_hostname(matches: &ArgMatches) -> i32 {
ip.truncate(len - 2);
}
output.push_str(&ip);
output.push_str(" ");
output.push(' ');
hashset.insert(addr);
}
}

Some files were not shown because too many files have changed in this diff Show more