1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge branch 'main' into tail_notify

This commit is contained in:
Jan Scheer 2022-05-16 22:38:23 +02:00
commit 409878e323
No known key found for this signature in database
GPG key ID: C62AD4C29E2B9828
150 changed files with 2505 additions and 863 deletions

View file

@ -9,3 +9,12 @@ rustflags = [
"-Wclippy::single_char_pattern", "-Wclippy::single_char_pattern",
"-Wclippy::explicit_iter_loop", "-Wclippy::explicit_iter_loop",
] ]
[build]
# See https://github.com/time-rs/time/issues/293#issuecomment-1005002386. The
# unsoundness here is not in the `time` library, but in the Rust stdlib, and as
# such it needs to be fixed there.
rustflags = "--cfg unsound_local_offset"
[target.'cfg(target_os = "linux")']
rustflags = ["--cfg", "unsound_local_offset"]

View file

@ -5,3 +5,8 @@ updates:
schedule: schedule:
interval: "daily" interval: "daily"
open-pull-requests-limit: 5 open-pull-requests-limit: 5
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 5

View file

@ -24,7 +24,7 @@ jobs:
name: Style/cargo-deny name: Style/cargo-deny
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1 - uses: EmbarkStudios/cargo-deny-action@v1
style_deps: style_deps:
@ -43,7 +43,7 @@ jobs:
- { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" } - { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" }
- { os: windows-latest , features: feat_os_windows } - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
@ -101,7 +101,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
@ -165,7 +165,7 @@ jobs:
- { os: macos-latest , features: feat_os_macos } - { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows } - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
@ -223,7 +223,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
@ -275,7 +275,7 @@ jobs:
# - { os: macos-latest , features: feat_os_macos } # - { os: macos-latest , features: feat_os_macos }
# - { os: windows-latest , features: feat_os_windows } # - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
@ -320,7 +320,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
@ -384,9 +384,9 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils args: -v ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils
env: env:
RUSTFLAGS: "-Awarnings" RUSTFLAGS: "-Awarnings --cfg unsound_local_offset"
deps: deps:
name: Dependencies name: Dependencies
@ -397,7 +397,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@ -422,7 +422,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@ -452,7 +452,7 @@ jobs:
- { os: macos-latest , features: feat_os_macos } - { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows } - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@ -478,7 +478,7 @@ jobs:
- { os: macos-latest , features: feat_os_macos } - { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows } - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@ -502,7 +502,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Install dependencies - name: Install dependencies
shell: bash shell: bash
@ -534,7 +534,7 @@ jobs:
--arg size "$SIZE" \ --arg size "$SIZE" \
--arg multisize "$SIZEMULTI" \ --arg multisize "$SIZEMULTI" \
'{($date): { sha: $sha, size: $size, multisize: $multisize, }}' > size-result.json '{($date): { sha: $sha, size: $size, multisize: $multisize, }}' > size-result.json
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v3
with: with:
name: size-result name: size-result
path: size-result.json path: size-result.json
@ -568,7 +568,7 @@ jobs:
- { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly
- { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
@ -762,7 +762,7 @@ jobs:
args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }}
toolchain: ${{ env.RUST_MIN_SRV }} toolchain: ${{ env.RUST_MIN_SRV }}
- name: Archive executable artifacts - name: Archive executable artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }} name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }} path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}
@ -820,7 +820,7 @@ jobs:
job: job:
- { os: ubuntu-latest } - { os: ubuntu-latest }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Install/setup prerequisites - name: Install/setup prerequisites
shell: bash shell: bash
@ -857,9 +857,9 @@ jobs:
env: env:
TERMUX: v0.118.0 TERMUX: v0.118.0
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: AVD cache - name: AVD cache
uses: actions/cache@v2 uses: actions/cache@v3
id: avd-cache id: avd-cache
with: with:
path: | path: |
@ -911,11 +911,11 @@ jobs:
env: env:
mem: 2048 mem: 2048
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Prepare, build and test - name: Prepare, build and test
## spell-checker:ignore (ToDO) sshfs usesh vmactions ## spell-checker:ignore (ToDO) sshfs usesh vmactions
uses: vmactions/freebsd-vm@v0.1.5 uses: vmactions/freebsd-vm@v0.1.6
with: with:
usesh: true usesh: true
# sync: sshfs # sync: sshfs
@ -979,7 +979,7 @@ jobs:
- { os: macos-latest , features: macos } - { os: macos-latest , features: macos }
- { os: windows-latest , features: windows } - { os: windows-latest , features: windows }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
# - name: Reattach HEAD ## may be needed for accurate code coverage info # - name: Reattach HEAD ## may be needed for accurate code coverage info
# run: git checkout ${{ github.head_ref }} # run: git checkout ${{ github.head_ref }}
@ -1100,7 +1100,7 @@ jobs:
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
echo ::set-output name=report::${COVERAGE_REPORT_FILE} echo ::set-output name=report::${COVERAGE_REPORT_FILE}
- name: Upload coverage results (to Codecov.io) - name: Upload coverage results (to Codecov.io)
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v3
# if: steps.vars.outputs.HAS_CODECOV_TOKEN # if: steps.vars.outputs.HAS_CODECOV_TOKEN
with: with:
# token: ${{ secrets.CODECOV_TOKEN }} # token: ${{ secrets.CODECOV_TOKEN }}

View file

@ -28,7 +28,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Initialize job variables - name: Initialize job variables
id: vars id: vars
@ -80,7 +80,7 @@ jobs:
## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors
RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}') - name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
uses: EndBug/add-and-commit@v7 uses: EndBug/add-and-commit@v9
with: with:
branch: ${{ env.BRANCH_TARGET }} branch: ${{ env.BRANCH_TARGET }}
default_author: github_actions default_author: github_actions
@ -100,7 +100,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Initialize job variables - name: Initialize job variables
id: vars id: vars
@ -130,7 +130,7 @@ jobs:
# `cargo fmt` of tests # `cargo fmt` of tests
find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- find tests -name "*.rs" -print0 | xargs -0 cargo fmt --
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}') - name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
uses: EndBug/add-and-commit@v7 uses: EndBug/add-and-commit@v9
with: with:
branch: ${{ env.BRANCH_TARGET }} branch: ${{ env.BRANCH_TARGET }}
default_author: github_actions default_author: github_actions

View file

@ -8,6 +8,10 @@ on: [push, pull_request]
jobs: jobs:
gnu: gnu:
permissions:
actions: read # for dawidd6/action-download-artifact to query and download artifacts
contents: read # for actions/checkout to fetch code
pull-requests: read # for dawidd6/action-download-artifact to query commit hash
name: Run GNU tests name: Run GNU tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -37,11 +41,11 @@ jobs:
TEST_FULL_SUMMARY_FILE='gnu-full-result.json' TEST_FULL_SUMMARY_FILE='gnu-full-result.json'
outputs SUITE_LOG_FILE TEST_FILESET_PREFIX TEST_FILESET_SUFFIX TEST_LOGS_GLOB TEST_SUMMARY_FILE TEST_FULL_SUMMARY_FILE outputs SUITE_LOG_FILE TEST_FILESET_PREFIX TEST_FILESET_SUFFIX TEST_LOGS_GLOB TEST_SUMMARY_FILE TEST_FULL_SUMMARY_FILE
- name: Checkout code (uutil) - name: Checkout code (uutil)
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
path: '${{ steps.vars.outputs.path_UUTILS }}' path: '${{ steps.vars.outputs.path_UUTILS }}'
- name: Checkout code (GNU coreutils) - name: Checkout code (GNU coreutils)
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
repository: 'coreutils/coreutils' repository: 'coreutils/coreutils'
path: '${{ steps.vars.outputs.path_GNU }}' path: '${{ steps.vars.outputs.path_GNU }}'
@ -142,22 +146,22 @@ jobs:
# Compress logs before upload (fails otherwise) # Compress logs before upload (fails otherwise)
gzip ${{ steps.vars.outputs.TEST_LOGS_GLOB }} gzip ${{ steps.vars.outputs.TEST_LOGS_GLOB }}
- name: Reserve SHA1/ID of 'test-summary' - name: Reserve SHA1/ID of 'test-summary'
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: "${{ steps.summary.outputs.HASH }}" name: "${{ steps.summary.outputs.HASH }}"
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
- name: Reserve test results summary - name: Reserve test results summary
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: test-summary name: test-summary
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
- name: Reserve test logs - name: Reserve test logs
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: test-logs name: test-logs
path: "${{ steps.vars.outputs.TEST_LOGS_GLOB }}" path: "${{ steps.vars.outputs.TEST_LOGS_GLOB }}"
- name: Upload full json results - name: Upload full json results
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: gnu-full-result.json name: gnu-full-result.json
path: ${{ steps.vars.outputs.TEST_FULL_SUMMARY_FILE }} path: ${{ steps.vars.outputs.TEST_FULL_SUMMARY_FILE }}
@ -170,6 +174,8 @@ jobs:
REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}' REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}'
if test -f "${REF_LOG_FILE}"; then if test -f "${REF_LOG_FILE}"; then
echo "Reference SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")" echo "Reference SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")"
REF_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort)
NEW_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | sort)
REF_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort) REF_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort)
NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | sort) NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | sort)
for LINE in ${REF_FAILING} for LINE in ${REF_FAILING}
@ -186,6 +192,21 @@ jobs:
have_new_failures="true" have_new_failures="true"
fi fi
done done
for LINE in ${REF_ERROR}
do
if ! grep -Fxq ${LINE}<<<"${NEW_ERROR}"; then
echo "::warning ::Congrats! The gnu test ${LINE} is no longer ERROR!"
fi
done
for LINE in ${NEW_ERROR}
do
if ! grep -Fxq ${LINE}<<<"${REF_ERROR}"
then
echo "::error ::GNU test error: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
have_new_failures="true"
fi
done
else else
echo "::warning ::Skipping test failure comparison; no prior reference test logs are available." echo "::warning ::Skipping test failure comparison; no prior reference test logs are available."
fi fi
@ -208,11 +229,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code uutil - name: Checkout code uutil
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
path: 'uutils' path: 'uutils'
- name: Checkout GNU coreutils - name: Checkout GNU coreutils
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
repository: 'coreutils/coreutils' repository: 'coreutils/coreutils'
path: 'gnu' path: 'gnu'
@ -272,7 +293,7 @@ jobs:
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
echo ::set-output name=report::${COVERAGE_REPORT_FILE} echo ::set-output name=report::${COVERAGE_REPORT_FILE}
- name: Upload coverage results (to Codecov.io) - name: Upload coverage results (to Codecov.io)
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v3
with: with:
file: ${{ steps.coverage.outputs.report }} file: ${{ steps.coverage.outputs.report }}
flags: gnutests flags: gnutests

380
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -254,6 +254,7 @@ feat_os_windows_legacy = [
## ##
# * bypass/override ~ translate 'test' feature name to avoid dependency collision with rust core 'test' crate (o/w surfaces as compiler errors during testing) # * bypass/override ~ translate 'test' feature name to avoid dependency collision with rust core 'test' crate (o/w surfaces as compiler errors during testing)
test = [ "uu_test" ] test = [ "uu_test" ]
uudoc = [ "zip" ]
[workspace] [workspace]
@ -265,7 +266,7 @@ lazy_static = { version="1.3" }
textwrap = { version="0.15", features=["terminal_size"] } textwrap = { version="0.15", features=["terminal_size"] }
uucore = { version=">=0.0.11", package="uucore", path="src/uucore" } uucore = { version=">=0.0.11", package="uucore", path="src/uucore" }
selinux = { version="0.2", optional = true } selinux = { version="0.2", optional = true }
zip = { version = "0.6.0", default_features=false, features=["deflate"] } zip = { version = "0.6.0", optional=true, default_features=false, features=["deflate"] }
# * uutils # * uutils
uu_test = { optional=true, version="0.0.13", package="uu_test", path="src/uu/test" } uu_test = { optional=true, version="0.0.13", package="uu_test", path="src/uu/test" }
# #
@ -390,7 +391,7 @@ rand = "0.8"
regex = "1.5" regex = "1.5"
sha1 = { version="0.10", features=["std"] } sha1 = { version="0.10", features=["std"] }
tempfile = "3" tempfile = "3"
time = "0.1" time = {version="0.3", features=["local-offset"]}
unindent = "0.1" unindent = "0.1"
uucore = { version=">=0.0.11", package="uucore", path="src/uucore", features=["entries", "process"] } uucore = { version=">=0.0.11", package="uucore", path="src/uucore", features=["entries", "process"] }
walkdir = "2.2" walkdir = "2.2"
@ -401,7 +402,7 @@ hex-literal = "0.3.1"
rlimit = "0.8.3" rlimit = "0.8.3"
[target.'cfg(unix)'.dev-dependencies] [target.'cfg(unix)'.dev-dependencies]
nix = "0.23.1" nix = { version = "0.24.1", default-features = false, features = ["process", "signal", "user"] }
rust-users = { version="0.10", package="users" } rust-users = { version="0.10", package="users" }
unix_socket = "0.5.0" unix_socket = "0.5.0"
@ -415,3 +416,4 @@ path = "src/bin/coreutils.rs"
[[bin]] [[bin]]
name = "uudoc" name = "uudoc"
path = "src/bin/uudoc.rs" path = "src/bin/uudoc.rs"
required-features = ["uudoc"]

View file

@ -28,8 +28,9 @@ pub fn main() {
if val == "1" && key.starts_with(ENV_FEATURE_PREFIX) { if val == "1" && key.starts_with(ENV_FEATURE_PREFIX) {
let krate = key[ENV_FEATURE_PREFIX.len()..].to_lowercase(); let krate = key[ENV_FEATURE_PREFIX.len()..].to_lowercase();
match krate.as_ref() { match krate.as_ref() {
"default" | "macos" | "unix" | "windows" | "selinux" => continue, // common/standard feature names "default" | "macos" | "unix" | "windows" | "selinux" | "zip" => continue, // common/standard feature names
"nightly" | "test_unimplemented" => continue, // crate-local custom features "nightly" | "test_unimplemented" => continue, // crate-local custom features
"uudoc" => continue, // is not a utility
"test" => continue, // over-ridden with 'uu_test' to avoid collision with rust core crate 'test' "test" => continue, // over-ridden with 'uu_test' to avoid collision with rust core crate 'test'
s if s.starts_with(FEATURE_PREFIX) => continue, // crate feature sets s if s.starts_with(FEATURE_PREFIX) => continue, // crate feature sets
_ => {} // util feature name _ => {} // util feature name

View file

@ -11,6 +11,8 @@ unmaintained = "warn"
yanked = "warn" yanked = "warn"
notice = "warn" notice = "warn"
ignore = [ ignore = [
"RUSTSEC-2020-0159",
"RUSTSEC-2020-0071",
#"RUSTSEC-0000-0000", #"RUSTSEC-0000-0000",
] ]
@ -62,7 +64,7 @@ highlight = "all"
# spell-checker: disable # spell-checker: disable
skip = [ skip = [
# getrandom # getrandom
{ name = "wasi", version="0.10.2+wasi-snapshot-preview1" }, { name = "wasi", version="0.10.0+wasi-snapshot-preview1" },
# blake2d_simd # blake2d_simd
{ name = "arrayvec", version = "=0.7.2" }, { name = "arrayvec", version = "=0.7.2" },
# flimit/unix_socket # flimit/unix_socket
@ -84,8 +86,8 @@ skip = [
{ name = "memchr", version = "=1.0.2" }, { name = "memchr", version = "=1.0.2" },
{ name = "quote", version = "=0.3.15" }, { name = "quote", version = "=0.3.15" },
{ name = "unicode-xid", version = "=0.0.4" }, { name = "unicode-xid", version = "=0.0.4" },
# exacl # chrono
{ name = "nix", version = "=0.21.0" }, { name = "time", version = "=0.1.44" },
] ]
# spell-checker: enable # spell-checker: enable

View file

@ -5,7 +5,7 @@ utilities in [Rust](https://www.rust-lang.org). It is available for
Linux, Windows, Mac and other platforms. Linux, Windows, Mac and other platforms.
The API reference for `uucore`, the library of functions shared between The API reference for `uucore`, the library of functions shared between
various utils, is hosted at at various utils, is hosted at
[docs.rs](https://docs.rs/uucore/latest/uucore/). [docs.rs](https://docs.rs/uucore/latest/uucore/).
uutils is licensed under the [MIT License](https://github.com/uutils/coreutils/blob/main/LICENSE). uutils is licensed under the [MIT License](https://github.com/uutils/coreutils/blob/main/LICENSE).

View file

@ -90,7 +90,7 @@ pub fn parse_base_cmd_args(args: impl uucore::Args, about: &str, usage: &str) ->
let arg_list = args let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
Config::from(&command.get_matches_from(arg_list)) Config::from(&command.try_get_matches_from(arg_list)?)
} }
pub fn base_app<'a>(about: &'a str, usage: &'a str) -> Command<'a> { pub fn base_app<'a>(about: &'a str, usage: &'a str) -> Command<'a> {
@ -123,7 +123,12 @@ pub fn base_app<'a>(about: &'a str, usage: &'a str) -> Command<'a> {
) )
// "multiple" arguments are used to check whether there is more than one // "multiple" arguments are used to check whether there is more than one
// file passed in. // file passed in.
.arg(Arg::new(options::FILE).index(1).multiple_occurrences(true)) .arg(
Arg::new(options::FILE)
.index(1)
.multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
)
} }
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> UResult<Box<dyn Read + 'a>> { pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> UResult<Box<dyn Read + 'a>> {

View file

@ -102,6 +102,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::NAME) Arg::new(options::NAME)
.multiple_occurrences(true) .multiple_occurrences(true)
.value_hint(clap::ValueHint::AnyPath)
.hide(true), .hide(true),
) )
.arg( .arg(

View file

@ -22,7 +22,7 @@ uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=[
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
unix_socket = "0.5.0" unix_socket = "0.5.0"
nix = "0.23.1" nix = { version = "0.24.1", default-features = false }
[[bin]] [[bin]]
name = "cat" name = "cat"

View file

@ -188,7 +188,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let matches = uu_app().get_matches_from(args); let matches = uu_app().try_get_matches_from(args)?;
let number_mode = if matches.is_present(options::NUMBER_NONBLANK) { let number_mode = if matches.is_present(options::NUMBER_NONBLANK) {
NumberingMode::NonEmpty NumberingMode::NonEmpty
@ -249,7 +249,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::SHOW_ALL) Arg::new(options::SHOW_ALL)

View file

@ -197,6 +197,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::REFERENCE) .long(options::REFERENCE)
.takes_value(true) .takes_value(true)
.value_name("RFILE") .value_name("RFILE")
.value_hint(clap::ValueHint::FilePath)
.conflicts_with_all(&[options::USER, options::ROLE, options::TYPE, options::RANGE]) .conflicts_with_all(&[options::USER, options::ROLE, options::TYPE, options::RANGE])
.help( .help(
"Use security context of RFILE, rather than specifying \ "Use security context of RFILE, rather than specifying \
@ -210,6 +211,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::USER) .long(options::USER)
.takes_value(true) .takes_value(true)
.value_name("USER") .value_name("USER")
.value_hint(clap::ValueHint::Username)
.help("Set user USER in the target security context.") .help("Set user USER in the target security context.")
.allow_invalid_utf8(true), .allow_invalid_utf8(true),
) )
@ -294,6 +296,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new("FILE") Arg::new("FILE")
.multiple_occurrences(true) .multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath)
.min_values(1) .min_values(1)
.allow_invalid_utf8(true), .allow_invalid_utf8(true),
) )

View file

@ -113,6 +113,7 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::REFERENCE) Arg::new(options::REFERENCE)
.long(options::REFERENCE) .long(options::REFERENCE)
.value_name("RFILE") .value_name("RFILE")
.value_hint(clap::ValueHint::FilePath)
.help("use RFILE's group rather than specifying GROUP values") .help("use RFILE's group rather than specifying GROUP values")
.takes_value(true) .takes_value(true)
.multiple_occurrences(false), .multiple_occurrences(false),

View file

@ -16,7 +16,7 @@ path = "src/chmod.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.121" libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode"] }
[[bin]] [[bin]]

View file

@ -157,6 +157,7 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::REFERENCE) Arg::new(options::REFERENCE)
.long("reference") .long("reference")
.takes_value(true) .takes_value(true)
.value_hint(clap::ValueHint::FilePath)
.help("use RFILE's mode instead of MODE values"), .help("use RFILE's mode instead of MODE values"),
) )
.arg( .arg(
@ -170,7 +171,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.required_unless_present(options::MODE) .required_unless_present(options::MODE)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::AnyPath),
) )
} }

View file

@ -134,6 +134,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::REFERENCE) .long(options::REFERENCE)
.help("use RFILE's owner and group rather than specifying OWNER:GROUP values") .help("use RFILE's owner and group rather than specifying OWNER:GROUP values")
.value_name("RFILE") .value_name("RFILE")
.value_hint(clap::ValueHint::FilePath)
.min_values(1), .min_values(1),
) )
.arg( .arg(
@ -167,17 +168,18 @@ pub fn uu_app<'a>() -> Command<'a> {
) )
} }
/// Parse the username and groupname /// Parse the owner/group specifier string into a user ID and a group ID.
/// ///
/// In theory, it should be username:groupname /// The `spec` can be of the form:
/// but ...
/// it can user.name:groupname
/// or username.groupname
/// ///
/// # Arguments /// * `"owner:group"`,
/// * `"owner"`,
/// * `":group"`,
/// ///
/// * `spec` - The input from the user /// and the owner or group can be specified either as an ID or a
/// * `sep` - Should be ':' or '.' /// name. The `sep` argument specifies which character to use as a
/// separator between the owner and group; calling code should set
/// this to `':'`.
fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> { fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
assert!(['.', ':'].contains(&sep)); assert!(['.', ':'].contains(&sep));
let mut args = spec.splitn(2, sep); let mut args = spec.splitn(2, sep);
@ -197,10 +199,17 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
// So, try to parse it this way // So, try to parse it this way
return parse_spec(spec, '.'); return parse_spec(spec, '.');
} else { } else {
// It's possible that the `user` string contains a
// numeric user ID, in which case, we respect that.
match user.parse() {
Ok(uid) => uid,
Err(_) => {
return Err(USimpleError::new( return Err(USimpleError::new(
1, 1,
format!("invalid user: {}", spec.quote()), format!("invalid user: {}", spec.quote()),
)); ))
}
}
} }
} }
}) })
@ -208,11 +217,18 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
None None
}; };
let gid = if !group.is_empty() { let gid = if !group.is_empty() {
Some( Some(match Group::locate(group) {
Group::locate(group) Ok(g) => g.gid,
.map_err(|_| USimpleError::new(1, format!("invalid group: {}", spec.quote())))? Err(_) => match group.parse() {
.gid, Ok(gid) => gid,
) Err(_) => {
return Err(USimpleError::new(
1,
format!("invalid group: {}", spec.quote()),
));
}
},
})
} else { } else {
None None
}; };
@ -231,4 +247,17 @@ mod test {
assert!(format!("{}", parse_spec("::", ':').err().unwrap()).starts_with("invalid group: ")); assert!(format!("{}", parse_spec("::", ':').err().unwrap()).starts_with("invalid group: "));
assert!(format!("{}", parse_spec("..", ':').err().unwrap()).starts_with("invalid group: ")); assert!(format!("{}", parse_spec("..", ':').err().unwrap()).starts_with("invalid group: "));
} }
/// Test for parsing IDs that don't correspond to a named user or group.
#[test]
fn test_parse_spec_nameless_ids() {
// This assumes that there is no named user with ID 12345.
assert!(matches!(parse_spec("12345", ':'), Ok((Some(12345), None))));
// This assumes that there is no named group with ID 54321.
assert!(matches!(parse_spec(":54321", ':'), Ok((None, Some(54321)))));
assert!(matches!(
parse_spec("12345:54321", ':'),
Ok((Some(12345), Some(54321)))
));
}
} }

View file

@ -102,6 +102,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.infer_long_args(true) .infer_long_args(true)
.arg( .arg(
Arg::new(options::NEWROOT) Arg::new(options::NEWROOT)
.value_hint(clap::ValueHint::DirPath)
.hide(true) .hide(true)
.required(true) .required(true)
.index(1), .index(1),
@ -139,6 +140,7 @@ pub fn uu_app<'a>() -> Command<'a> {
) )
.arg( .arg(
Arg::new(options::COMMAND) Arg::new(options::COMMAND)
.value_hint(clap::ValueHint::CommandName)
.hide(true) .hide(true)
.multiple_occurrences(true) .multiple_occurrences(true)
.index(2), .index(2),

View file

@ -150,6 +150,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -173,6 +173,14 @@ pub fn uu_app<'a>() -> Command<'a> {
.default_value(options::DELIMITER_DEFAULT) .default_value(options::DELIMITER_DEFAULT)
.hide_default_value(true), .hide_default_value(true),
) )
.arg(Arg::new(options::FILE_1).required(true)) .arg(
.arg(Arg::new(options::FILE_2).required(true)) Arg::new(options::FILE_1)
.required(true)
.value_hint(clap::ValueHint::FilePath),
)
.arg(
Arg::new(options::FILE_2)
.required(true)
.value_hint(clap::ValueHint::FilePath),
)
} }

View file

@ -21,7 +21,7 @@ path = "src/cp.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
filetime = "0.2" filetime = "0.2"
libc = "0.2.121" libc = "0.2.125"
quick-error = "2.0.1" quick-error = "2.0.1"
selinux = { version="0.2", optional=true } selinux = { version="0.2", optional=true }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] }
@ -34,7 +34,7 @@ ioctl-sys = "0.8"
winapi = { version="0.3", features=["fileapi"] } winapi = { version="0.3", features=["fileapi"] }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
xattr="0.2.1" xattr="0.2.3"
exacl= { version = "0.8.0", optional=true } exacl= { version = "0.8.0", optional=true }
[[bin]] [[bin]]

View file

@ -57,7 +57,7 @@ use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr; use std::str::FromStr;
use std::string::ToString; use std::string::ToString;
use uucore::backup_control::{self, BackupMode}; use uucore::backup_control::{self, BackupMode};
use uucore::error::{set_exit_code, ExitCode, UError, UResult}; use uucore::error::{set_exit_code, ExitCode, UClapError, UError, UResult};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use walkdir::WalkDir; use walkdir::WalkDir;
@ -314,6 +314,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.conflicts_with(options::NO_TARGET_DIRECTORY) .conflicts_with(options::NO_TARGET_DIRECTORY)
.long(options::TARGET_DIRECTORY) .long(options::TARGET_DIRECTORY)
.value_name(options::TARGET_DIRECTORY) .value_name(options::TARGET_DIRECTORY)
.value_hint(clap::ValueHint::DirPath)
.takes_value(true) .takes_value(true)
.validator(|s| { .validator(|s| {
if Path::new(s).is_dir() { if Path::new(s).is_dir() {
@ -464,19 +465,31 @@ pub fn uu_app<'a>() -> Command<'a> {
// END TODO // END TODO
.arg(Arg::new(options::PATHS) .arg(Arg::new(options::PATHS)
.multiple_occurrences(true)) .multiple_occurrences(true)
.value_hint(clap::ValueHint::AnyPath))
} }
#[uucore::main] #[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app() let after_help = &*format!(
.after_help(&*format!(
"{}\n{}", "{}\n{}",
LONG_HELP, LONG_HELP,
backup_control::BACKUP_CONTROL_LONG_HELP backup_control::BACKUP_CONTROL_LONG_HELP
)) );
.try_get_matches_from(args)?; let matches = uu_app().after_help(after_help).try_get_matches_from(args);
// The error is parsed here because we do not want version or help being printed to stderr.
if let Err(e) = matches {
let mut app = uu_app().after_help(after_help);
match e.kind() {
clap::ErrorKind::DisplayHelp => {
app.print_help()?;
}
clap::ErrorKind::DisplayVersion => println!("{}", app.render_version()),
_ => return Err(Box::new(e.with_exit_code(1))),
};
} else if let Ok(matches) = matches {
let options = Options::from_matches(&matches)?; let options = Options::from_matches(&matches)?;
if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup { if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup {
@ -501,6 +514,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}; };
set_exit_code(EXIT_ERR); set_exit_code(EXIT_ERR);
} }
}
Ok(()) Ok(())
} }
@ -980,7 +994,9 @@ fn copy_directory(
} }
// if no-dereference is enabled and this is a symlink, copy it as a file // if no-dereference is enabled and this is a symlink, copy it as a file
if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink() { if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink()
// replace by is_symlink in rust>=1.58
{
return copy_file(root, target, options, symlinked_files); return copy_file(root, target, options, symlinked_files);
} }
@ -1024,6 +1040,7 @@ fn copy_directory(
{ {
let p = or_continue!(path); let p = or_continue!(path);
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink(); let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
// replace by is_symlink in rust >=1.58
let path = current_dir.join(&p.path()); let path = current_dir.join(&p.path());
let local_to_root_parent = match root_parent { let local_to_root_parent = match root_parent {
@ -1276,7 +1293,7 @@ fn copy_file(
// Fail if dest is a dangling symlink or a symlink this program created previously // Fail if dest is a dangling symlink or a symlink this program created previously
if fs::symlink_metadata(dest) if fs::symlink_metadata(dest)
.map(|m| m.file_type().is_symlink()) .map(|m| m.file_type().is_symlink()) // replace by is_symlink in rust>=1.58
.unwrap_or(false) .unwrap_or(false)
{ {
if FileInformation::from_path(dest, false) if FileInformation::from_path(dest, false)
@ -1289,7 +1306,7 @@ fn copy_file(
dest.display() dest.display()
))); )));
} }
if !dest.exists() { if options.dereference && !dest.exists() {
return Err(Error::Error(format!( return Err(Error::Error(format!(
"not writing through dangling symlink '{}'", "not writing through dangling symlink '{}'",
dest.display() dest.display()
@ -1523,7 +1540,7 @@ fn copy_link(
} else { } else {
// we always need to remove the file to be able to create a symlink, // we always need to remove the file to be able to create a symlink,
// even if it is writeable. // even if it is writeable.
if dest.exists() { if dest.is_file() {
fs::remove_file(dest)?; fs::remove_file(dest)?;
} }
dest.into() dest.into()

View file

@ -797,7 +797,12 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::ELIDE_EMPTY_FILES) .long(options::ELIDE_EMPTY_FILES)
.help("remove empty output files"), .help("remove empty output files"),
) )
.arg(Arg::new(options::FILE).hide(true).required(true)) .arg(
Arg::new(options::FILE)
.hide(true)
.required(true)
.value_hint(clap::ValueHint::FilePath),
)
.arg( .arg(
Arg::new(options::PATTERN) Arg::new(options::PATTERN)
.hide(true) .hide(true)

View file

@ -615,5 +615,6 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.multiple_occurrences(true) .multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath)
) )
} }

View file

@ -272,6 +272,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.short('f') .short('f')
.long(OPT_FILE) .long(OPT_FILE)
.takes_value(true) .takes_value(true)
.value_hint(clap::ValueHint::FilePath)
.help("like --date; once for each line of DATEFILE"), .help("like --date; once for each line of DATEFILE"),
) )
.arg( .arg(
@ -303,6 +304,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.short('r') .short('r')
.long(OPT_REFERENCE) .long(OPT_REFERENCE)
.takes_value(true) .takes_value(true)
.value_hint(clap::ValueHint::AnyPath)
.help("display the last modification time of FILE"), .help("display the last modification time of FILE"),
) )
.arg( .arg(

View file

@ -742,6 +742,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::INFILE) .long(options::INFILE)
.overrides_with(options::INFILE) .overrides_with(options::INFILE)
.takes_value(true) .takes_value(true)
.value_hint(clap::ValueHint::FilePath)
.require_equals(true) .require_equals(true)
.value_name("FILE") .value_name("FILE")
.help("(alternatively if=FILE) specifies the file used for input. When not specified, stdin is used instead") .help("(alternatively if=FILE) specifies the file used for input. When not specified, stdin is used instead")
@ -751,6 +752,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::OUTFILE) .long(options::OUTFILE)
.overrides_with(options::OUTFILE) .overrides_with(options::OUTFILE)
.takes_value(true) .takes_value(true)
.value_hint(clap::ValueHint::FilePath)
.require_equals(true) .require_equals(true)
.value_name("FILE") .value_name("FILE")
.help("(alternatively of=FILE) specifies the file used for output. When not specified, stdout is used instead") .help("(alternatively of=FILE) specifies the file used for output. When not specified, stdout is used instead")

View file

@ -3,10 +3,14 @@
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
//! Types for representing and displaying block sizes. //! Types for representing and displaying block sizes.
use crate::{OPT_BLOCKSIZE, OPT_HUMAN_READABLE_BINARY, OPT_HUMAN_READABLE_DECIMAL}; use crate::OPT_BLOCKSIZE;
use clap::ArgMatches; use clap::ArgMatches;
use std::fmt; use std::{env, fmt};
use std::num::ParseIntError;
use uucore::{
display::Quotable,
parse_size::{parse_size, ParseSizeError},
};
/// The first ten powers of 1024. /// The first ten powers of 1024.
const IEC_BASES: [u128; 10] = [ const IEC_BASES: [u128; 10] = [
@ -25,6 +29,22 @@ const IEC_BASES: [u128; 10] = [
/// Suffixes for the first nine multi-byte unit suffixes. /// Suffixes for the first nine multi-byte unit suffixes.
const SUFFIXES: [char; 9] = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; const SUFFIXES: [char; 9] = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
const SI_BASES: [u128; 10] = [
1,
1_000,
1_000_000,
1_000_000_000,
1_000_000_000_000,
1_000_000_000_000_000,
1_000_000_000_000_000_000,
1_000_000_000_000_000_000_000,
1_000_000_000_000_000_000_000_000,
1_000_000_000_000_000_000_000_000_000,
];
// we use "kB" instead of "KB" because of GNU df
const SI_SUFFIXES: [&str; 9] = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
/// Convert a multiple of 1024 into a string like "12K" or "34M". /// Convert a multiple of 1024 into a string like "12K" or "34M".
/// ///
/// # Examples /// # Examples
@ -56,65 +76,129 @@ fn to_magnitude_and_suffix_1024(n: u128) -> Result<String, ()> {
Err(()) Err(())
} }
/// Convert a number into a string like "12kB" or "34MB".
///
/// Powers of 1000 become "1kB", "1MB", "1GB", etc.
///
/// The returned string has a maximum length of 5 chars, for example: "1.1kB", "999kB", "1MB".
fn to_magnitude_and_suffix_not_powers_of_1024(n: u128) -> Result<String, ()> {
let mut i = 0;
while SI_BASES[i + 1] - SI_BASES[i] < n && i < SI_SUFFIXES.len() {
i += 1;
}
let quot = n / SI_BASES[i];
let rem = n % SI_BASES[i];
let suffix = SI_SUFFIXES[i];
if rem == 0 {
Ok(format!("{}{}", quot, suffix))
} else {
let tenths_place = rem / (SI_BASES[i] / 10);
if rem % (SI_BASES[i] / 10) == 0 {
Ok(format!("{}.{}{}", quot, tenths_place, suffix))
} else if tenths_place + 1 == 10 {
Ok(format!("{}{}", quot + 1, suffix))
} else {
Ok(format!("{}.{}{}", quot, tenths_place + 1, suffix))
}
}
}
/// Convert a number into a magnitude and a multi-byte unit suffix. /// Convert a number into a magnitude and a multi-byte unit suffix.
/// ///
/// # Errors /// # Errors
/// ///
/// If the number is too large to represent. /// If the number is too large to represent.
fn to_magnitude_and_suffix(n: u128) -> Result<String, ()> { fn to_magnitude_and_suffix(n: u128) -> Result<String, ()> {
if n % 1024 == 0 { if n % 1024 == 0 && n % 1000 != 0 {
to_magnitude_and_suffix_1024(n) to_magnitude_and_suffix_1024(n)
} else { } else {
// TODO Implement this, probably using code from `numfmt`. to_magnitude_and_suffix_not_powers_of_1024(n)
Ok("1kB".into())
} }
} }
/// A mode to use in condensing the display of a large number of bytes.
pub(crate) enum SizeFormat {
HumanReadable(HumanReadable),
StaticBlockSize,
}
impl Default for SizeFormat {
fn default() -> Self {
Self::StaticBlockSize
}
}
/// A mode to use in condensing the human readable display of a large number
/// of bytes.
///
/// The [`HumanReadable::Decimal`] and[`HumanReadable::Binary`] variants
/// represent dynamic block sizes: as the number of bytes increases, the
/// divisor increases as well (for example, from 1 to 1,000 to 1,000,000
/// and so on in the case of [`HumanReadable::Decimal`]).
#[derive(Clone, Copy)]
pub(crate) enum HumanReadable {
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,000. Contrast with
/// [`HumanReadable::Binary`], which represents powers of
/// 1,024.
Decimal,
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,024. Contrast with
/// [`HumanReadable::Decimal`], which represents powers
/// of 1,000.
Binary,
}
/// A block size to use in condensing the display of a large number of bytes. /// A block size to use in condensing the display of a large number of bytes.
/// ///
/// The [`BlockSize::Bytes`] variant represents a static block /// The [`BlockSize::Bytes`] variant represents a static block
/// size. The [`BlockSize::HumanReadableDecimal`] and /// size.
/// [`BlockSize::HumanReadableBinary`] variants represent dynamic
/// block sizes: as the number of bytes increases, the divisor
/// increases as well (for example, from 1 to 1,000 to 1,000,000 and
/// so on in the case of [`BlockSize::HumanReadableDecimal`]).
/// ///
/// The default variant is `Bytes(1024)`. /// The default variant is `Bytes(1024)`.
#[derive(Debug, PartialEq)]
pub(crate) enum BlockSize { pub(crate) enum BlockSize {
/// A fixed number of bytes. /// A fixed number of bytes.
/// ///
/// The number must be positive. /// The number must be positive.
Bytes(u64), Bytes(u64),
}
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc. impl BlockSize {
/// /// Returns the associated value
/// This variant represents powers of 1,000. Contrast with pub(crate) fn as_u64(&self) -> u64 {
/// [`BlockSize::HumanReadableBinary`], which represents powers of match *self {
/// 1,024. Self::Bytes(n) => n,
HumanReadableDecimal, }
}
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,024. Contrast with
/// [`BlockSize::HumanReadableDecimal`], which represents powers
/// of 1,000.
HumanReadableBinary,
} }
impl Default for BlockSize { impl Default for BlockSize {
fn default() -> Self { fn default() -> Self {
if env::var("POSIXLY_CORRECT").is_ok() {
Self::Bytes(512)
} else {
Self::Bytes(1024) Self::Bytes(1024)
} }
} }
}
pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize, ParseIntError> { pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> {
if matches.is_present(OPT_HUMAN_READABLE_BINARY) { if matches.is_present(OPT_BLOCKSIZE) {
Ok(BlockSize::HumanReadableBinary)
} else if matches.is_present(OPT_HUMAN_READABLE_DECIMAL) {
Ok(BlockSize::HumanReadableDecimal)
} else if matches.is_present(OPT_BLOCKSIZE) {
let s = matches.value_of(OPT_BLOCKSIZE).unwrap(); let s = matches.value_of(OPT_BLOCKSIZE).unwrap();
Ok(BlockSize::Bytes(s.parse()?)) let bytes = parse_size(s)?;
if bytes > 0 {
Ok(BlockSize::Bytes(bytes))
} else {
Err(ParseSizeError::ParseFailure(format!("{}", s.quote())))
}
} else { } else {
Ok(Default::default()) Ok(Default::default())
} }
@ -123,10 +207,8 @@ pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize,
impl fmt::Display for BlockSize { impl fmt::Display for BlockSize {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::HumanReadableBinary => write!(f, "Size"),
Self::HumanReadableDecimal => write!(f, "Size"),
Self::Bytes(n) => match to_magnitude_and_suffix(*n as u128) { Self::Bytes(n) => match to_magnitude_and_suffix(*n as u128) {
Ok(s) => write!(f, "{}-blocks", s), Ok(s) => write!(f, "{}", s),
Err(_) => Err(fmt::Error), Err(_) => Err(fmt::Error),
}, },
} }
@ -136,6 +218,8 @@ impl fmt::Display for BlockSize {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::env;
use crate::blocks::{to_magnitude_and_suffix, BlockSize}; use crate::blocks::{to_magnitude_and_suffix, BlockSize};
#[test] #[test]
@ -152,46 +236,53 @@ mod tests {
); );
} }
// TODO We have not yet implemented this behavior, but when we do, #[test]
// uncomment this test. fn test_to_magnitude_and_suffix_not_powers_of_1024() {
assert_eq!(to_magnitude_and_suffix(1).unwrap(), "1B");
assert_eq!(to_magnitude_and_suffix(999).unwrap(), "999B");
// #[test] assert_eq!(to_magnitude_and_suffix(1000).unwrap(), "1kB");
// fn test_to_magnitude_and_suffix_not_powers_of_1024() { assert_eq!(to_magnitude_and_suffix(1001).unwrap(), "1.1kB");
// assert_eq!(to_magnitude_and_suffix(1).unwrap(), "1B"); assert_eq!(to_magnitude_and_suffix(1023).unwrap(), "1.1kB");
// assert_eq!(to_magnitude_and_suffix(999).unwrap(), "999B"); assert_eq!(to_magnitude_and_suffix(1025).unwrap(), "1.1kB");
assert_eq!(to_magnitude_and_suffix(999_000).unwrap(), "999kB");
// assert_eq!(to_magnitude_and_suffix(1000).unwrap(), "1kB"); assert_eq!(to_magnitude_and_suffix(999_001).unwrap(), "1MB");
// assert_eq!(to_magnitude_and_suffix(1001).unwrap(), "1.1kB"); assert_eq!(to_magnitude_and_suffix(999_999).unwrap(), "1MB");
// assert_eq!(to_magnitude_and_suffix(1023).unwrap(), "1.1kB"); assert_eq!(to_magnitude_and_suffix(1_000_000).unwrap(), "1MB");
// assert_eq!(to_magnitude_and_suffix(1025).unwrap(), "1.1kB"); assert_eq!(to_magnitude_and_suffix(1_000_001).unwrap(), "1.1MB");
// assert_eq!(to_magnitude_and_suffix(999_000).unwrap(), "999kB"); assert_eq!(to_magnitude_and_suffix(1_100_000).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_100_001).unwrap(), "1.2MB");
assert_eq!(to_magnitude_and_suffix(1_900_000).unwrap(), "1.9MB");
assert_eq!(to_magnitude_and_suffix(1_900_001).unwrap(), "2MB");
assert_eq!(to_magnitude_and_suffix(9_900_000).unwrap(), "9.9MB");
assert_eq!(to_magnitude_and_suffix(9_900_001).unwrap(), "10MB");
assert_eq!(to_magnitude_and_suffix(999_000_000).unwrap(), "999MB");
// assert_eq!(to_magnitude_and_suffix(999_001).unwrap(), "1MB"); assert_eq!(to_magnitude_and_suffix(999_000_001).unwrap(), "1GB");
// assert_eq!(to_magnitude_and_suffix(999_999).unwrap(), "1MB"); assert_eq!(to_magnitude_and_suffix(1_000_000_000).unwrap(), "1GB");
// assert_eq!(to_magnitude_and_suffix(1_000_000).unwrap(), "1MB"); assert_eq!(to_magnitude_and_suffix(1_000_000_001).unwrap(), "1.1GB");
// assert_eq!(to_magnitude_and_suffix(1_000_001).unwrap(), "1.1MB"); }
// assert_eq!(to_magnitude_and_suffix(1_100_000).unwrap(), "1.1MB");
// assert_eq!(to_magnitude_and_suffix(1_100_001).unwrap(), "1.2MB");
// assert_eq!(to_magnitude_and_suffix(1_900_000).unwrap(), "1.9MB");
// assert_eq!(to_magnitude_and_suffix(1_900_001).unwrap(), "2MB");
// assert_eq!(to_magnitude_and_suffix(9_900_000).unwrap(), "9.9MB");
// assert_eq!(to_magnitude_and_suffix(9_900_001).unwrap(), "10MB");
// assert_eq!(to_magnitude_and_suffix(999_000_000).unwrap(), "999MB");
// assert_eq!(to_magnitude_and_suffix(999_000_001).unwrap(), "1GB"); #[test]
// assert_eq!(to_magnitude_and_suffix(1_000_000_000).unwrap(), "1GB"); fn test_to_magnitude_and_suffix_multiples_of_1000_and_1024() {
// // etc. assert_eq!(to_magnitude_and_suffix(128_000).unwrap(), "128kB");
// } assert_eq!(to_magnitude_and_suffix(1000 * 1024).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_000_000_000_000).unwrap(), "1TB");
}
#[test] #[test]
fn test_block_size_display() { fn test_block_size_display() {
assert_eq!(format!("{}", BlockSize::HumanReadableBinary), "Size"); assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K");
assert_eq!(format!("{}", BlockSize::HumanReadableDecimal), "Size"); assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K");
assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K-blocks"); assert_eq!(format!("{}", BlockSize::Bytes(3 * 1024 * 1024)), "3M");
assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K-blocks"); }
assert_eq!(
format!("{}", BlockSize::Bytes(3 * 1024 * 1024)), #[test]
"3M-blocks" fn test_default_block_size() {
); assert_eq!(BlockSize::Bytes(1024), BlockSize::default());
env::set_var("POSIXLY_CORRECT", "1");
assert_eq!(BlockSize::Bytes(512), BlockSize::default());
env::remove_var("POSIXLY_CORRECT");
} }
} }

View file

@ -197,6 +197,7 @@ impl Column {
match column { match column {
// 14 = length of "Filesystem" plus 4 spaces // 14 = length of "Filesystem" plus 4 spaces
Self::Source => 14, Self::Source => 14,
Self::Used => 5,
// the shortest headers have a length of 4 chars so we use that as the minimum width // the shortest headers have a length of 4 chars so we use that as the minimum width
_ => 4, _ => 4,
} }

View file

@ -11,9 +11,12 @@ mod columns;
mod filesystem; mod filesystem;
mod table; mod table;
use blocks::{HumanReadable, SizeFormat};
use table::HeaderMode;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{UError, UResult, USimpleError}; use uucore::error::{UError, UResult, USimpleError};
use uucore::fsext::{read_fs_list, MountInfo}; use uucore::fsext::{read_fs_list, MountInfo};
use uucore::parse_size::ParseSizeError;
use uucore::{format_usage, show}; use uucore::{format_usage, show};
use clap::{crate_version, Arg, ArgMatches, Command}; use clap::{crate_version, Arg, ArgMatches, Command};
@ -30,6 +33,13 @@ use crate::table::Table;
static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\ static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
or all file systems by default."; or all file systems by default.";
const USAGE: &str = "{} [OPTION]... [FILE]..."; const USAGE: &str = "{} [OPTION]... [FILE]...";
const LONG_HELP: &str = "Display values are in units of the first available SIZE from --block-size,
and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.
Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).
SIZE is an integer and optional unit (example: 10M is 10*1024*1024).
Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers
of 1000).";
static OPT_HELP: &str = "help"; static OPT_HELP: &str = "help";
static OPT_ALL: &str = "all"; static OPT_ALL: &str = "all";
@ -61,7 +71,9 @@ static OUTPUT_FIELD_LIST: [&str; 12] = [
struct Options { struct Options {
show_local_fs: bool, show_local_fs: bool,
show_all_fs: bool, show_all_fs: bool,
size_format: SizeFormat,
block_size: BlockSize, block_size: BlockSize,
header_mode: HeaderMode,
/// Optional list of filesystem types to include in the output table. /// Optional list of filesystem types to include in the output table.
/// ///
@ -88,6 +100,8 @@ impl Default for Options {
show_local_fs: Default::default(), show_local_fs: Default::default(),
show_all_fs: Default::default(), show_all_fs: Default::default(),
block_size: Default::default(), block_size: Default::default(),
size_format: Default::default(),
header_mode: Default::default(),
include: Default::default(), include: Default::default(),
exclude: Default::default(), exclude: Default::default(),
show_total: Default::default(), show_total: Default::default(),
@ -105,7 +119,8 @@ impl Default for Options {
#[derive(Debug)] #[derive(Debug)]
enum OptionsError { enum OptionsError {
InvalidBlockSize, BlockSizeTooLarge(String),
InvalidBlockSize(String),
/// An error getting the columns to display in the output table. /// An error getting the columns to display in the output table.
ColumnError(ColumnError), ColumnError(ColumnError),
@ -116,11 +131,14 @@ enum OptionsError {
impl fmt::Display for OptionsError { impl fmt::Display for OptionsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
// TODO This should include the raw string provided as the argument.
//
// TODO This needs to vary based on whether `--block-size` // TODO This needs to vary based on whether `--block-size`
// or `-B` were provided. // or `-B` were provided.
Self::InvalidBlockSize => write!(f, "invalid --block-size argument"), Self::BlockSizeTooLarge(s) => {
write!(f, "--block-size argument {} too large", s.quote())
}
// TODO This needs to vary based on whether `--block-size`
// or `-B` were provided.
Self::InvalidBlockSize(s) => write!(f, "invalid --block-size argument {}", s),
Self::ColumnError(ColumnError::MultipleColumns(s)) => write!( Self::ColumnError(ColumnError::MultipleColumns(s)) => write!(
f, f,
"option --output: field {} used more than once", "option --output: field {} used more than once",
@ -155,8 +173,36 @@ impl Options {
Ok(Self { Ok(Self {
show_local_fs: matches.is_present(OPT_LOCAL), show_local_fs: matches.is_present(OPT_LOCAL),
show_all_fs: matches.is_present(OPT_ALL), show_all_fs: matches.is_present(OPT_ALL),
block_size: block_size_from_matches(matches) block_size: block_size_from_matches(matches).map_err(|e| match e {
.map_err(|_| OptionsError::InvalidBlockSize)?, ParseSizeError::SizeTooBig(_) => OptionsError::BlockSizeTooLarge(
matches.value_of(OPT_BLOCKSIZE).unwrap().to_string(),
),
ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s),
})?,
header_mode: {
if matches.is_present(OPT_HUMAN_READABLE_BINARY)
|| matches.is_present(OPT_HUMAN_READABLE_DECIMAL)
{
HeaderMode::HumanReadable
} else if matches.is_present(OPT_PORTABILITY) {
HeaderMode::PosixPortability
// is_present() doesn't work here, it always returns true because OPT_OUTPUT has
// default values and hence is always present
} else if matches.occurrences_of(OPT_OUTPUT) > 0 {
HeaderMode::Output
} else {
HeaderMode::Default
}
},
size_format: {
if matches.is_present(OPT_HUMAN_READABLE_BINARY) {
SizeFormat::HumanReadable(HumanReadable::Binary)
} else if matches.is_present(OPT_HUMAN_READABLE_DECIMAL) {
SizeFormat::HumanReadable(HumanReadable::Decimal)
} else {
SizeFormat::StaticBlockSize
}
},
include, include,
exclude, exclude,
show_total: matches.is_present(OPT_TOTAL), show_total: matches.is_present(OPT_TOTAL),
@ -292,7 +338,7 @@ fn get_all_filesystems(opt: &Options) -> Vec<Filesystem> {
} }
/// For each path, get the filesystem that contains that path. /// For each path, get the filesystem that contains that path.
fn get_named_filesystems<P>(paths: &[P]) -> Vec<Filesystem> fn get_named_filesystems<P>(paths: &[P], opt: &Options) -> Vec<Filesystem>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
@ -302,21 +348,35 @@ where
// considered. The "lofs" filesystem is a loopback // considered. The "lofs" filesystem is a loopback
// filesystem present on Solaris and FreeBSD systems. It // filesystem present on Solaris and FreeBSD systems. It
// is similar to a symbolic link. // is similar to a symbolic link.
let mounts: Vec<MountInfo> = read_fs_list() let mounts: Vec<MountInfo> = filter_mount_list(read_fs_list(), opt)
.into_iter() .into_iter()
.filter(|mi| mi.fs_type != "lofs" && !mi.dummy) .filter(|mi| mi.fs_type != "lofs" && !mi.dummy)
.collect(); .collect();
let mut result = vec![];
// this happens if the file system type doesn't exist
if mounts.is_empty() {
show!(USimpleError::new(1, "no file systems processed"));
return result;
}
// Convert each path into a `Filesystem`, which contains // Convert each path into a `Filesystem`, which contains
// both the mount information and usage information. // both the mount information and usage information.
let mut result = vec![];
for path in paths { for path in paths {
match Filesystem::from_path(&mounts, path) { match Filesystem::from_path(&mounts, path) {
Some(fs) => result.push(fs), Some(fs) => result.push(fs),
None => show!(USimpleError::new( None => {
// this happens if specified file system type != file system type of the file
if path.as_ref().exists() {
show!(USimpleError::new(1, "no file systems processed"));
} else {
show!(USimpleError::new(
1, 1,
format!("{}: No such file or directory", path.as_ref().display()) format!("{}: No such file or directory", path.as_ref().display())
)), ));
}
}
} }
} }
result result
@ -370,7 +430,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
Some(paths) => { Some(paths) => {
let paths: Vec<&str> = paths.collect(); let paths: Vec<&str> = paths.collect();
let filesystems = get_named_filesystems(&paths); let filesystems = get_named_filesystems(&paths, &opt);
// This can happen if paths are given as command-line arguments // This can happen if paths are given as command-line arguments
// but none of the paths exist. // but none of the paths exist.
@ -382,12 +442,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
}; };
// This can happen if paths are given as command-line arguments
// but none of the paths exist.
if filesystems.is_empty() {
return Ok(());
}
println!("{}", Table::new(&opt, filesystems)); println!("{}", Table::new(&opt, filesystems));
Ok(()) Ok(())
@ -398,6 +452,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.override_usage(format_usage(USAGE)) .override_usage(format_usage(USAGE))
.after_help(LONG_HELP)
.infer_long_args(true) .infer_long_args(true)
.arg( .arg(
Arg::new(OPT_HELP) Arg::new(OPT_HELP)
@ -416,6 +471,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.short('B') .short('B')
.long("block-size") .long("block-size")
.takes_value(true) .takes_value(true)
.value_name("SIZE")
.overrides_with_all(&[OPT_KILO, OPT_BLOCKSIZE]) .overrides_with_all(&[OPT_KILO, OPT_BLOCKSIZE])
.help( .help(
"scale sizes by SIZE before printing them; e.g.\ "scale sizes by SIZE before printing them; e.g.\
@ -472,6 +528,7 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(OPT_OUTPUT) Arg::new(OPT_OUTPUT)
.long("output") .long("output")
.takes_value(true) .takes_value(true)
.value_name("FIELD_LIST")
.min_values(0) .min_values(0)
.require_equals(true) .require_equals(true)
.use_value_delimiter(true) .use_value_delimiter(true)
@ -504,6 +561,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long("type") .long("type")
.allow_invalid_utf8(true) .allow_invalid_utf8(true)
.takes_value(true) .takes_value(true)
.value_name("TYPE")
.multiple_occurrences(true) .multiple_occurrences(true)
.help("limit listing to file systems of type TYPE"), .help("limit listing to file systems of type TYPE"),
) )
@ -520,11 +578,16 @@ pub fn uu_app<'a>() -> Command<'a> {
.long("exclude-type") .long("exclude-type")
.allow_invalid_utf8(true) .allow_invalid_utf8(true)
.takes_value(true) .takes_value(true)
.value_name("TYPE")
.use_value_delimiter(true) .use_value_delimiter(true)
.multiple_occurrences(true) .multiple_occurrences(true)
.help("limit listing to file systems not of type TYPE"), .help("limit listing to file systems not of type TYPE"),
) )
.arg(Arg::new(OPT_PATHS).multiple_occurrences(true)) .arg(
Arg::new(OPT_PATHS)
.multiple_occurrences(true)
.value_hint(clap::ValueHint::AnyPath),
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -10,6 +10,7 @@
use number_prefix::NumberPrefix; use number_prefix::NumberPrefix;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use crate::blocks::{HumanReadable, SizeFormat};
use crate::columns::{Alignment, Column}; use crate::columns::{Alignment, Column};
use crate::filesystem::Filesystem; use crate::filesystem::Filesystem;
use crate::{BlockSize, Options}; use crate::{BlockSize, Options};
@ -213,15 +214,10 @@ impl<'a> RowFormatter<'a> {
} }
/// Get a human readable string giving the scaled version of the input number. /// Get a human readable string giving the scaled version of the input number.
/// fn scaled_human_readable(&self, size: u64, human_readable: HumanReadable) -> String {
/// The scaling factor is defined in the `options` field. let number_prefix = match human_readable {
/// HumanReadable::Decimal => NumberPrefix::decimal(size as f64),
/// This function is supposed to be used by `scaled_bytes()` and `scaled_inodes()` only. HumanReadable::Binary => NumberPrefix::binary(size as f64),
fn scaled_human_readable(&self, size: u64) -> String {
let number_prefix = match self.options.block_size {
BlockSize::HumanReadableDecimal => NumberPrefix::decimal(size as f64),
BlockSize::HumanReadableBinary => NumberPrefix::binary(size as f64),
_ => unreachable!(),
}; };
match number_prefix { match number_prefix {
NumberPrefix::Standalone(bytes) => bytes.to_string(), NumberPrefix::Standalone(bytes) => bytes.to_string(),
@ -233,10 +229,12 @@ impl<'a> RowFormatter<'a> {
/// ///
/// The scaling factor is defined in the `options` field. /// The scaling factor is defined in the `options` field.
fn scaled_bytes(&self, size: u64) -> String { fn scaled_bytes(&self, size: u64) -> String {
if let BlockSize::Bytes(d) = self.options.block_size { match self.options.size_format {
(size / d).to_string() SizeFormat::HumanReadable(h) => self.scaled_human_readable(size, h),
} else { SizeFormat::StaticBlockSize => {
self.scaled_human_readable(size) let BlockSize::Bytes(d) = self.options.block_size;
(size as f64 / d as f64).ceil().to_string()
}
} }
} }
@ -244,10 +242,9 @@ impl<'a> RowFormatter<'a> {
/// ///
/// The scaling factor is defined in the `options` field. /// The scaling factor is defined in the `options` field.
fn scaled_inodes(&self, size: u64) -> String { fn scaled_inodes(&self, size: u64) -> String {
if let BlockSize::Bytes(_) = self.options.block_size { match self.options.size_format {
size.to_string() SizeFormat::HumanReadable(h) => self.scaled_human_readable(size, h),
} else { SizeFormat::StaticBlockSize => size.to_string(),
self.scaled_human_readable(size)
} }
} }
@ -292,6 +289,23 @@ impl<'a> RowFormatter<'a> {
} }
} }
/// A HeaderMode defines what header labels should be shown.
pub(crate) enum HeaderMode {
Default,
// the user used -h or -H
HumanReadable,
// the user used -P
PosixPortability,
// the user used --output
Output,
}
impl Default for HeaderMode {
fn default() -> Self {
Self::Default
}
}
/// The data of the header row. /// The data of the header row.
struct Header {} struct Header {}
@ -305,10 +319,22 @@ impl Header {
for column in &options.columns { for column in &options.columns {
let header = match column { let header = match column {
Column::Source => String::from("Filesystem"), Column::Source => String::from("Filesystem"),
Column::Size => options.block_size.to_string(), Column::Size => match options.header_mode {
HeaderMode::HumanReadable => String::from("Size"),
HeaderMode::PosixPortability => {
format!("{}-blocks", options.block_size.as_u64())
}
_ => format!("{}-blocks", options.block_size),
},
Column::Used => String::from("Used"), Column::Used => String::from("Used"),
Column::Avail => String::from("Available"), Column::Avail => match options.header_mode {
Column::Pcent => String::from("Use%"), HeaderMode::HumanReadable | HeaderMode::Output => String::from("Avail"),
_ => String::from("Available"),
},
Column::Pcent => match options.header_mode {
HeaderMode::PosixPortability => String::from("Capacity"),
_ => String::from("Use%"),
},
Column::Target => String::from("Mounted on"), Column::Target => String::from("Mounted on"),
Column::Itotal => String::from("Inodes"), Column::Itotal => String::from("Inodes"),
Column::Iused => String::from("IUsed"), Column::Iused => String::from("IUsed"),
@ -401,21 +427,12 @@ impl fmt::Display for Table {
while let Some(row) = row_iter.next() { while let Some(row) = row_iter.next() {
let mut col_iter = row.iter().enumerate().peekable(); let mut col_iter = row.iter().enumerate().peekable();
while let Some((i, elem)) = col_iter.next() { while let Some((i, elem)) = col_iter.next() {
let is_last_col = col_iter.peek().is_none();
match self.alignments[i] { match self.alignments[i] {
Alignment::Left => { Alignment::Left => write!(f, "{:<width$}", elem, width = self.widths[i])?,
if is_last_col {
// no trailing spaces in last column
write!(f, "{}", elem)?;
} else {
write!(f, "{:<width$}", elem, width = self.widths[i])?;
}
}
Alignment::Right => write!(f, "{:>width$}", elem, width = self.widths[i])?, Alignment::Right => write!(f, "{:>width$}", elem, width = self.widths[i])?,
} }
if !is_last_col { if col_iter.peek().is_some() {
// column separator // column separator
write!(f, " ")?; write!(f, " ")?;
} }
@ -433,8 +450,9 @@ impl fmt::Display for Table {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::blocks::{HumanReadable, SizeFormat};
use crate::columns::Column; use crate::columns::Column;
use crate::table::{Header, Row, RowFormatter}; use crate::table::{Header, HeaderMode, Row, RowFormatter};
use crate::{BlockSize, Options}; use crate::{BlockSize, Options};
const COLUMNS_WITH_FS_TYPE: [Column; 7] = [ const COLUMNS_WITH_FS_TYPE: [Column; 7] = [
@ -455,6 +473,30 @@ mod tests {
Column::Target, Column::Target,
]; ];
impl Default for Row {
fn default() -> Self {
Self {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
}
}
}
#[test] #[test]
fn test_default_header() { fn test_default_header() {
let options = Default::default(); let options = Default::default();
@ -530,37 +572,49 @@ mod tests {
} }
#[test] #[test]
fn test_header_with_human_readable_binary() { fn test_human_readable_header() {
let options = Options { let options = Options {
block_size: BlockSize::HumanReadableBinary, header_mode: HeaderMode::HumanReadable,
..Default::default()
};
assert_eq!(
Header::get_headers(&options),
vec!("Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on")
);
}
#[test]
fn test_posix_portability_header() {
let options = Options {
header_mode: HeaderMode::PosixPortability,
..Default::default() ..Default::default()
}; };
assert_eq!( assert_eq!(
Header::get_headers(&options), Header::get_headers(&options),
vec!( vec!(
"Filesystem", "Filesystem",
"Size", "1024-blocks",
"Used", "Used",
"Available", "Available",
"Use%", "Capacity",
"Mounted on" "Mounted on"
) )
); );
} }
#[test] #[test]
fn test_header_with_human_readable_si() { fn test_output_header() {
let options = Options { let options = Options {
block_size: BlockSize::HumanReadableDecimal, header_mode: HeaderMode::Output,
..Default::default() ..Default::default()
}; };
assert_eq!( assert_eq!(
Header::get_headers(&options), Header::get_headers(&options),
vec!( vec!(
"Filesystem", "Filesystem",
"Size", "1K-blocks",
"Used", "Used",
"Available", "Avail",
"Use%", "Use%",
"Mounted on" "Mounted on"
) )
@ -574,9 +628,7 @@ mod tests {
..Default::default() ..Default::default()
}; };
let row = Row { let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(), fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(), fs_mount: "my_mount".to_string(),
bytes: 100, bytes: 100,
@ -584,13 +636,7 @@ mod tests {
bytes_avail: 75, bytes_avail: 75,
bytes_usage: Some(0.25), bytes_usage: Some(0.25),
#[cfg(target_os = "macos")] ..Default::default()
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
}; };
let fmt = RowFormatter::new(&row, &options); let fmt = RowFormatter::new(&row, &options);
assert_eq!( assert_eq!(
@ -607,7 +653,6 @@ mod tests {
..Default::default() ..Default::default()
}; };
let row = Row { let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(), fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(), fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(), fs_mount: "my_mount".to_string(),
@ -617,13 +662,7 @@ mod tests {
bytes_avail: 75, bytes_avail: 75,
bytes_usage: Some(0.25), bytes_usage: Some(0.25),
#[cfg(target_os = "macos")] ..Default::default()
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
}; };
let fmt = RowFormatter::new(&row, &options); let fmt = RowFormatter::new(&row, &options);
assert_eq!( assert_eq!(
@ -640,23 +679,15 @@ mod tests {
..Default::default() ..Default::default()
}; };
let row = Row { let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(), fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(), fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10, inodes: 10,
inodes_used: 2, inodes_used: 2,
inodes_free: 8, inodes_free: 8,
inodes_usage: Some(0.2), inodes_usage: Some(0.2),
..Default::default()
}; };
let fmt = RowFormatter::new(&row, &options); let fmt = RowFormatter::new(&row, &options);
assert_eq!( assert_eq!(
@ -673,23 +704,9 @@ mod tests {
..Default::default() ..Default::default()
}; };
let row = Row { let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100, bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10, inodes: 10,
inodes_used: 2, ..Default::default()
inodes_free: 8,
inodes_usage: Some(0.2),
}; };
let fmt = RowFormatter::new(&row, &options); let fmt = RowFormatter::new(&row, &options);
assert_eq!(fmt.get_values(), vec!("1", "10")); assert_eq!(fmt.get_values(), vec!("1", "10"));
@ -698,12 +715,11 @@ mod tests {
#[test] #[test]
fn test_row_formatter_with_human_readable_si() { fn test_row_formatter_with_human_readable_si() {
let options = Options { let options = Options {
block_size: BlockSize::HumanReadableDecimal, size_format: SizeFormat::HumanReadable(HumanReadable::Decimal),
columns: COLUMNS_WITH_FS_TYPE.to_vec(), columns: COLUMNS_WITH_FS_TYPE.to_vec(),
..Default::default() ..Default::default()
}; };
let row = Row { let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(), fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(), fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(), fs_mount: "my_mount".to_string(),
@ -713,13 +729,7 @@ mod tests {
bytes_avail: 3000, bytes_avail: 3000,
bytes_usage: Some(0.25), bytes_usage: Some(0.25),
#[cfg(target_os = "macos")] ..Default::default()
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
}; };
let fmt = RowFormatter::new(&row, &options); let fmt = RowFormatter::new(&row, &options);
assert_eq!( assert_eq!(
@ -739,12 +749,11 @@ mod tests {
#[test] #[test]
fn test_row_formatter_with_human_readable_binary() { fn test_row_formatter_with_human_readable_binary() {
let options = Options { let options = Options {
block_size: BlockSize::HumanReadableBinary, size_format: SizeFormat::HumanReadable(HumanReadable::Binary),
columns: COLUMNS_WITH_FS_TYPE.to_vec(), columns: COLUMNS_WITH_FS_TYPE.to_vec(),
..Default::default() ..Default::default()
}; };
let row = Row { let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(), fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(), fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(), fs_mount: "my_mount".to_string(),
@ -754,13 +763,7 @@ mod tests {
bytes_avail: 3072, bytes_avail: 3072,
bytes_usage: Some(0.25), bytes_usage: Some(0.25),
#[cfg(target_os = "macos")] ..Default::default()
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
}; };
let fmt = RowFormatter::new(&row, &options); let fmt = RowFormatter::new(&row, &options);
assert_eq!( assert_eq!(
@ -780,32 +783,38 @@ mod tests {
#[test] #[test]
fn test_row_formatter_with_round_up_usage() { fn test_row_formatter_with_round_up_usage() {
let options = Options { let options = Options {
block_size: BlockSize::Bytes(1), columns: vec![Column::Pcent],
..Default::default() ..Default::default()
}; };
let row = Row { let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.251), bytes_usage: Some(0.251),
..Default::default()
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
}; };
let fmt = RowFormatter::new(&row, &options); let fmt = RowFormatter::new(&row, &options);
assert_eq!( assert_eq!(fmt.get_values(), vec!("26%"));
fmt.get_values(), }
vec!("my_device", "100", "25", "75", "26%", "my_mount")
); #[test]
fn test_row_formatter_with_round_up_byte_values() {
fn get_formatted_values(bytes: u64, bytes_used: u64, bytes_avail: u64) -> Vec<String> {
let options = Options {
block_size: BlockSize::Bytes(1000),
columns: vec![Column::Size, Column::Used, Column::Avail],
..Default::default()
};
let row = Row {
bytes,
bytes_used,
bytes_avail,
..Default::default()
};
RowFormatter::new(&row, &options).get_values()
}
assert_eq!(get_formatted_values(100, 100, 0), vec!("1", "1", "0"));
assert_eq!(get_formatted_values(100, 99, 1), vec!("1", "1", "1"));
assert_eq!(get_formatted_values(1000, 1000, 0), vec!("1", "1", "0"));
assert_eq!(get_formatted_values(1001, 1000, 1), vec!("2", "1", "1"));
} }
} }

View file

@ -187,6 +187,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.value_hint(clap::ValueHint::FilePath)
.multiple_occurrences(true), .multiple_occurrences(true),
) )
} }

View file

@ -88,5 +88,10 @@ pub fn uu_app<'a>() -> Command<'a> {
.short('z') .short('z')
.help("separate output with NUL rather than newline"), .help("separate output with NUL rather than newline"),
) )
.arg(Arg::new(options::DIR).hide(true).multiple_occurrences(true)) .arg(
Arg::new(options::DIR)
.hide(true)
.multiple_occurrences(true)
.value_hint(clap::ValueHint::AnyPath),
)
} }

View file

@ -20,7 +20,7 @@ use std::fs::File;
use std::fs::Metadata; use std::fs::Metadata;
use std::io::BufRead; use std::io::BufRead;
use std::io::BufReader; use std::io::BufReader;
use std::io::{ErrorKind, Result}; use std::io::Result;
use std::iter; use std::iter;
#[cfg(not(windows))] #[cfg(not(windows))]
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
@ -34,7 +34,8 @@ use std::str::FromStr;
use std::time::{Duration, UNIX_EPOCH}; use std::time::{Duration, UNIX_EPOCH};
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
use uucore::display::{print_verbatim, Quotable}; use uucore::display::{print_verbatim, Quotable};
use uucore::error::{set_exit_code, UError, UResult}; use uucore::error::FromIo;
use uucore::error::{UError, UResult};
use uucore::format_usage; use uucore::format_usage;
use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
@ -102,7 +103,6 @@ const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2
struct Options { struct Options {
all: bool, all: bool,
util_name: String,
max_depth: Option<usize>, max_depth: Option<usize>,
total: bool, total: bool,
separate_dirs: bool, separate_dirs: bool,
@ -309,13 +309,9 @@ fn du(
let read = match fs::read_dir(&my_stat.path) { let read = match fs::read_dir(&my_stat.path) {
Ok(read) => read, Ok(read) => read,
Err(e) => { Err(e) => {
eprintln!( show!(
"{}: cannot read directory {}: {}", e.map_err_context(|| format!("cannot read directory {}", my_stat.path.quote()))
options.util_name,
my_stat.path.quote(),
e
); );
set_exit_code(1);
return Box::new(iter::once(my_stat)); return Box::new(iter::once(my_stat));
} }
}; };
@ -368,18 +364,9 @@ fn du(
} }
} }
} }
Err(error) => match error.kind() { Err(e) => show!(
ErrorKind::PermissionDenied => { e.map_err_context(|| format!("cannot access {}", entry.path().quote()))
let description = format!("cannot access {}", entry.path().quote()); ),
let error_message = "Permission denied";
show_error_custom_description!(description, "{}", error_message);
set_exit_code(1);
}
_ => {
set_exit_code(1);
show_error!("cannot access {}: {}", entry.path().quote(), error);
}
},
} }
} }
Err(error) => show_error!("{}", error), Err(error) => show_error!("{}", error),
@ -567,7 +554,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let options = Options { let options = Options {
all: matches.is_present(options::ALL), all: matches.is_present(options::ALL),
util_name: uucore::util_name().to_owned(),
max_depth, max_depth,
total: matches.is_present(options::TOTAL), total: matches.is_present(options::TOTAL),
separate_dirs: matches.is_present(options::SEPARATE_DIRS), separate_dirs: matches.is_present(options::SEPARATE_DIRS),
@ -898,6 +884,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.short('X') .short('X')
.long("exclude-from") .long("exclude-from")
.value_name("FILE") .value_name("FILE")
.value_hint(clap::ValueHint::FilePath)
.help("exclude files that match any pattern in FILE") .help("exclude files that match any pattern in FILE")
.multiple_occurrences(true) .multiple_occurrences(true)
@ -927,6 +914,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.value_hint(clap::ValueHint::AnyPath)
.multiple_occurrences(true) .multiple_occurrences(true)
) )
} }

View file

@ -144,6 +144,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.takes_value(true) .takes_value(true)
.number_of_values(1) .number_of_values(1)
.value_name("DIR") .value_name("DIR")
.value_hint(clap::ValueHint::DirPath)
.help("change working directory to DIR")) .help("change working directory to DIR"))
.arg(Arg::new("null") .arg(Arg::new("null")
.short('0') .short('0')
@ -156,6 +157,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.takes_value(true) .takes_value(true)
.number_of_values(1) .number_of_values(1)
.value_name("PATH") .value_name("PATH")
.value_hint(clap::ValueHint::FilePath)
.multiple_occurrences(true) .multiple_occurrences(true)
.help("read and set variables from a \".env\"-style configuration file (prior to any \ .help("read and set variables from a \".env\"-style configuration file (prior to any \
unset and/or set)")) unset and/or set)"))

View file

@ -207,6 +207,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.multiple_occurrences(true) .multiple_occurrences(true)
.hide(true) .hide(true)
.takes_value(true) .takes_value(true)
.value_hint(clap::ValueHint::FilePath)
) )
} }

View file

@ -17,7 +17,7 @@ path = "src/expr.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
num-bigint = "0.4.0" num-bigint = "0.4.0"
num-traits = "0.2.14" num-traits = "0.2.15"
onig = { version = "~6.3", default-features = false } onig = { version = "~6.3", default-features = false }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }

View file

@ -12,12 +12,12 @@ categories = ["command-line-utilities"]
edition = "2021" edition = "2021"
[build-dependencies] [build-dependencies]
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs num-traits = "0.2.15" # used in src/numerics.rs, which is included by build.rs
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
coz = { version = "0.1.3", optional = true } coz = { version = "0.1.3", optional = true }
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd" num-traits = "0.2.15" # Needs at least version 0.2.15 for "OverflowingAdd"
rand = { version = "0.8", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or later. smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or later.
uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" } uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }

View file

@ -28,7 +28,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if let Ok(matches) = command.try_get_matches_from_mut(args) { if let Ok(matches) = command.try_get_matches_from_mut(args) {
let error = if matches.index_of("help").is_some() { let error = if matches.index_of("help").is_some() {
command.print_long_help() command.print_help()
} else if matches.index_of("version").is_some() { } else if matches.index_of("version").is_some() {
writeln!(std::io::stdout(), "{}", command.render_version()) writeln!(std::io::stdout(), "{}", command.render_version())
} else { } else {

View file

@ -344,6 +344,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(ARG_FILES) Arg::new(ARG_FILES)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true), .takes_value(true)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -99,7 +99,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -112,6 +112,7 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::USERS) Arg::new(options::USERS)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.value_name(options::USERS), .value_name(options::USERS)
.value_hint(clap::ValueHint::Username),
) )
} }

View file

@ -423,6 +423,7 @@ pub fn uu_app_common<'a>() -> Command<'a> {
.index(1) .index(1)
.multiple_occurrences(true) .multiple_occurrences(true)
.value_name("FILE") .value_name("FILE")
.value_hint(clap::ValueHint::FilePath)
.allow_invalid_utf8(true), .allow_invalid_utf8(true),
) )
} }

View file

@ -107,7 +107,11 @@ pub fn uu_app<'a>() -> Command<'a> {
.help("line delimiter is NUL, not newline") .help("line delimiter is NUL, not newline")
.overrides_with(options::ZERO_NAME), .overrides_with(options::ZERO_NAME),
) )
.arg(Arg::new(options::FILES_NAME).multiple_occurrences(true)) .arg(
Arg::new(options::FILES_NAME)
.multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
)
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]

View file

@ -16,7 +16,7 @@ path = "src/hostid.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.121" libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
[[bin]] [[bin]]

View file

@ -105,7 +105,11 @@ pub fn uu_app<'a>() -> Command<'a> {
.overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT]) .overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT])
.help("Display the short hostname (the portion before the first dot) if possible"), .help("Display the short hostname (the portion before the first dot) if possible"),
) )
.arg(Arg::new(OPT_HOST).allow_invalid_utf8(true)) .arg(
Arg::new(OPT_HOST)
.allow_invalid_utf8(true)
.value_hint(clap::ValueHint::Hostname),
)
} }
fn display_hostname(matches: &ArgMatches) -> UResult<()> { fn display_hostname(matches: &ArgMatches) -> UResult<()> {

View file

@ -443,7 +443,8 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::ARG_USERS) Arg::new(options::ARG_USERS)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.value_name(options::ARG_USERS), .value_name(options::ARG_USERS)
.value_hint(clap::ValueHint::Username),
) )
} }

View file

@ -24,6 +24,9 @@ file_diff = "1.0.0"
libc = ">= 0.2" libc = ">= 0.2"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode", "perms", "entries"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode", "perms", "entries"] }
[dev-dependencies]
time = "0.3"
[[bin]] [[bin]]
name = "install" name = "install"
path = "src/main.rs" path = "src/main.rs"

View file

@ -248,6 +248,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.help("set ownership (super-user only)") .help("set ownership (super-user only)")
.value_name("OWNER") .value_name("OWNER")
.takes_value(true) .takes_value(true)
.value_hint(clap::ValueHint::Username)
) )
.arg( .arg(
Arg::new(OPT_PRESERVE_TIMESTAMPS) Arg::new(OPT_PRESERVE_TIMESTAMPS)
@ -266,6 +267,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(OPT_STRIP_PROGRAM) .long(OPT_STRIP_PROGRAM)
.help("program used to strip binaries (no action Windows)") .help("program used to strip binaries (no action Windows)")
.value_name("PROGRAM") .value_name("PROGRAM")
.value_hint(clap::ValueHint::CommandName)
) )
.arg( .arg(
backup_control::arguments::suffix() backup_control::arguments::suffix()
@ -277,6 +279,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(OPT_TARGET_DIRECTORY) .long(OPT_TARGET_DIRECTORY)
.help("move all SOURCE arguments into DIRECTORY") .help("move all SOURCE arguments into DIRECTORY")
.value_name("DIRECTORY") .value_name("DIRECTORY")
.value_hint(clap::ValueHint::DirPath)
) )
.arg( .arg(
// TODO implement flag // TODO implement flag
@ -307,7 +310,13 @@ pub fn uu_app<'a>() -> Command<'a> {
.help("(unimplemented) set security context of files and directories") .help("(unimplemented) set security context of files and directories")
.value_name("CONTEXT") .value_name("CONTEXT")
) )
.arg(Arg::new(ARG_FILES).multiple_occurrences(true).takes_value(true).min_values(1)) .arg(
Arg::new(ARG_FILES)
.multiple_occurrences(true)
.takes_value(true)
.min_values(1)
.value_hint(clap::ValueHint::AnyPath)
)
} }
/// Check for unimplemented command line arguments. /// Check for unimplemented command line arguments.

View file

@ -801,12 +801,14 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2",
Arg::new("file1") Arg::new("file1")
.required(true) .required(true)
.value_name("FILE1") .value_name("FILE1")
.value_hint(clap::ValueHint::FilePath)
.hide(true), .hide(true),
) )
.arg( .arg(
Arg::new("file2") Arg::new("file2")
.required(true) .required(true)
.value_name("FILE2") .value_name("FILE2")
.value_hint(clap::ValueHint::FilePath)
.hide(true), .hide(true),
) )
} }

View file

@ -16,7 +16,7 @@ path = "src/kill.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.121" libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["signals"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["signals"] }
[[bin]] [[bin]]

View file

@ -46,6 +46,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.min_values(2) .min_values(2)
.max_values(2) .max_values(2)
.takes_value(true) .takes_value(true)
.value_hint(clap::ValueHint::AnyPath)
.allow_invalid_utf8(true), .allow_invalid_utf8(true),
) )
} }

View file

@ -232,6 +232,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::TARGET_DIRECTORY) .long(options::TARGET_DIRECTORY)
.help("specify the DIRECTORY in which to create the links") .help("specify the DIRECTORY in which to create the links")
.value_name("DIRECTORY") .value_name("DIRECTORY")
.value_hint(clap::ValueHint::DirPath)
.conflicts_with(options::NO_TARGET_DIRECTORY), .conflicts_with(options::NO_TARGET_DIRECTORY),
) )
.arg( .arg(
@ -257,6 +258,7 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(ARG_FILES) Arg::new(ARG_FILES)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.value_hint(clap::ValueHint::AnyPath)
.required(true) .required(true)
.min_values(1), .min_values(1),
) )

View file

@ -15,7 +15,7 @@ edition = "2021"
path = "src/logname.rs" path = "src/logname.rs"
[dependencies] [dependencies]
libc = "0.2.121" libc = "0.2.125"
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }

View file

@ -1402,7 +1402,8 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::PATHS) Arg::new(options::PATHS)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.allow_invalid_utf8(true), .value_hint(clap::ValueHint::AnyPath)
.allow_invalid_utf8(true)
) )
.after_help( .after_help(
"The TIME_STYLE argument can be full-iso, long-iso, iso. \ "The TIME_STYLE argument can be full-iso, long-iso, iso. \

View file

@ -137,7 +137,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.min_values(1) .min_values(1)
.allow_invalid_utf8(true), .allow_invalid_utf8(true)
.value_hint(clap::ValueHint::DirPath),
) )
} }

View file

@ -16,7 +16,7 @@ path = "src/mkfifo.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.121" libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
[[bin]] [[bin]]

View file

@ -102,6 +102,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FIFO) Arg::new(options::FIFO)
.hide(true) .hide(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::AnyPath),
) )
} }

View file

@ -17,7 +17,7 @@ path = "src/mknod.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "^0.2.121" libc = "^0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["mode"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["mode"] }
[[bin]] [[bin]]

View file

@ -162,7 +162,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.value_name("NAME") .value_name("NAME")
.help("name of the new file") .help("name of the new file")
.required(true) .required(true)
.index(1), .index(1)
.value_hint(clap::ValueHint::AnyPath),
) )
.arg( .arg(
Arg::new("type") Arg::new("type")

View file

@ -41,7 +41,12 @@ enum MkTempError {
PersistError(PathBuf), PersistError(PathBuf),
MustEndInX(String), MustEndInX(String),
TooFewXs(String), TooFewXs(String),
ContainsDirSeparator(String),
/// The template prefix contains a path separator (e.g. `"a/bXXX"`).
PrefixContainsDirSeparator(String),
/// The template suffix contains a path separator (e.g. `"XXXa/b"`).
SuffixContainsDirSeparator(String),
InvalidTemplate(String), InvalidTemplate(String),
} }
@ -56,7 +61,14 @@ impl Display for MkTempError {
PersistError(p) => write!(f, "could not persist file {}", p.quote()), PersistError(p) => write!(f, "could not persist file {}", p.quote()),
MustEndInX(s) => write!(f, "with --suffix, template {} must end in X", s.quote()), MustEndInX(s) => write!(f, "with --suffix, template {} must end in X", s.quote()),
TooFewXs(s) => write!(f, "too few X's in template {}", s.quote()), TooFewXs(s) => write!(f, "too few X's in template {}", s.quote()),
ContainsDirSeparator(s) => { PrefixContainsDirSeparator(s) => {
write!(
f,
"invalid template, {}, contains directory separator",
s.quote()
)
}
SuffixContainsDirSeparator(s) => {
write!( write!(
f, f,
"invalid suffix {}, contains directory separator", "invalid suffix {}, contains directory separator",
@ -79,7 +91,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let template = matches.value_of(ARG_TEMPLATE).unwrap(); let template = matches.value_of(ARG_TEMPLATE).unwrap();
let tmpdir = matches.value_of(OPT_TMPDIR).unwrap_or_default(); let tmpdir = matches.value_of(OPT_TMPDIR).unwrap_or_default();
let (template, mut tmpdir) = if matches.is_present(OPT_TMPDIR) // Treat the template string as a path to get the directory
// containing the last component.
let path = PathBuf::from(template);
let (template, tmpdir) = if matches.is_present(OPT_TMPDIR)
&& !PathBuf::from(tmpdir).is_dir() // if a temp dir is provided, it must be an actual path && !PathBuf::from(tmpdir).is_dir() // if a temp dir is provided, it must be an actual path
&& tmpdir.contains("XXX") && tmpdir.contains("XXX")
// If this is a template, it has to contain at least 3 X // If this is a template, it has to contain at least 3 X
@ -97,8 +113,24 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let tmp = env::temp_dir(); let tmp = env::temp_dir();
(tmpdir, tmp) (tmpdir, tmp)
} else if !matches.is_present(OPT_TMPDIR) { } else if !matches.is_present(OPT_TMPDIR) {
// In this case, the command line was `mktemp -t XXX`, so we
// treat the argument `XXX` as though it were a filename
// regardless of whether it has path separators in it.
if matches.is_present(OPT_T) {
let tmp = env::temp_dir(); let tmp = env::temp_dir();
(template, tmp) (template, tmp)
// In this case, the command line was `mktemp XXX`, so we need
// to parse out the parent directory and the filename from the
// argument `XXX`, since it may be include path separators.
} else {
let tmp = match path.parent() {
None => PathBuf::from("."),
Some(d) => PathBuf::from(d),
};
let filename = path.file_name();
let template = filename.unwrap().to_str().unwrap();
(template, tmp)
}
} else { } else {
(template, PathBuf::from(tmpdir)) (template, PathBuf::from(tmpdir))
}; };
@ -113,10 +145,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
return Err(MkTempError::InvalidTemplate(template.into()).into()); return Err(MkTempError::InvalidTemplate(template.into()).into());
} }
if matches.is_present(OPT_T) {
tmpdir = env::temp_dir();
}
let res = if dry_run { let res = if dry_run {
dry_exec(tmpdir, prefix, rand, suffix) dry_exec(tmpdir, prefix, rand, suffix)
} else { } else {
@ -174,7 +202,8 @@ pub fn uu_app<'a>() -> Command<'a> {
be an absolute name; unlike with -t, TEMPLATE may contain \ be an absolute name; unlike with -t, TEMPLATE may contain \
slashes, but mktemp creates only the final component", slashes, but mktemp creates only the final component",
) )
.value_name("DIR"), .value_name("DIR")
.value_hint(clap::ValueHint::DirPath),
) )
.arg(Arg::new(OPT_T).short('t').help( .arg(Arg::new(OPT_T).short('t').help(
"Generate a template (using the supplied prefix and TMPDIR (TMP on windows) if set) \ "Generate a template (using the supplied prefix and TMPDIR (TMP on windows) if set) \
@ -189,20 +218,41 @@ pub fn uu_app<'a>() -> Command<'a> {
) )
} }
/// Parse a template string into prefix, suffix, and random components.
///
/// `temp` is the template string, with three or more consecutive `X`s
/// representing a placeholder for randomly generated characters (for
/// example, `"abc_XXX.txt"`). If `temp` ends in an `X`, then a suffix
/// can be specified by `suffix` instead.
///
/// # Errors
///
/// * If there are fewer than three consecutive `X`s in `temp`.
/// * If `suffix` is a [`Some`] object but `temp` does not end in `X`.
/// * If the suffix (specified either way) contains a path separator.
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(parse_template("XXX", None).unwrap(), ("", 3, ""));
/// assert_eq!(parse_template("abcXXX", None).unwrap(), ("abc", 3, ""));
/// assert_eq!(parse_template("XXXdef", None).unwrap(), ("", 3, "def"));
/// assert_eq!(parse_template("abcXXXdef", None).unwrap(), ("abc", 3, "def"));
/// ```
fn parse_template<'a>( fn parse_template<'a>(
temp: &'a str, temp: &'a str,
suffix: Option<&'a str>, suffix: Option<&'a str>,
) -> UResult<(&'a str, usize, &'a str)> { ) -> Result<(&'a str, usize, &'a str), MkTempError> {
let right = match temp.rfind('X') { let right = match temp.rfind('X') {
Some(r) => r + 1, Some(r) => r + 1,
None => return Err(MkTempError::TooFewXs(temp.into()).into()), None => return Err(MkTempError::TooFewXs(temp.into())),
}; };
let left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1); let left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1);
let prefix = &temp[..left]; let prefix = &temp[..left];
let rand = right - left; let rand = right - left;
if rand < 3 { if rand < 3 {
return Err(MkTempError::TooFewXs(temp.into()).into()); return Err(MkTempError::TooFewXs(temp.into()));
} }
let mut suf = &temp[right..]; let mut suf = &temp[right..];
@ -211,12 +261,16 @@ fn parse_template<'a>(
if suf.is_empty() { if suf.is_empty() {
suf = s; suf = s;
} else { } else {
return Err(MkTempError::MustEndInX(temp.into()).into()); return Err(MkTempError::MustEndInX(temp.into()));
} }
}; };
if prefix.chars().any(is_separator) {
return Err(MkTempError::PrefixContainsDirSeparator(temp.into()));
}
if suf.chars().any(is_separator) { if suf.chars().any(is_separator) {
return Err(MkTempError::ContainsDirSeparator(suf.into()).into()); return Err(MkTempError::SuffixContainsDirSeparator(suf.into()));
} }
Ok((prefix, rand, suf)) Ok((prefix, rand, suf))
@ -272,5 +326,53 @@ fn exec(dir: &Path, prefix: &str, rand: usize, suffix: &str, make_dir: bool) ->
.map_err(|e| MkTempError::PersistError(e.file.path().to_path_buf()))? .map_err(|e| MkTempError::PersistError(e.file.path().to_path_buf()))?
.1 .1
}; };
// Get just the last component of the path to the created
// temporary file or directory.
let filename = path.file_name();
let filename = filename.unwrap().to_str().unwrap();
// Join the directory to the path to get the path to print. We
// cannot use the path returned by the `Builder` because it gives
// the absolute path and we need to return a filename that matches
// the template given on the command-line which might be a
// relative path.
let mut path = dir.to_path_buf();
path.push(filename);
println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned()) println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned())
} }
#[cfg(test)]
mod tests {
use crate::parse_template;
#[test]
fn test_parse_template_no_suffix() {
assert_eq!(parse_template("XXX", None).unwrap(), ("", 3, ""));
assert_eq!(parse_template("abcXXX", None).unwrap(), ("abc", 3, ""));
assert_eq!(parse_template("XXXdef", None).unwrap(), ("", 3, "def"));
assert_eq!(
parse_template("abcXXXdef", None).unwrap(),
("abc", 3, "def")
);
}
#[test]
fn test_parse_template_suffix() {
assert_eq!(parse_template("XXX", Some("def")).unwrap(), ("", 3, "def"));
assert_eq!(
parse_template("abcXXX", Some("def")).unwrap(),
("abc", 3, "def")
);
}
#[test]
fn test_parse_template_errors() {
assert!(parse_template("a/bXXX", None).is_err());
assert!(parse_template("XXXa/b", None).is_err());
assert!(parse_template("XX", None).is_err());
assert!(parse_template("XXXabc", Some("def")).is_err());
assert!(parse_template("XXX", Some("a/b")).is_err());
}
}

View file

@ -23,7 +23,7 @@ unicode-width = "0.1.7"
unicode-segmentation = "1.9.0" unicode-segmentation = "1.9.0"
[target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies] [target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies]
nix = "0.23.1" nix = { version = "0.24.1", default-features = false }
[[bin]] [[bin]]
name = "more" name = "more"

View file

@ -183,7 +183,8 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::FILES) Arg::new(options::FILES)
.required(false) .required(false)
.multiple_occurrences(true) .multiple_occurrences(true)
.help("Path to the files to be read"), .help("Path to the files to be read")
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -156,14 +156,15 @@ pub fn uu_app<'a>() -> Command<'a> {
.help("move all SOURCE arguments into DIRECTORY") .help("move all SOURCE arguments into DIRECTORY")
.takes_value(true) .takes_value(true)
.value_name("DIRECTORY") .value_name("DIRECTORY")
.value_hint(clap::ValueHint::DirPath)
.conflicts_with(OPT_NO_TARGET_DIRECTORY) .conflicts_with(OPT_NO_TARGET_DIRECTORY)
.allow_invalid_utf8(true) .allow_invalid_utf8(true)
) )
.arg( .arg(
Arg::new(OPT_NO_TARGET_DIRECTORY) Arg::new(OPT_NO_TARGET_DIRECTORY)
.short('T') .short('T')
.long(OPT_NO_TARGET_DIRECTORY). .long(OPT_NO_TARGET_DIRECTORY)
help("treat DEST as a normal file") .help("treat DEST as a normal file")
) )
.arg( .arg(
Arg::new(OPT_UPDATE) Arg::new(OPT_UPDATE)
@ -183,6 +184,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.min_values(2) .min_values(2)
.required(true) .required(true)
.allow_invalid_utf8(true) .allow_invalid_utf8(true)
.value_hint(clap::ValueHint::AnyPath)
) )
} }

View file

@ -16,8 +16,8 @@ path = "src/nice.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.121" libc = "0.2.125"
nix = "0.23.1" nix = { version = "0.24.1", default-features = false }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
[[bin]] [[bin]]

View file

@ -17,7 +17,7 @@ use std::ptr;
use clap::{crate_version, Arg, Command}; use clap::{crate_version, Arg, Command};
use uucore::{ use uucore::{
error::{set_exit_code, UResult, USimpleError, UUsageError}, error::{set_exit_code, UClapError, UResult, USimpleError, UUsageError},
format_usage, format_usage,
}; };
@ -35,7 +35,7 @@ const USAGE: &str = "{} [OPTIONS] [COMMAND [ARGS]]";
#[uucore::main] #[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().get_matches_from(args); let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?;
let mut niceness = unsafe { let mut niceness = unsafe {
nix::errno::Errno::clear(); nix::errno::Errno::clear();
@ -117,5 +117,9 @@ pub fn uu_app<'a>() -> Command<'a> {
.takes_value(true) .takes_value(true)
.allow_hyphen_values(true), .allow_hyphen_values(true),
) )
.arg(Arg::new(options::COMMAND).multiple_occurrences(true)) .arg(
Arg::new(options::COMMAND)
.multiple_occurrences(true)
.value_hint(clap::ValueHint::CommandName),
)
} }

View file

@ -154,7 +154,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::BODY_NUMBERING) Arg::new(options::BODY_NUMBERING)

View file

@ -16,7 +16,7 @@ path = "src/nohup.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.121" libc = "0.2.125"
atty = "0.2" atty = "0.2"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] }

View file

@ -126,7 +126,8 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::CMD) Arg::new(options::CMD)
.hide(true) .hide(true)
.required(true) .required(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::CommandName),
) )
.trailing_var_arg(true) .trailing_var_arg(true)
.infer_long_args(true) .infer_long_args(true)

View file

@ -15,7 +15,7 @@ edition = "2021"
path = "src/nproc.rs" path = "src/nproc.rs"
[dependencies] [dependencies]
libc = "0.2.121" libc = "0.2.125"
num_cpus = "1.10" num_cpus = "1.10"
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] }

View file

@ -512,7 +512,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILENAME) Arg::new(options::FILENAME)
.hide(true) .hide(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -71,7 +71,8 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::FILE) Arg::new(options::FILE)
.value_name("FILE") .value_name("FILE")
.multiple_occurrences(true) .multiple_occurrences(true)
.default_value("-"), .default_value("-")
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -16,7 +16,7 @@ path = "src/pathchk.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.121" libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
[[bin]] [[bin]]

View file

@ -108,7 +108,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::PATH) Arg::new(options::PATH)
.hide(true) .hide(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::AnyPath),
) )
} }

View file

@ -179,7 +179,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::USER) Arg::new(options::USER)
.takes_value(true) .takes_value(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::Username),
) )
.arg( .arg(
// Redefine the help argument to not include the short flag // Redefine the help argument to not include the short flag
@ -221,10 +222,10 @@ impl Capitalize for str {
fn idle_string(when: i64) -> String { fn idle_string(when: i64) -> String {
thread_local! { thread_local! {
static NOW: time::Tm = time::now() static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
} }
NOW.with(|n| { NOW.with(|n| {
let duration = n.to_timespec().sec - when; let duration = n.unix_timestamp() - when;
if duration < 60 { if duration < 60 {
// less than 1min // less than 1min
" ".to_owned() " ".to_owned()
@ -242,7 +243,11 @@ fn idle_string(when: i64) -> String {
} }
fn time_string(ut: &Utmpx) -> String { fn time_string(ut: &Utmpx) -> String {
time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C // "%b %e %H:%M"
let time_format: Vec<time::format_description::FormatItem> =
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
.unwrap();
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
} }
fn gecos_to_fullname(pw: &Passwd) -> Option<String> { fn gecos_to_fullname(pw: &Passwd) -> Option<String> {

View file

@ -372,6 +372,7 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::FILES) Arg::new(options::FILES)
.multiple_occurrences(true) .multiple_occurrences(true)
.multiple_values(true) .multiple_values(true)
.value_hint(clap::ValueHint::FilePath)
) )
} }
@ -399,7 +400,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
if matches.is_present(options::HELP) { if matches.is_present(options::HELP) {
command.print_long_help()?; command.print_help()?;
return Ok(()); return Ok(());
} }

View file

@ -5,7 +5,7 @@
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
// spell-checker:ignore (ToDOs) corasick memchr Roff trunc oset iset // spell-checker:ignore (ToDOs) corasick memchr Roff trunc oset iset CHARCLASS
use clap::{crate_version, Arg, Command}; use clap::{crate_version, Arg, Command};
use regex::Regex; use regex::Regex;
@ -31,6 +31,8 @@ const ABOUT: &str = "\
Mandatory arguments to long options are mandatory for short options too.\n\ Mandatory arguments to long options are mandatory for short options too.\n\
With no FILE, or when FILE is -, read standard input. Default is '-F /'."; With no FILE, or when FILE is -, read standard input. Default is '-F /'.";
const REGEX_CHARCLASS: &str = "^-]\\";
#[derive(Debug)] #[derive(Debug)]
enum OutFormat { enum OutFormat {
Dumb, Dumb,
@ -88,6 +90,18 @@ fn read_word_filter_file(
Ok(words) Ok(words)
} }
/// reads contents of file as unique set of characters to be used with the break-file option
fn read_char_filter_file(
matches: &clap::ArgMatches,
option: &str,
) -> std::io::Result<HashSet<char>> {
let filename = matches.value_of(option).expect("parsing options failed!");
let mut reader = File::open(filename)?;
let mut buffer = String::new();
reader.read_to_string(&mut buffer)?;
Ok(buffer.chars().collect())
}
#[derive(Debug)] #[derive(Debug)]
struct WordFilter { struct WordFilter {
only_specified: bool, only_specified: bool,
@ -113,9 +127,23 @@ impl WordFilter {
} else { } else {
(false, HashSet::new()) (false, HashSet::new())
}; };
if matches.is_present(options::BREAK_FILE) { let break_set: Option<HashSet<char>> = if matches.is_present(options::BREAK_FILE)
return Err(PtxError::NotImplemented("-b").into()); && !matches.is_present(options::WORD_REGEXP)
} {
let chars =
read_char_filter_file(matches, options::BREAK_FILE).map_err_context(String::new)?;
let mut hs: HashSet<char> = if config.gnu_ext {
HashSet::new() // really only chars found in file
} else {
// GNU off means at least these are considered
[' ', '\t', '\n'].iter().cloned().collect()
};
hs.extend(chars);
Some(hs)
} else {
// if -W takes precedence or default
None
};
// Ignore empty string regex from cmd-line-args // Ignore empty string regex from cmd-line-args
let arg_reg: Option<String> = if matches.is_present(options::WORD_REGEXP) { let arg_reg: Option<String> = if matches.is_present(options::WORD_REGEXP) {
match matches.value_of(options::WORD_REGEXP) { match matches.value_of(options::WORD_REGEXP) {
@ -134,7 +162,21 @@ impl WordFilter {
let reg = match arg_reg { let reg = match arg_reg {
Some(arg_reg) => arg_reg, Some(arg_reg) => arg_reg,
None => { None => {
if config.gnu_ext { if break_set.is_some() {
format!(
"[^{}]+",
break_set
.unwrap()
.into_iter()
.map(|c| if REGEX_CHARCLASS.contains(c) {
format!("\\{}", c)
} else {
c.to_string()
})
.collect::<Vec<String>>()
.join("")
)
} else if config.gnu_ext {
"\\w+".to_owned() "\\w+".to_owned()
} else { } else {
"[^ \t\n]+".to_owned() "[^ \t\n]+".to_owned()
@ -712,7 +754,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::AUTO_REFERENCE) Arg::new(options::AUTO_REFERENCE)
@ -784,7 +827,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::BREAK_FILE) .long(options::BREAK_FILE)
.help("word break characters in this FILE") .help("word break characters in this FILE")
.value_name("FILE") .value_name("FILE")
.takes_value(true), .takes_value(true)
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::IGNORE_CASE) Arg::new(options::IGNORE_CASE)
@ -807,7 +851,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::IGNORE_FILE) .long(options::IGNORE_FILE)
.help("read ignore word list from FILE") .help("read ignore word list from FILE")
.value_name("FILE") .value_name("FILE")
.takes_value(true), .takes_value(true)
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::ONLY_FILE) Arg::new(options::ONLY_FILE)
@ -815,7 +860,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::ONLY_FILE) .long(options::ONLY_FILE)
.help("read only word list from this FILE") .help("read only word list from this FILE")
.value_name("FILE") .value_name("FILE")
.takes_value(true), .takes_value(true)
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::REFERENCES) Arg::new(options::REFERENCES)

View file

@ -161,7 +161,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(ARG_FILES) Arg::new(ARG_FILES)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true), .takes_value(true)
.value_hint(clap::ValueHint::AnyPath),
) )
} }

View file

@ -130,7 +130,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.min_values(1), .min_values(1)
.value_hint(clap::ValueHint::AnyPath),
) )
} }

View file

@ -87,6 +87,15 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg(Arg::new(options::DIR).short('d').takes_value(true).help( .arg(Arg::new(options::DIR).short('d').takes_value(true).help(
"If any of FROM and TO is not subpath of DIR, output absolute path instead of relative", "If any of FROM and TO is not subpath of DIR, output absolute path instead of relative",
)) ))
.arg(Arg::new(options::TO).required(true).takes_value(true)) .arg(
.arg(Arg::new(options::FROM).takes_value(true)) Arg::new(options::TO)
.required(true)
.takes_value(true)
.value_hint(clap::ValueHint::AnyPath),
)
.arg(
Arg::new(options::FROM)
.takes_value(true)
.value_hint(clap::ValueHint::AnyPath),
)
} }

View file

@ -227,6 +227,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.min_values(1) .min_values(1)
.value_hint(clap::ValueHint::AnyPath)
) )
} }

