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:
commit
409878e323
150 changed files with 2505 additions and 863 deletions
|
@ -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"]
|
||||||
|
|
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
|
@ -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
|
||||||
|
|
48
.github/workflows/CICD.yml
vendored
48
.github/workflows/CICD.yml
vendored
|
@ -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 }}
|
||||||
|
|
8
.github/workflows/FixPR.yml
vendored
8
.github/workflows/FixPR.yml
vendored
|
@ -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
|
||||||
|
|
39
.github/workflows/GnuTests.yml
vendored
39
.github/workflows/GnuTests.yml
vendored
|
@ -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
380
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"]
|
||||||
|
|
3
build.rs
3
build.rs
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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>> {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
return Err(USimpleError::new(
|
// It's possible that the `user` string contains a
|
||||||
1,
|
// numeric user ID, in which case, we respect that.
|
||||||
format!("invalid user: {}", spec.quote()),
|
match user.parse() {
|
||||||
));
|
Ok(uid) => uid,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
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)))
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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,42 +465,55 @@ 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
|
);
|
||||||
))
|
let matches = uu_app().after_help(after_help).try_get_matches_from(args);
|
||||||
.try_get_matches_from(args)?;
|
|
||||||
|
|
||||||
let options = Options::from_matches(&matches)?;
|
// 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);
|
||||||
|
|
||||||
if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup {
|
match e.kind() {
|
||||||
show_usage_error!("options --backup and --no-clobber are mutually exclusive");
|
clap::ErrorKind::DisplayHelp => {
|
||||||
return Err(ExitCode(EXIT_ERR).into());
|
app.print_help()?;
|
||||||
}
|
}
|
||||||
|
clap::ErrorKind::DisplayVersion => println!("{}", app.render_version()),
|
||||||
let paths: Vec<String> = matches
|
_ => return Err(Box::new(e.with_exit_code(1))),
|
||||||
.values_of(options::PATHS)
|
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let (sources, target) = parse_path_args(&paths, &options)?;
|
|
||||||
|
|
||||||
if let Err(error) = copy(&sources, &target, &options) {
|
|
||||||
match error {
|
|
||||||
// Error::NotAllFilesCopied is non-fatal, but the error
|
|
||||||
// code should still be EXIT_ERR as does GNU cp
|
|
||||||
Error::NotAllFilesCopied => {}
|
|
||||||
// Else we caught a fatal bubbled-up error, log it to stderr
|
|
||||||
_ => show_error!("{}", error),
|
|
||||||
};
|
};
|
||||||
set_exit_code(EXIT_ERR);
|
} else if let Ok(matches) = matches {
|
||||||
|
let options = Options::from_matches(&matches)?;
|
||||||
|
|
||||||
|
if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup {
|
||||||
|
show_usage_error!("options --backup and --no-clobber are mutually exclusive");
|
||||||
|
return Err(ExitCode(EXIT_ERR).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let paths: Vec<String> = matches
|
||||||
|
.values_of(options::PATHS)
|
||||||
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let (sources, target) = parse_path_args(&paths, &options)?;
|
||||||
|
|
||||||
|
if let Err(error) = copy(&sources, &target, &options) {
|
||||||
|
match error {
|
||||||
|
// Error::NotAllFilesCopied is non-fatal, but the error
|
||||||
|
// code should still be EXIT_ERR as does GNU cp
|
||||||
|
Error::NotAllFilesCopied => {}
|
||||||
|
// Else we caught a fatal bubbled-up error, log it to stderr
|
||||||
|
_ => show_error!("{}", error),
|
||||||
|
};
|
||||||
|
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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -614,6 +614,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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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 {
|
||||||
Self::Bytes(1024)
|
if env::var("POSIXLY_CORRECT").is_ok() {
|
||||||
|
Self::Bytes(512)
|
||||||
|
} else {
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 => {
|
||||||
1,
|
// this happens if specified file system type != file system type of the file
|
||||||
format!("{}: No such file or directory", path.as_ref().display())
|
if path.as_ref().exists() {
|
||||||
)),
|
show!(USimpleError::new(1, "no file systems processed"));
|
||||||
|
} else {
|
||||||
|
show!(USimpleError::new(
|
||||||
|
1,
|
||||||
|
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)
|
||||||
|
@ -481,7 +538,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
.default_values(&["source", "size", "used", "avail", "pcent", "target"])
|
.default_values(&["source", "size", "used", "avail", "pcent", "target"])
|
||||||
.conflicts_with_all(&[OPT_INODES, OPT_PORTABILITY, OPT_PRINT_TYPE])
|
.conflicts_with_all(&[OPT_INODES, OPT_PORTABILITY, OPT_PRINT_TYPE])
|
||||||
.help(
|
.help(
|
||||||
"use the output format defined by FIELD_LIST,\
|
"use the output format defined by FIELD_LIST, \
|
||||||
or print all fields if FIELD_LIST is omitted.",
|
or print all fields if FIELD_LIST is omitted.",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -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)]
|
||||||
|
|
|
@ -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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
2
src/uu/env/src/env.rs
vendored
2
src/uu/env/src/env.rs
vendored
|
@ -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)"))
|
||||||
|
|
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
||||||
|
|
|
@ -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. \
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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) {
|
||||||
let tmp = env::temp_dir();
|
// In this case, the command line was `mktemp -t XXX`, so we
|
||||||
(template, tmp)
|
// 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();
|
||||||
|
(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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>> {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,9 +111,11 @@ 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(
|
||||||
Arg::new(OPT_ELIDE_EMPTY_FILES)
|
Arg::new(OPT_ELIDE_EMPTY_FILES)
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue