Merge branch 'master' into pr

This commit is contained in:
Sylvestre Ledru 2021-04-09 22:02:25 +02:00 committed by GitHub
commit 844e318a67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
235 changed files with 12115 additions and 4330 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

@ -150,7 +150,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --features "feat_os_unix"
args: --features "feat_os_unix" -p uucore -p coreutils
env:
RUSTFLAGS: '-Awarnings'
@ -181,6 +181,28 @@ jobs:
cd tmp/busybox-*/testsuite
S=$(bindir=$bindir ./runtest) && printf "%s\n" "$S" || { printf "%s\n" "$S" | grep "FAIL:" | sed -e "s/FAIL: /::warning ::Test failure:/g" ; }
makefile_build:
name: Test the build target of the Makefile
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest }
steps:
- uses: actions/checkout@v1
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: "Run make build"
shell: bash
run: |
sudo apt-get -y update ; sudo apt-get -y install python3-sphinx;
make build
build:
name: Build
runs-on: ${{ matrix.job.os }}
@ -514,6 +536,17 @@ jobs:
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
echo set-output name=UTILITY_LIST::${UTILITY_LIST}
echo ::set-output name=CARGO_UTILITY_LIST_OPTIONS::${CARGO_UTILITY_LIST_OPTIONS}
- name: Test uucore
uses: actions-rs/cargo@v1
with:
command: test
args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast -p uucore
env:
CARGO_INCREMENTAL: '0'
RUSTC_WRAPPER: ''
RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort'
RUSTDOCFLAGS: '-Cpanic=abort'
# RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }}
- name: Test
uses: actions-rs/cargo@v1
with:

View file

@ -84,8 +84,8 @@ jobs:
sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh
sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh
sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh
sed -i 's|timeout \([[:digit:]]\)| /usr/bin/timeout \1|' tests/tail-2/inotify-rotate.sh tests/tail-2/inotify-dir-recreate.sh tests/tail-2/inotify-rotate-resources.sh tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.sh tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh tests/tail-2/pid.sh tests/dd/stats.sh tests/tail-2/follow-name.sh # Don't break the function called 'grep_timeout'
sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh tests/chgrp/basic.sh
sed -i 's|timeout \([[:digit:]]\)| /usr/bin/timeout \1|' tests/tail-2/inotify-rotate.sh tests/tail-2/inotify-dir-recreate.sh tests/tail-2/inotify-rotate-resources.sh tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.sh tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh tests/tail-2/pid.sh tests/dd/stats.sh tests/tail-2/follow-name.sh tests/misc/shuf.sh # Don't break the function called 'grep_timeout'
sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh tests/chgrp/basic.sh tests/misc/shuf.sh
sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh
sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh
sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh

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

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

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.

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

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
@ -237,7 +237,7 @@ EXES := \
INSTALLEES := ${EXES}
ifeq (${MULTICALL}, y)
INSTALLEES := ${INSTALLEES} uutils
INSTALLEES := ${INSTALLEES} coreutils
endif
all: build
@ -250,13 +250,13 @@ ifneq (${MULTICALL}, y)
${CARGO} build ${CARGOFLAGS} ${PROFILE_CMD} $(foreach pkg,$(EXES),-p uu_$(pkg))
endif
build-uutils:
build-coreutils:
${CARGO} build ${CARGOFLAGS} --features "${EXES}" ${PROFILE_CMD} --no-default-features
build-manpages:
cd $(DOCSDIR) && $(MAKE) man
build: build-uutils build-pkgs build-manpages
build: build-coreutils build-pkgs build-manpages
$(foreach test,$(filter-out $(SKIP_UTILS),$(PROGS)),$(eval $(call TEST_BUSYBOX,$(test))))
@ -275,7 +275,7 @@ $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
cp $< $@
# Test under the busybox testsuite
$(BUILDDIR)/busybox: busybox-src build-uutils $(BUILDDIR)/.config
$(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config
cp $(BUILDDIR)/coreutils $(BUILDDIR)/busybox; \
chmod +x $@;
@ -298,10 +298,10 @@ install: build
mkdir -p $(INSTALLDIR_BIN)
mkdir -p $(INSTALLDIR_MAN)
ifeq (${MULTICALL}, y)
$(INSTALL) $(BUILDDIR)/uutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)uutils
cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out uutils, $(INSTALLEES)), \
ln -fs $(PROG_PREFIX)uutils $(PROG_PREFIX)$(prog) &&) :
cat $(DOCSDIR)/_build/man/uutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)uutils.1.gz
$(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils
cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out coreutils, $(INSTALLEES)), \
ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)$(prog) &&) :
cat $(DOCSDIR)/_build/man/coreutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)coreutils.1.gz
else
$(foreach prog, $(INSTALLEES), \
$(INSTALL) $(BUILDDIR)/$(prog) $(INSTALLDIR_BIN)/$(PROG_PREFIX)$(prog);)
@ -311,10 +311,10 @@ endif
uninstall:
ifeq (${MULTICALL}, y)
rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)uutils)
rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)coreutils)
endif
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)uutils.1.gz)
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz)
rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS))
rm -f $(addprefix $(INSTALLDIR_MAN)/$(PROG_PREFIX),$(addsuffix .1.gz,$(PROGS)))
.PHONY: all build build-uutils build-pkgs build-docs test distclean clean busytest install uninstall
.PHONY: all build build-coreutils build-pkgs build-docs test distclean clean busytest install uninstall

167
README.md
View file

@ -1,5 +1,4 @@
uutils coreutils
================
# uutils coreutils
[![Crates.io](https://img.shields.io/crates/v/coreutils.svg)](https://crates.io/crates/coreutils)
[![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ)
@ -9,16 +8,18 @@ uutils coreutils
[![Build Status](https://api.travis-ci.org/uutils/coreutils.svg?branch=master)](https://travis-ci.org/uutils/coreutils)
[![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master)
[![codecov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils)
[![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils)
-----------------------------------------------
<!-- markdownlint-disable commands-show-output no-duplicate-heading -->
<!-- spell-checker:ignore markdownlint ; (jargon) multicall ; (misc) aarch riscv uutil uutils ; (names/acronyms) BusyBox BusyBox's BusyTest MSVC NixOS PowerPC WASI WASM ; (options) DESTDIR RUNTEST UTILNAME -->
uutils is an attempt at writing universal (as in cross-platform) CLI
utilities in [Rust](http://www.rust-lang.org). This repository is intended to
aggregate GNU coreutils rewrites.
Why?
----
## Why?
Many GNU, Linux and other utilities are useful, and obviously
[some](http://gnuwin32.sourceforge.net) [effort](http://unxutils.sourceforge.net)
@ -29,260 +30,300 @@ have other issues.
Rust provides a good, platform-agnostic way of writing systems utilities that are easy
to compile anywhere, and this is as good a way as any to try and learn it.
Requirements
------------
## Requirements
* Rust (`cargo`, `rustc`)
* GNU Make (required to build documentation)
* [Sphinx](http://www.sphinx-doc.org/) (for documentation)
* gzip (for installing documentation)
### Rust Version ###
### Rust Version
uutils follows Rust's release channels and is tested against stable, beta and nightly.
The current oldest supported version of the Rust compiler is `1.40.0`.
On both Windows and Redox, only the nightly version is tested currently.
Build Instructions
------------------
## Build Instructions
There are currently two methods to build uutils: GNU Make and Cargo. However,
while there may be two methods, both systems are required to build on Unix
(only Cargo is required on Windows).
There are currently two methods to build the uutils binaries: either Cargo
or GNU Make.
> Building the full package, including all documentation, requires both Cargo
> and Gnu Make on a Unix platform.
For either method, we first need to fetch the repository:
First, for both methods, we need to fetch the repository:
```bash
$ git clone https://github.com/uutils/coreutils
$ cd coreutils
```
### Cargo ###
### Cargo
Building uutils using Cargo is easy because the process is the same as for
every other Rust program:
```bash
# to keep debug information, compile without --release
$ cargo build --release
```
Because the above command attempts to build utilities that only work on
Unix-like platforms at the moment, to build on Windows, you must do the
following:
This command builds the most portable common core set of uutils into a multicall
(BusyBox-type) binary, named 'coreutils', on most Rust-supported platforms.
Additional platform-specific uutils are often available. Building these
expanded sets of uutils for a platform (on that platform) is as simple as
specifying it as a feature:
```bash
# to keep debug information, compile without --release
$ cargo build --release --no-default-features --features windows
$ cargo build --release --features macos
# or ...
$ cargo build --release --features windows
# or ...
$ cargo build --release --features unix
```
If you don't want to build every utility available on your platform into the
multicall binary (the Busybox-esque binary), you can also specify which ones
you want to build manually. For example:
final binary, you can also specify which ones you want to build manually.
For example:
```bash
$ cargo build --features "base32 cat echo rm" --no-default-features
```
If you don't even want to build the multicall binary and would prefer to just
build the utilities as individual binaries, that is possible too. For example:
If you don't want to build the multicall binary and would prefer to build
the utilities as individual binaries, that is also possible. Each utility
is contained in it's own package within the main repository, named
"uu_UTILNAME". To build individual utilities, use cargo to build just the
specific packages (using the `--package` [aka `-p`] option). For example:
```bash
$ cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm
```
### GNU Make ###
### GNU Make
Building using `make` is a simple process as well.
To simply build all available utilities:
```bash
$ make
```
To build all but a few of the available utilities:
```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2'
```
To build only a few of the available utilities:
```bash
$ make UTILS='UTILITY_1 UTILITY_2'
```
Installation Instructions
-------------------------
## Installation Instructions
### Cargo ###
### Cargo
Likewise, installing can simply be done using:
```bash
$ cargo install --path .
```
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
### GNU Make ###
### GNU Make
To install all available utilities:
```bash
$ make install
```
To install using `sudo` switch `-E` must be used:
```bash
$ sudo -E make install
```
To install all but a few of the available utilities:
```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' install
```
To install only a few of the available utilities:
```bash
$ make UTILS='UTILITY_1 UTILITY_2' install
```
To install every program with a prefix (e.g. uu-echo uu-cat):
```bash
$ make PROG_PREFIX=PREFIX_GOES_HERE install
```
To install the multicall binary:
```bash
$ make MULTICALL=y install
```
Set install parent directory (default value is /usr/local):
```bash
# DESTDIR is also supported
$ make PREFIX=/my/path install
```
### NixOS ###
### NixOS
The [standard package set](https://nixos.org/nixpkgs/manual/) of [NixOS](https://nixos.org/)
provides this package out of the box since 18.03:
```
nix-env -iA nixos.uutils-coreutils
```shell
$ nix-env -iA nixos.uutils-coreutils
```
Uninstallation Instructions
---------------------------
## Un-installation Instructions
Uninstallation differs depending on how you have installed uutils. If you used
Un-installation differs depending on how you have installed uutils. If you used
Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use
Make to uninstall.
### Cargo ###
### Cargo
To uninstall uutils:
```bash
$ cargo uninstall uutils
```
### GNU Make ###
### GNU Make
To uninstall all utilities:
```bash
$ make uninstall
```
To uninstall every program with a set prefix:
```bash
$ make PROG_PREFIX=PREFIX_GOES_HERE uninstall
```
To uninstall the multicall binary:
```bash
$ make MULTICALL=y uninstall
```
To uninstall from a custom parent directory:
```bash
# DESTDIR is also supported
$ make PREFIX=/my/path uninstall
```
Test Instructions
-----------------
## Test Instructions
Testing can be done using either Cargo or `make`.
### Cargo ###
### Cargo
Just like with building, we follow the standard procedure for testing using
Cargo:
```bash
$ cargo test
```
By default, `cargo test` only runs the common programs. To run also platform
specific tests, run:
```bash
$ cargo test --features unix
```
If you would prefer to test a select few utilities:
```bash
$ cargo test --features "chmod mv tail" --no-default-features
```
If you also want to test the core utilities:
```bash
$ cargo test -p uucore -p coreutils
```
To debug:
```bash
$ gdb --args target/debug/coreutils ls
(gdb) b ls.rs:79
(gdb) run
```
### GNU Make ###
### GNU Make
To simply test all available utilities:
```bash
$ make test
```
To test all but a few of the available utilities:
```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' test
```
To test only a few of the available utilities:
```bash
$ make UTILS='UTILITY_1 UTILITY_2' test
```
To include tests for unimplemented behavior:
```bash
$ make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
```
Run Busybox Tests
-----------------
## Run Busybox Tests
This testing functionality is only available on *nix operating systems and
requires `make`.
To run busybox's tests for all utilities for which busybox has tests
```bash
$ make busytest
```
To run busybox's tests for a few of the available utilities
```bash
$ make UTILS='UTILITY_1 UTILITY_2' busytest
```
To pass an argument like "-v" to the busybox test runtime
```bash
$ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
```
Contribute
----------
## Contribute
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
Utilities
---------
## Utilities
| Done | Semi-Done | To Do |
|-----------|-----------|--------|
@ -372,8 +413,34 @@ Utilities
| whoami | | |
| yes | | |
License
-------
## Targets that compile
This is an auto-generated table showing which binaries compile for each target-triple. Note that this **does not** indicate that they are fully implemented, or that the tests pass.
|######OS######|###ARCH####|arch|base32|base64|basename|cat|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|df|dircolors|dirname|du|echo|env|expand|expr|factor|false|fmt|fold|groups|hashsum|head|hostid|hostname|id|install|join|kill|link|ln|logname|ls|mkdir|mkfifo|mknod|mktemp|more|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|printenv|printf|ptx|pwd|readlink|realpath|relpath|rm|rmdir|seq|shred|shuf|sleep|sort|split|stat|stdbuf|sum|sync|tac|tail|tee|test|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|uptime|users|wc|who|whoami|yes|
|--------------|-----------|----|------|------|--------|---|-----|-----|-----|------|-----|----|--|------|---|----|--|---------|-------|--|----|---|------|----|------|-----|---|----|------|-------|----|------|--------|--|-------|----|----|----|--|-------|--|-----|------|-----|------|----|--|----|--|-----|-----|------|--|-----|-------|-----|--------|------|---|---|--------|--------|-------|--|-----|---|-----|----|-----|----|-----|----|------|---|----|---|----|---|----|-------|-----|--|----|--------|-----|---|-----|--------|----|------|------|-----|--|---|------|---|
|linux-gnu|aarch64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|i686|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|powerpc64|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|riscv64gc| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|linux-gnu|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|windows-msvc|aarch64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y| |y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| | |y|
|windows-gnu|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|
|windows-msvc|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|
|windows-gnu|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|
|windows-msvc|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|
|apple MacOS|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|freebsd|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|netbsd|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|
|android|aarch64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|
|android|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|
|solaris|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|wasi|wasm32| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|redox|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|fuchsia|aarch64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|fuchsia|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
## License
uutils is licensed under the MIT License - see the `LICENSE` file for details

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

@ -1,6 +1,6 @@
[package]
name = "uu_arch"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "arch ~ (uutils) display machine architecture"
@ -16,7 +16,7 @@ path = "src/arch.rs"
[dependencies]
platform-info = "0.1"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

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

View file

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

View file

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

View file

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

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.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chgrp ~ (uutils) change the group ownership of FILE"
@ -15,7 +15,7 @@ edition = "2018"
path = "src/chgrp.rs"
[dependencies]
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chmod"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chmod ~ (uutils) change mode of FILE"
@ -15,8 +15,9 @@ edition = "2018"
path = "src/chmod.rs"
[dependencies]
clap = "2.33.3"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs", "mode"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"

View file

@ -10,6 +10,7 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
@ -18,81 +19,137 @@ use uucore::fs::display_permissions_unix;
use uucore::mode;
use walkdir::WalkDir;
const NAME: &str = "chmod";
static SUMMARY: &str = "Change the mode of each FILE to MODE.
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Change the mode of each FILE to MODE.
With --reference, change the mode of each FILE to that of RFILE.";
static LONG_HELP: &str = "
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.
";
mod options {
pub const CHANGES: &str = "changes";
pub const QUIET: &str = "quiet"; // visible_alias("silent")
pub const VERBOSE: &str = "verbose";
pub const NO_PRESERVE_ROOT: &str = "no-preserve-root";
pub const PRESERVE_ROOT: &str = "preserve-root";
pub const REFERENCE: &str = "RFILE";
pub const RECURSIVE: &str = "recursive";
pub const MODE: &str = "MODE";
pub const FILE: &str = "FILE";
}
fn get_usage() -> String {
format!(
"{0} [OPTION]... MODE[,MODE]... FILE...
or: {0} [OPTION]... OCTAL-MODE FILE...
or: {0} [OPTION]... --reference=RFILE FILE...",
executable!()
)
}
fn get_long_usage() -> String {
String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.")
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let mut args = args.collect_str();
let syntax = format!(
"[OPTION]... MODE[,MODE]... FILE...
{0} [OPTION]... OCTAL-MODE FILE...
{0} [OPTION]... --reference=RFILE FILE...",
NAME
);
let mut opts = app!(&syntax, SUMMARY, LONG_HELP);
opts.optflag(
"c",
"changes",
"like verbose but report only when a change is made",
)
// TODO: support --silent (can be done using clap)
.optflag("f", "quiet", "suppress most error messages")
.optflag(
"v",
"verbose",
"output a diagnostic for every file processed",
)
.optflag(
"",
"no-preserve-root",
"do not treat '/' specially (the default)",
)
.optflag("", "preserve-root", "fail to operate recursively on '/'")
.optopt(
"",
"reference",
"use RFILE's mode instead of MODE values",
"RFILE",
)
.optflag("R", "recursive", "change files and directories recursively");
// Before we can parse 'args' with clap (and previously getopts),
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
let mode_had_minus_prefix = strip_minus_from_mode(&mut args);
// sanitize input for - at beginning (e.g. chmod -x test_file). Remove
// the option and save it for later, after parsing is finished.
let negative_option = sanitize_input(&mut args);
let usage = get_usage();
let after_help = get_long_usage();
let mut matches = opts.parse(args);
if matches.free.is_empty() {
show_error!("missing an argument");
show_error!("for help, try '{} --help'", NAME);
return 1;
} else {
let changes = matches.opt_present("changes");
let quiet = matches.opt_present("quiet");
let verbose = matches.opt_present("verbose");
let preserve_root = matches.opt_present("preserve-root");
let recursive = matches.opt_present("recursive");
let fmode = matches
.opt_str("reference")
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.after_help(&after_help[..])
.arg(
Arg::with_name(options::CHANGES)
.long(options::CHANGES)
.short("c")
.help("like verbose but report only when a change is made"),
)
.arg(
Arg::with_name(options::QUIET)
.long(options::QUIET)
.visible_alias("silent")
.short("f")
.help("suppress most error messages"),
)
.arg(
Arg::with_name(options::VERBOSE)
.long(options::VERBOSE)
.short("v")
.help("output a diagnostic for every file processed"),
)
.arg(
Arg::with_name(options::NO_PRESERVE_ROOT)
.long(options::NO_PRESERVE_ROOT)
.help("do not treat '/' specially (the default)"),
)
.arg(
Arg::with_name(options::PRESERVE_ROOT)
.long(options::PRESERVE_ROOT)
.help("fail to operate recursively on '/'"),
)
.arg(
Arg::with_name(options::RECURSIVE)
.long(options::RECURSIVE)
.short("R")
.help("change files and directories recursively"),
)
.arg(
Arg::with_name(options::REFERENCE)
.long("reference")
.takes_value(true)
.help("use RFILE's mode instead of MODE values"),
)
.arg(
Arg::with_name(options::MODE)
.required_unless(options::REFERENCE)
.takes_value(true),
// It would be nice if clap could parse with delimeter, e.g. "g-x,u+x",
// however .multiple(true) cannot be used here because FILE already needs that.
// Only one positional argument with .multiple(true) set is allowed per command
)
.arg(
Arg::with_name(options::FILE)
.required_unless(options::MODE)
.multiple(true),
)
.get_matches_from(args);
let changes = matches.is_present(options::CHANGES);
let quiet = matches.is_present(options::QUIET);
let verbose = matches.is_present(options::VERBOSE);
let preserve_root = matches.is_present(options::PRESERVE_ROOT);
let recursive = matches.is_present(options::RECURSIVE);
let fmode =
matches
.value_of(options::REFERENCE)
.and_then(|ref fref| match fs::metadata(fref) {
Ok(meta) => Some(meta.mode()),
Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err),
});
let cmode = if fmode.is_none() {
// If there was a negative option, now it's a good time to
// use it.
if negative_option.is_some() {
negative_option
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(matches.free.remove(0))
}
} else {
None
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,
@ -102,31 +159,33 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
fmode,
cmode,
};
match chmoder.chmod(matches.free) {
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 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' => {
return Some(args.remove(i));
// TODO: use strip_prefix() once minimum rust version reaches 1.45.0
args[i] = args[i][1..args[i].len()].to_string();
return true;
}
_ => {}
}
}
}
None
}
false
}
struct Chmoder {
@ -147,7 +206,17 @@ impl Chmoder {
let filename = &filename[..];
let file = Path::new(filename);
if !file.exists() {
show_error!("no such file or directory '{}'", filename);
if is_symlink(file) {
println!(
"failed to change mode of '{}' from 0000 (---------) to 0000 (---------)",
filename
);
if !self.quiet {
show_error!("cannot operate on dangling symlink '{}'", filename);
}
} else {
show_error!("cannot access '{}': No such file or directory", filename);
}
return Err(1);
}
if self.recursive && self.preserve_root && filename == "/" {
@ -181,10 +250,9 @@ impl Chmoder {
let mut fperm = match fs::metadata(file) {
Ok(meta) => meta.mode() & 0o7777,
Err(err) => {
if !self.quiet {
if is_symlink(file) {
if self.verbose {
show_info!(
println!(
"neither symbolic link '{}' nor referent has been changed",
file.display()
);
@ -193,7 +261,6 @@ impl Chmoder {
} else {
show_error!("{}: '{}'", err, file.display());
}
}
return Err(1);
}
};
@ -232,11 +299,11 @@ impl Chmoder {
fn change_file(&self, fperm: u32, mode: u32, file: &Path) -> Result<(), i32> {
if fperm == mode {
if self.verbose && !self.changes {
show_info!(
"mode of '{}' retained as {:o} ({})",
println!(
"mode of '{}' retained as {:04o} ({})",
file.display(),
fperm,
display_permissions_unix(fperm)
display_permissions_unix(fperm),
);
}
Ok(())

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chown"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chown ~ (uutils) change the ownership of FILE"
@ -17,7 +17,7 @@ path = "src/chown.rs"
[dependencies]
clap = "2.33"
glob = "0.3.0"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chroot"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "chroot ~ (uutils) run COMMAND under a new root directory"
@ -16,7 +16,7 @@ path = "src/chroot.rs"
[dependencies]
clap= "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

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

View file

@ -10,6 +10,7 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs::File;
use std::io::{self, stdin, BufReader, Read};
use std::path::Path;
@ -18,9 +19,10 @@ use std::path::Path;
const CRC_TABLE_LEN: usize = 256;
const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table();
const VERSION: &str = env!("CARGO_PKG_VERSION");
const NAME: &str = "cksum";
const SYNTAX: &str = "[OPTIONS] [FILE]...";
const SUMMARY: &str = "Print CRC and size for each file";
const LONG_HELP: &str = "";
// this is basically a hack to get "loops" to work on Rust 1.33. Once we update to Rust 1.46 or
// greater, we can just use while loops
@ -138,7 +140,20 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
let mut rd: Box<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))
}
};
@ -160,10 +175,25 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
}
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str());
mod options {
pub static FILE: &str = "file";
}
let files = matches.free;
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let matches = App::new(executable!())
.name(NAME)
.version(VERSION)
.about(SUMMARY)
.usage(SYNTAX)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.get_matches_from(args);
let files: Vec<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.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "comm ~ (uutils) compare sorted inputs"
@ -15,9 +15,9 @@ edition = "2018"
path = "src/comm.rs"
[dependencies]
getopts = "0.2.18"
clap = "2.33"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -15,21 +15,34 @@ use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Stdin};
use std::path::Path;
static SYNTAX: &str = "[OPTIONS] FILE1 FILE2";
static SUMMARY: &str = "Compare sorted files line by line";
use clap::{App, Arg, ArgMatches};
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "compare two sorted files line by line";
static LONG_HELP: &str = "";
fn mkdelim(col: usize, opts: &getopts::Matches) -> String {
let mut s = String::new();
let delim = match opts.opt_str("output-delimiter") {
Some(d) => d,
None => "\t".to_owned(),
};
mod options {
pub const COLUMN_1: &str = "1";
pub const COLUMN_2: &str = "2";
pub const COLUMN_3: &str = "3";
pub const DELIMITER: &str = "output-delimiter";
pub const DELIMITER_DEFAULT: &str = "\t";
pub const FILE_1: &str = "FILE1";
pub const FILE_2: &str = "FILE2";
}
if col > 1 && !opts.opt_present("1") {
fn get_usage() -> String {
format!("{} [OPTION]... FILE1 FILE2", executable!())
}
fn mkdelim(col: usize, opts: &ArgMatches) -> String {
let mut s = String::new();
let delim = opts.value_of(options::DELIMITER).unwrap();
if col > 1 && !opts.is_present(options::COLUMN_1) {
s.push_str(delim.as_ref());
}
if col > 2 && !opts.opt_present("2") {
if col > 2 && !opts.is_present(options::COLUMN_2) {
s.push_str(delim.as_ref());
}
@ -57,7 +70,7 @@ impl LineReader {
}
}
fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
let delim: Vec<String> = (0..4).map(|col| mkdelim(col, opts)).collect();
let ra = &mut String::new();
@ -80,7 +93,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
match ord {
Ordering::Less => {
if !opts.opt_present("1") {
if !opts.is_present(options::COLUMN_1) {
ensure_nl(ra);
print!("{}{}", delim[1], ra);
}
@ -88,7 +101,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
na = a.read_line(ra);
}
Ordering::Greater => {
if !opts.opt_present("2") {
if !opts.is_present(options::COLUMN_2) {
ensure_nl(rb);
print!("{}{}", delim[2], rb);
}
@ -96,7 +109,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) {
nb = b.read_line(rb);
}
Ordering::Equal => {
if !opts.opt_present("3") {
if !opts.is_present(options::COLUMN_3) {
ensure_nl(ra);
print!("{}{}", delim[3], ra);
}
@ -120,21 +133,42 @@ fn open_file(name: &str) -> io::Result<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.4"
version = "0.0.6"
authors = [
"Jordy Dickinson <jordy.dickinson@gmail.com>",
"Joshua S. Miller <jsmiller@uchicago.edu>",
@ -23,7 +23,7 @@ clap = "2.33"
filetime = "0.2"
libc = "0.2.85"
quick-error = "1.2.3"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"

View file

@ -1,6 +1,6 @@
[package]
name = "uu_csplit"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output"
@ -15,11 +15,11 @@ edition = "2018"
path = "src/csplit.rs"
[dependencies]
getopts = "0.2.17"
clap = "2.33"
thiserror = "1.0"
regex = "1.0.0"
glob = "0.2.11"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "fs"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -2,7 +2,7 @@
#[macro_use]
extern crate uucore;
use getopts::Matches;
use clap::{App, Arg, ArgMatches};
use regex::Regex;
use std::cmp::Ordering;
use std::io::{self, BufReader};
@ -18,17 +18,25 @@ mod splitname;
use crate::csplit_error::CsplitError;
use crate::splitname::SplitName;
static SYNTAX: &str = "[OPTION]... FILE PATTERN...";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "split a file into sections determined by context lines";
static LONG_HELP: &str = "Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output.";
static SUFFIX_FORMAT_OPT: &str = "suffix-format";
static SUPPRESS_MATCHED_OPT: &str = "suppress-matched";
static DIGITS_OPT: &str = "digits";
static PREFIX_OPT: &str = "prefix";
static KEEP_FILES_OPT: &str = "keep-files";
static QUIET_OPT: &str = "quiet";
static ELIDE_EMPTY_FILES_OPT: &str = "elide-empty-files";
mod options {
pub const SUFFIX_FORMAT: &str = "suffix-format";
pub const SUPPRESS_MATCHED: &str = "suppress-matched";
pub const DIGITS: &str = "digits";
pub const PREFIX: &str = "prefix";
pub const KEEP_FILES: &str = "keep-files";
pub const QUIET: &str = "quiet";
pub const ELIDE_EMPTY_FILES: &str = "elide-empty-files";
pub const FILE: &str = "file";
pub const PATTERN: &str = "pattern";
}
fn get_usage() -> String {
format!("{0} [OPTION]... FILE PATTERN...", executable!())
}
/// Command line options for csplit.
pub struct CsplitOptions {
@ -40,19 +48,19 @@ pub struct CsplitOptions {
}
impl CsplitOptions {
fn new(matches: &Matches) -> CsplitOptions {
let keep_files = matches.opt_present(KEEP_FILES_OPT);
let quiet = matches.opt_present(QUIET_OPT);
let elide_empty_files = matches.opt_present(ELIDE_EMPTY_FILES_OPT);
let suppress_matched = matches.opt_present(SUPPRESS_MATCHED_OPT);
fn new(matches: &ArgMatches) -> CsplitOptions {
let keep_files = matches.is_present(options::KEEP_FILES);
let quiet = matches.is_present(options::QUIET);
let elide_empty_files = matches.is_present(options::ELIDE_EMPTY_FILES);
let suppress_matched = matches.is_present(options::SUPPRESS_MATCHED);
CsplitOptions {
split_name: crash_if_err!(
1,
SplitName::new(
matches.opt_str(PREFIX_OPT),
matches.opt_str(SUFFIX_FORMAT_OPT),
matches.opt_str(DIGITS_OPT)
matches.value_of(options::PREFIX).map(str::to_string),
matches.value_of(options::SUFFIX_FORMAT).map(str::to_string),
matches.value_of(options::DIGITS).map(str::to_string)
)
),
keep_files,
@ -702,45 +710,78 @@ mod tests {
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let args = args.collect_str();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optopt(
"b",
SUFFIX_FORMAT_OPT,
"use sprintf FORMAT instead of %02d",
"FORMAT",
let matches = App::new(executable!())
.version(VERSION)
.about(SUMMARY)
.usage(&usage[..])
.arg(
Arg::with_name(options::SUFFIX_FORMAT)
.short("b")
.long(options::SUFFIX_FORMAT)
.value_name("FORMAT")
.help("use sprintf FORMAT instead of %02d"),
)
.optopt("f", PREFIX_OPT, "use PREFIX instead of 'xx'", "PREFIX")
.optflag("k", KEEP_FILES_OPT, "do not remove output files on errors")
.optflag(
"",
SUPPRESS_MATCHED_OPT,
"suppress the lines matching PATTERN",
.arg(
Arg::with_name(options::PREFIX)
.short("f")
.long(options::PREFIX)
.value_name("PREFIX")
.help("use PREFIX instead of 'xx'"),
)
.optopt(
"n",
DIGITS_OPT,
"use specified number of digits instead of 2",
"DIGITS",
.arg(
Arg::with_name(options::KEEP_FILES)
.short("k")
.long(options::KEEP_FILES)
.help("do not remove output files on errors"),
)
.optflag("s", QUIET_OPT, "do not print counts of output file sizes")
.optflag("z", ELIDE_EMPTY_FILES_OPT, "remove empty output files")
.parse(args);
.arg(
Arg::with_name(options::SUPPRESS_MATCHED)
.long(options::SUPPRESS_MATCHED)
.help("suppress the lines matching PATTERN"),
)
.arg(
Arg::with_name(options::DIGITS)
.short("n")
.long(options::DIGITS)
.value_name("DIGITS")
.help("use specified number of digits instead of 2"),
)
.arg(
Arg::with_name(options::QUIET)
.short("s")
.long(options::QUIET)
.visible_alias("silent")
.help("do not print counts of output file sizes"),
)
.arg(
Arg::with_name(options::ELIDE_EMPTY_FILES)
.short("z")
.long(options::ELIDE_EMPTY_FILES)
.help("remove empty output files"),
)
.arg(Arg::with_name(options::FILE).hidden(true).required(true))
.arg(
Arg::with_name(options::PATTERN)
.hidden(true)
.multiple(true)
.required(true),
)
.after_help(LONG_HELP)
.get_matches_from(args);
// check for mandatory arguments
if matches.free.is_empty() {
show_error!("missing operand");
exit!(1);
}
if matches.free.len() == 1 {
show_error!("missing operand after '{}'", matches.free[0]);
exit!(1);
}
// get the patterns to split on
let patterns = return_if_err!(1, patterns::get_patterns(&matches.free[1..]));
// get the file to split
let file_name: &str = &matches.free[0];
let file_name = matches.value_of(options::FILE).unwrap();
// get the patterns to split on
let patterns: Vec<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();

View file

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

View file

@ -10,6 +10,7 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, Read, Stdout, Write};
use std::path::Path;
@ -20,6 +21,8 @@ use uucore::ranges::Range;
mod buffer;
mod searcher;
static NAME: &str = "cut";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SYNTAX: &str =
"[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+";
static SUMMARY: &str =
@ -398,8 +401,13 @@ fn cut_files(mut filenames: Vec<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;
}
@ -422,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),
},
)
})
@ -459,29 +556,34 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Mode::Characters(
ranges,
Options {
out_delim: matches.opt_str("output-delimiter"),
zero_terminated: matches.opt_present("zero-terminated"),
out_delim: Some(
matches
.value_of(options::OUTPUT_DELIMITER)
.unwrap_or_default()
.to_owned(),
),
zero_terminated: matches.is_present(options::ZERO_TERMINATED),
},
)
})
}
(None, None, Some(field_ranges)) => {
list_to_ranges(&field_ranges[..], complement).and_then(|ranges| {
let out_delim = match matches.opt_str("output-delimiter") {
let out_delim = match matches.value_of(options::OUTPUT_DELIMITER) {
Some(s) => {
if s.is_empty() {
Some("\0".to_owned())
} else {
Some(s)
Some(s.to_owned())
}
}
None => None,
};
let only_delimited = matches.opt_present("only-delimited");
let zero_terminated = matches.opt_present("zero-terminated");
let only_delimited = matches.is_present(options::ONLY_DELIMITED);
let zero_terminated = matches.is_present(options::ZERO_TERMINATED);
match matches.opt_str("delimiter") {
match matches.value_of(options::DELIMITER) {
Some(delim) => {
if delim.chars().count() > 1 {
Err(msg_opt_invalid_should_be!(
@ -494,7 +596,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let delim = if delim.is_empty() {
"\0".to_owned()
} else {
delim
delim.to_owned()
};
Ok(Mode::Fields(
@ -533,10 +635,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let mode_parse = match mode_parse {
Err(_) => mode_parse,
Ok(mode) => match mode {
Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("delimiter") => Err(
msg_opt_only_usable_if!("printing a sequence of fields", "--delimiter", "-d"),
),
Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("only-delimited") => {
Mode::Bytes(_, _) | Mode::Characters(_, _)
if matches.is_present(options::DELIMITER) =>
{
Err(msg_opt_only_usable_if!(
"printing a sequence of fields",
"--delimiter",
"-d"
))
}
Mode::Bytes(_, _) | Mode::Characters(_, _)
if matches.is_present(options::ONLY_DELIMITED) =>
{
Err(msg_opt_only_usable_if!(
"printing a sequence of fields",
"--only-delimited",
@ -547,8 +657,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
},
};
let files: Vec<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.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "date ~ (uutils) display or set the current time"
@ -17,7 +17,7 @@ path = "src/date.rs"
[dependencies]
chrono = "0.4.4"
clap = "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(unix)'.dependencies]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_df"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "df ~ (uutils) display file system information"
@ -18,7 +18,7 @@ path = "src/df.rs"
clap = "2.33"
libc = "0.2"
number_prefix = "0.4"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(target_os = "windows")'.dependencies]

View file

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

View file

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

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.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "du ~ (uutils) display disk usage"
@ -15,9 +15,10 @@ edition = "2018"
path = "src/du.rs"
[dependencies]
time = "0.1.40"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
chrono = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
winapi = { version="0.3", features=[] }
[[bin]]
name = "du"

View file

@ -10,14 +10,31 @@
#[macro_use]
extern crate uucore;
use chrono::prelude::DateTime;
use chrono::Local;
use std::collections::HashSet;
use std::env;
use std::fs;
use std::io::{stderr, Result, Write};
use std::iter;
#[cfg(not(windows))]
use std::os::unix::fs::MetadataExt;
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
#[cfg(windows)]
use std::os::windows::io::AsRawHandle;
use std::path::PathBuf;
use time::Timespec;
use std::time::{Duration, UNIX_EPOCH};
#[cfg(windows)]
use winapi::shared::minwindef::{DWORD, LPVOID};
#[cfg(windows)]
use winapi::um::fileapi::{FILE_ID_INFO, FILE_STANDARD_INFO};
#[cfg(windows)]
use winapi::um::minwinbase::{FileIdInfo, FileStandardInfo};
#[cfg(windows)]
use winapi::um::winbase::GetFileInformationByHandleEx;
#[cfg(windows)]
use winapi::um::winnt::{FILE_ID_128, ULONGLONG};
const NAME: &str = "du";
const SUMMARY: &str = "estimate file space usage";
@ -43,12 +60,18 @@ struct Options {
separate_dirs: bool,
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
struct FileInfo {
file_id: u128,
dev_id: u64,
}
struct Stat {
path: PathBuf,
is_dir: bool,
size: u64,
blocks: u64,
inode: u64,
inode: Option<FileInfo>,
created: u64,
accessed: u64,
modified: u64,
@ -57,19 +80,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_000 - 11_644_473_600
}
#[cfg(windows)]
fn get_size_on_disk(path: &PathBuf) -> u64 {
let mut size_on_disk = 0;
// bind file so it stays in scope until end of function
// if it goes out of scope the handle below becomes invalid
let file = match fs::File::open(path) {
Ok(file) => file,
Err(_) => return size_on_disk, // opening directories will fail
};
let handle = file.as_raw_handle();
unsafe {
let mut file_info: FILE_STANDARD_INFO = core::mem::zeroed();
let file_info_ptr: *mut FILE_STANDARD_INFO = &mut file_info;
let success = GetFileInformationByHandleEx(
handle,
FileStandardInfo,
file_info_ptr as LPVOID,
std::mem::size_of::<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();
@ -137,7 +255,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![];
@ -164,10 +282,13 @@ fn du(
if this_stat.is_dir {
futures.push(du(this_stat, options, depth + 1, inodes));
} else {
if inodes.contains(&this_stat.inode) {
if this_stat.inode.is_some() {
let inode = this_stat.inode.unwrap();
if inodes.contains(&inode) {
continue;
}
inodes.insert(this_stat.inode);
inodes.insert(inode);
}
my_stat.size += this_stat.size;
my_stat.blocks += this_stat.blocks;
if options.all {
@ -200,6 +321,9 @@ fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String {
return format!("{:.1}{}", (size as f64) / (limit as f64), unit);
}
}
if size == 0 {
return format!("0");
}
format!("{}B", size)
}
@ -418,7 +542,7 @@ Try '{} --help' for more information.",
let path = PathBuf::from(&path_str);
match Stat::new(path) {
Ok(stat) => {
let mut inodes: HashSet<u64> = HashSet::new();
let mut inodes: HashSet<FileInfo> = HashSet::new();
let iter = du(stat, &options, 0, &mut inodes);
let (_, len) = iter.size_hint();
@ -433,8 +557,8 @@ Try '{} --help' for more information.",
};
if matches.opt_present("time") {
let tm = {
let (secs, nsecs) = {
let time = match matches.opt_str("time") {
let secs = {
match matches.opt_str("time") {
Some(s) => match &s[..] {
"accessed" => stat.accessed,
"created" => stat.created,
@ -451,13 +575,12 @@ Try '{} --help' for more information.",
}
},
None => stat.modified,
}
};
((time / 1000) as i64, (time % 1000 * 1_000_000) as i32)
};
time::at(Timespec::new(secs, nsecs))
DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs))
};
if !summarize || index == len - 1 {
let time_str = tm.strftime(time_format_str).unwrap();
let time_str = tm.format(time_format_str).to_string();
print!(
"{}\t{}\t{}{}",
convert_size(size),

View file

@ -1,6 +1,6 @@
[package]
name = "uu_echo"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "echo ~ (uutils) display TEXT"
@ -16,7 +16,7 @@ path = "src/echo.rs"
[dependencies]
clap = "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -14,10 +14,10 @@ use std::io::{self, Write};
use std::iter::Peekable;
use std::str::Chars;
static NAME: &str = "echo";
static USAGE: &str = "[OPTIONS]... [STRING]...";
static SUMMARY: &str = "display a line of text";
static AFTER_HELP: &str = r#"
const NAME: &str = "echo";
const SUMMARY: &str = "display a line of text";
const USAGE: &str = "[OPTIONS]... [STRING]...";
const AFTER_HELP: &str = r#"
Echo the STRING(s) to standard output.
If -e is in effect, the following sequences are recognized:
@ -37,10 +37,10 @@ static AFTER_HELP: &str = r#"
"#;
mod options {
pub static STRING: &str = "STRING";
pub static NO_NEWLINE: &str = "no_newline";
pub static ENABLE_BACKSLASH_ESCAPE: &str = "enable_backslash_escape";
pub static DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape";
pub const STRING: &str = "STRING";
pub const NO_NEWLINE: &str = "no_newline";
pub const ENABLE_BACKSLASH_ESCAPE: &str = "enable_backslash_escape";
pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape";
}
fn parse_code(
@ -113,8 +113,6 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result<bool> {
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let matches = App::new(executable!())
.name(NAME)
// TrailingVarArg specifies the final positional argument is a VarArg
@ -123,9 +121,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.setting(clap::AppSettings::TrailingVarArg)
.setting(clap::AppSettings::AllowLeadingHyphen)
.version(crate_version!())
.usage(USAGE)
.about(SUMMARY)
.after_help(AFTER_HELP)
.usage(USAGE)
.arg(
Arg::with_name(options::NO_NEWLINE)
.short("n")
@ -149,7 +147,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
)
.arg(
Arg::with_name(options::STRING)
.hidden(true)
.multiple(true)
.allow_hyphen_values(true),
)

View file

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

View file

@ -1,6 +1,6 @@
[package]
name = "uu_expand"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "expand ~ (uutils) convert input tabs to spaces"
@ -17,7 +17,7 @@ path = "src/expand.rs"
[dependencies]
clap = "2.33"
unicode-width = "0.1.5"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

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

View file

@ -1,6 +1,6 @@
[package]
name = "uu_factor"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "factor ~ (uutils) display the prime factors of each NUMBER"
@ -19,7 +19,7 @@ num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
rand = { version="0.7", features=["small_rng"] }
smallvec = { version="0.6.14, < 1.0" }
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[dev-dependencies]

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

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

View file

@ -1,6 +1,6 @@
[package]
name = "uu_fmt"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "fmt ~ (uutils) reformat each paragraph of input"
@ -18,7 +18,7 @@ path = "src/fmt.rs"
clap = "2.33"
libc = "0.2.42"
unicode-width = "0.1.5"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

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

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

View file

@ -10,46 +10,71 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path;
const TAB_WIDTH: usize = 8;
static NAME: &str = "fold";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SYNTAX: &str = "[OPTION]... [FILE]...";
static SUMMARY: &str = "Writes each file (or standard input if no files are given)
to standard output whilst breaking long lines";
static LONG_HELP: &str = "";
mod options {
pub const BYTES: &str = "bytes";
pub const SPACES: &str = "spaces";
pub const WIDTH: &str = "width";
pub const FILE: &str = "file";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let (args, obs_width) = handle_obsolete(&args[..]);
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optflag(
"b",
"bytes",
let matches = App::new(executable!())
.name(NAME)
.version(VERSION)
.usage(SYNTAX)
.about(SUMMARY)
.arg(
Arg::with_name(options::BYTES)
.long(options::BYTES)
.short("b")
.help(
"count using bytes rather than columns (meaning control characters \
such as newline are not treated specially)",
)
.optflag(
"s",
"spaces",
"break lines at word boundaries rather than a hard cut-off",
.takes_value(false),
)
.optopt(
"w",
"width",
"set WIDTH as the maximum line width rather than 80",
"WIDTH",
.arg(
Arg::with_name(options::SPACES)
.long(options::SPACES)
.short("s")
.help("break lines at word boundaries rather than a hard cut-off")
.takes_value(false),
)
.parse(args);
.arg(
Arg::with_name(options::WIDTH)
.long(options::WIDTH)
.short("w")
.help("set WIDTH as the maximum line width rather than 80")
.value_name("WIDTH")
.allow_hyphen_values(true)
.takes_value(true),
)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.get_matches_from(args.clone());
let bytes = matches.opt_present("b");
let spaces = matches.opt_present("s");
let poss_width = if matches.opt_present("w") {
matches.opt_str("w")
} else {
obs_width
let bytes = matches.is_present(options::BYTES);
let spaces = matches.is_present(options::SPACES);
let poss_width = match matches.value_of(options::WIDTH) {
Some(v) => Some(v.to_owned()),
None => obs_width,
};
let width = match poss_width {
Some(inp_width) => match inp_width.parse::<usize>() {
Ok(width) => width,
@ -57,11 +82,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
},
None => 80,
};
let files = if matches.free.is_empty() {
vec!["-".to_owned()]
} else {
matches.free
let files = match matches.values_of(options::FILE) {
Some(v) => v.map(|v| v.to_owned()).collect(),
None => vec!["-".to_owned()],
};
fold(files, bytes, spaces, width);
0
@ -79,7 +105,6 @@ fn handle_obsolete(args: &[String]) -> (Vec<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,23 +117,46 @@ 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>(file: BufReader<T>, bytes: bool, spaces: bool, width: usize) {
for line_result in file.lines() {
let mut line = safe_unwrap!(line_result);
if bytes {
/// 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();
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) {
match slice.rfind(|c: char| c.is_whitespace() && c != '\r') {
Some(m) => &slice[..=m],
None => slice,
}
@ -116,96 +164,116 @@ fn fold_file<T: Read>(file: BufReader<T>, bytes: bool, spaces: bool, width: usiz
slice
}
};
print!("{}", slice);
// Don't duplicate trailing newlines: if the slice is "\n", the
// previous iteration folded just before the end of the line and
// has already printed this newline.
if slice == "\n" {
break;
}
i += slice.len();
}
let at_eol = i >= len;
if at_eol {
print!("{}", slice);
} else {
let mut len = line.chars().count();
let newline = line.ends_with('\n');
if newline {
if len == 1 {
println!();
continue;
}
len -= 1;
line.truncate(len);
}
let mut output = String::new();
let mut count = 0;
for (i, ch) in line.chars().enumerate() {
if count >= width {
let (val, ncount) = {
let slice = &output[..];
let (out, val, ncount) = if spaces && i + 1 < len {
match rfind_whitespace(slice) {
Some(m) => {
let routput = &slice[m + 1..slice.chars().count()];
let ncount = routput.chars().fold(0, |out, ch: char| {
out + match ch {
'\t' => 8,
'\x08' => {
if out > 0 {
!0
} else {
0
}
}
'\r' => return 0,
_ => 1,
}
});
(&slice[0..=m], routput, ncount)
}
None => (slice, "", 0),
}
} else {
(slice, "", 0)
};
println!("{}", out);
(val.to_owned(), ncount)
};
output = val;
count = ncount;
}
match ch {
'\t' => {
count += 8;
if count > width {
println!("{}", output);
output.truncate(0);
count = 8;
}
}
'\x08' => {
if count > 0 {
count -= 1;
let len = output.len() - 1;
output.truncate(len);
}
continue;
}
'\r' => {
output.truncate(0);
count = 0;
continue;
}
_ => count += 1,
};
output.push(ch);
}
if count > 0 {
println!("{}", output);
println!("{}", slice);
}
}
line.truncate(0);
}
}
#[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));
/// 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 last_space = None;
/// Print the output line, resetting the column and character counts.
///
/// If `spaces` is `true`, print the output line up to the last
/// encountered whitespace character (inclusive) and set the remaining
/// characters as the start of the next line.
macro_rules! emit_output {
() => {
let consume = match last_space {
Some(i) => i + 1,
None => output.len(),
};
println!("{}", &output[..consume]);
output.replace_range(..consume, "");
// we know there are no tabs left in output, so each char counts
// as 1 column
col_count = output.len();
last_space = None;
};
}
loop {
if let Ok(0) = file.read_line(&mut line) {
break;
}
for ch in line.chars() {
if ch == '\n' {
// make sure to _not_ split output at whitespace, since we
// know the entire output will fit
last_space = None;
emit_output!();
break;
}
if col_count >= width {
emit_output!();
}
match ch {
'\r' => col_count = 0,
'\t' => {
let next_tab_stop = col_count + TAB_WIDTH - col_count % TAB_WIDTH;
if next_tab_stop > width && !output.is_empty() {
emit_output!();
}
col_count = next_tab_stop;
last_space = if spaces { Some(output.len()) } else { None };
}
'\x08' => {
if col_count > 0 {
col_count -= 1;
}
}
None
_ if spaces && ch.is_whitespace() => {
last_space = Some(output.len());
col_count += 1;
}
_ => col_count += 1,
};
output.push(ch);
}
if !output.is_empty() {
print!("{}", output);
output.truncate(0);
}
line.truncate(0);
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_groups"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "groups ~ (uutils) display group memberships for USERNAME"
@ -15,7 +15,7 @@ edition = "2018"
path = "src/groups.rs"
[dependencies]
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
clap = "2.33"

View file

@ -1,6 +1,6 @@
[package]
name = "uu_hashsum"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "hashsum ~ (uutils) display or check input digests"
@ -26,7 +26,7 @@ sha1 = "0.6.0"
sha2 = "0.6.0"
sha3 = "0.6.0"
blake2-rfc = "0.2.18"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

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

View file

@ -1,240 +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,
zero_terminated: bool,
}
impl Default for Settings {
fn default() -> Settings {
Settings {
mode: FilterMode::Lines(10),
verbose: false,
zero_terminated: false,
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))
}
},
}
}
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("z", "zero-terminated", "line delimiter is NUL, not newline")
.optflag("h", "help", "display this help and exit")
.optflag("V", "version", "output version information and exit")
.parse(new_args);
let use_bytes = matches.opt_present("c");
// TODO: suffixes (e.g. b, kB, etc.)
match matches.opt_str("n") {
Some(n) => {
if use_bytes {
show_error!("cannot specify both --bytes and --lines.");
return 1;
}
match n.parse::<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");
settings.zero_terminated = matches.opt_present("z");
let files = matches.free;
EXIT_SUCCESS
}
// 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;
}
#[cfg(test)]
mod tests {
use std::ffi::OsString;
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!();
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)))
}
println!("==> {} <==", file);
#[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));
}
first_time = false;
#[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();
let path = Path::new(file);
if path.is_dir() || !path.metadata().is_ok() {
eprintln!(
"cannot open '{}' for reading: No such file or directory",
&path.to_str().unwrap()
assert_eq!(opts, opts2);
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))
);
continue;
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());
}
let reader = File::open(&path).unwrap();
let mut buffer = BufReader::new(reader);
if !head(&mut buffer, &settings) {
break;
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),
}
}
#[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());
}
0
}
// 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();
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;
}
(options, None)
}
// 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) => {
if settings.zero_terminated {
for line in reader.split(0).take(count) {
print!("{}\0", String::from_utf8(line.unwrap()).unwrap())
}
} else {
for line in reader.lines().take(count) {
println!("{}", line.unwrap());
}
}
}
FilterMode::NLines(count) => {
let mut vector: VecDeque<String> = VecDeque::new();
for line in reader.lines() {
vector.push_back(line.unwrap());
if vector.len() <= count {
continue;
}
println!("{}", vector.pop_front().unwrap());
}
}
}
true
}

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.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "hostid ~ (uutils) display the numeric identifier of the current host"
@ -16,7 +16,7 @@ path = "src/hostid.rs"
[dependencies]
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_hostname"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "hostname ~ (uutils) display or set the host name of the current host"
@ -18,7 +18,7 @@ path = "src/hostname.rs"
clap = "2.33"
libc = "0.2.42"
hostname = { version = "0.3", features = ["set"] }
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["wide"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["wide"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
winapi = { version="0.3", features=["sysinfoapi", "winsock2"] }

View file

@ -1,6 +1,6 @@
[package]
name = "uu_id"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "id ~ (uutils) display user and group information for USER"
@ -16,7 +16,7 @@ path = "src/id.rs"
[dependencies]
clap = "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "process"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "process"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_install"
version = "0.0.4"
version = "0.0.6"
authors = [
"Ben Eills <ben@beneills.com>",
"uutils developers",
@ -20,8 +20,9 @@ path = "src/install.rs"
[dependencies]
clap = "2.33"
filetime = "0.2"
file_diff = "1.0.0"
libc = ">= 0.2"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["mode", "perms", "entries"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["mode", "perms", "entries"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[dev-dependencies]

View file

@ -13,10 +13,12 @@ mod mode;
extern crate uucore;
use clap::{App, Arg, ArgMatches};
use file_diff::diff;
use filetime::{set_file_times, FileTime};
use uucore::entries::{grp2gid, usr2uid};
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
use libc::{getegid, geteuid};
use std::fs;
use std::fs::File;
use std::os::unix::fs::MetadataExt;
@ -34,6 +36,7 @@ pub struct Behavior {
group: String,
verbose: bool,
preserve_timestamps: bool,
compare: bool,
}
#[derive(Clone, Eq, PartialEq)]
@ -112,11 +115,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("ignored")
)
.arg(
// TODO implement flag
Arg::with_name(OPT_COMPARE)
.short("C")
.long(OPT_COMPARE)
.help("(unimplemented) compare each pair of source and destination files, and in some cases, do not modify the destination at all")
.help("compare each pair of source and destination files, and in some cases, do not modify the destination at all")
)
.arg(
Arg::with_name(OPT_DIRECTORY)
@ -262,8 +264,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
Err("--backup")
} else if matches.is_present(OPT_BACKUP_2) {
Err("-b")
} else if matches.is_present(OPT_COMPARE) {
Err("--compare, -C")
} else if matches.is_present(OPT_CREATED) {
Err("-D")
} else if matches.is_present(OPT_STRIP) {
@ -338,6 +338,7 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(),
verbose: matches.is_present(OPT_VERBOSE),
preserve_timestamps: matches.is_present(OPT_PRESERVE_TIMESTAMPS),
compare: matches.is_present(OPT_COMPARE),
})
}
@ -355,23 +356,29 @@ fn directory(paths: Vec<String>, b: Behavior) -> i32 {
} else {
let mut all_successful = true;
for directory in paths.iter() {
let path = Path::new(directory);
for path in paths.iter().map(Path::new) {
// if the path already exist, don't try to create it again
if !path.exists() {
if let Err(e) = fs::create_dir(directory) {
show_info!("{}: {}", path.display(), e.to_string());
// Differently than the primary functionality (MainFunction::Standard), the directory
// functionality should create all ancestors (or components) of a directory regardless
// of the presence of the "-D" flag.
// NOTE: the GNU "install" sets the expected mode only for the target directory. All
// created ancestor directories will have the default mode. Hence it is safe to use
// fs::create_dir_all and then only modify the target's dir mode.
if let Err(e) = fs::create_dir_all(path) {
show_info!("{}: {}", path.display(), e);
all_successful = false;
continue;
}
if b.verbose {
show_info!("creating directory '{}'", path.display());
}
}
if mode::chmod(&path, b.mode()).is_err() {
all_successful = false;
}
if b.verbose {
show_info!("created directory '{}'", path.display());
continue;
}
}
if all_successful {
@ -487,6 +494,10 @@ fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behavior) -> i32 {
/// If the copy system call fails, we print a verbose error and return an empty error value.
///
fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
if b.compare && !need_copy(from, to, b) {
return Ok(());
}
if from.to_string_lossy() == "/dev/null" {
/* workaround a limitation of fs::copy
* https://github.com/rust-lang/rust/issues/79390
@ -583,3 +594,81 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
Ok(())
}
/// Return true if a file is necessary to copy. This is the case when:
/// - _from_ or _to_ is nonexistent;
/// - either file has a sticky bit or set[ug]id bit, or the user specified one;
/// - either file isn't a regular file;
/// - the sizes of _from_ and _to_ differ;
/// - _to_'s owner differs from intended; or
/// - the contents of _from_ and _to_ differ.
///
/// # Parameters
///
/// _from_ and _to_, if existent, must be non-directories.
///
/// # Errors
///
/// Crashes the program if a nonexistent owner or group is specified in _b_.
///
fn need_copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> bool {
let from_meta = match fs::metadata(from) {
Ok(meta) => meta,
Err(_) => return true,
};
let to_meta = match fs::metadata(to) {
Ok(meta) => meta,
Err(_) => return true,
};
// setuid || setgid || sticky
let extra_mode: u32 = 0o7000;
if b.specified_mode.unwrap_or(0) & extra_mode != 0
|| from_meta.mode() & extra_mode != 0
|| to_meta.mode() & extra_mode != 0
{
return true;
}
if !from_meta.is_file() || !to_meta.is_file() {
return true;
}
if from_meta.len() != to_meta.len() {
return true;
}
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
if !b.owner.is_empty() {
let owner_id = match usr2uid(&b.owner) {
Ok(id) => id,
_ => crash!(1, "no such user: {}", b.owner),
};
if owner_id != to_meta.uid() {
return true;
}
} else if !b.group.is_empty() {
let group_id = match grp2gid(&b.group) {
Ok(id) => id,
_ => crash!(1, "no such group: {}", b.group),
};
if group_id != to_meta.gid() {
return true;
}
} else {
#[cfg(not(target_os = "windows"))]
unsafe {
if to_meta.uid() != geteuid() || to_meta.gid() != getegid() {
return true;
}
}
}
if !diff(from.to_str().unwrap(), to.to_str().unwrap()) {
return true;
}
false
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_join"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "join ~ (uutils) merge lines from inputs with matching join fields"
@ -16,7 +16,7 @@ path = "src/join.rs"
[dependencies]
clap = "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_kill"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "kill ~ (uutils) send a signal to a process"
@ -16,7 +16,7 @@ path = "src/kill.rs"
[dependencies]
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["signals"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["signals"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_link"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "link ~ (uutils) create a hard (file system) link to FILE"
@ -16,7 +16,7 @@ path = "src/link.rs"
[dependencies]
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_ln"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "ln ~ (uutils) create a (file system) link to TARGET"
@ -17,7 +17,7 @@ path = "src/ln.rs"
[dependencies]
clap = "2.33"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -223,7 +223,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} else if matches.is_present(OPT_BACKUP) {
match matches.value_of(OPT_BACKUP) {
None => BackupMode::ExistingBackup,
Some(mode) => match &mode[..] {
Some(mode) => match mode {
"simple" | "never" => BackupMode::SimpleBackup,
"numbered" | "t" => BackupMode::NumberedBackup,
"existing" | "nil" => BackupMode::ExistingBackup,

View file

@ -1,6 +1,6 @@
[package]
name = "uu_logname"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "logname ~ (uutils) display the login name of the current user"
@ -16,7 +16,7 @@ path = "src/logname.rs"
[dependencies]
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_ls"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "ls ~ (uutils) display directory contents"
@ -22,7 +22,8 @@ term_grid = "0.1.5"
termsize = "0.1.6"
time = "0.1.40"
unicode-width = "0.1.5"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries", "fs"] }
globset = "0.4.6"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(unix)'.dependencies]

View file

@ -13,10 +13,13 @@ extern crate lazy_static;
#[macro_use]
extern crate uucore;
mod quoting_style;
mod version_cmp;
use clap::{App, Arg};
use globset::{self, Glob, GlobSet, GlobSetBuilder};
use number_prefix::NumberPrefix;
use quoting_style::{escape_name, QuotingStyle};
#[cfg(unix)]
use std::collections::HashMap;
use std::fs;
@ -84,6 +87,7 @@ pub mod options {
pub static COMMAS: &str = "m";
pub static LONG_NO_OWNER: &str = "g";
pub static LONG_NO_GROUP: &str = "o";
pub static LONG_NUMERIC_UID_GID: &str = "numeric-uid-gid";
}
pub mod files {
pub static ALL: &str = "all";
@ -103,6 +107,21 @@ pub mod options {
pub static HUMAN_READABLE: &str = "human-readable";
pub static SI: &str = "si";
}
pub mod quoting {
pub static ESCAPE: &str = "escape";
pub static LITERAL: &str = "literal";
pub static C: &str = "quote-name";
}
pub static QUOTING_STYLE: &str = "quoting-style";
pub mod indicator_style {
pub static NONE: &str = "none";
pub static SLASH: &str = "slash";
pub static FILE_TYPE: &str = "file-type";
pub static CLASSIFY: &str = "classify";
}
pub static HIDE_CONTROL_CHARS: &str = "hide-control-chars";
pub static SHOW_CONTROL_CHARS: &str = "show-control-chars";
pub static WIDTH: &str = "width";
pub static AUTHOR: &str = "author";
pub static NO_GROUP: &str = "no-group";
@ -112,13 +131,17 @@ pub mod options {
pub static IGNORE_BACKUPS: &str = "ignore-backups";
pub static DIRECTORY: &str = "directory";
pub static CLASSIFY: &str = "classify";
pub static FILE_TYPE: &str = "file-type";
pub static SLASH: &str = "p";
pub static INODE: &str = "inode";
pub static DEREFERENCE: &str = "dereference";
pub static NUMERIC_UID_GID: &str = "numeric-uid-gid";
pub static REVERSE: &str = "reverse";
pub static RECURSIVE: &str = "recursive";
pub static COLOR: &str = "color";
pub static PATHS: &str = "paths";
pub static INDICATOR_STYLE: &str = "indicator-style";
pub static HIDE: &str = "hide";
pub static IGNORE: &str = "ignore";
}
#[derive(PartialEq, Eq)]
@ -157,6 +180,14 @@ enum Time {
Change,
}
#[derive(PartialEq, Eq)]
enum IndicatorStyle {
None,
Slash,
FileType,
Classify,
}
struct Config {
format: Format,
files: Files,
@ -164,10 +195,8 @@ struct Config {
recursive: bool,
reverse: bool,
dereference: bool,
classify: bool,
ignore_backups: bool,
ignore_patterns: GlobSet,
size_format: SizeFormat,
numeric_uid_gid: bool,
directory: bool,
time: Time,
#[cfg(unix)]
@ -176,6 +205,8 @@ struct Config {
color: bool,
long: LongFormat,
width: Option<u16>,
quoting_style: QuotingStyle,
indicator_style: IndicatorStyle,
}
// Fields that can be removed or added to the long format
@ -183,6 +214,8 @@ struct LongFormat {
author: bool,
group: bool,
owner: bool,
#[cfg(unix)]
numeric_uid_gid: bool,
}
impl Config {
@ -210,7 +243,7 @@ impl Config {
(Format::Columns, options::format::COLUMNS)
};
// The -o and -g options are tricky. They cannot override with each
// The -o, -n and -g options are tricky. They cannot override with each
// other because it's possible to combine them. For example, the option
// -og should hide both owner and group. Furthermore, they are not
// reset if -l or --format=long is used. So these should just show the
@ -223,41 +256,29 @@ impl Config {
// which always applies.
//
// The idea here is to not let these options override with the other
// options, but manually check the last index they occur. If this index
// is larger than the index for the other format options, we apply the
// long format.
match options.indices_of(opt).map(|x| x.max().unwrap()) {
None => {
if options.is_present(options::format::LONG_NO_GROUP)
|| options.is_present(options::format::LONG_NO_OWNER)
// options, but manually whether they have an index that's greater than
// the other format options. If so, we set the appropriate format.
if format != Format::Long {
let idx = options
.indices_of(opt)
.map(|x| x.max().unwrap())
.unwrap_or(0);
if [
options::format::LONG_NO_OWNER,
options::format::LONG_NO_GROUP,
options::format::LONG_NUMERIC_UID_GID,
]
.iter()
.flat_map(|opt| options.indices_of(opt))
.flatten()
.any(|i| i >= idx)
{
format = Format::Long;
} else if options.is_present(options::format::ONELINE) {
} else if let Some(mut indices) = options.indices_of(options::format::ONELINE) {
if indices.any(|i| i > idx) {
format = Format::OneLine;
}
}
Some(mut idx) => {
if let Some(indices) = options.indices_of(options::format::LONG_NO_OWNER) {
let i = indices.max().unwrap();
if i > idx {
format = Format::Long;
idx = i;
}
}
if let Some(indices) = options.indices_of(options::format::LONG_NO_GROUP) {
let i = indices.max().unwrap();
if i > idx {
format = Format::Long;
idx = i;
}
}
if let Some(indices) = options.indices_of(options::format::ONELINE) {
let i = indices.max().unwrap();
if i > idx && format != Format::Long {
format = Format::OneLine;
}
}
}
}
let files = if options.is_present(options::files::ALL) {
@ -328,10 +349,14 @@ impl Config {
let group = !options.is_present(options::NO_GROUP)
&& !options.is_present(options::format::LONG_NO_GROUP);
let owner = !options.is_present(options::format::LONG_NO_OWNER);
#[cfg(unix)]
let numeric_uid_gid = options.is_present(options::format::LONG_NUMERIC_UID_GID);
LongFormat {
author,
group,
owner,
#[cfg(unix)]
numeric_uid_gid,
}
};
@ -345,6 +370,118 @@ impl Config {
})
.or_else(|| termsize::get().map(|s| s.cols));
let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) {
false
} else if options.is_present(options::SHOW_CONTROL_CHARS) {
true
} else {
false // TODO: only if output is a terminal and the program is `ls`
};
let quoting_style = if let Some(style) = options.value_of(options::QUOTING_STYLE) {
match style {
"literal" => QuotingStyle::Literal { show_control },
"shell" => QuotingStyle::Shell {
escape: false,
always_quote: false,
show_control,
},
"shell-always" => QuotingStyle::Shell {
escape: false,
always_quote: true,
show_control,
},
"shell-escape" => QuotingStyle::Shell {
escape: true,
always_quote: false,
show_control,
},
"shell-escape-always" => QuotingStyle::Shell {
escape: true,
always_quote: true,
show_control,
},
"c" => QuotingStyle::C {
quotes: quoting_style::Quotes::Double,
},
"escape" => QuotingStyle::C {
quotes: quoting_style::Quotes::None,
},
_ => unreachable!("Should have been caught by Clap"),
}
} else if options.is_present(options::quoting::LITERAL) {
QuotingStyle::Literal { show_control }
} else if options.is_present(options::quoting::ESCAPE) {
QuotingStyle::C {
quotes: quoting_style::Quotes::None,
}
} else if options.is_present(options::quoting::C) {
QuotingStyle::C {
quotes: quoting_style::Quotes::Double,
}
} else {
// TODO: use environment variable if available
QuotingStyle::Shell {
escape: true,
always_quote: false,
show_control,
}
};
let indicator_style = if let Some(field) = options.value_of(options::INDICATOR_STYLE) {
match field {
"none" => IndicatorStyle::None,
"file-type" => IndicatorStyle::FileType,
"classify" => IndicatorStyle::Classify,
"slash" => IndicatorStyle::Slash,
&_ => IndicatorStyle::None,
}
} else if options.is_present(options::indicator_style::NONE) {
IndicatorStyle::None
} else if options.is_present(options::indicator_style::CLASSIFY)
|| options.is_present(options::CLASSIFY)
{
IndicatorStyle::Classify
} else if options.is_present(options::indicator_style::SLASH)
|| options.is_present(options::SLASH)
{
IndicatorStyle::Slash
} else if options.is_present(options::indicator_style::FILE_TYPE)
|| options.is_present(options::FILE_TYPE)
{
IndicatorStyle::FileType
} else {
IndicatorStyle::None
};
let mut ignore_patterns = GlobSetBuilder::new();
if options.is_present(options::IGNORE_BACKUPS) {
ignore_patterns.add(Glob::new("*~").unwrap());
ignore_patterns.add(Glob::new(".*~").unwrap());
}
for pattern in options.values_of(options::IGNORE).into_iter().flatten() {
match Glob::new(pattern) {
Ok(p) => {
ignore_patterns.add(p);
}
Err(_) => show_warning!("Invalid pattern for ignore: '{}'", pattern),
}
}
if files == Files::Normal {
for pattern in options.values_of(options::HIDE).into_iter().flatten() {
match Glob::new(pattern) {
Ok(p) => {
ignore_patterns.add(p);
}
Err(_) => show_warning!("Invalid pattern for hide: '{}'", pattern),
}
}
}
let ignore_patterns = ignore_patterns.build().unwrap();
Config {
format,
files,
@ -352,10 +489,8 @@ impl Config {
recursive: options.is_present(options::RECURSIVE),
reverse: options.is_present(options::REVERSE),
dereference: options.is_present(options::DEREFERENCE),
classify: options.is_present(options::CLASSIFY),
ignore_backups: options.is_present(options::IGNORE_BACKUPS),
ignore_patterns,
size_format,
numeric_uid_gid: options.is_present(options::NUMERIC_UID_GID),
directory: options.is_present(options::DIRECTORY),
time,
#[cfg(unix)]
@ -364,6 +499,8 @@ impl Config {
inode: options.is_present(options::INODE),
long,
width,
quoting_style,
indicator_style,
}
}
}
@ -444,22 +581,108 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
options::format::COLUMNS,
]),
)
// The next three arguments do not override with the other format
// The next four arguments do not override with the other format
// options, see the comment in Config::from for the reason.
// Ideally, they would use Arg::override_with, with their own name
// but that doesn't seem to work in all cases. Example:
// ls -1g1
// even though `ls -11` and `ls -1 -g -1` work.
.arg(
Arg::with_name(options::format::ONELINE)
.short(options::format::ONELINE)
.help("List one file per line.")
.multiple(true)
)
.arg(
Arg::with_name(options::format::LONG_NO_GROUP)
.short(options::format::LONG_NO_GROUP)
.help("Long format without group information. Identical to --format=long with --no-group.")
.multiple(true)
)
.arg(
Arg::with_name(options::format::LONG_NO_OWNER)
.short(options::format::LONG_NO_OWNER)
.help("Long format without owner information.")
.multiple(true)
)
.arg(
Arg::with_name(options::format::LONG_NUMERIC_UID_GID)
.short("n")
.long(options::format::LONG_NUMERIC_UID_GID)
.help("-l with numeric UIDs and GIDs.")
.multiple(true)
)
// Quoting style
.arg(
Arg::with_name(options::QUOTING_STYLE)
.long(options::QUOTING_STYLE)
.takes_value(true)
.help("Set quoting style.")
.possible_values(&["literal", "shell", "shell-always", "shell-escape", "shell-escape-always", "c", "escape"])
.overrides_with_all(&[
options::QUOTING_STYLE,
options::quoting::LITERAL,
options::quoting::ESCAPE,
options::quoting::C,
])
)
.arg(
Arg::with_name(options::quoting::LITERAL)
.short("N")
.long(options::quoting::LITERAL)
.help("Use literal quoting style. Equivalent to `--quoting-style=literal`")
.overrides_with_all(&[
options::QUOTING_STYLE,
options::quoting::LITERAL,
options::quoting::ESCAPE,
options::quoting::C,
])
)
.arg(
Arg::with_name(options::quoting::ESCAPE)
.short("b")
.long(options::quoting::ESCAPE)
.help("Use escape quoting style. Equivalent to `--quoting-style=escape`")
.overrides_with_all(&[
options::QUOTING_STYLE,
options::quoting::LITERAL,
options::quoting::ESCAPE,
options::quoting::C,
])
)
.arg(
Arg::with_name(options::quoting::C)
.short("Q")
.long(options::quoting::C)
.help("Use C quoting style. Equivalent to `--quoting-style=c`")
.overrides_with_all(&[
options::QUOTING_STYLE,
options::quoting::LITERAL,
options::quoting::ESCAPE,
options::quoting::C,
])
)
// Control characters
.arg(
Arg::with_name(options::HIDE_CONTROL_CHARS)
.short("q")
.long(options::HIDE_CONTROL_CHARS)
.help("Replace control characters with '?' if they are not escaped.")
.overrides_with_all(&[
options::HIDE_CONTROL_CHARS,
options::SHOW_CONTROL_CHARS,
])
)
.arg(
Arg::with_name(options::SHOW_CONTROL_CHARS)
.long(options::SHOW_CONTROL_CHARS)
.help("Show control characters 'as is' if they are not escaped.")
.overrides_with_all(&[
options::HIDE_CONTROL_CHARS,
options::SHOW_CONTROL_CHARS,
])
)
// Time arguments
@ -507,6 +730,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
])
)
// Hide and ignore
.arg(
Arg::with_name(options::HIDE)
.long(options::HIDE)
.takes_value(true)
.multiple(true)
)
.arg(
Arg::with_name(options::IGNORE)
.short("I")
.long(options::IGNORE)
.takes_value(true)
.multiple(true)
)
.arg(
Arg::with_name(options::IGNORE_BACKUPS)
.short("B")
.long(options::IGNORE_BACKUPS)
.help("Ignore entries which end with ~."),
)
// Sort arguments
.arg(
Arg::with_name(options::SORT)
@ -604,12 +848,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
'.' and '..'.",
),
)
.arg(
Arg::with_name(options::IGNORE_BACKUPS)
.short("B")
.long(options::IGNORE_BACKUPS)
.help("Ignore entries which end with ~."),
)
.arg(
Arg::with_name(options::DIRECTORY)
.short("d")
@ -621,15 +859,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
specified.",
),
)
.arg(
Arg::with_name(options::CLASSIFY)
.short("F")
.long(options::CLASSIFY)
.help("Append a character to each file name indicating the file type. Also, for \
regular files that are executable, append '*'. The file type indicators are \
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
'>' for doors, and nothing for regular files.",
))
.arg(
Arg::with_name(options::size::HUMAN_READABLE)
.short("h")
@ -657,12 +886,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
file the link references rather than the link itself.",
),
)
.arg(
Arg::with_name(options::NUMERIC_UID_GID)
.short("n")
.long(options::NUMERIC_UID_GID)
.help("-l with numeric UIDs and GIDs."),
)
.arg(
Arg::with_name(options::REVERSE)
.short("r")
@ -692,6 +915,55 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.require_equals(true)
.min_values(0),
)
.arg(
Arg::with_name(options::INDICATOR_STYLE)
.long(options::INDICATOR_STYLE)
.help(" append indicator with style WORD to entry names: none (default), slash\
(-p), file-type (--file-type), classify (-F)")
.takes_value(true)
.possible_values(&["none", "slash", "file-type", "classify"])
.overrides_with_all(&[
options::FILE_TYPE,
options::SLASH,
options::CLASSIFY,
options::INDICATOR_STYLE,
]))
.arg(
Arg::with_name(options::CLASSIFY)
.short("F")
.long(options::CLASSIFY)
.help("Append a character to each file name indicating the file type. Also, for \
regular files that are executable, append '*'. The file type indicators are \
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
'>' for doors, and nothing for regular files.")
.overrides_with_all(&[
options::FILE_TYPE,
options::SLASH,
options::CLASSIFY,
options::INDICATOR_STYLE,
])
)
.arg(
Arg::with_name(options::FILE_TYPE)
.long(options::FILE_TYPE)
.help("Same as --classify, but do not append '*'")
.overrides_with_all(&[
options::FILE_TYPE,
options::SLASH,
options::CLASSIFY,
options::INDICATOR_STYLE,
]))
.arg(
Arg::with_name(options::SLASH)
.short(options::SLASH)
.help("Append / indicator to directories."
)
.overrides_with_all(&[
options::FILE_TYPE,
options::SLASH,
options::CLASSIFY,
options::INDICATOR_STYLE,
]))
// Positional arguments
.arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true));
@ -793,11 +1065,12 @@ fn is_hidden(file_path: &DirEntry) -> bool {
fn should_display(entry: &DirEntry, config: &Config) -> bool {
let ffi_name = entry.file_name();
let name = ffi_name.to_string_lossy();
if config.files == Files::Normal && is_hidden(entry) {
return false;
}
if config.ignore_backups && name.ends_with('~') {
if config.ignore_patterns.is_match(&ffi_name) {
return false;
}
true
@ -852,7 +1125,7 @@ fn pad_left(string: String, count: usize) -> String {
}
fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) {
if config.format == Format::Long || config.numeric_uid_gid {
if config.format == Format::Long {
let (mut max_links, mut max_size) = (1, 1);
for item in items {
let (links, size) = display_dir_entry_size(item, config);
@ -994,7 +1267,7 @@ use uucore::entries;
#[cfg(unix)]
fn display_uname(metadata: &Metadata, config: &Config) -> String {
if config.numeric_uid_gid {
if config.long.numeric_uid_gid {
metadata.uid().to_string()
} else {
entries::uid2usr(metadata.uid()).unwrap_or_else(|_| metadata.uid().to_string())
@ -1003,7 +1276,7 @@ fn display_uname(metadata: &Metadata, config: &Config) -> String {
#[cfg(unix)]
fn display_group(metadata: &Metadata, config: &Config) -> String {
if config.numeric_uid_gid {
if config.long.numeric_uid_gid {
metadata.gid().to_string()
} else {
entries::gid2grp(metadata.gid()).unwrap_or_else(|_| metadata.gid().to_string())
@ -1119,16 +1392,25 @@ fn display_file_name(
metadata: &Metadata,
config: &Config,
) -> Cell {
let mut name = get_file_name(path, strip);
if config.classify {
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
let file_type = metadata.file_type();
match config.indicator_style {
IndicatorStyle::Classify | IndicatorStyle::FileType => {
if file_type.is_dir() {
name.push('/');
} else if file_type.is_symlink() {
}
if file_type.is_symlink() {
name.push('@');
}
}
IndicatorStyle::Slash => {
if file_type.is_dir() {
name.push('/');
}
}
_ => (),
};
if config.format == Format::Long && metadata.file_type().is_symlink() {
if let Ok(target) = path.read_link() {
@ -1177,15 +1459,14 @@ fn display_file_name(
metadata: &Metadata,
config: &Config,
) -> Cell {
let mut name = get_file_name(path, strip);
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
if config.format != Format::Long && config.inode {
name = get_inode(metadata) + " " + &name;
}
let mut width = UnicodeWidthStr::width(&*name);
let ext;
if config.color || config.classify {
if config.color || config.indicator_style != IndicatorStyle::None {
let file_type = metadata.file_type();
let (code, sym) = if file_type.is_dir() {
@ -1238,12 +1519,30 @@ fn display_file_name(
if config.color {
name = color_name(name, code);
}
if config.classify {
if let Some(s) = sym {
name.push(s);
width += 1;
let char_opt = match config.indicator_style {
IndicatorStyle::Classify => sym,
IndicatorStyle::FileType => {
// Don't append an asterisk.
match sym {
Some('*') => None,
_ => sym,
}
}
IndicatorStyle::Slash => {
// Append only a slash.
match sym {
Some('/') => Some('/'),
_ => None,
}
}
IndicatorStyle::None => None,
};
if let Some(c) = char_opt {
name.push(c);
width += 1;
}
}
if config.format == Format::Long && metadata.file_type().is_symlink() {

View file

@ -0,0 +1,630 @@
use std::char::from_digit;
const SPECIAL_SHELL_CHARS: &str = "~`#$&*()\\|[]{};'\"<>?! ";
pub(super) enum QuotingStyle {
Shell {
escape: bool,
always_quote: bool,
show_control: bool,
},
C {
quotes: Quotes,
},
Literal {
show_control: bool,
},
}
#[derive(Clone, Copy)]
pub(super) enum Quotes {
None,
Single,
Double,
// TODO: Locale
}
// This implementation is heavily inspired by the std::char::EscapeDefault implementation
// in the Rust standard library. This custom implementation is needed because the
// characters \a, \b, \e, \f & \v are not recognized by Rust.
#[derive(Clone, Debug)]
struct EscapedChar {
state: EscapeState,
}
#[derive(Clone, Debug)]
enum EscapeState {
Done,
Char(char),
Backslash(char),
ForceQuote(char),
Octal(EscapeOctal),
}
#[derive(Clone, Debug)]
struct EscapeOctal {
c: char,
state: EscapeOctalState,
idx: usize,
}
#[derive(Clone, Debug)]
enum EscapeOctalState {
Done,
Backslash,
Value,
}
impl Iterator for EscapeOctal {
type Item = char;
fn next(&mut self) -> Option<char> {
match self.state {
EscapeOctalState::Done => None,
EscapeOctalState::Backslash => {
self.state = EscapeOctalState::Value;
Some('\\')
}
EscapeOctalState::Value => {
let octal_digit = ((self.c as u32) >> (self.idx * 3)) & 0o7;
if self.idx == 0 {
self.state = EscapeOctalState::Done;
} else {
self.idx -= 1;
}
Some(from_digit(octal_digit, 8).unwrap())
}
}
}
}
impl EscapeOctal {
fn from(c: char) -> EscapeOctal {
EscapeOctal {
c,
idx: 2,
state: EscapeOctalState::Backslash,
}
}
}
impl EscapedChar {
fn new_literal(c: char) -> Self {
Self {
state: EscapeState::Char(c),
}
}
fn new_c(c: char, quotes: Quotes) -> Self {
use EscapeState::*;
let init_state = match c {
'\x07' => Backslash('a'),
'\x08' => Backslash('b'),
'\t' => Backslash('t'),
'\n' => Backslash('n'),
'\x0B' => Backslash('v'),
'\x0C' => Backslash('f'),
'\r' => Backslash('r'),
'\\' => Backslash('\\'),
'\'' => match quotes {
Quotes::Single => Backslash('\''),
_ => Char('\''),
},
'"' => match quotes {
Quotes::Double => Backslash('"'),
_ => Char('"'),
},
' ' => match quotes {
Quotes::None => Backslash(' '),
_ => Char(' '),
},
_ if c.is_ascii_control() => Octal(EscapeOctal::from(c)),
_ => Char(c),
};
Self { state: init_state }
}
fn new_shell(c: char, escape: bool, quotes: Quotes) -> Self {
use EscapeState::*;
let init_state = match c {
_ if !escape && c.is_control() => Char(c),
'\x07' => Backslash('a'),
'\x08' => Backslash('b'),
'\t' => Backslash('t'),
'\n' => Backslash('n'),
'\x0B' => Backslash('v'),
'\x0C' => Backslash('f'),
'\r' => Backslash('r'),
'\\' => Backslash('\\'),
'\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c)),
'\'' => match quotes {
Quotes::Single => Backslash('\''),
_ => Char('\''),
},
_ if SPECIAL_SHELL_CHARS.contains(c) => ForceQuote(c),
_ => Char(c),
};
Self { state: init_state }
}
fn hide_control(self) -> Self {
match self.state {
EscapeState::Char(c) if c.is_control() => Self {
state: EscapeState::Char('?'),
},
_ => self,
}
}
}
impl Iterator for EscapedChar {
type Item = char;
fn next(&mut self) -> Option<char> {
match self.state {
EscapeState::Backslash(c) => {
self.state = EscapeState::Char(c);
Some('\\')
}
EscapeState::Char(c) | EscapeState::ForceQuote(c) => {
self.state = EscapeState::Done;
Some(c)
}
EscapeState::Done => None,
EscapeState::Octal(ref mut iter) => iter.next(),
}
}
}
fn shell_without_escape(name: String, quotes: Quotes, show_control_chars: bool) -> (String, bool) {
let mut must_quote = false;
let mut escaped_str = String::with_capacity(name.len());
for c in name.chars() {
let escaped = {
let ec = EscapedChar::new_shell(c, false, quotes);
if show_control_chars {
ec
} else {
ec.hide_control()
}
};
match escaped.state {
EscapeState::Backslash('\'') => escaped_str.push_str("'\\''"),
EscapeState::ForceQuote(x) => {
must_quote = true;
escaped_str.push(x);
}
_ => {
for char in escaped {
escaped_str.push(char);
}
}
}
}
(escaped_str, must_quote)
}
fn shell_with_escape(name: String, quotes: Quotes) -> (String, bool) {
// We need to keep track of whether we are in a dollar expression
// because e.g. \b\n is escaped as $'\b\n' and not like $'b'$'n'
let mut in_dollar = false;
let mut must_quote = false;
let mut escaped_str = String::with_capacity(name.len());
for c in name.chars() {
let escaped = EscapedChar::new_shell(c, true, quotes);
match escaped.state {
EscapeState::Char(x) => {
if in_dollar {
escaped_str.push_str("''");
in_dollar = false;
}
escaped_str.push(x);
}
EscapeState::ForceQuote(x) => {
if in_dollar {
escaped_str.push_str("''");
in_dollar = false;
}
must_quote = true;
escaped_str.push(x);
}
// Single quotes are not put in dollar expressions, but are escaped
// if the string also contains double quotes. In that case, they must
// be handled separately.
EscapeState::Backslash('\'') => {
must_quote = true;
in_dollar = false;
escaped_str.push_str("'\\''");
}
_ => {
if !in_dollar {
escaped_str.push_str("'$'");
in_dollar = true;
}
must_quote = true;
for char in escaped {
escaped_str.push(char);
}
}
}
}
(escaped_str, must_quote)
}
pub(super) fn escape_name(name: String, style: &QuotingStyle) -> String {
match style {
QuotingStyle::Literal { show_control } => {
if !show_control {
name.chars()
.flat_map(|c| EscapedChar::new_literal(c).hide_control())
.collect()
} else {
name
}
}
QuotingStyle::C { quotes } => {
let escaped_str: String = name
.chars()
.flat_map(|c| EscapedChar::new_c(c, *quotes))
.collect();
match quotes {
Quotes::Single => format!("'{}'", escaped_str),
Quotes::Double => format!("\"{}\"", escaped_str),
_ => escaped_str,
}
}
QuotingStyle::Shell {
escape,
always_quote,
show_control,
} => {
let (quotes, must_quote) = if name.contains('"') {
(Quotes::Single, true)
} else if name.contains('\'') {
(Quotes::Double, true)
} else if *always_quote {
(Quotes::Single, true)
} else {
(Quotes::Single, false)
};
let (escaped_str, contains_quote_chars) = if *escape {
shell_with_escape(name, quotes)
} else {
shell_without_escape(name, quotes, *show_control)
};
match (must_quote | contains_quote_chars, quotes) {
(true, Quotes::Single) => format!("'{}'", escaped_str),
(true, Quotes::Double) => format!("\"{}\"", escaped_str),
_ => escaped_str,
}
}
}
}
#[cfg(test)]
mod tests {
use crate::quoting_style::{escape_name, Quotes, QuotingStyle};
fn get_style(s: &str) -> QuotingStyle {
match s {
"literal" => QuotingStyle::Literal {
show_control: false,
},
"literal-show" => QuotingStyle::Literal { show_control: true },
"escape" => QuotingStyle::C {
quotes: Quotes::None,
},
"c" => QuotingStyle::C {
quotes: Quotes::Double,
},
"shell" => QuotingStyle::Shell {
escape: false,
always_quote: false,
show_control: false,
},
"shell-show" => QuotingStyle::Shell {
escape: false,
always_quote: false,
show_control: true,
},
"shell-always" => QuotingStyle::Shell {
escape: false,
always_quote: true,
show_control: false,
},
"shell-always-show" => QuotingStyle::Shell {
escape: false,
always_quote: true,
show_control: true,
},
"shell-escape" => QuotingStyle::Shell {
escape: true,
always_quote: false,
show_control: false,
},
"shell-escape-always" => QuotingStyle::Shell {
escape: true,
always_quote: true,
show_control: false,
},
_ => panic!("Invalid name!"),
}
}
fn check_names(name: &str, map: Vec<(&str, &str)>) {
assert_eq!(
map.iter()
.map(|(_, style)| escape_name(name.to_string(), &get_style(style)))
.collect::<Vec<String>>(),
map.iter()
.map(|(correct, _)| correct.to_string())
.collect::<Vec<String>>()
);
}
#[test]
fn test_simple_names() {
check_names(
"one_two",
vec![
("one_two", "literal"),
("one_two", "literal-show"),
("one_two", "escape"),
("\"one_two\"", "c"),
("one_two", "shell"),
("one_two", "shell-show"),
("\'one_two\'", "shell-always"),
("\'one_two\'", "shell-always-show"),
("one_two", "shell-escape"),
("\'one_two\'", "shell-escape-always"),
],
);
}
#[test]
fn test_spaces() {
check_names(
"one two",
vec![
("one two", "literal"),
("one two", "literal-show"),
("one\\ two", "escape"),
("\"one two\"", "c"),
("\'one two\'", "shell"),
("\'one two\'", "shell-show"),
("\'one two\'", "shell-always"),
("\'one two\'", "shell-always-show"),
("\'one two\'", "shell-escape"),
("\'one two\'", "shell-escape-always"),
],
);
check_names(
" one",
vec![
(" one", "literal"),
(" one", "literal-show"),
("\\ one", "escape"),
("\" one\"", "c"),
("' one'", "shell"),
("' one'", "shell-show"),
("' one'", "shell-always"),
("' one'", "shell-always-show"),
("' one'", "shell-escape"),
("' one'", "shell-escape-always"),
],
);
}
#[test]
fn test_quotes() {
// One double quote
check_names(
"one\"two",
vec![
("one\"two", "literal"),
("one\"two", "literal-show"),
("one\"two", "escape"),
("\"one\\\"two\"", "c"),
("'one\"two'", "shell"),
("'one\"two'", "shell-show"),
("'one\"two'", "shell-always"),
("'one\"two'", "shell-always-show"),
("'one\"two'", "shell-escape"),
("'one\"two'", "shell-escape-always"),
],
);
// One single quote
check_names(
"one\'two",
vec![
("one'two", "literal"),
("one'two", "literal-show"),
("one'two", "escape"),
("\"one'two\"", "c"),
("\"one'two\"", "shell"),
("\"one'two\"", "shell-show"),
("\"one'two\"", "shell-always"),
("\"one'two\"", "shell-always-show"),
("\"one'two\"", "shell-escape"),
("\"one'two\"", "shell-escape-always"),
],
);
// One single quote and one double quote
check_names(
"one'two\"three",
vec![
("one'two\"three", "literal"),
("one'two\"three", "literal-show"),
("one'two\"three", "escape"),
("\"one'two\\\"three\"", "c"),
("'one'\\''two\"three'", "shell"),
("'one'\\''two\"three'", "shell-show"),
("'one'\\''two\"three'", "shell-always"),
("'one'\\''two\"three'", "shell-always-show"),
("'one'\\''two\"three'", "shell-escape"),
("'one'\\''two\"three'", "shell-escape-always"),
],
);
// Consecutive quotes
check_names(
"one''two\"\"three",
vec![
("one''two\"\"three", "literal"),
("one''two\"\"three", "literal-show"),
("one''two\"\"three", "escape"),
("\"one''two\\\"\\\"three\"", "c"),
("'one'\\'''\\''two\"\"three'", "shell"),
("'one'\\'''\\''two\"\"three'", "shell-show"),
("'one'\\'''\\''two\"\"three'", "shell-always"),
("'one'\\'''\\''two\"\"three'", "shell-always-show"),
("'one'\\'''\\''two\"\"three'", "shell-escape"),
("'one'\\'''\\''two\"\"three'", "shell-escape-always"),
],
);
}
#[test]
fn test_control_chars() {
// A simple newline
check_names(
"one\ntwo",
vec![
("one?two", "literal"),
("one\ntwo", "literal-show"),
("one\\ntwo", "escape"),
("\"one\\ntwo\"", "c"),
("one?two", "shell"),
("one\ntwo", "shell-show"),
("'one?two'", "shell-always"),
("'one\ntwo'", "shell-always-show"),
("'one'$'\\n''two'", "shell-escape"),
("'one'$'\\n''two'", "shell-escape-always"),
],
);
// The first 16 control characters. NUL is also included, even though it is of
// no importance for file names.
check_names(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F",
vec![
("????????????????", "literal"),
(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F",
"literal-show",
),
(
"\\000\\001\\002\\003\\004\\005\\006\\a\\b\\t\\n\\v\\f\\r\\016\\017",
"escape",
),
(
"\"\\000\\001\\002\\003\\004\\005\\006\\a\\b\\t\\n\\v\\f\\r\\016\\017\"",
"c",
),
("????????????????", "shell"),
(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F",
"shell-show",
),
("'????????????????'", "shell-always"),
(
"'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F'",
"shell-always-show",
),
(
"''$'\\000\\001\\002\\003\\004\\005\\006\\a\\b\\t\\n\\v\\f\\r\\016\\017'",
"shell-escape",
),
(
"''$'\\000\\001\\002\\003\\004\\005\\006\\a\\b\\t\\n\\v\\f\\r\\016\\017'",
"shell-escape-always",
),
],
);
// The last 16 control characters.
check_names(
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F",
vec![
("????????????????", "literal"),
(
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F",
"literal-show",
),
(
"\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037",
"escape",
),
(
"\"\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\"",
"c",
),
("????????????????", "shell"),
(
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F",
"shell-show",
),
("'????????????????'", "shell-always"),
(
"'\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F'",
"shell-always-show",
),
(
"''$'\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037'",
"shell-escape",
),
(
"''$'\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037'",
"shell-escape-always",
),
],
);
// DEL
check_names(
"\x7F",
vec![
("?", "literal"),
("\x7F", "literal-show"),
("\\177", "escape"),
("\"\\177\"", "c"),
("?", "shell"),
("\x7F", "shell-show"),
("'?'", "shell-always"),
("'\x7F'", "shell-always-show"),
("''$'\\177'", "shell-escape"),
("''$'\\177'", "shell-escape-always"),
],
);
}
#[test]
fn test_question_mark() {
// A question mark must force quotes in shell and shell-always, unless
// it is in place of a control character (that case is already covered
// in other tests)
check_names(
"one?two",
vec![
("one?two", "literal"),
("one?two", "literal-show"),
("one?two", "escape"),
("\"one?two\"", "c"),
("'one?two'", "shell"),
("'one?two'", "shell-show"),
("'one?two'", "shell-always"),
("'one?two'", "shell-always-show"),
("'one?two'", "shell-escape"),
("'one?two'", "shell-escape-always"),
],
);
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_mkdir"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "mkdir ~ (uutils) create DIRECTORY"
@ -17,7 +17,7 @@ path = "src/mkdir.rs"
[dependencies]
clap = "2.33"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs", "mode"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs", "mode"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_mkfifo"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "mkfifo ~ (uutils) create FIFOs (named pipes)"
@ -15,9 +15,9 @@ edition = "2018"
path = "src/mkfifo.rs"
[dependencies]
getopts = "0.2.18"
clap = "2.33"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -8,56 +8,55 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use libc::mkfifo;
use std::ffi::CString;
use std::io::Error;
static NAME: &str = "mkfifo";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static USAGE: &str = "mkfifo [OPTION]... NAME...";
static SUMMARY: &str = "Create a FIFO with the given name.";
mod options {
pub static MODE: &str = "mode";
pub static SE_LINUX_SECURITY_CONTEXT: &str = "Z";
pub static CONTEXT: &str = "context";
pub static FIFO: &str = "fifo";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let mut opts = getopts::Options::new();
let matches = App::new(executable!())
.name(NAME)
.version(VERSION)
.usage(USAGE)
.about(SUMMARY)
.arg(
Arg::with_name(options::MODE)
.short("m")
.long(options::MODE)
.help("file permissions for the fifo")
.default_value("0666")
.value_name("0666"),
)
.arg(
Arg::with_name(options::SE_LINUX_SECURITY_CONTEXT)
.short(options::SE_LINUX_SECURITY_CONTEXT)
.help("set the SELinux security context to default type")
)
.arg(Arg::with_name(options::CONTEXT).long(options::CONTEXT).value_name("CTX").help("like -Z, or if CTX is specified then set the SELinux\nor SMACK security context to CTX"))
.arg(Arg::with_name(options::FIFO).hidden(true).multiple(true))
.get_matches_from(args);
opts.optopt(
"m",
"mode",
"file permissions for the fifo",
"(default 0666)",
);
opts.optflag("h", "help", "display this help and exit");
opts.optflag("V", "version", "output version information and exit");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(err) => panic!("{}", err),
};
if matches.opt_present("version") {
println!("{} {}", NAME, VERSION);
return 0;
if matches.is_present(options::CONTEXT) {
crash!(1, "--context is not implemented");
}
if matches.is_present(options::SE_LINUX_SECURITY_CONTEXT) {
crash!(1, "-Z is not implemented");
}
if matches.opt_present("help") || matches.free.is_empty() {
let msg = format!(
"{0} {1}
Usage:
{0} [OPTIONS] NAME...
Create a FIFO with the given name.",
NAME, VERSION
);
print!("{}", opts.usage(&msg));
if matches.free.is_empty() {
return 1;
}
return 0;
}
let mode = match matches.opt_str("m") {
let mode = match matches.value_of(options::MODE) {
Some(m) => match usize::from_str_radix(&m, 8) {
Ok(m) => m,
Err(e) => {
@ -68,21 +67,22 @@ Create a FIFO with the given name.",
None => 0o666,
};
let mut exit_status = 0;
for f in &matches.free {
let fifos: Vec<String> = match matches.values_of(options::FIFO) {
Some(v) => v.clone().map(|s| s.to_owned()).collect(),
None => crash!(1, "missing operand"),
};
let mut exit_code = 0;
for f in fifos {
let err = unsafe {
let name = CString::new(f.as_bytes()).unwrap();
mkfifo(name.as_ptr(), mode as libc::mode_t)
};
if err == -1 {
show_error!(
"creating '{}': {}",
f,
Error::last_os_error().raw_os_error().unwrap()
);
exit_status = 1;
show_error!("cannot create fifo '{}': File exists", f);
exit_code = 1;
}
}
exit_status
exit_code
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_mknod"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "mknod ~ (uutils) create special file NAME of TYPE"
@ -18,7 +18,7 @@ path = "src/mknod.rs"
[dependencies]
getopts = "0.2.18"
libc = "^0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["mode"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["mode"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_mktemp"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "mktemp ~ (uutils) create and display a temporary file or directory from TEMPLATE"
@ -18,7 +18,7 @@ path = "src/mktemp.rs"
clap = "2.33"
rand = "0.5"
tempfile = "3.1"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_more"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "more ~ (uutils) input perusal filter"
@ -15,7 +15,7 @@ edition = "2018"
path = "src/more.rs"
[dependencies]
getopts = "0.2.18"
clap = "2.33"
uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" }
uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }

View file

@ -10,9 +10,8 @@
#[macro_use]
extern crate uucore;
use getopts::Options;
use std::fs::File;
use std::io::{stdout, Read, Write};
use std::io::{stdin, stdout, BufRead, BufReader, Read, Write};
#[cfg(all(unix, not(target_os = "fuchsia")))]
extern crate nix;
@ -24,70 +23,52 @@ extern crate redox_termios;
#[cfg(target_os = "redox")]
extern crate syscall;
#[derive(Clone, Eq, PartialEq)]
pub enum Mode {
More,
Help,
Version,
use clap::{App, Arg, ArgMatches};
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "A file perusal filter for CRT viewing.";
mod options {
pub const FILE: &str = "file";
}
static NAME: &str = "more";
static VERSION: &str = env!("CARGO_PKG_VERSION");
fn get_usage() -> String {
format!("{} [options] <file>...", executable!())
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let usage = get_usage();
let mut opts = Options::new();
let matches = App::new(executable!())
.version(VERSION)
.usage(usage.as_str())
.about(ABOUT)
.arg(
Arg::with_name(options::FILE)
.number_of_values(1)
.multiple(true),
)
.get_matches_from(args);
// FixME: fail without panic for now; but `more` should work with no arguments (ie, for piped input)
if args.len() < 2 {
println!("{}: incorrect usage", args[0]);
if let None | Some("-") = matches.value_of(options::FILE) {
show_usage_error!("Reading from stdin isn't supported yet.");
return 1;
}
opts.optflag("h", "help", "display this help and exit");
opts.optflag("v", "version", "output version information and exit");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(e) => {
show_error!("{}", e);
panic!()
if let Some(x) = matches.value_of(options::FILE) {
let path = std::path::Path::new(x);
if path.is_dir() {
show_usage_error!("'{}' is a directory.", x);
return 1;
}
};
let usage = opts.usage("more TARGET.");
let mode = if matches.opt_present("version") {
Mode::Version
} else if matches.opt_present("help") {
Mode::Help
} else {
Mode::More
};
match mode {
Mode::More => more(matches),
Mode::Help => help(&usage),
Mode::Version => version(),
}
more(matches);
0
}
fn version() {
println!("{} {}", NAME, VERSION);
}
fn help(usage: &str) {
let msg = format!(
"{0} {1}\n\n\
Usage: {0} TARGET\n \
\n\
{2}",
NAME, VERSION, usage
);
println!("{}", msg);
}
#[cfg(all(unix, not(target_os = "fuchsia")))]
fn setup_term() -> termios::Termios {
let mut term = termios::tcgetattr(0).unwrap();
@ -138,9 +119,11 @@ fn reset_term(term: &mut redox_termios::Termios) {
let _ = syscall::close(fd);
}
fn more(matches: getopts::Matches) {
let files = matches.free;
let mut f = File::open(files.first().unwrap()).unwrap();
fn more(matches: ArgMatches) {
let mut f: Box<dyn BufRead> = match matches.value_of(options::FILE) {
None | Some("-") => Box::new(BufReader::new(stdin())),
Some(filename) => Box::new(BufReader::new(File::open(filename).unwrap())),
};
let mut buffer = [0; 1024];
let mut term = setup_term();

View file

@ -1,6 +1,6 @@
[package]
name = "uu_mv"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "mv ~ (uutils) move (rename) SOURCE to DESTINATION"
@ -17,7 +17,7 @@ path = "src/mv.rs"
[dependencies]
clap = "2.33"
fs_extra = "1.1.0"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_nice"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "nice ~ (uutils) run PROGRAM with modified scheduling priority"
@ -18,7 +18,7 @@ path = "src/nice.rs"
clap = "2.33"
libc = "0.2.42"
nix = { version="<=0.13" }
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_nl"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "nl ~ (uutils) display input with added line numbers"
@ -15,13 +15,13 @@ edition = "2018"
path = "src/nl.rs"
[dependencies]
clap = "2.33.3"
aho-corasick = "0.7.3"
getopts = "0.2.18"
libc = "0.2.42"
memchr = "2.2.0"
regex = "1.0.1"
regex-syntax = "0.6.7"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,5 +1,7 @@
// spell-checker:ignore (ToDO) conv
use crate::options;
// parse_style parses a style string into a NumberingStyle.
fn parse_style(chars: &[char]) -> Result<crate::NumberingStyle, String> {
if chars.len() == 1 && chars[0] == 'a' {
@ -23,19 +25,19 @@ fn parse_style(chars: &[char]) -> Result<crate::NumberingStyle, String> {
// parse_options loads the options into the settings, returning an array of
// error messages.
pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) -> Vec<String> {
pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> Vec<String> {
// This vector holds error messages encountered.
let mut errs: Vec<String> = vec![];
settings.renumber = !opts.opt_present("p");
match opts.opt_str("s") {
settings.renumber = !opts.is_present(options::NO_RENUMBER);
match opts.value_of(options::NUMER_SEPARATOR) {
None => {}
Some(val) => {
settings.number_separator = val;
settings.number_separator = val.to_owned();
}
}
match opts.opt_str("n") {
match opts.value_of(options::NUMBER_FORMAT) {
None => {}
Some(val) => match val.as_ref() {
Some(val) => match val {
"ln" => {
settings.number_format = crate::NumberFormat::Left;
}
@ -50,7 +52,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) ->
}
},
}
match opts.opt_str("b") {
match opts.value_of(options::BODY_NUMBERING) {
None => {}
Some(val) => {
let chars: Vec<char> = val.chars().collect();
@ -64,7 +66,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) ->
}
}
}
match opts.opt_str("f") {
match opts.value_of(options::FOOTER_NUMBERING) {
None => {}
Some(val) => {
let chars: Vec<char> = val.chars().collect();
@ -78,7 +80,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) ->
}
}
}
match opts.opt_str("h") {
match opts.value_of(options::HEADER_NUMBERING) {
None => {}
Some(val) => {
let chars: Vec<char> = val.chars().collect();
@ -92,7 +94,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) ->
}
}
}
match opts.opt_str("i") {
match opts.value_of(options::LINE_INCREMENT) {
None => {}
Some(val) => {
let conv: Option<u64> = val.parse().ok();
@ -104,7 +106,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) ->
}
}
}
match opts.opt_str("w") {
match opts.value_of(options::NUMBER_WIDTH) {
None => {}
Some(val) => {
let conv: Option<usize> = val.parse().ok();
@ -116,7 +118,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) ->
}
}
}
match opts.opt_str("v") {
match opts.value_of(options::STARTING_LINE_NUMER) {
None => {}
Some(val) => {
let conv: Option<u64> = val.parse().ok();
@ -128,7 +130,7 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &getopts::Matches) ->
}
}
}
match opts.opt_str("l") {
match opts.value_of(options::JOIN_BLANK_LINES) {
None => {}
Some(val) => {
let conv: Option<u64> = val.parse().ok();

View file

@ -11,6 +11,7 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
use std::iter::repeat;
@ -67,78 +68,106 @@ enum NumberFormat {
RightZero,
}
pub mod options {
pub const FILE: &str = "file";
pub const BODY_NUMBERING: &str = "body-numbering";
pub const SECTION_DELIMITER: &str = "section-delimiter";
pub const FOOTER_NUMBERING: &str = "footer-numbering";
pub const HEADER_NUMBERING: &str = "header-numbering";
pub const LINE_INCREMENT: &str = "line-increment";
pub const JOIN_BLANK_LINES: &str = "join-blank-lines";
pub const NUMBER_FORMAT: &str = "number-format";
pub const NO_RENUMBER: &str = "no-renumber";
pub const NUMER_SEPARATOR: &str = "number-separator";
pub const STARTING_LINE_NUMER: &str = "starting-line-number";
pub const NUMBER_WIDTH: &str = "number-width";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let mut opts = getopts::Options::new();
opts.optopt(
"b",
"body-numbering",
"use STYLE for numbering body lines",
"STYLE",
);
opts.optopt(
"d",
"section-delimiter",
"use CC for separating logical pages",
"CC",
);
opts.optopt(
"f",
"footer-numbering",
"use STYLE for numbering footer lines",
"STYLE",
);
opts.optopt(
"h",
"header-numbering",
"use STYLE for numbering header lines",
"STYLE",
);
opts.optopt(
"i",
"line-increment",
"line number increment at each line",
"",
);
opts.optopt(
"l",
"join-blank-lines",
"group of NUMBER empty lines counted as one",
"NUMBER",
);
opts.optopt(
"n",
"number-format",
"insert line numbers according to FORMAT",
"FORMAT",
);
opts.optflag(
"p",
"no-renumber",
"do not reset line numbers at logical pages",
);
opts.optopt(
"s",
"number-separator",
"add STRING after (possible) line number",
"STRING",
);
opts.optopt(
"v",
"starting-line-number",
"first line number on each logical page",
"NUMBER",
);
opts.optopt(
"w",
"number-width",
"use NUMBER columns for line numbers",
"NUMBER",
);
opts.optflag("", "help", "display this help and exit");
opts.optflag("V", "version", "version");
let matches = App::new(executable!())
.name(NAME)
.version(VERSION)
.usage(USAGE)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.arg(
Arg::with_name(options::BODY_NUMBERING)
.short("b")
.long(options::BODY_NUMBERING)
.help("use STYLE for numbering body lines")
.value_name("SYNTAX"),
)
.arg(
Arg::with_name(options::SECTION_DELIMITER)
.short("d")
.long(options::SECTION_DELIMITER)
.help("use CC for separating logical pages")
.value_name("CC"),
)
.arg(
Arg::with_name(options::FOOTER_NUMBERING)
.short("f")
.long(options::FOOTER_NUMBERING)
.help("use STYLE for numbering footer lines")
.value_name("STYLE"),
)
.arg(
Arg::with_name(options::HEADER_NUMBERING)
.short("h")
.long(options::HEADER_NUMBERING)
.help("use STYLE for numbering header lines")
.value_name("STYLE"),
)
.arg(
Arg::with_name(options::LINE_INCREMENT)
.short("i")
.long(options::LINE_INCREMENT)
.help("line number increment at each line")
.value_name("NUMBER"),
)
.arg(
Arg::with_name(options::JOIN_BLANK_LINES)
.short("l")
.long(options::JOIN_BLANK_LINES)
.help("group of NUMBER empty lines counted as one")
.value_name("NUMBER"),
)
.arg(
Arg::with_name(options::NUMBER_FORMAT)
.short("n")
.long(options::NUMBER_FORMAT)
.help("insert line numbers according to FORMAT")
.value_name("FORMAT"),
)
.arg(
Arg::with_name(options::NO_RENUMBER)
.short("p")
.long(options::NO_RENUMBER)
.help("do not reset line numbers at logical pages"),
)
.arg(
Arg::with_name(options::NUMER_SEPARATOR)
.short("s")
.long(options::NUMER_SEPARATOR)
.help("add STRING after (possible) line number")
.value_name("STRING"),
)
.arg(
Arg::with_name(options::STARTING_LINE_NUMER)
.short("v")
.long(options::STARTING_LINE_NUMER)
.help("first line number on each logical page")
.value_name("NUMBER"),
)
.arg(
Arg::with_name(options::NUMBER_WIDTH)
.short("w")
.long(options::NUMBER_WIDTH)
.help("use NUMBER columns for line numbers")
.value_name("NUMBER"),
)
.get_matches_from(args);
// A mutable settings object, initialized with the defaults.
let mut settings = Settings {
@ -155,27 +184,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
number_separator: String::from("\t"),
};
let given_options = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
show_error!("{}", f);
print_usage(&opts);
return 1;
}
};
if given_options.opt_present("help") {
print_usage(&opts);
return 0;
}
if given_options.opt_present("version") {
version();
return 0;
}
// Update the settings from the command line options, and terminate the
// program if some options could not successfully be parsed.
let parse_errors = helper::parse_options(&mut settings, &given_options);
let parse_errors = helper::parse_options(&mut settings, &matches);
if !parse_errors.is_empty() {
show_error!("Invalid arguments supplied.");
for message in &parse_errors {
@ -184,8 +195,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
return 1;
}
let files = given_options.free;
let mut read_stdin = files.is_empty();
let mut read_stdin = false;
let files: Vec<String> = match matches.values_of(options::FILE) {
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
None => vec!["-".to_owned()],
};
for file in &files {
if file == "-" {
@ -370,11 +384,3 @@ fn pass_none(_: &str, _: &regex::Regex) -> bool {
fn pass_all(_: &str, _: &regex::Regex) -> bool {
true
}
fn print_usage(opts: &getopts::Options) {
println!("{}", opts.usage(USAGE));
}
fn version() {
println!("{} {}", NAME, VERSION);
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_nohup"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "nohup ~ (uutils) run COMMAND, ignoring hangup signals"
@ -17,7 +17,7 @@ path = "src/nohup.rs"
[dependencies]
clap = "2.33"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_nproc"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "nproc ~ (uutils) display the number of processing units available"
@ -18,7 +18,7 @@ path = "src/nproc.rs"
libc = "0.2.42"
num_cpus = "1.10"
clap = "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_numfmt"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "numfmt ~ (uutils) reformat NUMBER"
@ -16,7 +16,7 @@ path = "src/numfmt.rs"
[dependencies]
clap = "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_od"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "od ~ (uutils) display formatted representation of input"
@ -19,7 +19,7 @@ byteorder = "1.3.2"
clap = "2.33"
half = "1.6"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -166,8 +166,9 @@ mod tests {
let mut input = PeekReader::new(Cursor::new(&data));
let mut sut = InputDecoder::new(&mut input, 8, 2, ByteOrder::Little);
match sut.peek_read() {
Ok(mut mem) => {
// Peek normal length
let mut mem = sut.peek_read().unwrap();
assert_eq!(8, mem.length());
assert_eq!(-2.0, mem.read_float(0, 8));
@ -185,20 +186,10 @@ mod tests {
mem.zero_out_buffer(7, 8);
assert_eq!(&[0, 0, 0xff, 0xff], mem.get_full_buffer(6));
}
Err(e) => {
assert!(false, e);
}
}
match sut.peek_read() {
Ok(mem) => {
// Peek tail
let mem = sut.peek_read().unwrap();
assert_eq!(2, mem.length());
assert_eq!(0xffff, mem.read_uint(0, 2));
}
Err(e) => {
assert!(false, e);
}
}
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "uu_paste"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "paste ~ (uutils) merge lines from inputs"
@ -16,7 +16,7 @@ path = "src/paste.rs"
[dependencies]
clap = "2.33.3"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_pathchk"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "pathchk ~ (uutils) diagnose invalid or non-portable PATHNAME"
@ -17,7 +17,7 @@ path = "src/pathchk.rs"
[dependencies]
clap = "2.33"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_pinky"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "pinky ~ (uutils) display user information"
@ -15,7 +15,7 @@ edition = "2018"
path = "src/pinky.rs"
[dependencies]
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["utmpx", "entries"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx", "entries"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_printenv"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "printenv ~ (uutils) display value of environment VAR"
@ -16,7 +16,7 @@ path = "src/printenv.rs"
[dependencies]
clap = "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -1,6 +1,6 @@
[package]
name = "uu_printf"
version = "0.0.4"
version = "0.0.6"
authors = [
"Nathan Ross",
"uutils developers",
@ -19,7 +19,7 @@ path = "src/printf.rs"
[dependencies]
itertools = "0.8.0"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -59,7 +59,7 @@ fn get_primitive_hex(
// assign 0
let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos {
Some(pos) => (&str_in[..pos], &str_in[pos + 1..]),
None => (&str_in[..], "0"),
None => (str_in, "0"),
};
if first_segment_raw.is_empty() {
first_segment_raw = "0";

View file

@ -218,7 +218,7 @@ pub fn get_primitive_dec(
// assign 0
let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos {
Some(pos) => (&str_in[..pos], &str_in[pos + 1..]),
None => (&str_in[..], "0"),
None => (str_in, "0"),
};
if first_segment_raw.is_empty() {
first_segment_raw = "0";

View file

@ -1,6 +1,6 @@
[package]
name = "uu_ptx"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "ptx ~ (uutils) display a permuted index of input"
@ -17,12 +17,11 @@ path = "src/ptx.rs"
[dependencies]
clap = "2.33"
aho-corasick = "0.7.3"
getopts = "0.2.18"
libc = "0.2.42"
memchr = "2.2.0"
regex = "1.0.1"
regex-syntax = "0.6.7"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -169,9 +169,9 @@ fn get_config(matches: &clap::ArgMatches) -> Config {
.expect(err_msg)
.to_string();
}
if matches.is_present(options::IGNORE_CASE) {
if matches.is_present(options::FLAG_TRUNCATION) {
config.trunc_str = matches
.value_of(options::IGNORE_CASE)
.value_of(options::FLAG_TRUNCATION)
.expect(err_msg)
.to_string();
}
@ -195,8 +195,16 @@ fn get_config(matches: &clap::ArgMatches) -> Config {
config
}
fn read_input(input_files: &[String], config: &Config) -> HashMap<String, (Vec<String>, usize)> {
let mut file_map: HashMap<String, (Vec<String>, usize)> = HashMap::new();
struct FileContent {
lines: Vec<String>,
chars_lines: Vec<Vec<char>>,
offset: usize,
}
type FileMap = HashMap<String, FileContent>;
fn read_input(input_files: &[String], config: &Config) -> FileMap {
let mut file_map: FileMap = HashMap::new();
let mut files = Vec::new();
if input_files.is_empty() {
files.push("-");
@ -207,7 +215,7 @@ fn read_input(input_files: &[String], config: &Config) -> HashMap<String, (Vec<S
} else {
files.push(&input_files[0]);
}
let mut lines_so_far: usize = 0;
let mut offset: usize = 0;
for filename in files {
let reader: BufReader<Box<dyn Read>> = BufReader::new(if filename == "-" {
Box::new(stdin())
@ -216,25 +224,33 @@ fn read_input(input_files: &[String], config: &Config) -> HashMap<String, (Vec<S
Box::new(file)
});
let lines: Vec<String> = reader.lines().map(|x| crash_if_err!(1, x)).collect();
// Indexing UTF-8 string requires walking from the beginning, which can hurts performance badly when the line is long.
// Since we will be jumping around the line a lot, we dump the content into a Vec<char>, which can be indexed in constant time.
let chars_lines: Vec<Vec<char>> = lines.iter().map(|x| x.chars().collect()).collect();
let size = lines.len();
file_map.insert(filename.to_owned(), (lines, lines_so_far));
lines_so_far += size
file_map.insert(
filename.to_owned(),
FileContent {
lines,
chars_lines,
offset,
},
);
offset += size
}
file_map
}
fn create_word_set(
config: &Config,
filter: &WordFilter,
file_map: &HashMap<String, (Vec<String>, usize)>,
) -> BTreeSet<WordRef> {
/// Go through every lines in the input files and record each match occurance as a `WordRef`.
fn create_word_set(config: &Config, filter: &WordFilter, file_map: &FileMap) -> BTreeSet<WordRef> {
let reg = Regex::new(&filter.word_regex).unwrap();
let ref_reg = Regex::new(&config.context_regex).unwrap();
let mut word_set: BTreeSet<WordRef> = BTreeSet::new();
for (file, lines) in file_map.iter() {
let mut count: usize = 0;
let offs = lines.1;
for line in &lines.0 {
let offs = lines.offset;
for line in &lines.lines {
// if -r, exclude reference from word set
let (ref_beg, ref_end) = match ref_reg.find(line) {
Some(x) => (x.start(), x.end()),
@ -271,12 +287,11 @@ fn create_word_set(
word_set
}
fn get_reference(config: &Config, word_ref: &WordRef, line: &str) -> String {
fn get_reference(config: &Config, word_ref: &WordRef, line: &str, context_reg: &Regex) -> String {
if config.auto_ref {
format!("{}:{}", word_ref.filename, word_ref.local_line_nr + 1)
} else if config.input_ref {
let reg = Regex::new(&config.context_regex).unwrap();
let (beg, end) = match reg.find(line) {
let (beg, end) = match context_reg.find(line) {
Some(x) => (x.start(), x.end()),
None => (0, 0),
};
@ -329,57 +344,107 @@ fn trim_idx(s: &[char], beg: usize, end: usize) -> (usize, usize) {
}
fn get_output_chunks(
all_before: &str,
all_before: &[char],
keyword: &str,
all_after: &str,
all_after: &[char],
config: &Config,
) -> (String, String, String, String) {
assert_eq!(all_before.trim(), all_before);
assert_eq!(keyword.trim(), keyword);
assert_eq!(all_after.trim(), all_after);
let mut head = String::new();
let mut before = String::new();
let mut after = String::new();
let mut tail = String::new();
let half_line_size = cmp::max(
(config.line_width / 2) as isize - (2 * config.trunc_str.len()) as isize,
// Chunk size logics are mostly copied from the GNU ptx source.
// https://github.com/MaiZure/coreutils-8.3/blob/master/src/ptx.c#L1234
let half_line_size = (config.line_width / 2) as usize;
let max_before_size = cmp::max(half_line_size as isize - config.gap_size as isize, 0) as usize;
let max_after_size = cmp::max(
half_line_size as isize
- (2 * config.trunc_str.len()) as isize
- keyword.len() as isize
- 1,
0,
) as usize;
let max_after_size = cmp::max(half_line_size as isize - keyword.len() as isize - 1, 0) as usize;
let max_before_size = half_line_size;
let all_before_vec: Vec<char> = all_before.chars().collect();
let all_after_vec: Vec<char> = all_after.chars().collect();
// get before
let mut bb_tmp = cmp::max(all_before.len() as isize - max_before_size as isize, 0) as usize;
bb_tmp = trim_broken_word_left(&all_before_vec, bb_tmp, all_before.len());
let (before_beg, before_end) = trim_idx(&all_before_vec, bb_tmp, all_before.len());
before.push_str(&all_before[before_beg..before_end]);
// Allocate plenty space for all the chunks.
let mut head = String::with_capacity(half_line_size);
let mut before = String::with_capacity(half_line_size);
let mut after = String::with_capacity(half_line_size);
let mut tail = String::with_capacity(half_line_size);
// the before chunk
// trim whitespace away from all_before to get the index where the before chunk should end.
let (_, before_end) = trim_idx(all_before, 0, all_before.len());
// the minimum possible begin index of the before_chunk is the end index minus the length.
let before_beg = cmp::max(before_end as isize - max_before_size as isize, 0) as usize;
// in case that falls in the middle of a word, trim away the word.
let before_beg = trim_broken_word_left(all_before, before_beg, before_end);
// trim away white space.
let (before_beg, before_end) = trim_idx(all_before, before_beg, before_end);
// and get the string.
let before_str: String = all_before[before_beg..before_end].iter().collect();
before.push_str(&before_str);
assert!(max_before_size >= before.len());
// get after
let mut ae_tmp = cmp::min(max_after_size, all_after.len());
ae_tmp = trim_broken_word_right(&all_after_vec, 0, ae_tmp);
let (after_beg, after_end) = trim_idx(&all_after_vec, 0, ae_tmp);
after.push_str(&all_after[after_beg..after_end]);
// the after chunk
// must be no longer than the minimum between the max size and the total available string.
let after_end = cmp::min(max_after_size, all_after.len());
// in case that falls in the middle of a word, trim away the word.
let after_end = trim_broken_word_right(all_after, 0, after_end);
// trim away white space.
let (_, after_end) = trim_idx(all_after, 0, after_end);
// and get the string
let after_str: String = all_after[0..after_end].iter().collect();
after.push_str(&after_str);
assert!(max_after_size >= after.len());
// get tail
let max_tail_size = max_before_size - before.len();
let (tb, _) = trim_idx(&all_after_vec, after_end, all_after.len());
let mut te_tmp = cmp::min(tb + max_tail_size, all_after.len());
te_tmp = trim_broken_word_right(&all_after_vec, tb, te_tmp);
let (tail_beg, tail_end) = trim_idx(&all_after_vec, tb, te_tmp);
tail.push_str(&all_after[tail_beg..tail_end]);
// the tail chunk
// get head
let max_head_size = max_after_size - after.len();
let (_, he) = trim_idx(&all_before_vec, 0, before_beg);
let mut hb_tmp = cmp::max(he as isize - max_head_size as isize, 0) as usize;
hb_tmp = trim_broken_word_left(&all_before_vec, hb_tmp, he);
let (head_beg, head_end) = trim_idx(&all_before_vec, hb_tmp, he);
head.push_str(&all_before[head_beg..head_end]);
// max size of the tail chunk = max size of left half - space taken by before chunk - gap size.
let max_tail_size = cmp::max(
max_before_size as isize - before.len() as isize - config.gap_size as isize,
0,
) as usize;
// the tail chunk takes text starting from where the after chunk ends (with whitespaces trimmed).
let (tail_beg, _) = trim_idx(all_after, after_end, all_after.len());
// end = begin + max length
let tail_end = cmp::min(all_after.len(), tail_beg + max_tail_size) as usize;
// in case that falls in the middle of a word, trim away the word.
let tail_end = trim_broken_word_right(all_after, tail_beg, tail_end);
// trim away whitespace again.
let (tail_beg, tail_end) = trim_idx(all_after, tail_beg, tail_end);
// and get the string
let tail_str: String = all_after[tail_beg..tail_end].iter().collect();
tail.push_str(&tail_str);
// the head chunk
// max size of the head chunk = max size of right half - space taken by after chunk - gap size.
let max_head_size = cmp::max(
max_after_size as isize - after.len() as isize - config.gap_size as isize,
0,
) as usize;
// the head chunk takes text from before the before chunk
let (_, head_end) = trim_idx(all_before, 0, before_beg);
// begin = end - max length
let head_beg = cmp::max(head_end as isize - max_head_size as isize, 0) as usize;
// in case that falls in the middle of a word, trim away the word.
let head_beg = trim_broken_word_left(all_before, head_beg, head_end);
// trim away white space again.
let (head_beg, head_end) = trim_idx(all_before, head_beg, head_end);
// and get the string.
let head_str: String = all_before[head_beg..head_end].iter().collect();
head.push_str(&head_str);
// put right context truncation string if needed
if after_end != all_after.len() && tail_beg == tail_end {
@ -395,11 +460,6 @@ fn get_output_chunks(
head = format!("{}{}", config.trunc_str, head);
}
// add space before "after" if needed
if !after.is_empty() {
after = format!(" {}", after);
}
(tail, before, after, head)
}
@ -412,70 +472,95 @@ fn tex_mapper(x: char) -> String {
}
}
fn adjust_tex_str(context: &str) -> String {
let ws_reg = Regex::new(r"[\t\n\v\f\r ]").unwrap();
let mut fix: String = ws_reg.replace_all(context, " ").trim().to_owned();
let mapped_chunks: Vec<String> = fix.chars().map(tex_mapper).collect();
fix = mapped_chunks.join("");
fix
/// Escape special characters for TeX.
fn format_tex_field(s: &str) -> String {
let mapped_chunks: Vec<String> = s.chars().map(tex_mapper).collect();
mapped_chunks.join("")
}
fn format_tex_line(config: &Config, word_ref: &WordRef, line: &str, reference: &str) -> String {
fn format_tex_line(
config: &Config,
word_ref: &WordRef,
line: &str,
chars_line: &[char],
reference: &str,
) -> String {
let mut output = String::new();
output.push_str(&format!("\\{} ", config.macro_name));
let all_before = if config.input_ref {
let before = &line[0..word_ref.position];
adjust_tex_str(before.trim().trim_start_matches(reference))
let before_start_trimoff =
word_ref.position - before.trim_start_matches(reference).trim_start().len();
let before_end_index = before.len();
&chars_line[before_start_trimoff..cmp::max(before_end_index, before_start_trimoff)]
} else {
adjust_tex_str(&line[0..word_ref.position])
let before_chars_trim_idx = (0, word_ref.position);
&chars_line[before_chars_trim_idx.0..before_chars_trim_idx.1]
};
let keyword = adjust_tex_str(&line[word_ref.position..word_ref.position_end]);
let all_after = adjust_tex_str(&line[word_ref.position_end..line.len()]);
let keyword = &line[word_ref.position..word_ref.position_end];
let after_chars_trim_idx = (word_ref.position_end, chars_line.len());
let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1];
let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config);
output.push_str(&format!(
"{5}{0}{6}{5}{1}{6}{5}{2}{6}{5}{3}{6}{5}{4}{6}",
tail, before, keyword, after, head, "{", "}"
format_tex_field(&tail),
format_tex_field(&before),
format_tex_field(keyword),
format_tex_field(&after),
format_tex_field(&head),
"{",
"}"
));
if config.auto_ref || config.input_ref {
output.push_str(&format!("{}{}{}", "{", adjust_tex_str(&reference), "}"));
output.push_str(&format!("{}{}{}", "{", format_tex_field(&reference), "}"));
}
output
}
fn adjust_roff_str(context: &str) -> String {
let ws_reg = Regex::new(r"[\t\n\v\f\r]").unwrap();
ws_reg
.replace_all(context, " ")
.replace("\"", "\"\"")
.trim()
.to_owned()
fn format_roff_field(s: &str) -> String {
s.replace("\"", "\"\"")
}
fn format_roff_line(config: &Config, word_ref: &WordRef, line: &str, reference: &str) -> String {
fn format_roff_line(
config: &Config,
word_ref: &WordRef,
line: &str,
chars_line: &[char],
reference: &str,
) -> String {
let mut output = String::new();
output.push_str(&format!(".{}", config.macro_name));
let all_before = if config.input_ref {
let before = &line[0..word_ref.position];
adjust_roff_str(before.trim().trim_start_matches(reference))
let before_start_trimoff =
word_ref.position - before.trim_start_matches(reference).trim_start().len();
let before_end_index = before.len();
&chars_line[before_start_trimoff..cmp::max(before_end_index, before_start_trimoff)]
} else {
adjust_roff_str(&line[0..word_ref.position])
let before_chars_trim_idx = (0, word_ref.position);
&chars_line[before_chars_trim_idx.0..before_chars_trim_idx.1]
};
let keyword = adjust_roff_str(&line[word_ref.position..word_ref.position_end]);
let all_after = adjust_roff_str(&line[word_ref.position_end..line.len()]);
let keyword = &line[word_ref.position..word_ref.position_end];
let after_chars_trim_idx = (word_ref.position_end, chars_line.len());
let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1];
let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config);
output.push_str(&format!(
" \"{}\" \"{}\" \"{}{}\" \"{}\"",
tail, before, keyword, after, head
format_roff_field(&tail),
format_roff_field(&before),
format_roff_field(keyword),
format_roff_field(&after),
format_roff_field(&head)
));
if config.auto_ref || config.input_ref {
output.push_str(&format!(" \"{}\"", adjust_roff_str(&reference)));
output.push_str(&format!(" \"{}\"", format_roff_field(&reference)));
}
output
}
fn write_traditional_output(
config: &Config,
file_map: &HashMap<String, (Vec<String>, usize)>,
file_map: &FileMap,
words: &BTreeSet<WordRef>,
output_filename: &str,
) {
@ -485,19 +570,39 @@ fn write_traditional_output(
let file = crash_if_err!(1, File::create(output_filename));
Box::new(file)
});
let context_reg = Regex::new(&config.context_regex).unwrap();
for word_ref in words.iter() {
let file_map_value: &(Vec<String>, usize) = file_map
let file_map_value: &FileContent = file_map
.get(&(word_ref.filename))
.expect("Missing file in file map");
let (ref lines, _) = *(file_map_value);
let reference = get_reference(config, word_ref, &lines[word_ref.local_line_nr]);
let FileContent {
ref lines,
ref chars_lines,
offset: _,
} = *(file_map_value);
let reference = get_reference(
config,
word_ref,
&lines[word_ref.local_line_nr],
&context_reg,
);
let output_line: String = match config.format {
OutFormat::Tex => {
format_tex_line(config, word_ref, &lines[word_ref.local_line_nr], &reference)
}
OutFormat::Roff => {
format_roff_line(config, word_ref, &lines[word_ref.local_line_nr], &reference)
}
OutFormat::Tex => format_tex_line(
config,
word_ref,
&lines[word_ref.local_line_nr],
&chars_lines[word_ref.local_line_nr],
&reference,
),
OutFormat::Roff => format_roff_line(
config,
word_ref,
&lines[word_ref.local_line_nr],
&chars_lines[word_ref.local_line_nr],
&reference,
),
OutFormat::Dumb => crash!(1, "There is no dumb format with GNU extensions disabled"),
};
crash_if_err!(1, writeln!(writer, "{}", output_line));

View file

@ -1,6 +1,6 @@
[package]
name = "uu_pwd"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "pwd ~ (uutils) display current working directory"
@ -16,7 +16,7 @@ path = "src/pwd.rs"
[dependencies]
clap = "2.33"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

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