View file

@ -17,7 +17,7 @@ path = "src/rmdir.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
libc = "0.2.121" libc = "0.2.125"
[[bin]] [[bin]]
name = "rmdir" name = "rmdir"

View file

@ -197,6 +197,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.takes_value(true) .takes_value(true)
.min_values(1) .min_values(1)
.required(true) .required(true)
.allow_invalid_utf8(true), .allow_invalid_utf8(true)
.value_hint(clap::ValueHint::DirPath),
) )
} }

View file

@ -156,7 +156,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new("ARG") Arg::new("ARG")
.multiple_occurrences(true) .multiple_occurrences(true)
.allow_invalid_utf8(true), .allow_invalid_utf8(true)
.value_hint(clap::ValueHint::CommandName),
) )
// Once "ARG" is parsed, everything after that belongs to it. // Once "ARG" is parsed, everything after that belongs to it.
// //

View file

@ -19,7 +19,7 @@ path = "src/seq.rs"
bigdecimal = "0.3" bigdecimal = "0.3"
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
num-bigint = "0.4.0" num-bigint = "0.4.0"
num-traits = "0.2.14" num-traits = "0.2.15"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["memo"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["memo"] }
[[bin]] [[bin]]

View file

@ -374,7 +374,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -158,14 +158,16 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::OUTPUT) .long(options::OUTPUT)
.takes_value(true) .takes_value(true)
.value_name("FILE") .value_name("FILE")
.help("write result to FILE instead of standard output"), .help("write result to FILE instead of standard output")
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::RANDOM_SOURCE) Arg::new(options::RANDOM_SOURCE)
.long(options::RANDOM_SOURCE) .long(options::RANDOM_SOURCE)
.takes_value(true) .takes_value(true)
.value_name("FILE") .value_name("FILE")
.help("get random bytes from FILE"), .help("get random bytes from FILE")
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::REPEAT) Arg::new(options::REPEAT)
@ -179,7 +181,11 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::ZERO_TERMINATED) .long(options::ZERO_TERMINATED)
.help("line delimiter is NUL, not newline"), .help("line delimiter is NUL, not newline"),
) )
.arg(Arg::new(options::FILE).takes_value(true)) .arg(
Arg::new(options::FILE)
.takes_value(true)
.value_hint(clap::ValueHint::FilePath),
)
} }
fn read_input_file(filename: &str) -> UResult<Vec<u8>> { fn read_input_file(filename: &str) -> UResult<Vec<u8>> {

View file

@ -21,7 +21,7 @@ compare = "0.1.0"
ctrlc = { version = "3.0", features = ["termination"] } ctrlc = { version = "3.0", features = ["termination"] }
fnv = "1.0.7" fnv = "1.0.7"
itertools = "0.10.0" itertools = "0.10.0"
memchr = "2.4.0" memchr = "2.5.0"
ouroboros = "0.15.0" ouroboros = "0.15.0"
rand = "0.8" rand = "0.8"
rayon = "1.5" rayon = "1.5"

View file

@ -1272,6 +1272,7 @@ pub fn uu_app<'a>() -> Command<'a> {
Command::new(uucore::util_name()) Command::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.after_help(LONG_HELP_KEYS)
.override_usage(format_usage(USAGE)) .override_usage(format_usage(USAGE))
.infer_long_args(true) .infer_long_args(true)
.arg( .arg(
@ -1396,7 +1397,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::OUTPUT) .long(options::OUTPUT)
.help("write output to FILENAME instead of stdout") .help("write output to FILENAME instead of stdout")
.takes_value(true) .takes_value(true)
.value_name("FILENAME"), .value_name("FILENAME")
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::REVERSE) Arg::new(options::REVERSE)
@ -1421,7 +1423,6 @@ pub fn uu_app<'a>() -> Command<'a> {
.short('k') .short('k')
.long(options::KEY) .long(options::KEY)
.help("sort by a key") .help("sort by a key")
.long_help(LONG_HELP_KEYS)
.multiple_occurrences(true) .multiple_occurrences(true)
.number_of_values(1) .number_of_values(1)
.takes_value(true), .takes_value(true),
@ -1461,14 +1462,15 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::TMP_DIR) .long(options::TMP_DIR)
.help("use DIR for temporaries, not $TMPDIR or /tmp") .help("use DIR for temporaries, not $TMPDIR or /tmp")
.takes_value(true) .takes_value(true)
.value_name("DIR"), .value_name("DIR")
.value_hint(clap::ValueHint::DirPath),
) )
.arg( .arg(
Arg::new(options::COMPRESS_PROG) Arg::new(options::COMPRESS_PROG)
.long(options::COMPRESS_PROG) .long(options::COMPRESS_PROG)
.help("compress temporary files with PROG, decompress with PROG -d") .help("compress temporary files with PROG, decompress with PROG -d; PROG has to take input from stdin and output to stdout")
.long_help("PROG has to take input from stdin and output to stdout") .value_name("PROG")
.value_name("PROG"), .value_hint(clap::ValueHint::CommandName),
) )
.arg( .arg(
Arg::new(options::BATCH_SIZE) Arg::new(options::BATCH_SIZE)
@ -1483,7 +1485,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.takes_value(true) .takes_value(true)
.value_name("NUL_FILES") .value_name("NUL_FILES")
.multiple_occurrences(true) .multiple_occurrences(true)
.allow_invalid_utf8(true), .allow_invalid_utf8(true)
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::DEBUG) Arg::new(options::DEBUG)
@ -1494,7 +1497,8 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::FILES) Arg::new(options::FILES)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.allow_invalid_utf8(true), .allow_invalid_utf8(true)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -111,8 +111,10 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(OPT_FILTER) Arg::new(OPT_FILTER)
.long(OPT_FILTER) .long(OPT_FILTER)
.takes_value(true) .takes_value(true)
.value_name("COMMAND")
.value_hint(clap::ValueHint::CommandName)
.help( .help(
"write to shell COMMAND file name is $FILE (Currently not implemented for Windows)", "write to shell COMMAND; file name is $FILE (Currently not implemented for Windows)",
), ),
) )
.arg( .arg(
@ -162,7 +164,8 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(ARG_INPUT) Arg::new(ARG_INPUT)
.takes_value(true) .takes_value(true)
.default_value("-") .default_value("-")
.index(1), .index(1)
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(ARG_PREFIX) Arg::new(ARG_PREFIX)

View file

@ -19,7 +19,9 @@ use uucore::{entries, format_usage};
use clap::{crate_version, Arg, ArgMatches, Command}; use clap::{crate_version, Arg, ArgMatches, Command};
use std::borrow::Cow; use std::borrow::Cow;
use std::convert::AsRef; use std::convert::AsRef;
use std::ffi::{OsStr, OsString};
use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::os::unix::fs::{FileTypeExt, MetadataExt};
use std::os::unix::prelude::OsStrExt;
use std::path::Path; use std::path::Path;
use std::{cmp, fs, iter}; use std::{cmp, fs, iter};
@ -221,7 +223,7 @@ pub struct Stater {
follow: bool, follow: bool,
show_fs: bool, show_fs: bool,
from_user: bool, from_user: bool,
files: Vec<String>, files: Vec<OsString>,
mount_list: Option<Vec<String>>, mount_list: Option<Vec<String>>,
default_tokens: Vec<Token>, default_tokens: Vec<Token>,
default_dev_tokens: Vec<Token>, default_dev_tokens: Vec<Token>,
@ -471,24 +473,10 @@ impl Stater {
} }
fn new(matches: &ArgMatches) -> UResult<Self> { fn new(matches: &ArgMatches) -> UResult<Self> {
let mut files: Vec<String> = matches let files = matches
.values_of(ARG_FILES) .values_of_os(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(OsString::from).collect())
.unwrap_or_default(); .unwrap_or_default();
#[cfg(unix)]
if files.contains(&String::from("-")) {
let redirected_path = Path::new("/dev/stdin")
.canonicalize()
.expect("unable to canonicalize /dev/stdin")
.into_os_string()
.into_string()
.unwrap();
for file in &mut files {
if file == "-" {
*file = redirected_path.clone();
}
}
}
let format_str = if matches.is_present(options::PRINTF) { let format_str = if matches.is_present(options::PRINTF) {
matches matches
.value_of(options::PRINTF) .value_of(options::PRINTF)
@ -550,19 +538,37 @@ impl Stater {
} }
fn exec(&self) -> i32 { fn exec(&self) -> i32 {
let mut stdin_is_fifo = false;
if cfg!(unix) {
if let Ok(md) = fs::metadata("/dev/stdin") {
stdin_is_fifo = md.file_type().is_fifo();
}
}
let mut ret = 0; let mut ret = 0;
for f in &self.files { for f in &self.files {
ret |= self.do_stat(f.as_str()); ret |= self.do_stat(f, stdin_is_fifo);
} }
ret ret
} }
fn do_stat(&self, file: &str) -> i32 { fn do_stat(&self, file: &OsStr, stdin_is_fifo: bool) -> i32 {
if !self.show_fs { let display_name = file.to_string_lossy();
let result = if self.follow { let file: OsString = if cfg!(unix) && display_name.eq("-") {
fs::metadata(file) if let Ok(p) = Path::new("/dev/stdin").canonicalize() {
p.into_os_string()
} else { } else {
fs::symlink_metadata(file) OsString::from("/dev/stdin")
}
} else {
OsString::from(file)
};
if !self.show_fs {
let result = if self.follow || stdin_is_fifo && display_name.eq("-") {
fs::metadata(&file)
} else {
fs::symlink_metadata(&file)
}; };
match result { match result {
Ok(meta) => { Ok(meta) => {
@ -658,28 +664,32 @@ impl Stater {
// mount point // mount point
'm' => { 'm' => {
arg = self.find_mount_point(file).unwrap(); arg = self.find_mount_point(&file).unwrap();
output_type = OutputType::Str; output_type = OutputType::Str;
} }
// file name // file name
'n' => { 'n' => {
arg = file.to_owned(); arg = display_name.to_string();
output_type = OutputType::Str; output_type = OutputType::Str;
} }
// quoted file name with dereference if symbolic link // quoted file name with dereference if symbolic link
'N' => { 'N' => {
if file_type.is_symlink() { if file_type.is_symlink() {
let dst = match fs::read_link(file) { let dst = match fs::read_link(&file) {
Ok(path) => path, Ok(path) => path,
Err(e) => { Err(e) => {
println!("{}", e); println!("{}", e);
return 1; return 1;
} }
}; };
arg = format!("{} -> {}", file.quote(), dst.quote()); arg = format!(
"{} -> {}",
display_name.quote(),
dst.quote()
);
} else { } else {
arg = file.to_string(); arg = display_name.to_string();
} }
output_type = OutputType::Str; output_type = OutputType::Str;
} }
@ -771,12 +781,16 @@ impl Stater {
} }
} }
Err(e) => { Err(e) => {
show_error!("cannot stat {}: {}", file.quote(), e); show_error!("cannot stat {}: {}", display_name.quote(), e);
return 1; return 1;
} }
} }
} else { } else {
match statfs(file) { #[cfg(unix)]
let p = file.as_bytes();
#[cfg(not(unix))]
let p = file.into_string().unwrap();
match statfs(p) {
Ok(meta) => { Ok(meta) => {
let tokens = &self.default_tokens; let tokens = &self.default_tokens;
@ -829,7 +843,7 @@ impl Stater {
} }
// file name // file name
'n' => { 'n' => {
arg = file.to_owned(); arg = display_name.to_string();
output_type = OutputType::Str; output_type = OutputType::Str;
} }
// block size (for faster transfers) // block size (for faster transfers)
@ -866,7 +880,7 @@ impl Stater {
Err(e) => { Err(e) => {
show_error!( show_error!(
"cannot read file system information for {}: {}", "cannot read file system information for {}: {}",
file.quote(), display_name.quote(),
e e
); );
return 1; return 1;
@ -1028,6 +1042,8 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(ARG_FILES) Arg::new(ARG_FILES)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.min_values(1), .allow_invalid_utf8(true)
.min_values(1)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -232,6 +232,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.hide(true) .hide(true)
.required(true), .required(true)
.value_hint(clap::ValueHint::CommandName),
) )
} }

View file

@ -150,7 +150,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.multiple_occurrences(true) .multiple_occurrences(true)
.hide(true), .hide(true)
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::BSD_COMPATIBLE) Arg::new(options::BSD_COMPATIBLE)

View file

@ -16,7 +16,7 @@ path = "src/sync.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.121" libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["wide"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["wide"] }
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "std", "winbase", "winerror"] } winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "std", "winbase", "winerror"] }

View file

@ -217,7 +217,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(ARG_FILES) Arg::new(ARG_FILES)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true), .takes_value(true)
.value_hint(clap::ValueHint::AnyPath),
) )
} }

View file

@ -91,7 +91,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -17,7 +17,7 @@ path = "src/tail.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.121" libc = "0.2.125"
# notify = { version = "5.0.0-pre.14", features=["macos_kqueue"]} # notify = { version = "5.0.0-pre.14", features=["macos_kqueue"]}
notify = { git = "https://github.com/notify-rs/notify", features=["macos_kqueue"]} notify = { git = "https://github.com/notify-rs/notify", features=["macos_kqueue"]}
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["ringbuffer", "lines"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["ringbuffer", "lines"] }
@ -26,7 +26,7 @@ uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=[
winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] } winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = "0.23.1" nix = { version = "0.24.1", default-features = false, features=["fs"] }
[[bin]] [[bin]]
name = "tail" name = "tail"

View file

@ -600,7 +600,8 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::ARG_FILES) Arg::new(options::ARG_FILES)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.min_values(1), .min_values(1)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -16,8 +16,8 @@ path = "src/tee.rs"
[dependencies] [dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] } clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.121" libc = "0.2.125"
retain_mut = "=0.1.2" # ToDO: [2021-01-01; rivy; maint/MinSRV] ~ v0.1.5 uses const generics which aren't stabilized until rust v1.51.0 retain_mut = "=0.1.7" # ToDO: [2021-01-01; rivy; maint/MinSRV] ~ v0.1.5 uses const generics which aren't stabilized until rust v1.51.0
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] }
[[bin]] [[bin]]

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