mirror of
https://github.com/uutils/coreutils
synced 2024-11-13 00:17:09 +00:00
Merge branch 'master' into pr
This commit is contained in:
commit
844e318a67
235 changed files with 12115 additions and 4330 deletions
|
@ -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
18
.github/stale.yml
vendored
Normal 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
|
35
.github/workflows/CICD.yml
vendored
35
.github/workflows/CICD.yml
vendored
|
@ -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:
|
||||
|
|
4
.github/workflows/GNU.yml
vendored
4
.github/workflows/GNU.yml
vendored
|
@ -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
8
.pre-commit-config.yaml
Normal 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
|
|
@ -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
128
CODE_OF_CONDUCT.md
Normal 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
2277
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
203
Cargo.toml
203
Cargo.toml
|
@ -3,7 +3,7 @@
|
|||
|
||||
[package]
|
||||
name = "coreutils"
|
||||
version = "0.0.4"
|
||||
version = "0.0.6"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust"
|
||||
|
@ -43,6 +43,7 @@ feat_common_core = [
|
|||
"df",
|
||||
"dircolors",
|
||||
"dirname",
|
||||
"du",
|
||||
"echo",
|
||||
"env",
|
||||
"expand",
|
||||
|
@ -150,7 +151,6 @@ feat_require_unix = [
|
|||
"chmod",
|
||||
"chown",
|
||||
"chroot",
|
||||
"du",
|
||||
"groups",
|
||||
"hostid",
|
||||
"id",
|
||||
|
@ -227,105 +227,105 @@ test = [ "uu_test" ]
|
|||
[dependencies]
|
||||
lazy_static = { version="1.3" }
|
||||
textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review
|
||||
uucore = { version=">=0.0.7", package="uucore", path="src/uucore" }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="src/uucore" }
|
||||
# * uutils
|
||||
uu_test = { optional=true, version="0.0.4", package="uu_test", path="src/uu/test" }
|
||||
uu_test = { optional=true, version="0.0.6", package="uu_test", path="src/uu/test" }
|
||||
#
|
||||
arch = { optional=true, version="0.0.4", package="uu_arch", path="src/uu/arch" }
|
||||
base32 = { optional=true, version="0.0.4", package="uu_base32", path="src/uu/base32" }
|
||||
base64 = { optional=true, version="0.0.4", package="uu_base64", path="src/uu/base64" }
|
||||
basename = { optional=true, version="0.0.4", package="uu_basename", path="src/uu/basename" }
|
||||
cat = { optional=true, version="0.0.4", package="uu_cat", path="src/uu/cat" }
|
||||
chgrp = { optional=true, version="0.0.4", package="uu_chgrp", path="src/uu/chgrp" }
|
||||
chmod = { optional=true, version="0.0.4", package="uu_chmod", path="src/uu/chmod" }
|
||||
chown = { optional=true, version="0.0.4", package="uu_chown", path="src/uu/chown" }
|
||||
chroot = { optional=true, version="0.0.4", package="uu_chroot", path="src/uu/chroot" }
|
||||
cksum = { optional=true, version="0.0.4", package="uu_cksum", path="src/uu/cksum" }
|
||||
comm = { optional=true, version="0.0.4", package="uu_comm", path="src/uu/comm" }
|
||||
cp = { optional=true, version="0.0.4", package="uu_cp", path="src/uu/cp" }
|
||||
csplit = { optional=true, version="0.0.4", package="uu_csplit", path="src/uu/csplit" }
|
||||
cut = { optional=true, version="0.0.4", package="uu_cut", path="src/uu/cut" }
|
||||
date = { optional=true, version="0.0.4", package="uu_date", path="src/uu/date" }
|
||||
df = { optional=true, version="0.0.4", package="uu_df", path="src/uu/df" }
|
||||
dircolors= { optional=true, version="0.0.4", package="uu_dircolors", path="src/uu/dircolors" }
|
||||
dirname = { optional=true, version="0.0.4", package="uu_dirname", path="src/uu/dirname" }
|
||||
du = { optional=true, version="0.0.4", package="uu_du", path="src/uu/du" }
|
||||
echo = { optional=true, version="0.0.4", package="uu_echo", path="src/uu/echo" }
|
||||
env = { optional=true, version="0.0.4", package="uu_env", path="src/uu/env" }
|
||||
expand = { optional=true, version="0.0.4", package="uu_expand", path="src/uu/expand" }
|
||||
expr = { optional=true, version="0.0.4", package="uu_expr", path="src/uu/expr" }
|
||||
factor = { optional=true, version="0.0.4", package="uu_factor", path="src/uu/factor" }
|
||||
false = { optional=true, version="0.0.4", package="uu_false", path="src/uu/false" }
|
||||
fmt = { optional=true, version="0.0.4", package="uu_fmt", path="src/uu/fmt" }
|
||||
fold = { optional=true, version="0.0.4", package="uu_fold", path="src/uu/fold" }
|
||||
groups = { optional=true, version="0.0.4", package="uu_groups", path="src/uu/groups" }
|
||||
hashsum = { optional=true, version="0.0.4", package="uu_hashsum", path="src/uu/hashsum" }
|
||||
head = { optional=true, version="0.0.4", package="uu_head", path="src/uu/head" }
|
||||
hostid = { optional=true, version="0.0.4", package="uu_hostid", path="src/uu/hostid" }
|
||||
hostname = { optional=true, version="0.0.4", package="uu_hostname", path="src/uu/hostname" }
|
||||
id = { optional=true, version="0.0.4", package="uu_id", path="src/uu/id" }
|
||||
install = { optional=true, version="0.0.4", package="uu_install", path="src/uu/install" }
|
||||
join = { optional=true, version="0.0.4", package="uu_join", path="src/uu/join" }
|
||||
kill = { optional=true, version="0.0.4", package="uu_kill", path="src/uu/kill" }
|
||||
link = { optional=true, version="0.0.4", package="uu_link", path="src/uu/link" }
|
||||
ln = { optional=true, version="0.0.4", package="uu_ln", path="src/uu/ln" }
|
||||
ls = { optional=true, version="0.0.4", package="uu_ls", path="src/uu/ls" }
|
||||
logname = { optional=true, version="0.0.4", package="uu_logname", path="src/uu/logname" }
|
||||
mkdir = { optional=true, version="0.0.4", package="uu_mkdir", path="src/uu/mkdir" }
|
||||
mkfifo = { optional=true, version="0.0.4", package="uu_mkfifo", path="src/uu/mkfifo" }
|
||||
mknod = { optional=true, version="0.0.4", package="uu_mknod", path="src/uu/mknod" }
|
||||
mktemp = { optional=true, version="0.0.4", package="uu_mktemp", path="src/uu/mktemp" }
|
||||
more = { optional=true, version="0.0.4", package="uu_more", path="src/uu/more" }
|
||||
mv = { optional=true, version="0.0.4", package="uu_mv", path="src/uu/mv" }
|
||||
nice = { optional=true, version="0.0.4", package="uu_nice", path="src/uu/nice" }
|
||||
nl = { optional=true, version="0.0.4", package="uu_nl", path="src/uu/nl" }
|
||||
nohup = { optional=true, version="0.0.4", package="uu_nohup", path="src/uu/nohup" }
|
||||
nproc = { optional=true, version="0.0.4", package="uu_nproc", path="src/uu/nproc" }
|
||||
numfmt = { optional=true, version="0.0.4", package="uu_numfmt", path="src/uu/numfmt" }
|
||||
od = { optional=true, version="0.0.4", package="uu_od", path="src/uu/od" }
|
||||
paste = { optional=true, version="0.0.4", package="uu_paste", path="src/uu/paste" }
|
||||
pathchk = { optional=true, version="0.0.4", package="uu_pathchk", path="src/uu/pathchk" }
|
||||
pinky = { optional=true, version="0.0.4", package="uu_pinky", path="src/uu/pinky" }
|
||||
pr = { optional=true, version="0.0.4", package="uu_pr", path="src/uu/pr" }
|
||||
printenv = { optional=true, version="0.0.4", package="uu_printenv", path="src/uu/printenv" }
|
||||
printf = { optional=true, version="0.0.4", package="uu_printf", path="src/uu/printf" }
|
||||
ptx = { optional=true, version="0.0.4", package="uu_ptx", path="src/uu/ptx" }
|
||||
pwd = { optional=true, version="0.0.4", package="uu_pwd", path="src/uu/pwd" }
|
||||
readlink = { optional=true, version="0.0.4", package="uu_readlink", path="src/uu/readlink" }
|
||||
realpath = { optional=true, version="0.0.4", package="uu_realpath", path="src/uu/realpath" }
|
||||
relpath = { optional=true, version="0.0.4", package="uu_relpath", path="src/uu/relpath" }
|
||||
rm = { optional=true, version="0.0.4", package="uu_rm", path="src/uu/rm" }
|
||||
rmdir = { optional=true, version="0.0.4", package="uu_rmdir", path="src/uu/rmdir" }
|
||||
seq = { optional=true, version="0.0.4", package="uu_seq", path="src/uu/seq" }
|
||||
shred = { optional=true, version="0.0.4", package="uu_shred", path="src/uu/shred" }
|
||||
shuf = { optional=true, version="0.0.4", package="uu_shuf", path="src/uu/shuf" }
|
||||
sleep = { optional=true, version="0.0.4", package="uu_sleep", path="src/uu/sleep" }
|
||||
sort = { optional=true, version="0.0.4", package="uu_sort", path="src/uu/sort" }
|
||||
split = { optional=true, version="0.0.4", package="uu_split", path="src/uu/split" }
|
||||
stat = { optional=true, version="0.0.4", package="uu_stat", path="src/uu/stat" }
|
||||
stdbuf = { optional=true, version="0.0.4", package="uu_stdbuf", path="src/uu/stdbuf" }
|
||||
sum = { optional=true, version="0.0.4", package="uu_sum", path="src/uu/sum" }
|
||||
sync = { optional=true, version="0.0.4", package="uu_sync", path="src/uu/sync" }
|
||||
tac = { optional=true, version="0.0.4", package="uu_tac", path="src/uu/tac" }
|
||||
tail = { optional=true, version="0.0.4", package="uu_tail", path="src/uu/tail" }
|
||||
tee = { optional=true, version="0.0.4", package="uu_tee", path="src/uu/tee" }
|
||||
timeout = { optional=true, version="0.0.4", package="uu_timeout", path="src/uu/timeout" }
|
||||
touch = { optional=true, version="0.0.4", package="uu_touch", path="src/uu/touch" }
|
||||
tr = { optional=true, version="0.0.4", package="uu_tr", path="src/uu/tr" }
|
||||
true = { optional=true, version="0.0.4", package="uu_true", path="src/uu/true" }
|
||||
truncate = { optional=true, version="0.0.4", package="uu_truncate", path="src/uu/truncate" }
|
||||
tsort = { optional=true, version="0.0.4", package="uu_tsort", path="src/uu/tsort" }
|
||||
tty = { optional=true, version="0.0.4", package="uu_tty", path="src/uu/tty" }
|
||||
uname = { optional=true, version="0.0.4", package="uu_uname", path="src/uu/uname" }
|
||||
unexpand = { optional=true, version="0.0.4", package="uu_unexpand", path="src/uu/unexpand" }
|
||||
uniq = { optional=true, version="0.0.4", package="uu_uniq", path="src/uu/uniq" }
|
||||
unlink = { optional=true, version="0.0.4", package="uu_unlink", path="src/uu/unlink" }
|
||||
uptime = { optional=true, version="0.0.4", package="uu_uptime", path="src/uu/uptime" }
|
||||
users = { optional=true, version="0.0.4", package="uu_users", path="src/uu/users" }
|
||||
wc = { optional=true, version="0.0.4", package="uu_wc", path="src/uu/wc" }
|
||||
who = { optional=true, version="0.0.4", package="uu_who", path="src/uu/who" }
|
||||
whoami = { optional=true, version="0.0.4", package="uu_whoami", path="src/uu/whoami" }
|
||||
yes = { optional=true, version="0.0.4", package="uu_yes", path="src/uu/yes" }
|
||||
arch = { optional=true, version="0.0.6", package="uu_arch", path="src/uu/arch" }
|
||||
base32 = { optional=true, version="0.0.6", package="uu_base32", path="src/uu/base32" }
|
||||
base64 = { optional=true, version="0.0.6", package="uu_base64", path="src/uu/base64" }
|
||||
basename = { optional=true, version="0.0.6", package="uu_basename", path="src/uu/basename" }
|
||||
cat = { optional=true, version="0.0.6", package="uu_cat", path="src/uu/cat" }
|
||||
chgrp = { optional=true, version="0.0.6", package="uu_chgrp", path="src/uu/chgrp" }
|
||||
chmod = { optional=true, version="0.0.6", package="uu_chmod", path="src/uu/chmod" }
|
||||
chown = { optional=true, version="0.0.6", package="uu_chown", path="src/uu/chown" }
|
||||
chroot = { optional=true, version="0.0.6", package="uu_chroot", path="src/uu/chroot" }
|
||||
cksum = { optional=true, version="0.0.6", package="uu_cksum", path="src/uu/cksum" }
|
||||
comm = { optional=true, version="0.0.6", package="uu_comm", path="src/uu/comm" }
|
||||
cp = { optional=true, version="0.0.6", package="uu_cp", path="src/uu/cp" }
|
||||
csplit = { optional=true, version="0.0.6", package="uu_csplit", path="src/uu/csplit" }
|
||||
cut = { optional=true, version="0.0.6", package="uu_cut", path="src/uu/cut" }
|
||||
date = { optional=true, version="0.0.6", package="uu_date", path="src/uu/date" }
|
||||
df = { optional=true, version="0.0.6", package="uu_df", path="src/uu/df" }
|
||||
dircolors= { optional=true, version="0.0.6", package="uu_dircolors", path="src/uu/dircolors" }
|
||||
dirname = { optional=true, version="0.0.6", package="uu_dirname", path="src/uu/dirname" }
|
||||
du = { optional=true, version="0.0.6", package="uu_du", path="src/uu/du" }
|
||||
echo = { optional=true, version="0.0.6", package="uu_echo", path="src/uu/echo" }
|
||||
env = { optional=true, version="0.0.6", package="uu_env", path="src/uu/env" }
|
||||
expand = { optional=true, version="0.0.6", package="uu_expand", path="src/uu/expand" }
|
||||
expr = { optional=true, version="0.0.6", package="uu_expr", path="src/uu/expr" }
|
||||
factor = { optional=true, version="0.0.6", package="uu_factor", path="src/uu/factor" }
|
||||
false = { optional=true, version="0.0.6", package="uu_false", path="src/uu/false" }
|
||||
fmt = { optional=true, version="0.0.6", package="uu_fmt", path="src/uu/fmt" }
|
||||
fold = { optional=true, version="0.0.6", package="uu_fold", path="src/uu/fold" }
|
||||
groups = { optional=true, version="0.0.6", package="uu_groups", path="src/uu/groups" }
|
||||
hashsum = { optional=true, version="0.0.6", package="uu_hashsum", path="src/uu/hashsum" }
|
||||
head = { optional=true, version="0.0.6", package="uu_head", path="src/uu/head" }
|
||||
hostid = { optional=true, version="0.0.6", package="uu_hostid", path="src/uu/hostid" }
|
||||
hostname = { optional=true, version="0.0.6", package="uu_hostname", path="src/uu/hostname" }
|
||||
id = { optional=true, version="0.0.6", package="uu_id", path="src/uu/id" }
|
||||
install = { optional=true, version="0.0.6", package="uu_install", path="src/uu/install" }
|
||||
join = { optional=true, version="0.0.6", package="uu_join", path="src/uu/join" }
|
||||
kill = { optional=true, version="0.0.6", package="uu_kill", path="src/uu/kill" }
|
||||
link = { optional=true, version="0.0.6", package="uu_link", path="src/uu/link" }
|
||||
ln = { optional=true, version="0.0.6", package="uu_ln", path="src/uu/ln" }
|
||||
ls = { optional=true, version="0.0.6", package="uu_ls", path="src/uu/ls" }
|
||||
logname = { optional=true, version="0.0.6", package="uu_logname", path="src/uu/logname" }
|
||||
mkdir = { optional=true, version="0.0.6", package="uu_mkdir", path="src/uu/mkdir" }
|
||||
mkfifo = { optional=true, version="0.0.6", package="uu_mkfifo", path="src/uu/mkfifo" }
|
||||
mknod = { optional=true, version="0.0.6", package="uu_mknod", path="src/uu/mknod" }
|
||||
mktemp = { optional=true, version="0.0.6", package="uu_mktemp", path="src/uu/mktemp" }
|
||||
more = { optional=true, version="0.0.6", package="uu_more", path="src/uu/more" }
|
||||
mv = { optional=true, version="0.0.6", package="uu_mv", path="src/uu/mv" }
|
||||
nice = { optional=true, version="0.0.6", package="uu_nice", path="src/uu/nice" }
|
||||
nl = { optional=true, version="0.0.6", package="uu_nl", path="src/uu/nl" }
|
||||
nohup = { optional=true, version="0.0.6", package="uu_nohup", path="src/uu/nohup" }
|
||||
nproc = { optional=true, version="0.0.6", package="uu_nproc", path="src/uu/nproc" }
|
||||
numfmt = { optional=true, version="0.0.6", package="uu_numfmt", path="src/uu/numfmt" }
|
||||
od = { optional=true, version="0.0.6", package="uu_od", path="src/uu/od" }
|
||||
paste = { optional=true, version="0.0.6", package="uu_paste", path="src/uu/paste" }
|
||||
pathchk = { optional=true, version="0.0.6", package="uu_pathchk", path="src/uu/pathchk" }
|
||||
pinky = { optional=true, version="0.0.6", package="uu_pinky", path="src/uu/pinky" }
|
||||
pr = { optional=true, version="0.0.6", package="uu_pr", path="src/uu/pr" }
|
||||
printenv = { optional=true, version="0.0.6", package="uu_printenv", path="src/uu/printenv" }
|
||||
printf = { optional=true, version="0.0.6", package="uu_printf", path="src/uu/printf" }
|
||||
ptx = { optional=true, version="0.0.6", package="uu_ptx", path="src/uu/ptx" }
|
||||
pwd = { optional=true, version="0.0.6", package="uu_pwd", path="src/uu/pwd" }
|
||||
readlink = { optional=true, version="0.0.6", package="uu_readlink", path="src/uu/readlink" }
|
||||
realpath = { optional=true, version="0.0.6", package="uu_realpath", path="src/uu/realpath" }
|
||||
relpath = { optional=true, version="0.0.6", package="uu_relpath", path="src/uu/relpath" }
|
||||
rm = { optional=true, version="0.0.6", package="uu_rm", path="src/uu/rm" }
|
||||
rmdir = { optional=true, version="0.0.6", package="uu_rmdir", path="src/uu/rmdir" }
|
||||
seq = { optional=true, version="0.0.6", package="uu_seq", path="src/uu/seq" }
|
||||
shred = { optional=true, version="0.0.6", package="uu_shred", path="src/uu/shred" }
|
||||
shuf = { optional=true, version="0.0.6", package="uu_shuf", path="src/uu/shuf" }
|
||||
sleep = { optional=true, version="0.0.6", package="uu_sleep", path="src/uu/sleep" }
|
||||
sort = { optional=true, version="0.0.6", package="uu_sort", path="src/uu/sort" }
|
||||
split = { optional=true, version="0.0.6", package="uu_split", path="src/uu/split" }
|
||||
stat = { optional=true, version="0.0.6", package="uu_stat", path="src/uu/stat" }
|
||||
stdbuf = { optional=true, version="0.0.6", package="uu_stdbuf", path="src/uu/stdbuf" }
|
||||
sum = { optional=true, version="0.0.6", package="uu_sum", path="src/uu/sum" }
|
||||
sync = { optional=true, version="0.0.6", package="uu_sync", path="src/uu/sync" }
|
||||
tac = { optional=true, version="0.0.6", package="uu_tac", path="src/uu/tac" }
|
||||
tail = { optional=true, version="0.0.6", package="uu_tail", path="src/uu/tail" }
|
||||
tee = { optional=true, version="0.0.6", package="uu_tee", path="src/uu/tee" }
|
||||
timeout = { optional=true, version="0.0.6", package="uu_timeout", path="src/uu/timeout" }
|
||||
touch = { optional=true, version="0.0.6", package="uu_touch", path="src/uu/touch" }
|
||||
tr = { optional=true, version="0.0.6", package="uu_tr", path="src/uu/tr" }
|
||||
true = { optional=true, version="0.0.6", package="uu_true", path="src/uu/true" }
|
||||
truncate = { optional=true, version="0.0.6", package="uu_truncate", path="src/uu/truncate" }
|
||||
tsort = { optional=true, version="0.0.6", package="uu_tsort", path="src/uu/tsort" }
|
||||
tty = { optional=true, version="0.0.6", package="uu_tty", path="src/uu/tty" }
|
||||
uname = { optional=true, version="0.0.6", package="uu_uname", path="src/uu/uname" }
|
||||
unexpand = { optional=true, version="0.0.6", package="uu_unexpand", path="src/uu/unexpand" }
|
||||
uniq = { optional=true, version="0.0.6", package="uu_uniq", path="src/uu/uniq" }
|
||||
unlink = { optional=true, version="0.0.6", package="uu_unlink", path="src/uu/unlink" }
|
||||
uptime = { optional=true, version="0.0.6", package="uu_uptime", path="src/uu/uptime" }
|
||||
users = { optional=true, version="0.0.6", package="uu_users", path="src/uu/users" }
|
||||
wc = { optional=true, version="0.0.6", package="uu_wc", path="src/uu/wc" }
|
||||
who = { optional=true, version="0.0.6", package="uu_who", path="src/uu/who" }
|
||||
whoami = { optional=true, version="0.0.6", package="uu_whoami", path="src/uu/whoami" }
|
||||
yes = { optional=true, version="0.0.6", package="uu_yes", path="src/uu/yes" }
|
||||
#
|
||||
# * pinned transitive dependencies
|
||||
# Not needed for now. Keep as examples:
|
||||
|
@ -337,6 +337,7 @@ conv = "0.3"
|
|||
filetime = "0.2"
|
||||
glob = "0.3.0"
|
||||
libc = "0.2"
|
||||
nix = "0.20.0"
|
||||
rand = "0.7"
|
||||
regex = "1.0"
|
||||
sha1 = { version="0.6", features=["std"] }
|
||||
|
@ -345,13 +346,15 @@ sha1 = { version="0.6", features=["std"] }
|
|||
tempfile = "= 3.1.0"
|
||||
time = "0.1"
|
||||
unindent = "0.1"
|
||||
uucore = { version=">=0.0.7", package="uucore", path="src/uucore", features=["entries"] }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] }
|
||||
walkdir = "2.2"
|
||||
tempdir = "0.3"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
rust-users = { version="0.10", package="users" }
|
||||
unix_socket = "0.5.0"
|
||||
|
||||
|
||||
[[bin]]
|
||||
name = "coreutils"
|
||||
path = "src/bin/coreutils.rs"
|
||||
|
|
38
DEVELOPER_INSTRUCTIONS.md
Normal file
38
DEVELOPER_INSTRUCTIONS.md
Normal 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.
|
24
GNUmakefile
24
GNUmakefile
|
@ -30,7 +30,7 @@ ifneq ($(.SHELLSTATUS),0)
|
|||
override INSTALLDIR_MAN=$(DESTDIR)$(PREFIX)$(MANDIR)
|
||||
endif
|
||||
|
||||
#prefix to apply to uutils binary and all tool binaries
|
||||
#prefix to apply to coreutils binary and all tool binaries
|
||||
PROG_PREFIX ?=
|
||||
|
||||
# This won't support any directory with spaces in its name, but you can just
|
||||
|
@ -237,7 +237,7 @@ EXES := \
|
|||
|
||||
INSTALLEES := ${EXES}
|
||||
ifeq (${MULTICALL}, y)
|
||||
INSTALLEES := ${INSTALLEES} uutils
|
||||
INSTALLEES := ${INSTALLEES} coreutils
|
||||
endif
|
||||
|
||||
all: build
|
||||
|
@ -250,13 +250,13 @@ ifneq (${MULTICALL}, y)
|
|||
${CARGO} build ${CARGOFLAGS} ${PROFILE_CMD} $(foreach pkg,$(EXES),-p uu_$(pkg))
|
||||
endif
|
||||
|
||||
build-uutils:
|
||||
build-coreutils:
|
||||
${CARGO} build ${CARGOFLAGS} --features "${EXES}" ${PROFILE_CMD} --no-default-features
|
||||
|
||||
build-manpages:
|
||||
cd $(DOCSDIR) && $(MAKE) man
|
||||
|
||||
build: build-uutils build-pkgs build-manpages
|
||||
build: build-coreutils build-pkgs build-manpages
|
||||
|
||||
$(foreach test,$(filter-out $(SKIP_UTILS),$(PROGS)),$(eval $(call TEST_BUSYBOX,$(test))))
|
||||
|
||||
|
@ -275,7 +275,7 @@ $(BUILDDIR)/.config: $(BASEDIR)/.busybox-config
|
|||
cp $< $@
|
||||
|
||||
# Test under the busybox testsuite
|
||||
$(BUILDDIR)/busybox: busybox-src build-uutils $(BUILDDIR)/.config
|
||||
$(BUILDDIR)/busybox: busybox-src build-coreutils $(BUILDDIR)/.config
|
||||
cp $(BUILDDIR)/coreutils $(BUILDDIR)/busybox; \
|
||||
chmod +x $@;
|
||||
|
||||
|
@ -298,10 +298,10 @@ install: build
|
|||
mkdir -p $(INSTALLDIR_BIN)
|
||||
mkdir -p $(INSTALLDIR_MAN)
|
||||
ifeq (${MULTICALL}, y)
|
||||
$(INSTALL) $(BUILDDIR)/uutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)uutils
|
||||
cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out uutils, $(INSTALLEES)), \
|
||||
ln -fs $(PROG_PREFIX)uutils $(PROG_PREFIX)$(prog) &&) :
|
||||
cat $(DOCSDIR)/_build/man/uutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)uutils.1.gz
|
||||
$(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils
|
||||
cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out coreutils, $(INSTALLEES)), \
|
||||
ln -fs $(PROG_PREFIX)coreutils $(PROG_PREFIX)$(prog) &&) :
|
||||
cat $(DOCSDIR)/_build/man/coreutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)coreutils.1.gz
|
||||
else
|
||||
$(foreach prog, $(INSTALLEES), \
|
||||
$(INSTALL) $(BUILDDIR)/$(prog) $(INSTALLDIR_BIN)/$(PROG_PREFIX)$(prog);)
|
||||
|
@ -311,10 +311,10 @@ endif
|
|||
|
||||
uninstall:
|
||||
ifeq (${MULTICALL}, y)
|
||||
rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)uutils)
|
||||
rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)coreutils)
|
||||
endif
|
||||
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)uutils.1.gz)
|
||||
rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)coreutils.1.gz)
|
||||
rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS))
|
||||
rm -f $(addprefix $(INSTALLDIR_MAN)/$(PROG_PREFIX),$(addsuffix .1.gz,$(PROGS)))
|
||||
|
||||
.PHONY: all build build-uutils build-pkgs build-docs test distclean clean busytest install uninstall
|
||||
.PHONY: all build build-coreutils build-pkgs build-docs test distclean clean busytest install uninstall
|
||||
|
|
167
README.md
167
README.md
|
@ -1,5 +1,4 @@
|
|||
uutils coreutils
|
||||
================
|
||||
# uutils coreutils
|
||||
|
||||
[![Crates.io](https://img.shields.io/crates/v/coreutils.svg)](https://crates.io/crates/coreutils)
|
||||
[![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ)
|
||||
|
@ -9,16 +8,18 @@ uutils coreutils
|
|||
|
||||
[![Build Status](https://api.travis-ci.org/uutils/coreutils.svg?branch=master)](https://travis-ci.org/uutils/coreutils)
|
||||
[![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master)
|
||||
[![codecov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils)
|
||||
[![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils)
|
||||
|
||||
-----------------------------------------------
|
||||
|
||||
<!-- 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
|
||||
|
||||
|
|
38
build.rs
38
build.rs
|
@ -44,10 +44,10 @@ pub fn main() {
|
|||
|
||||
mf.write_all(
|
||||
"type UtilityMap<T> = HashMap<&'static str, fn(T) -> i32>;\n\
|
||||
\n\
|
||||
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n\
|
||||
\tlet mut map = UtilityMap::new();\n\
|
||||
"
|
||||
\n\
|
||||
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n\
|
||||
\tlet mut map = UtilityMap::new();\n\
|
||||
"
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -97,21 +97,21 @@ pub fn main() {
|
|||
mf.write_all(
|
||||
format!(
|
||||
"\
|
||||
\tmap.insert(\"{krate}\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"md5sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha1sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha224sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha256sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha384sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha512sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha3sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha3-224sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha3-256sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha3-384sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha3-512sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"shake128sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"shake256sum\", {krate}::uumain);\n\
|
||||
",
|
||||
\tmap.insert(\"{krate}\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"md5sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha1sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha224sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha256sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha384sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha512sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha3sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha3-224sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha3-256sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha3-384sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"sha3-512sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"shake128sum\", {krate}::uumain);\n\
|
||||
\t\tmap.insert(\"shake256sum\", {krate}::uumain);\n\
|
||||
",
|
||||
krate = krate
|
||||
)
|
||||
.as_bytes(),
|
||||
|
|
21
docs/compiles_table.csv
Normal file
21
docs/compiles_table.csv
Normal 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
|
|
234
docs/compiles_table.py
Normal file
234
docs/compiles_table.py
Normal 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)
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::fs;
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
use std::path::Path;
|
||||
|
@ -18,115 +19,173 @@ use uucore::fs::display_permissions_unix;
|
|||
use uucore::mode;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
const NAME: &str = "chmod";
|
||||
static SUMMARY: &str = "Change the mode of each FILE to MODE.
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static ABOUT: &str = "Change the mode of each FILE to MODE.
|
||||
With --reference, change the mode of each FILE to that of RFILE.";
|
||||
static LONG_HELP: &str = "
|
||||
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.
|
||||
";
|
||||
|
||||
mod options {
|
||||
pub const CHANGES: &str = "changes";
|
||||
pub const QUIET: &str = "quiet"; // visible_alias("silent")
|
||||
pub const VERBOSE: &str = "verbose";
|
||||
pub const NO_PRESERVE_ROOT: &str = "no-preserve-root";
|
||||
pub const PRESERVE_ROOT: &str = "preserve-root";
|
||||
pub const REFERENCE: &str = "RFILE";
|
||||
pub const RECURSIVE: &str = "recursive";
|
||||
pub const MODE: &str = "MODE";
|
||||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!(
|
||||
"{0} [OPTION]... MODE[,MODE]... FILE...
|
||||
or: {0} [OPTION]... OCTAL-MODE FILE...
|
||||
or: {0} [OPTION]... --reference=RFILE FILE...",
|
||||
executable!()
|
||||
)
|
||||
}
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.")
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let mut args = args.collect_str();
|
||||
|
||||
let syntax = format!(
|
||||
"[OPTION]... MODE[,MODE]... FILE...
|
||||
{0} [OPTION]... OCTAL-MODE FILE...
|
||||
{0} [OPTION]... --reference=RFILE FILE...",
|
||||
NAME
|
||||
);
|
||||
let mut opts = app!(&syntax, SUMMARY, LONG_HELP);
|
||||
opts.optflag(
|
||||
"c",
|
||||
"changes",
|
||||
"like verbose but report only when a change is made",
|
||||
)
|
||||
// TODO: support --silent (can be done using clap)
|
||||
.optflag("f", "quiet", "suppress most error messages")
|
||||
.optflag(
|
||||
"v",
|
||||
"verbose",
|
||||
"output a diagnostic for every file processed",
|
||||
)
|
||||
.optflag(
|
||||
"",
|
||||
"no-preserve-root",
|
||||
"do not treat '/' specially (the default)",
|
||||
)
|
||||
.optflag("", "preserve-root", "fail to operate recursively on '/'")
|
||||
.optopt(
|
||||
"",
|
||||
"reference",
|
||||
"use RFILE's mode instead of MODE values",
|
||||
"RFILE",
|
||||
)
|
||||
.optflag("R", "recursive", "change files and directories recursively");
|
||||
// Before we can parse 'args' with clap (and previously getopts),
|
||||
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
|
||||
let mode_had_minus_prefix = strip_minus_from_mode(&mut args);
|
||||
|
||||
// sanitize input for - at beginning (e.g. chmod -x test_file). Remove
|
||||
// the option and save it for later, after parsing is finished.
|
||||
let negative_option = sanitize_input(&mut args);
|
||||
let usage = get_usage();
|
||||
let after_help = get_long_usage();
|
||||
|
||||
let mut matches = opts.parse(args);
|
||||
if matches.free.is_empty() {
|
||||
show_error!("missing an argument");
|
||||
show_error!("for help, try '{} --help'", NAME);
|
||||
return 1;
|
||||
} else {
|
||||
let changes = matches.opt_present("changes");
|
||||
let quiet = matches.opt_present("quiet");
|
||||
let verbose = matches.opt_present("verbose");
|
||||
let preserve_root = matches.opt_present("preserve-root");
|
||||
let recursive = matches.opt_present("recursive");
|
||||
let fmode = matches
|
||||
.opt_str("reference")
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.after_help(&after_help[..])
|
||||
.arg(
|
||||
Arg::with_name(options::CHANGES)
|
||||
.long(options::CHANGES)
|
||||
.short("c")
|
||||
.help("like verbose but report only when a change is made"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::QUIET)
|
||||
.long(options::QUIET)
|
||||
.visible_alias("silent")
|
||||
.short("f")
|
||||
.help("suppress most error messages"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::VERBOSE)
|
||||
.long(options::VERBOSE)
|
||||
.short("v")
|
||||
.help("output a diagnostic for every file processed"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::NO_PRESERVE_ROOT)
|
||||
.long(options::NO_PRESERVE_ROOT)
|
||||
.help("do not treat '/' specially (the default)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::PRESERVE_ROOT)
|
||||
.long(options::PRESERVE_ROOT)
|
||||
.help("fail to operate recursively on '/'"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::RECURSIVE)
|
||||
.long(options::RECURSIVE)
|
||||
.short("R")
|
||||
.help("change files and directories recursively"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::REFERENCE)
|
||||
.long("reference")
|
||||
.takes_value(true)
|
||||
.help("use RFILE's mode instead of MODE values"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::MODE)
|
||||
.required_unless(options::REFERENCE)
|
||||
.takes_value(true),
|
||||
// It would be nice if clap could parse with delimeter, e.g. "g-x,u+x",
|
||||
// however .multiple(true) cannot be used here because FILE already needs that.
|
||||
// Only one positional argument with .multiple(true) set is allowed per command
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::FILE)
|
||||
.required_unless(options::MODE)
|
||||
.multiple(true),
|
||||
)
|
||||
.get_matches_from(args);
|
||||
|
||||
let changes = matches.is_present(options::CHANGES);
|
||||
let quiet = matches.is_present(options::QUIET);
|
||||
let verbose = matches.is_present(options::VERBOSE);
|
||||
let preserve_root = matches.is_present(options::PRESERVE_ROOT);
|
||||
let recursive = matches.is_present(options::RECURSIVE);
|
||||
let fmode =
|
||||
matches
|
||||
.value_of(options::REFERENCE)
|
||||
.and_then(|ref fref| match fs::metadata(fref) {
|
||||
Ok(meta) => Some(meta.mode()),
|
||||
Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err),
|
||||
});
|
||||
let cmode = if fmode.is_none() {
|
||||
// If there was a negative option, now it's a good time to
|
||||
// use it.
|
||||
if negative_option.is_some() {
|
||||
negative_option
|
||||
} else {
|
||||
Some(matches.free.remove(0))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let chmoder = Chmoder {
|
||||
changes,
|
||||
quiet,
|
||||
verbose,
|
||||
preserve_root,
|
||||
recursive,
|
||||
fmode,
|
||||
cmode,
|
||||
};
|
||||
match chmoder.chmod(matches.free) {
|
||||
Ok(()) => {}
|
||||
Err(e) => return e,
|
||||
}
|
||||
let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
|
||||
let mut cmode = if mode_had_minus_prefix {
|
||||
// clap parsing is finished, now put prefix back
|
||||
Some(format!("-{}", modes))
|
||||
} else {
|
||||
Some(modes.to_string())
|
||||
};
|
||||
let mut files: Vec<String> = matches
|
||||
.values_of(options::FILE)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
if fmode.is_some() {
|
||||
// "--reference" and MODE are mutually exclusive
|
||||
// if "--reference" was used MODE needs to be interpreted as another FILE
|
||||
// it wasn't possible to implement this behavior directly with clap
|
||||
files.push(cmode.unwrap());
|
||||
cmode = None;
|
||||
}
|
||||
|
||||
let chmoder = Chmoder {
|
||||
changes,
|
||||
quiet,
|
||||
verbose,
|
||||
preserve_root,
|
||||
recursive,
|
||||
fmode,
|
||||
cmode,
|
||||
};
|
||||
match chmoder.chmod(files) {
|
||||
Ok(()) => {}
|
||||
Err(e) => return e,
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn sanitize_input(args: &mut Vec<String>) -> Option<String> {
|
||||
// Iterate 'args' and delete the first occurrence
|
||||
// of a prefix '-' if it's associated with MODE
|
||||
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
|
||||
pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
|
||||
for i in 0..args.len() {
|
||||
let first = args[i].chars().next().unwrap();
|
||||
if first != '-' {
|
||||
continue;
|
||||
}
|
||||
if let Some(second) = args[i].chars().nth(1) {
|
||||
match second {
|
||||
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
|
||||
return Some(args.remove(i));
|
||||
if args[i].starts_with("-") {
|
||||
if let Some(second) = args[i].chars().nth(1) {
|
||||
match second {
|
||||
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
|
||||
// TODO: use strip_prefix() once minimum rust version reaches 1.45.0
|
||||
args[i] = args[i][1..args[i].len()].to_string();
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
false
|
||||
}
|
||||
|
||||
struct Chmoder {
|
||||
|
@ -147,7 +206,17 @@ impl Chmoder {
|
|||
let filename = &filename[..];
|
||||
let file = Path::new(filename);
|
||||
if !file.exists() {
|
||||
show_error!("no such file or directory '{}'", filename);
|
||||
if is_symlink(file) {
|
||||
println!(
|
||||
"failed to change mode of '{}' from 0000 (---------) to 0000 (---------)",
|
||||
filename
|
||||
);
|
||||
if !self.quiet {
|
||||
show_error!("cannot operate on dangling symlink '{}'", filename);
|
||||
}
|
||||
} else {
|
||||
show_error!("cannot access '{}': No such file or directory", filename);
|
||||
}
|
||||
return Err(1);
|
||||
}
|
||||
if self.recursive && self.preserve_root && filename == "/" {
|
||||
|
@ -181,18 +250,16 @@ impl Chmoder {
|
|||
let mut fperm = match fs::metadata(file) {
|
||||
Ok(meta) => meta.mode() & 0o7777,
|
||||
Err(err) => {
|
||||
if !self.quiet {
|
||||
if is_symlink(file) {
|
||||
if self.verbose {
|
||||
show_info!(
|
||||
"neither symbolic link '{}' nor referent has been changed",
|
||||
file.display()
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
} else {
|
||||
show_error!("{}: '{}'", err, file.display());
|
||||
if is_symlink(file) {
|
||||
if self.verbose {
|
||||
println!(
|
||||
"neither symbolic link '{}' nor referent has been changed",
|
||||
file.display()
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
} else {
|
||||
show_error!("{}: '{}'", err, file.display());
|
||||
}
|
||||
return Err(1);
|
||||
}
|
||||
|
@ -232,11 +299,11 @@ impl Chmoder {
|
|||
fn change_file(&self, fperm: u32, mode: u32, file: &Path) -> Result<(), i32> {
|
||||
if fperm == mode {
|
||||
if self.verbose && !self.changes {
|
||||
show_info!(
|
||||
"mode of '{}' retained as {:o} ({})",
|
||||
println!(
|
||||
"mode of '{}' retained as {:04o} ({})",
|
||||
file.display(),
|
||||
fperm,
|
||||
display_permissions_unix(fperm)
|
||||
display_permissions_unix(fperm),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -65,8 +65,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(options::USERSPEC)
|
||||
.help(
|
||||
"Colon-separated user and group to switch to. \
|
||||
Same as -u USER -g GROUP. \
|
||||
Userspec has higher preference than -u and/or -g",
|
||||
Same as -u USER -g GROUP. \
|
||||
Userspec has higher preference than -u and/or -g",
|
||||
)
|
||||
.value_name("USER:GROUP"),
|
||||
)
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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("-") {
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
continue;
|
||||
if this_stat.inode.is_some() {
|
||||
let inode = this_stat.inode.unwrap();
|
||||
if inodes.contains(&inode) {
|
||||
continue;
|
||||
}
|
||||
inodes.insert(inode);
|
||||
}
|
||||
inodes.insert(this_stat.inode);
|
||||
my_stat.size += this_stat.size;
|
||||
my_stat.blocks += this_stat.blocks;
|
||||
if options.all {
|
||||
|
@ -200,6 +321,9 @@ fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String {
|
|||
return format!("{:.1}{}", (size as f64) / (limit as f64), unit);
|
||||
}
|
||||
}
|
||||
if size == 0 {
|
||||
return format!("0");
|
||||
}
|
||||
format!("{}B", size)
|
||||
}
|
||||
|
||||
|
@ -418,7 +542,7 @@ Try '{} --help' for more information.",
|
|||
let path = PathBuf::from(&path_str);
|
||||
match Stat::new(path) {
|
||||
Ok(stat) => {
|
||||
let mut inodes: HashSet<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),
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
4
src/uu/env/Cargo.toml
vendored
4
src/uu/env/Cargo.toml
vendored
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -10,46 +10,71 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, BufRead, BufReader, Read};
|
||||
use std::path::Path;
|
||||
|
||||
const TAB_WIDTH: usize = 8;
|
||||
|
||||
static NAME: &str = "fold";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static SYNTAX: &str = "[OPTION]... [FILE]...";
|
||||
static SUMMARY: &str = "Writes each file (or standard input if no files are given)
|
||||
to standard output whilst breaking long lines";
|
||||
static LONG_HELP: &str = "";
|
||||
|
||||
mod options {
|
||||
pub const BYTES: &str = "bytes";
|
||||
pub const SPACES: &str = "spaces";
|
||||
pub const WIDTH: &str = "width";
|
||||
pub const FILE: &str = "file";
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
|
||||
let (args, obs_width) = handle_obsolete(&args[..]);
|
||||
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
|
||||
.optflag(
|
||||
"b",
|
||||
"bytes",
|
||||
"count using bytes rather than columns (meaning control characters \
|
||||
such as newline are not treated specially)",
|
||||
let matches = App::new(executable!())
|
||||
.name(NAME)
|
||||
.version(VERSION)
|
||||
.usage(SYNTAX)
|
||||
.about(SUMMARY)
|
||||
.arg(
|
||||
Arg::with_name(options::BYTES)
|
||||
.long(options::BYTES)
|
||||
.short("b")
|
||||
.help(
|
||||
"count using bytes rather than columns (meaning control characters \
|
||||
such as newline are not treated specially)",
|
||||
)
|
||||
.takes_value(false),
|
||||
)
|
||||
.optflag(
|
||||
"s",
|
||||
"spaces",
|
||||
"break lines at word boundaries rather than a hard cut-off",
|
||||
.arg(
|
||||
Arg::with_name(options::SPACES)
|
||||
.long(options::SPACES)
|
||||
.short("s")
|
||||
.help("break lines at word boundaries rather than a hard cut-off")
|
||||
.takes_value(false),
|
||||
)
|
||||
.optopt(
|
||||
"w",
|
||||
"width",
|
||||
"set WIDTH as the maximum line width rather than 80",
|
||||
"WIDTH",
|
||||
.arg(
|
||||
Arg::with_name(options::WIDTH)
|
||||
.long(options::WIDTH)
|
||||
.short("w")
|
||||
.help("set WIDTH as the maximum line width rather than 80")
|
||||
.value_name("WIDTH")
|
||||
.allow_hyphen_values(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.parse(args);
|
||||
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
|
||||
.get_matches_from(args.clone());
|
||||
|
||||
let bytes = matches.opt_present("b");
|
||||
let spaces = matches.opt_present("s");
|
||||
let poss_width = if matches.opt_present("w") {
|
||||
matches.opt_str("w")
|
||||
} else {
|
||||
obs_width
|
||||
let bytes = matches.is_present(options::BYTES);
|
||||
let spaces = matches.is_present(options::SPACES);
|
||||
let poss_width = match matches.value_of(options::WIDTH) {
|
||||
Some(v) => Some(v.to_owned()),
|
||||
None => obs_width,
|
||||
};
|
||||
|
||||
let width = match poss_width {
|
||||
Some(inp_width) => match inp_width.parse::<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,120 +117,163 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
let len = line.len();
|
||||
let mut i = 0;
|
||||
while i < len {
|
||||
let width = if len - i >= width { width } else { len - i };
|
||||
let slice = {
|
||||
let slice = &line[i..i + width];
|
||||
if spaces && i + width < len {
|
||||
match slice.rfind(char::is_whitespace) {
|
||||
Some(m) => &slice[..=m],
|
||||
None => slice,
|
||||
}
|
||||
} else {
|
||||
slice
|
||||
}
|
||||
};
|
||||
print!("{}", slice);
|
||||
i += slice.len();
|
||||
}
|
||||
fold_file_bytewise(buffer, spaces, width);
|
||||
} else {
|
||||
let mut len = line.chars().count();
|
||||
let newline = line.ends_with('\n');
|
||||
if newline {
|
||||
if len == 1 {
|
||||
println!();
|
||||
continue;
|
||||
}
|
||||
len -= 1;
|
||||
line.truncate(len);
|
||||
}
|
||||
let mut output = String::new();
|
||||
let mut count = 0;
|
||||
for (i, ch) in line.chars().enumerate() {
|
||||
if count >= width {
|
||||
let (val, ncount) = {
|
||||
let slice = &output[..];
|
||||
let (out, val, ncount) = if spaces && i + 1 < len {
|
||||
match rfind_whitespace(slice) {
|
||||
Some(m) => {
|
||||
let routput = &slice[m + 1..slice.chars().count()];
|
||||
let ncount = routput.chars().fold(0, |out, ch: char| {
|
||||
out + match ch {
|
||||
'\t' => 8,
|
||||
'\x08' => {
|
||||
if out > 0 {
|
||||
!0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
'\r' => return 0,
|
||||
_ => 1,
|
||||
}
|
||||
});
|
||||
(&slice[0..=m], routput, ncount)
|
||||
}
|
||||
None => (slice, "", 0),
|
||||
}
|
||||
} else {
|
||||
(slice, "", 0)
|
||||
};
|
||||
println!("{}", out);
|
||||
(val.to_owned(), ncount)
|
||||
};
|
||||
output = val;
|
||||
count = ncount;
|
||||
}
|
||||
match ch {
|
||||
'\t' => {
|
||||
count += 8;
|
||||
if count > width {
|
||||
println!("{}", output);
|
||||
output.truncate(0);
|
||||
count = 8;
|
||||
}
|
||||
}
|
||||
'\x08' => {
|
||||
if count > 0 {
|
||||
count -= 1;
|
||||
let len = output.len() - 1;
|
||||
output.truncate(len);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
'\r' => {
|
||||
output.truncate(0);
|
||||
count = 0;
|
||||
continue;
|
||||
}
|
||||
_ => count += 1,
|
||||
};
|
||||
output.push(ch);
|
||||
}
|
||||
if count > 0 {
|
||||
println!("{}", output);
|
||||
}
|
||||
fold_file(buffer, spaces, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rfind_whitespace(slice: &str) -> Option<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), 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(|c: char| c.is_whitespace() && c != '\r') {
|
||||
Some(m) => &slice[..=m],
|
||||
None => slice,
|
||||
}
|
||||
} else {
|
||||
slice
|
||||
}
|
||||
};
|
||||
|
||||
// Don't duplicate trailing newlines: if the slice is "\n", the
|
||||
// previous iteration folded just before the end of the line and
|
||||
// has already printed this newline.
|
||||
if slice == "\n" {
|
||||
break;
|
||||
}
|
||||
|
||||
i += slice.len();
|
||||
|
||||
let at_eol = i >= len;
|
||||
|
||||
if at_eol {
|
||||
print!("{}", slice);
|
||||
} else {
|
||||
println!("{}", slice);
|
||||
}
|
||||
}
|
||||
|
||||
line.truncate(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fold `file` to fit `width` (number of columns).
|
||||
///
|
||||
/// By default `fold` treats tab, backspace, and carriage return specially:
|
||||
/// tab characters count as 8 columns, backspace decreases the
|
||||
/// column count, and carriage return resets the column count to 0.
|
||||
///
|
||||
/// If `spaces` is `true`, attempt to break lines at whitespace boundaries.
|
||||
#[allow(unused_assignments)]
|
||||
fn fold_file<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;
|
||||
}
|
||||
}
|
||||
_ if spaces && ch.is_whitespace() => {
|
||||
last_space = Some(output.len());
|
||||
col_count += 1;
|
||||
}
|
||||
_ => col_count += 1,
|
||||
};
|
||||
|
||||
output.push(ch);
|
||||
}
|
||||
|
||||
if !output.is_empty() {
|
||||
print!("{}", output);
|
||||
output.truncate(0);
|
||||
}
|
||||
|
||||
line.truncate(0);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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,
|
||||
fn parse_mode<F>(src: &str, closure: F) -> Result<(Modes, bool), String>
|
||||
where
|
||||
F: FnOnce(usize) -> Modes,
|
||||
{
|
||||
match parse::parse_num(src) {
|
||||
Ok((n, last)) => Ok((closure(n), last)),
|
||||
Err(reason) => match reason {
|
||||
parse::ParseError::Syntax => Err(format!("'{}'", src)),
|
||||
parse::ParseError::Overflow => {
|
||||
Err(format!("'{}': Value too large for defined datatype", src))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
mode: FilterMode::Lines(10),
|
||||
verbose: false,
|
||||
zero_terminated: false,
|
||||
fn arg_iterate<'a>(
|
||||
mut args: impl uucore::Args + 'a,
|
||||
) -> Result<Box<dyn Iterator<Item = OsString> + 'a>, String> {
|
||||
// argv[0] is always present
|
||||
let first = args.next().unwrap();
|
||||
if let Some(second) = args.next() {
|
||||
if let Some(s) = second.to_str() {
|
||||
match parse::parse_obsolete(s) {
|
||||
Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
|
||||
Some(Err(e)) => match e {
|
||||
parse::ParseError::Syntax => Err(format!("bad argument format: '{}'", s)),
|
||||
parse::ParseError::Overflow => Err(format!(
|
||||
"invalid argument: '{}' Value too large for defined datatype",
|
||||
s
|
||||
)),
|
||||
},
|
||||
None => Ok(Box::new(vec![first, second].into_iter().chain(args))),
|
||||
}
|
||||
} else {
|
||||
Err("bad argument encoding".to_owned())
|
||||
}
|
||||
} else {
|
||||
Ok(Box::new(vec![first].into_iter()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct HeadOptions {
|
||||
pub quiet: bool,
|
||||
pub verbose: bool,
|
||||
pub zeroed: bool,
|
||||
pub all_but_last: bool,
|
||||
pub mode: Modes,
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
impl HeadOptions {
|
||||
pub fn new() -> HeadOptions {
|
||||
HeadOptions {
|
||||
quiet: false,
|
||||
verbose: false,
|
||||
zeroed: false,
|
||||
all_but_last: false,
|
||||
mode: Modes::Lines(10),
|
||||
files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
///Construct options from matches
|
||||
pub fn get_from(args: impl uucore::Args) -> Result<Self, String> {
|
||||
let matches = app().get_matches_from(arg_iterate(args)?);
|
||||
|
||||
let mut options = HeadOptions::new();
|
||||
|
||||
options.quiet = matches.is_present(options::QUIET_NAME);
|
||||
options.verbose = matches.is_present(options::VERBOSE_NAME);
|
||||
options.zeroed = matches.is_present(options::ZERO_NAME);
|
||||
|
||||
let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) {
|
||||
match parse_mode(v, Modes::Bytes) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return Err(format!("invalid number of bytes: {}", err));
|
||||
}
|
||||
}
|
||||
} else if let Some(v) = matches.value_of(options::LINES_NAME) {
|
||||
match parse_mode(v, Modes::Lines) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return Err(format!("invalid number of lines: {}", err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(Modes::Lines(10), false)
|
||||
};
|
||||
|
||||
options.mode = mode_and_from_end.0;
|
||||
options.all_but_last = mode_and_from_end.1;
|
||||
|
||||
options.files = match matches.values_of(options::FILES_NAME) {
|
||||
Some(v) => v.map(|s| s.to_owned()).collect(),
|
||||
None => vec!["-".to_owned()],
|
||||
};
|
||||
//println!("{:#?}", options);
|
||||
Ok(options)
|
||||
}
|
||||
}
|
||||
// to make clippy shut up
|
||||
impl Default for HeadOptions {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn rbuf_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> {
|
||||
if n == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let mut readbuf = [0u8; BUF_SIZE];
|
||||
let mut i = 0usize;
|
||||
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
loop {
|
||||
let read = loop {
|
||||
match input.read(&mut readbuf) {
|
||||
Ok(n) => break n,
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::Interrupted => {}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
};
|
||||
if read == 0 {
|
||||
// might be unexpected if
|
||||
// we haven't read `n` bytes
|
||||
// but this mirrors GNU's behavior
|
||||
return Ok(());
|
||||
}
|
||||
stdout.write_all(&readbuf[..read.min(n - i)])?;
|
||||
i += read.min(n - i);
|
||||
if i == n {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rbuf_n_lines(input: &mut impl std::io::BufRead, n: usize, zero: bool) -> std::io::Result<()> {
|
||||
if n == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let mut lines = 0usize;
|
||||
split::walk_lines(input, zero, |e| match e {
|
||||
split::Event::Data(dat) => {
|
||||
stdout.write_all(dat)?;
|
||||
Ok(true)
|
||||
}
|
||||
split::Event::Line => {
|
||||
lines += 1;
|
||||
if lines == n {
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn rbuf_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> {
|
||||
if n == 0 {
|
||||
//prints everything
|
||||
return rbuf_n_bytes(input, std::usize::MAX);
|
||||
}
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
let mut ringbuf = vec![0u8; n];
|
||||
|
||||
// first we fill the ring buffer
|
||||
if let Err(e) = input.read_exact(&mut ringbuf) {
|
||||
if e.kind() == ErrorKind::UnexpectedEof {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
let mut buffer = [0u8; BUF_SIZE];
|
||||
loop {
|
||||
let read = loop {
|
||||
match input.read(&mut buffer) {
|
||||
Ok(n) => break n,
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::Interrupted => {}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
};
|
||||
if read == 0 {
|
||||
return Ok(());
|
||||
} else if read >= n {
|
||||
stdout.write_all(&ringbuf)?;
|
||||
stdout.write_all(&buffer[..read - n])?;
|
||||
for i in 0..n {
|
||||
ringbuf[i] = buffer[read - n + i];
|
||||
}
|
||||
} else {
|
||||
stdout.write_all(&ringbuf[..read])?;
|
||||
for i in 0..n - read {
|
||||
ringbuf[i] = ringbuf[read + i];
|
||||
}
|
||||
ringbuf[n - read..].copy_from_slice(&buffer[..read]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rbuf_but_last_n_lines(
|
||||
input: &mut impl std::io::BufRead,
|
||||
n: usize,
|
||||
zero: bool,
|
||||
) -> std::io::Result<()> {
|
||||
if n == 0 {
|
||||
//prints everything
|
||||
return rbuf_n_bytes(input, std::usize::MAX);
|
||||
}
|
||||
let mut ringbuf = vec![Vec::new(); n];
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let mut line = Vec::new();
|
||||
let mut lines = 0usize;
|
||||
split::walk_lines(input, zero, |e| match e {
|
||||
split::Event::Data(dat) => {
|
||||
line.extend_from_slice(dat);
|
||||
Ok(true)
|
||||
}
|
||||
split::Event::Line => {
|
||||
if lines < n {
|
||||
ringbuf[lines] = std::mem::replace(&mut line, Vec::new());
|
||||
lines += 1;
|
||||
} else {
|
||||
stdout.write_all(&ringbuf[0])?;
|
||||
ringbuf.rotate_left(1);
|
||||
ringbuf[n - 1] = std::mem::replace(&mut line, Vec::new());
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {
|
||||
assert!(options.all_but_last);
|
||||
let size = input.seek(SeekFrom::End(0))?;
|
||||
let size = usize::try_from(size).unwrap();
|
||||
match options.mode {
|
||||
Modes::Bytes(n) => {
|
||||
if n >= size {
|
||||
return Ok(());
|
||||
} else {
|
||||
input.seek(SeekFrom::Start(0))?;
|
||||
rbuf_n_bytes(
|
||||
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
|
||||
size - n,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Modes::Lines(n) => {
|
||||
let mut buffer = [0u8; BUF_SIZE];
|
||||
let buffer = &mut buffer[..BUF_SIZE.min(size)];
|
||||
let mut i = 0usize;
|
||||
let mut lines = 0usize;
|
||||
|
||||
let found = 'o: loop {
|
||||
// the casts here are ok, `buffer.len()` should never be above a few k
|
||||
input.seek(SeekFrom::Current(
|
||||
-((buffer.len() as i64).min((size - i) as i64)),
|
||||
))?;
|
||||
input.read_exact(buffer)?;
|
||||
for byte in buffer.iter().rev() {
|
||||
match byte {
|
||||
b'\n' if !options.zeroed => {
|
||||
lines += 1;
|
||||
}
|
||||
0u8 if options.zeroed => {
|
||||
lines += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// if it were just `n`,
|
||||
if lines == n + 1 {
|
||||
break 'o i;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if size - i == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
input.seek(SeekFrom::Start(0))?;
|
||||
rbuf_n_bytes(
|
||||
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
|
||||
size - found,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {
|
||||
if options.all_but_last {
|
||||
head_backwards_file(input, options)
|
||||
} else {
|
||||
match options.mode {
|
||||
Modes::Bytes(n) => {
|
||||
rbuf_n_bytes(&mut std::io::BufReader::with_capacity(BUF_SIZE, input), n)
|
||||
}
|
||||
Modes::Lines(n) => rbuf_n_lines(
|
||||
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
|
||||
n,
|
||||
options.zeroed,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn uu_head(options: &HeadOptions) {
|
||||
let mut first = true;
|
||||
for fname in &options.files {
|
||||
let res = match fname.as_str() {
|
||||
"-" => {
|
||||
if options.verbose {
|
||||
if !first {
|
||||
println!();
|
||||
}
|
||||
println!("==> standard input <==")
|
||||
}
|
||||
let stdin = std::io::stdin();
|
||||
let mut stdin = stdin.lock();
|
||||
match options.mode {
|
||||
Modes::Bytes(n) => {
|
||||
if options.all_but_last {
|
||||
rbuf_but_last_n_bytes(&mut stdin, n)
|
||||
} else {
|
||||
rbuf_n_bytes(&mut stdin, n)
|
||||
}
|
||||
}
|
||||
Modes::Lines(n) => {
|
||||
if options.all_but_last {
|
||||
rbuf_but_last_n_lines(&mut stdin, n, options.zeroed)
|
||||
} else {
|
||||
rbuf_n_lines(&mut stdin, n, options.zeroed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
name => {
|
||||
let mut file = match std::fs::File::open(name) {
|
||||
Ok(f) => f,
|
||||
Err(err) => match err.kind() {
|
||||
ErrorKind::NotFound => {
|
||||
crash!(
|
||||
EXIT_FAILURE,
|
||||
"head: cannot open '{}' for reading: No such file or directory",
|
||||
name
|
||||
);
|
||||
}
|
||||
ErrorKind::PermissionDenied => {
|
||||
crash!(
|
||||
EXIT_FAILURE,
|
||||
"head: cannot open '{}' for reading: Permission denied",
|
||||
name
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
crash!(
|
||||
EXIT_FAILURE,
|
||||
"head: cannot open '{}' for reading: {}",
|
||||
name,
|
||||
err
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
if (options.files.len() > 1 && !options.quiet) || options.verbose {
|
||||
println!("==> {} <==", name)
|
||||
}
|
||||
head_file(&mut file, options)
|
||||
}
|
||||
};
|
||||
if res.is_err() {
|
||||
if fname.as_str() == "-" {
|
||||
crash!(
|
||||
EXIT_FAILURE,
|
||||
"head: error reading standard input: Input/output error"
|
||||
);
|
||||
} else {
|
||||
crash!(
|
||||
EXIT_FAILURE,
|
||||
"head: error reading {}: Input/output error",
|
||||
fname
|
||||
);
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
|
||||
let mut settings: Settings = Default::default();
|
||||
|
||||
// handle obsolete -number syntax
|
||||
let new_args = match obsolete(&args[0..]) {
|
||||
(args, Some(n)) => {
|
||||
settings.mode = FilterMode::Lines(n);
|
||||
args
|
||||
}
|
||||
(args, None) => args,
|
||||
};
|
||||
|
||||
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
|
||||
.optopt(
|
||||
"c",
|
||||
"bytes",
|
||||
"Print the first K bytes. With the leading '-', print all but the last K bytes",
|
||||
"[-]K",
|
||||
)
|
||||
.optopt(
|
||||
"n",
|
||||
"lines",
|
||||
"Print the first K lines. With the leading '-', print all but the last K lines",
|
||||
"[-]K",
|
||||
)
|
||||
.optflag("q", "quiet", "never print headers giving file names")
|
||||
.optflag("v", "verbose", "always print headers giving file names")
|
||||
.optflag("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;
|
||||
|
||||
// GNU implementation allows multiple declarations of "-q" and "-v" with the
|
||||
// last flag winning. This can't be simulated with the getopts cargo unless
|
||||
// we manually parse the arguments. Given the declaration of both flags,
|
||||
// verbose mode always wins. This is a potential future improvement.
|
||||
if files.len() > 1 && !quiet && !verbose {
|
||||
settings.verbose = true;
|
||||
}
|
||||
if quiet {
|
||||
settings.verbose = false;
|
||||
}
|
||||
if verbose {
|
||||
settings.verbose = true;
|
||||
}
|
||||
|
||||
if files.is_empty() {
|
||||
let mut buffer = BufReader::new(stdin());
|
||||
head(&mut buffer, &settings);
|
||||
} else {
|
||||
let mut first_time = true;
|
||||
|
||||
for file in &files {
|
||||
if settings.verbose {
|
||||
if !first_time {
|
||||
println!();
|
||||
}
|
||||
println!("==> {} <==", file);
|
||||
}
|
||||
first_time = false;
|
||||
|
||||
let path = Path::new(file);
|
||||
if path.is_dir() || !path.metadata().is_ok() {
|
||||
eprintln!(
|
||||
"cannot open '{}' for reading: No such file or directory",
|
||||
&path.to_str().unwrap()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let reader = File::open(&path).unwrap();
|
||||
let mut buffer = BufReader::new(reader);
|
||||
if !head(&mut buffer, &settings) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
EXIT_SUCCESS
|
||||
}
|
||||
|
||||
// It searches for an option in the form of -123123
|
||||
//
|
||||
// In case is found, the options vector will get rid of that object so that
|
||||
// getopts works correctly.
|
||||
fn obsolete(options: &[String]) -> (Vec<String>, Option<usize>) {
|
||||
let mut options: Vec<String> = options.to_vec();
|
||||
let mut a = 1;
|
||||
let b = options.len();
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::OsString;
|
||||
|
||||
while a < b {
|
||||
let previous = options[a - 1].clone();
|
||||
let current = options[a].clone();
|
||||
let current = current.as_bytes();
|
||||
|
||||
if previous != "-n" && current.len() > 1 && current[0] == b'-' {
|
||||
let len = current.len();
|
||||
for pos in 1..len {
|
||||
// Ensure that the argument is only made out of digits
|
||||
if !(current[pos] as char).is_numeric() {
|
||||
break;
|
||||
}
|
||||
|
||||
// If this is the last number
|
||||
if pos == len - 1 {
|
||||
options.remove(a);
|
||||
let number: Option<usize> =
|
||||
from_utf8(¤t[1..len]).unwrap().parse::<usize>().ok();
|
||||
return (options, Some(number.unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a += 1;
|
||||
use super::*;
|
||||
fn options(args: &str) -> Result<HeadOptions, String> {
|
||||
let combined = "head ".to_owned() + args;
|
||||
let args = combined.split_whitespace();
|
||||
HeadOptions::get_from(args.map(|s| OsString::from(s)))
|
||||
}
|
||||
#[test]
|
||||
fn test_args_modes() {
|
||||
let args = options("-n -10M -vz").unwrap();
|
||||
assert!(args.zeroed);
|
||||
assert!(args.verbose);
|
||||
assert!(args.all_but_last);
|
||||
assert_eq!(args.mode, Modes::Lines(10 * 1024 * 1024));
|
||||
}
|
||||
#[test]
|
||||
fn test_gnu_compatibility() {
|
||||
let args = options("-n 1 -c 1 -n 5 -c kiB -vqvqv").unwrap();
|
||||
assert!(args.mode == Modes::Bytes(1024));
|
||||
assert!(args.verbose);
|
||||
assert_eq!(options("-5").unwrap().mode, Modes::Lines(5));
|
||||
assert_eq!(options("-2b").unwrap().mode, Modes::Bytes(1024));
|
||||
assert_eq!(options("-5 -c 1").unwrap().mode, Modes::Bytes(1));
|
||||
}
|
||||
#[test]
|
||||
fn all_args_test() {
|
||||
assert!(options("--silent").unwrap().quiet);
|
||||
assert!(options("--quiet").unwrap().quiet);
|
||||
assert!(options("-q").unwrap().quiet);
|
||||
assert!(options("--verbose").unwrap().verbose);
|
||||
assert!(options("-v").unwrap().verbose);
|
||||
assert!(options("--zero-terminated").unwrap().zeroed);
|
||||
assert!(options("-z").unwrap().zeroed);
|
||||
assert_eq!(options("--lines 15").unwrap().mode, Modes::Lines(15));
|
||||
assert_eq!(options("-n 15").unwrap().mode, Modes::Lines(15));
|
||||
assert_eq!(options("--bytes 15").unwrap().mode, Modes::Bytes(15));
|
||||
assert_eq!(options("-c 15").unwrap().mode, Modes::Bytes(15));
|
||||
}
|
||||
#[test]
|
||||
fn test_options_errors() {
|
||||
assert!(options("-n IsThisTheRealLife?").is_err());
|
||||
assert!(options("-c IsThisJustFantasy").is_err());
|
||||
}
|
||||
#[test]
|
||||
fn test_options_correct_defaults() {
|
||||
let opts = HeadOptions::new();
|
||||
let opts2: HeadOptions = Default::default();
|
||||
|
||||
(options, None)
|
||||
}
|
||||
assert_eq!(opts, opts2);
|
||||
|
||||
// TODO: handle errors on read
|
||||
fn head<T: Read>(reader: &mut BufReader<T>, settings: &Settings) -> bool {
|
||||
match settings.mode {
|
||||
FilterMode::Bytes(count) => {
|
||||
for byte in reader.bytes().take(count) {
|
||||
print!("{}", byte.unwrap() as char);
|
||||
}
|
||||
}
|
||||
FilterMode::Lines(count) => {
|
||||
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());
|
||||
assert!(opts.verbose == false);
|
||||
assert!(opts.quiet == false);
|
||||
assert!(opts.zeroed == false);
|
||||
assert!(opts.all_but_last == false);
|
||||
assert_eq!(opts.mode, Modes::Lines(10));
|
||||
assert!(opts.files.is_empty());
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_mode() {
|
||||
assert_eq!(
|
||||
parse_mode("123", Modes::Lines),
|
||||
Ok((Modes::Lines(123), false))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_mode("-456", Modes::Bytes),
|
||||
Ok((Modes::Bytes(456), true))
|
||||
);
|
||||
assert!(parse_mode("Nonsensical Nonsense", Modes::Bytes).is_err());
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
assert!(parse_mode("1Y", Modes::Lines).is_err());
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
assert!(parse_mode("1T", Modes::Bytes).is_err());
|
||||
}
|
||||
fn arg_outputs(src: &str) -> Result<String, String> {
|
||||
let split = src.split_whitespace().map(|x| OsString::from(x));
|
||||
match arg_iterate(split) {
|
||||
Ok(args) => {
|
||||
let vec = args
|
||||
.map(|s| s.to_str().unwrap().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
Ok(vec.join(" "))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
true
|
||||
#[test]
|
||||
fn test_arg_iterate() {
|
||||
// test that normal args remain unchanged
|
||||
assert_eq!(
|
||||
arg_outputs("head -n -5 -zv"),
|
||||
Ok("head -n -5 -zv".to_owned())
|
||||
);
|
||||
// tests that nonsensical args are unchanged
|
||||
assert_eq!(
|
||||
arg_outputs("head -to_be_or_not_to_be,..."),
|
||||
Ok("head -to_be_or_not_to_be,...".to_owned())
|
||||
);
|
||||
//test that the obsolete syntax is unrolled
|
||||
assert_eq!(
|
||||
arg_outputs("head -123qvqvqzc"),
|
||||
Ok("head -q -z -c 123".to_owned())
|
||||
);
|
||||
//test that bad obsoletes are an error
|
||||
assert!(arg_outputs("head -123FooBar").is_err());
|
||||
//test overflow
|
||||
assert!(arg_outputs("head -100000000000000000000000000000000000000000").is_err());
|
||||
//test that empty args remain unchanged
|
||||
assert_eq!(arg_outputs("head"), Ok("head".to_owned()));
|
||||
}
|
||||
#[test]
|
||||
#[cfg(linux)]
|
||||
fn test_arg_iterate_bad_encoding() {
|
||||
let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") };
|
||||
// this arises from a conversion from OsString to &str
|
||||
assert!(
|
||||
arg_iterate(vec![OsString::from("head"), OsString::from(invalid)].into_iter()).is_err()
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn rbuf_early_exit() {
|
||||
let mut empty = std::io::BufReader::new(std::io::Cursor::new(Vec::new()));
|
||||
assert!(rbuf_n_bytes(&mut empty, 0).is_ok());
|
||||
assert!(rbuf_n_lines(&mut empty, 0, false).is_ok());
|
||||
}
|
||||
}
|
||||
|
|
282
src/uu/head/src/parse.rs
Normal file
282
src/uu/head/src/parse.rs
Normal 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
60
src/uu/head/src/split.rs
Normal 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]))?;
|
||||
}
|
||||
}
|
|
@ -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]]
|
||||
|
|
|
@ -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"] }
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ fn execute(args: impl uucore::Args) -> i32 {
|
|||
)
|
||||
.arg(Arg::with_name(OPT_SHORT).short("s").long("short").help(
|
||||
"Display the short hostname (the portion before the first dot) if \
|
||||
possible",
|
||||
possible",
|
||||
))
|
||||
.arg(Arg::with_name(OPT_HOST))
|
||||
.get_matches_from(args);
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -111,7 +111,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(OPT_BACKUP)
|
||||
.help(
|
||||
"make a backup of each file that would otherwise be overwritten \
|
||||
or removed",
|
||||
or removed",
|
||||
)
|
||||
.takes_value(true)
|
||||
.possible_value("simple")
|
||||
|
@ -145,7 +145,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(OPT_NO_DEREFERENCE)
|
||||
.help(
|
||||
"treat LINK_executable!() as a normal file if it is a \
|
||||
symbolic link to a directory",
|
||||
symbolic link to a directory",
|
||||
),
|
||||
)
|
||||
// TODO: opts.arg(
|
||||
|
@ -223,7 +223,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
} else if matches.is_present(OPT_BACKUP) {
|
||||
match matches.value_of(OPT_BACKUP) {
|
||||
None => BackupMode::ExistingBackup,
|
||||
Some(mode) => match &mode[..] {
|
||||
Some(mode) => match mode {
|
||||
"simple" | "never" => BackupMode::SimpleBackup,
|
||||
"numbered" | "t" => BackupMode::NumberedBackup,
|
||||
"existing" | "nil" => BackupMode::ExistingBackup,
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
format = Format::Long;
|
||||
} else if options.is_present(options::format::ONELINE) {
|
||||
// options, but manually whether they have an index that's greater than
|
||||
// the other format options. If so, we set the appropriate format.
|
||||
if format != Format::Long {
|
||||
let idx = options
|
||||
.indices_of(opt)
|
||||
.map(|x| x.max().unwrap())
|
||||
.unwrap_or(0);
|
||||
if [
|
||||
options::format::LONG_NO_OWNER,
|
||||
options::format::LONG_NO_GROUP,
|
||||
options::format::LONG_NUMERIC_UID_GID,
|
||||
]
|
||||
.iter()
|
||||
.flat_map(|opt| options.indices_of(opt))
|
||||
.flatten()
|
||||
.any(|i| i >= idx)
|
||||
{
|
||||
format = Format::Long;
|
||||
} else if let Some(mut indices) = options.indices_of(options::format::ONELINE) {
|
||||
if indices.any(|i| i > idx) {
|
||||
format = Format::OneLine;
|
||||
}
|
||||
}
|
||||
Some(mut idx) => {
|
||||
if let Some(indices) = options.indices_of(options::format::LONG_NO_OWNER) {
|
||||
let i = indices.max().unwrap();
|
||||
if i > idx {
|
||||
format = Format::Long;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
if let Some(indices) = options.indices_of(options::format::LONG_NO_GROUP) {
|
||||
let i = indices.max().unwrap();
|
||||
if i > idx {
|
||||
format = Format::Long;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
if let Some(indices) = options.indices_of(options::format::ONELINE) {
|
||||
let i = indices.max().unwrap();
|
||||
if i > idx && format != Format::Long {
|
||||
format = Format::OneLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let files = if options.is_present(options::files::ALL) {
|
||||
|
@ -328,10 +349,14 @@ impl Config {
|
|||
let group = !options.is_present(options::NO_GROUP)
|
||||
&& !options.is_present(options::format::LONG_NO_GROUP);
|
||||
let owner = !options.is_present(options::format::LONG_NO_OWNER);
|
||||
#[cfg(unix)]
|
||||
let numeric_uid_gid = options.is_present(options::format::LONG_NUMERIC_UID_GID);
|
||||
LongFormat {
|
||||
author,
|
||||
group,
|
||||
owner,
|
||||
#[cfg(unix)]
|
||||
numeric_uid_gid,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -345,6 +370,118 @@ impl Config {
|
|||
})
|
||||
.or_else(|| termsize::get().map(|s| s.cols));
|
||||
|
||||
let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) {
|
||||
false
|
||||
} else if options.is_present(options::SHOW_CONTROL_CHARS) {
|
||||
true
|
||||
} else {
|
||||
false // TODO: only if output is a terminal and the program is `ls`
|
||||
};
|
||||
|
||||
let quoting_style = if let Some(style) = options.value_of(options::QUOTING_STYLE) {
|
||||
match style {
|
||||
"literal" => QuotingStyle::Literal { show_control },
|
||||
"shell" => QuotingStyle::Shell {
|
||||
escape: false,
|
||||
always_quote: false,
|
||||
show_control,
|
||||
},
|
||||
"shell-always" => QuotingStyle::Shell {
|
||||
escape: false,
|
||||
always_quote: true,
|
||||
show_control,
|
||||
},
|
||||
"shell-escape" => QuotingStyle::Shell {
|
||||
escape: true,
|
||||
always_quote: false,
|
||||
show_control,
|
||||
},
|
||||
"shell-escape-always" => QuotingStyle::Shell {
|
||||
escape: true,
|
||||
always_quote: true,
|
||||
show_control,
|
||||
},
|
||||
"c" => QuotingStyle::C {
|
||||
quotes: quoting_style::Quotes::Double,
|
||||
},
|
||||
"escape" => QuotingStyle::C {
|
||||
quotes: quoting_style::Quotes::None,
|
||||
},
|
||||
_ => unreachable!("Should have been caught by Clap"),
|
||||
}
|
||||
} else if options.is_present(options::quoting::LITERAL) {
|
||||
QuotingStyle::Literal { show_control }
|
||||
} else if options.is_present(options::quoting::ESCAPE) {
|
||||
QuotingStyle::C {
|
||||
quotes: quoting_style::Quotes::None,
|
||||
}
|
||||
} else if options.is_present(options::quoting::C) {
|
||||
QuotingStyle::C {
|
||||
quotes: quoting_style::Quotes::Double,
|
||||
}
|
||||
} else {
|
||||
// TODO: use environment variable if available
|
||||
QuotingStyle::Shell {
|
||||
escape: true,
|
||||
always_quote: false,
|
||||
show_control,
|
||||
}
|
||||
};
|
||||
|
||||
let indicator_style = if let Some(field) = options.value_of(options::INDICATOR_STYLE) {
|
||||
match field {
|
||||
"none" => IndicatorStyle::None,
|
||||
"file-type" => IndicatorStyle::FileType,
|
||||
"classify" => IndicatorStyle::Classify,
|
||||
"slash" => IndicatorStyle::Slash,
|
||||
&_ => IndicatorStyle::None,
|
||||
}
|
||||
} else if options.is_present(options::indicator_style::NONE) {
|
||||
IndicatorStyle::None
|
||||
} else if options.is_present(options::indicator_style::CLASSIFY)
|
||||
|| options.is_present(options::CLASSIFY)
|
||||
{
|
||||
IndicatorStyle::Classify
|
||||
} else if options.is_present(options::indicator_style::SLASH)
|
||||
|| options.is_present(options::SLASH)
|
||||
{
|
||||
IndicatorStyle::Slash
|
||||
} else if options.is_present(options::indicator_style::FILE_TYPE)
|
||||
|| options.is_present(options::FILE_TYPE)
|
||||
{
|
||||
IndicatorStyle::FileType
|
||||
} else {
|
||||
IndicatorStyle::None
|
||||
};
|
||||
|
||||
let mut ignore_patterns = GlobSetBuilder::new();
|
||||
if options.is_present(options::IGNORE_BACKUPS) {
|
||||
ignore_patterns.add(Glob::new("*~").unwrap());
|
||||
ignore_patterns.add(Glob::new(".*~").unwrap());
|
||||
}
|
||||
|
||||
for pattern in options.values_of(options::IGNORE).into_iter().flatten() {
|
||||
match Glob::new(pattern) {
|
||||
Ok(p) => {
|
||||
ignore_patterns.add(p);
|
||||
}
|
||||
Err(_) => show_warning!("Invalid pattern for ignore: '{}'", pattern),
|
||||
}
|
||||
}
|
||||
|
||||
if files == Files::Normal {
|
||||
for pattern in options.values_of(options::HIDE).into_iter().flatten() {
|
||||
match Glob::new(pattern) {
|
||||
Ok(p) => {
|
||||
ignore_patterns.add(p);
|
||||
}
|
||||
Err(_) => show_warning!("Invalid pattern for hide: '{}'", pattern),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ignore_patterns = ignore_patterns.build().unwrap();
|
||||
|
||||
Config {
|
||||
format,
|
||||
files,
|
||||
|
@ -352,10 +489,8 @@ impl Config {
|
|||
recursive: options.is_present(options::RECURSIVE),
|
||||
reverse: options.is_present(options::REVERSE),
|
||||
dereference: options.is_present(options::DEREFERENCE),
|
||||
classify: options.is_present(options::CLASSIFY),
|
||||
ignore_backups: options.is_present(options::IGNORE_BACKUPS),
|
||||
ignore_patterns,
|
||||
size_format,
|
||||
numeric_uid_gid: options.is_present(options::NUMERIC_UID_GID),
|
||||
directory: options.is_present(options::DIRECTORY),
|
||||
time,
|
||||
#[cfg(unix)]
|
||||
|
@ -364,6 +499,8 @@ impl Config {
|
|||
inode: options.is_present(options::INODE),
|
||||
long,
|
||||
width,
|
||||
quoting_style,
|
||||
indicator_style,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -444,22 +581,108 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
options::format::COLUMNS,
|
||||
]),
|
||||
)
|
||||
// The next three arguments do not override with the other format
|
||||
// The next four arguments do not override with the other format
|
||||
// options, see the comment in Config::from for the reason.
|
||||
// Ideally, they would use Arg::override_with, with their own name
|
||||
// but that doesn't seem to work in all cases. Example:
|
||||
// ls -1g1
|
||||
// even though `ls -11` and `ls -1 -g -1` work.
|
||||
.arg(
|
||||
Arg::with_name(options::format::ONELINE)
|
||||
.short(options::format::ONELINE)
|
||||
.help("List one file per line.")
|
||||
.multiple(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::format::LONG_NO_GROUP)
|
||||
.short(options::format::LONG_NO_GROUP)
|
||||
.help("Long format without group information. Identical to --format=long with --no-group.")
|
||||
.multiple(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::format::LONG_NO_OWNER)
|
||||
.short(options::format::LONG_NO_OWNER)
|
||||
.help("Long format without owner information.")
|
||||
.multiple(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::format::LONG_NUMERIC_UID_GID)
|
||||
.short("n")
|
||||
.long(options::format::LONG_NUMERIC_UID_GID)
|
||||
.help("-l with numeric UIDs and GIDs.")
|
||||
.multiple(true)
|
||||
)
|
||||
|
||||
// Quoting style
|
||||
.arg(
|
||||
Arg::with_name(options::QUOTING_STYLE)
|
||||
.long(options::QUOTING_STYLE)
|
||||
.takes_value(true)
|
||||
.help("Set quoting style.")
|
||||
.possible_values(&["literal", "shell", "shell-always", "shell-escape", "shell-escape-always", "c", "escape"])
|
||||
.overrides_with_all(&[
|
||||
options::QUOTING_STYLE,
|
||||
options::quoting::LITERAL,
|
||||
options::quoting::ESCAPE,
|
||||
options::quoting::C,
|
||||
])
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::quoting::LITERAL)
|
||||
.short("N")
|
||||
.long(options::quoting::LITERAL)
|
||||
.help("Use literal quoting style. Equivalent to `--quoting-style=literal`")
|
||||
.overrides_with_all(&[
|
||||
options::QUOTING_STYLE,
|
||||
options::quoting::LITERAL,
|
||||
options::quoting::ESCAPE,
|
||||
options::quoting::C,
|
||||
])
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::quoting::ESCAPE)
|
||||
.short("b")
|
||||
.long(options::quoting::ESCAPE)
|
||||
.help("Use escape quoting style. Equivalent to `--quoting-style=escape`")
|
||||
.overrides_with_all(&[
|
||||
options::QUOTING_STYLE,
|
||||
options::quoting::LITERAL,
|
||||
options::quoting::ESCAPE,
|
||||
options::quoting::C,
|
||||
])
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::quoting::C)
|
||||
.short("Q")
|
||||
.long(options::quoting::C)
|
||||
.help("Use C quoting style. Equivalent to `--quoting-style=c`")
|
||||
.overrides_with_all(&[
|
||||
options::QUOTING_STYLE,
|
||||
options::quoting::LITERAL,
|
||||
options::quoting::ESCAPE,
|
||||
options::quoting::C,
|
||||
])
|
||||
)
|
||||
|
||||
// Control characters
|
||||
.arg(
|
||||
Arg::with_name(options::HIDE_CONTROL_CHARS)
|
||||
.short("q")
|
||||
.long(options::HIDE_CONTROL_CHARS)
|
||||
.help("Replace control characters with '?' if they are not escaped.")
|
||||
.overrides_with_all(&[
|
||||
options::HIDE_CONTROL_CHARS,
|
||||
options::SHOW_CONTROL_CHARS,
|
||||
])
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::SHOW_CONTROL_CHARS)
|
||||
.long(options::SHOW_CONTROL_CHARS)
|
||||
.help("Show control characters 'as is' if they are not escaped.")
|
||||
.overrides_with_all(&[
|
||||
options::HIDE_CONTROL_CHARS,
|
||||
options::SHOW_CONTROL_CHARS,
|
||||
])
|
||||
)
|
||||
|
||||
// Time arguments
|
||||
|
@ -507,6 +730,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
])
|
||||
)
|
||||
|
||||
// Hide and ignore
|
||||
.arg(
|
||||
Arg::with_name(options::HIDE)
|
||||
.long(options::HIDE)
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::IGNORE)
|
||||
.short("I")
|
||||
.long(options::IGNORE)
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::IGNORE_BACKUPS)
|
||||
.short("B")
|
||||
.long(options::IGNORE_BACKUPS)
|
||||
.help("Ignore entries which end with ~."),
|
||||
)
|
||||
|
||||
// Sort arguments
|
||||
.arg(
|
||||
Arg::with_name(options::SORT)
|
||||
|
@ -604,12 +848,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
'.' and '..'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::IGNORE_BACKUPS)
|
||||
.short("B")
|
||||
.long(options::IGNORE_BACKUPS)
|
||||
.help("Ignore entries which end with ~."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::DIRECTORY)
|
||||
.short("d")
|
||||
|
@ -621,15 +859,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
specified.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::CLASSIFY)
|
||||
.short("F")
|
||||
.long(options::CLASSIFY)
|
||||
.help("Append a character to each file name indicating the file type. Also, for \
|
||||
regular files that are executable, append '*'. The file type indicators are \
|
||||
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
|
||||
'>' for doors, and nothing for regular files.",
|
||||
))
|
||||
.arg(
|
||||
Arg::with_name(options::size::HUMAN_READABLE)
|
||||
.short("h")
|
||||
|
@ -657,12 +886,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
file the link references rather than the link itself.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::NUMERIC_UID_GID)
|
||||
.short("n")
|
||||
.long(options::NUMERIC_UID_GID)
|
||||
.help("-l with numeric UIDs and GIDs."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::REVERSE)
|
||||
.short("r")
|
||||
|
@ -692,8 +915,57 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.require_equals(true)
|
||||
.min_values(0),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::INDICATOR_STYLE)
|
||||
.long(options::INDICATOR_STYLE)
|
||||
.help(" append indicator with style WORD to entry names: none (default), slash\
|
||||
(-p), file-type (--file-type), classify (-F)")
|
||||
.takes_value(true)
|
||||
.possible_values(&["none", "slash", "file-type", "classify"])
|
||||
.overrides_with_all(&[
|
||||
options::FILE_TYPE,
|
||||
options::SLASH,
|
||||
options::CLASSIFY,
|
||||
options::INDICATOR_STYLE,
|
||||
]))
|
||||
.arg(
|
||||
Arg::with_name(options::CLASSIFY)
|
||||
.short("F")
|
||||
.long(options::CLASSIFY)
|
||||
.help("Append a character to each file name indicating the file type. Also, for \
|
||||
regular files that are executable, append '*'. The file type indicators are \
|
||||
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
|
||||
'>' for doors, and nothing for regular files.")
|
||||
.overrides_with_all(&[
|
||||
options::FILE_TYPE,
|
||||
options::SLASH,
|
||||
options::CLASSIFY,
|
||||
options::INDICATOR_STYLE,
|
||||
])
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::FILE_TYPE)
|
||||
.long(options::FILE_TYPE)
|
||||
.help("Same as --classify, but do not append '*'")
|
||||
.overrides_with_all(&[
|
||||
options::FILE_TYPE,
|
||||
options::SLASH,
|
||||
options::CLASSIFY,
|
||||
options::INDICATOR_STYLE,
|
||||
]))
|
||||
.arg(
|
||||
Arg::with_name(options::SLASH)
|
||||
.short(options::SLASH)
|
||||
.help("Append / indicator to directories."
|
||||
)
|
||||
.overrides_with_all(&[
|
||||
options::FILE_TYPE,
|
||||
options::SLASH,
|
||||
options::CLASSIFY,
|
||||
options::INDICATOR_STYLE,
|
||||
]))
|
||||
|
||||
// Positional arguments
|
||||
// Positional arguments
|
||||
.arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true));
|
||||
|
||||
let matches = app.get_matches_from(args);
|
||||
|
@ -793,11 +1065,12 @@ fn is_hidden(file_path: &DirEntry) -> bool {
|
|||
|
||||
fn should_display(entry: &DirEntry, config: &Config) -> bool {
|
||||
let ffi_name = entry.file_name();
|
||||
let name = ffi_name.to_string_lossy();
|
||||
|
||||
if config.files == Files::Normal && is_hidden(entry) {
|
||||
return false;
|
||||
}
|
||||
if config.ignore_backups && name.ends_with('~') {
|
||||
|
||||
if config.ignore_patterns.is_match(&ffi_name) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
|
@ -852,7 +1125,7 @@ fn pad_left(string: String, count: usize) -> String {
|
|||
}
|
||||
|
||||
fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) {
|
||||
if config.format == Format::Long || config.numeric_uid_gid {
|
||||
if config.format == Format::Long {
|
||||
let (mut max_links, mut max_size) = (1, 1);
|
||||
for item in items {
|
||||
let (links, size) = display_dir_entry_size(item, config);
|
||||
|
@ -994,7 +1267,7 @@ use uucore::entries;
|
|||
|
||||
#[cfg(unix)]
|
||||
fn display_uname(metadata: &Metadata, config: &Config) -> String {
|
||||
if config.numeric_uid_gid {
|
||||
if config.long.numeric_uid_gid {
|
||||
metadata.uid().to_string()
|
||||
} else {
|
||||
entries::uid2usr(metadata.uid()).unwrap_or_else(|_| metadata.uid().to_string())
|
||||
|
@ -1003,7 +1276,7 @@ fn display_uname(metadata: &Metadata, config: &Config) -> String {
|
|||
|
||||
#[cfg(unix)]
|
||||
fn display_group(metadata: &Metadata, config: &Config) -> String {
|
||||
if config.numeric_uid_gid {
|
||||
if config.long.numeric_uid_gid {
|
||||
metadata.gid().to_string()
|
||||
} else {
|
||||
entries::gid2grp(metadata.gid()).unwrap_or_else(|_| metadata.gid().to_string())
|
||||
|
@ -1119,16 +1392,25 @@ fn display_file_name(
|
|||
metadata: &Metadata,
|
||||
config: &Config,
|
||||
) -> Cell {
|
||||
let mut name = get_file_name(path, strip);
|
||||
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
|
||||
let file_type = metadata.file_type();
|
||||
|
||||
if config.classify {
|
||||
let file_type = metadata.file_type();
|
||||
if file_type.is_dir() {
|
||||
name.push('/');
|
||||
} else if file_type.is_symlink() {
|
||||
name.push('@');
|
||||
match config.indicator_style {
|
||||
IndicatorStyle::Classify | IndicatorStyle::FileType => {
|
||||
if file_type.is_dir() {
|
||||
name.push('/');
|
||||
}
|
||||
if file_type.is_symlink() {
|
||||
name.push('@');
|
||||
}
|
||||
}
|
||||
}
|
||||
IndicatorStyle::Slash => {
|
||||
if file_type.is_dir() {
|
||||
name.push('/');
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if config.format == Format::Long && metadata.file_type().is_symlink() {
|
||||
if let Ok(target) = path.read_link() {
|
||||
|
@ -1177,15 +1459,14 @@ fn display_file_name(
|
|||
metadata: &Metadata,
|
||||
config: &Config,
|
||||
) -> Cell {
|
||||
let mut name = get_file_name(path, strip);
|
||||
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
|
||||
if config.format != Format::Long && config.inode {
|
||||
name = get_inode(metadata) + " " + &name;
|
||||
}
|
||||
let mut width = UnicodeWidthStr::width(&*name);
|
||||
|
||||
let ext;
|
||||
|
||||
if config.color || config.classify {
|
||||
if config.color || config.indicator_style != IndicatorStyle::None {
|
||||
let file_type = metadata.file_type();
|
||||
|
||||
let (code, sym) = if file_type.is_dir() {
|
||||
|
@ -1238,11 +1519,29 @@ fn display_file_name(
|
|||
if config.color {
|
||||
name = color_name(name, code);
|
||||
}
|
||||
if config.classify {
|
||||
if let Some(s) = sym {
|
||||
name.push(s);
|
||||
width += 1;
|
||||
|
||||
let char_opt = match config.indicator_style {
|
||||
IndicatorStyle::Classify => sym,
|
||||
IndicatorStyle::FileType => {
|
||||
// Don't append an asterisk.
|
||||
match sym {
|
||||
Some('*') => None,
|
||||
_ => sym,
|
||||
}
|
||||
}
|
||||
IndicatorStyle::Slash => {
|
||||
// Append only a slash.
|
||||
match sym {
|
||||
Some('/') => Some('/'),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
IndicatorStyle::None => None,
|
||||
};
|
||||
|
||||
if let Some(c) = char_opt {
|
||||
name.push(c);
|
||||
width += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
630
src/uu/ls/src/quoting_style.rs
Normal file
630
src/uu/ls/src/quoting_style.rs
Normal 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"),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -71,7 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(OPT_SUFFIX)
|
||||
.help(
|
||||
"append SUFF to TEMPLATE; SUFF must not contain a path separator. \
|
||||
This option is implied if TEMPLATE does not end with X.",
|
||||
This option is implied if TEMPLATE does not end with X.",
|
||||
)
|
||||
.value_name("SUFF"),
|
||||
)
|
||||
|
@ -81,15 +81,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(OPT_TMPDIR)
|
||||
.help(
|
||||
"interpret TEMPLATE relative to DIR; if DIR is not specified, use \
|
||||
$TMPDIR if set, else /tmp. With this option, TEMPLATE must not \
|
||||
be an absolute name; unlike with -t, TEMPLATE may contain \
|
||||
slashes, but mktemp creates only the final component",
|
||||
$TMPDIR if set, else /tmp. With this option, TEMPLATE must not \
|
||||
be an absolute name; unlike with -t, TEMPLATE may contain \
|
||||
slashes, but mktemp creates only the final component",
|
||||
)
|
||||
.value_name("DIR"),
|
||||
)
|
||||
.arg(Arg::with_name(OPT_T).short(OPT_T).help(
|
||||
"Generate a template (using the supplied prefix and TMPDIR if set) \
|
||||
to create a filename template [deprecated]",
|
||||
to create a filename template [deprecated]",
|
||||
))
|
||||
.arg(
|
||||
Arg::with_name(ARG_TEMPLATE)
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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, _: ®ex::Regex) -> bool {
|
|||
fn pass_all(_: &str, _: ®ex::Regex) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn print_usage(opts: &getopts::Options) {
|
||||
println!("{}", opts.usage(USAGE));
|
||||
}
|
||||
|
||||
fn version() {
|
||||
println!("{} {}", NAME, VERSION);
|
||||
}
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -187,9 +187,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(options::PADDING)
|
||||
.help(
|
||||
"pad the output to N characters; positive N will \
|
||||
right-align; negative N will left-align; padding is \
|
||||
ignored if the output is wider than N; the default is \
|
||||
to automatically pad if a whitespace is found",
|
||||
right-align; negative N will left-align; padding is \
|
||||
ignored if the output is wider than N; the default is \
|
||||
to automatically pad if a whitespace is found",
|
||||
)
|
||||
.value_name("N"),
|
||||
)
|
||||
|
@ -198,7 +198,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(options::HEADER)
|
||||
.help(
|
||||
"print (without converting) the first N header lines; \
|
||||
N defaults to 1 if not specified",
|
||||
N defaults to 1 if not specified",
|
||||
)
|
||||
.value_name("N")
|
||||
.default_value(options::HEADER_DEFAULT)
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -166,39 +166,30 @@ mod tests {
|
|||
let mut input = PeekReader::new(Cursor::new(&data));
|
||||
let mut sut = InputDecoder::new(&mut input, 8, 2, ByteOrder::Little);
|
||||
|
||||
match sut.peek_read() {
|
||||
Ok(mut mem) => {
|
||||
assert_eq!(8, mem.length());
|
||||
// Peek normal length
|
||||
let mut mem = sut.peek_read().unwrap();
|
||||
|
||||
assert_eq!(-2.0, mem.read_float(0, 8));
|
||||
assert_eq!(-2.0, mem.read_float(4, 4));
|
||||
assert_eq!(0xc000000000000000, mem.read_uint(0, 8));
|
||||
assert_eq!(0xc0000000, mem.read_uint(4, 4));
|
||||
assert_eq!(0xc000, mem.read_uint(6, 2));
|
||||
assert_eq!(0xc0, mem.read_uint(7, 1));
|
||||
assert_eq!(&[0, 0xc0], mem.get_buffer(6));
|
||||
assert_eq!(&[0, 0xc0, 0xff, 0xff], mem.get_full_buffer(6));
|
||||
assert_eq!(8, mem.length());
|
||||
|
||||
let mut copy: Vec<u8> = Vec::new();
|
||||
mem.clone_buffer(&mut copy);
|
||||
assert_eq!(vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0], copy);
|
||||
assert_eq!(-2.0, mem.read_float(0, 8));
|
||||
assert_eq!(-2.0, mem.read_float(4, 4));
|
||||
assert_eq!(0xc000000000000000, mem.read_uint(0, 8));
|
||||
assert_eq!(0xc0000000, mem.read_uint(4, 4));
|
||||
assert_eq!(0xc000, mem.read_uint(6, 2));
|
||||
assert_eq!(0xc0, mem.read_uint(7, 1));
|
||||
assert_eq!(&[0, 0xc0], mem.get_buffer(6));
|
||||
assert_eq!(&[0, 0xc0, 0xff, 0xff], mem.get_full_buffer(6));
|
||||
|
||||
mem.zero_out_buffer(7, 8);
|
||||
assert_eq!(&[0, 0, 0xff, 0xff], mem.get_full_buffer(6));
|
||||
}
|
||||
Err(e) => {
|
||||
assert!(false, e);
|
||||
}
|
||||
}
|
||||
let mut copy: Vec<u8> = Vec::new();
|
||||
mem.clone_buffer(&mut copy);
|
||||
assert_eq!(vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0], copy);
|
||||
|
||||
match sut.peek_read() {
|
||||
Ok(mem) => {
|
||||
assert_eq!(2, mem.length());
|
||||
assert_eq!(0xffff, mem.read_uint(0, 2));
|
||||
}
|
||||
Err(e) => {
|
||||
assert!(false, e);
|
||||
}
|
||||
}
|
||||
mem.zero_out_buffer(7, 8);
|
||||
assert_eq!(&[0, 0, 0xff, 0xff], mem.get_full_buffer(6));
|
||||
|
||||
// Peek tail
|
||||
let mem = sut.peek_read().unwrap();
|
||||
assert_eq!(2, mem.length());
|
||||
assert_eq!(0xffff, mem.read_uint(0, 2));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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]]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue