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

Merge branch 'main' into mktemp-set-dir-mode

This commit is contained in:
353fc443 aka Seagull 2022-05-16 19:07:18 +01:00 committed by 353fc443
commit 2086d04996
No known key found for this signature in database
GPG key ID: D58B14ED3D42A937
88 changed files with 725 additions and 221 deletions

View file

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

View file

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

View file

@ -41,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 }}'
@ -146,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 }}
@ -229,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'
@ -293,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

8
Cargo.lock generated
View file

@ -273,9 +273,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "3.1.3" version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7ca9141e27e6ebc52e3c378b0c07f3cea52db46ed1cc5861735fb697b56356" checksum = "da92e6facd8d73c22745a5d3cbb59bdf8e46e3235c923e516527d8e81eec14a4"
dependencies = [ dependencies = [
"clap 3.1.15", "clap 3.1.15",
] ]
@ -2027,9 +2027,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]] [[package]]
name = "unindent" name = "unindent"
version = "0.1.8" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8" checksum = "52fee519a3e570f7df377a06a1a7775cdbfb7aa460be7e08de2b1f0e69973a44"
[[package]] [[package]]
name = "unix_socket" name = "unix_socket"

View file

@ -123,7 +123,12 @@ pub fn base_app<'a>(about: &'a str, usage: &'a str) -> Command<'a> {
) )
// "multiple" arguments are used to check whether there is more than one // "multiple" arguments are used to check whether there is more than one
// file passed in. // file passed in.
.arg(Arg::new(options::FILE).index(1).multiple_occurrences(true)) .arg(
Arg::new(options::FILE)
.index(1)
.multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
)
} }
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> UResult<Box<dyn Read + 'a>> { pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> UResult<Box<dyn Read + 'a>> {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -134,6 +134,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::REFERENCE) .long(options::REFERENCE)
.help("use RFILE's owner and group rather than specifying OWNER:GROUP values") .help("use RFILE's owner and group rather than specifying OWNER:GROUP values")
.value_name("RFILE") .value_name("RFILE")
.value_hint(clap::ValueHint::FilePath)
.min_values(1), .min_values(1),
) )
.arg( .arg(
@ -167,17 +168,18 @@ pub fn uu_app<'a>() -> Command<'a> {
) )
} }
/// Parse the username and groupname /// Parse the owner/group specifier string into a user ID and a group ID.
/// ///
/// In theory, it should be username:groupname /// The `spec` can be of the form:
/// but ...
/// it can user.name:groupname
/// or username.groupname
/// ///
/// # Arguments /// * `"owner:group"`,
/// * `"owner"`,
/// * `":group"`,
/// ///
/// * `spec` - The input from the user /// and the owner or group can be specified either as an ID or a
/// * `sep` - Should be ':' or '.' /// name. The `sep` argument specifies which character to use as a
/// separator between the owner and group; calling code should set
/// this to `':'`.
fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> { fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
assert!(['.', ':'].contains(&sep)); assert!(['.', ':'].contains(&sep));
let mut args = spec.splitn(2, sep); let mut args = spec.splitn(2, sep);
@ -197,10 +199,17 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
// So, try to parse it this way // So, try to parse it this way
return parse_spec(spec, '.'); return parse_spec(spec, '.');
} else { } else {
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)))
));
}
} }

View file

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

View file

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

View file

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

View file

@ -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,7 +465,8 @@ 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]

View file

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

View file

@ -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)
) )
} }

View file

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

View file

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

View file

@ -7,7 +7,10 @@ use crate::OPT_BLOCKSIZE;
use clap::ArgMatches; use clap::ArgMatches;
use std::{env, fmt}; use std::{env, fmt};
use uucore::parse_size::{parse_size, ParseSizeError}; 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] = [
@ -167,6 +170,15 @@ pub(crate) enum BlockSize {
Bytes(u64), Bytes(u64),
} }
impl BlockSize {
/// Returns the associated value
pub(crate) fn as_u64(&self) -> u64 {
match *self {
Self::Bytes(n) => n,
}
}
}
impl Default for BlockSize { impl Default for BlockSize {
fn default() -> Self { fn default() -> Self {
if env::var("POSIXLY_CORRECT").is_ok() { if env::var("POSIXLY_CORRECT").is_ok() {
@ -180,7 +192,13 @@ impl Default for BlockSize {
pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> { pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> {
if matches.is_present(OPT_BLOCKSIZE) { 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(parse_size(s)?)) 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())
} }

View file

@ -12,6 +12,7 @@ mod filesystem;
mod table; mod table;
use blocks::{HumanReadable, SizeFormat}; 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};
@ -72,6 +73,7 @@ struct Options {
show_all_fs: bool, show_all_fs: bool,
size_format: SizeFormat, 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.
/// ///
@ -99,6 +101,7 @@ impl Default for Options {
show_all_fs: Default::default(), show_all_fs: Default::default(),
block_size: Default::default(), block_size: Default::default(),
size_format: 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(),
@ -176,6 +179,21 @@ impl Options {
), ),
ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s), 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: { size_format: {
if matches.is_present(OPT_HUMAN_READABLE_BINARY) { if matches.is_present(OPT_HUMAN_READABLE_BINARY) {
SizeFormat::HumanReadable(HumanReadable::Binary) SizeFormat::HumanReadable(HumanReadable::Binary)
@ -565,7 +583,11 @@ pub fn uu_app<'a>() -> Command<'a> {
.multiple_occurrences(true) .multiple_occurrences(true)
.help("limit listing to file systems not of type TYPE"), .help("limit listing to file systems not of type TYPE"),
) )
.arg(Arg::new(OPT_PATHS).multiple_occurrences(true)) .arg(
Arg::new(OPT_PATHS)
.multiple_occurrences(true)
.value_hint(clap::ValueHint::AnyPath),
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -233,7 +233,7 @@ impl<'a> RowFormatter<'a> {
SizeFormat::HumanReadable(h) => self.scaled_human_readable(size, h), SizeFormat::HumanReadable(h) => self.scaled_human_readable(size, h),
SizeFormat::StaticBlockSize => { SizeFormat::StaticBlockSize => {
let BlockSize::Bytes(d) = self.options.block_size; let BlockSize::Bytes(d) = self.options.block_size;
(size / d).to_string() (size as f64 / d as f64).ceil().to_string()
} }
} }
} }
@ -289,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 {}
@ -302,15 +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 => match options.size_format { Column::Size => match options.header_mode {
SizeFormat::HumanReadable(_) => String::from("Size"), HeaderMode::HumanReadable => String::from("Size"),
SizeFormat::StaticBlockSize => { HeaderMode::PosixPortability => {
format!("{}-blocks", options.block_size) 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"),
@ -428,7 +452,7 @@ mod tests {
use crate::blocks::{HumanReadable, SizeFormat}; 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] = [
@ -548,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 {
size_format: SizeFormat::HumanReadable(HumanReadable::Binary), 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 {
size_format: SizeFormat::HumanReadable(HumanReadable::Decimal), 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"
) )
@ -757,4 +793,28 @@ mod tests {
let fmt = RowFormatter::new(&row, &options); let fmt = RowFormatter::new(&row, &options);
assert_eq!(fmt.get_values(), vec!("26%")); assert_eq!(fmt.get_values(), vec!("26%"));
} }
#[test]
fn test_row_formatter_with_round_up_byte_values() {
fn get_formatted_values(bytes: u64, bytes_used: u64, bytes_avail: u64) -> Vec<String> {
let options = Options {
block_size: BlockSize::Bytes(1000),
columns: vec![Column::Size, Column::Used, Column::Avail],
..Default::default()
};
let row = Row {
bytes,
bytes_used,
bytes_avail,
..Default::default()
};
RowFormatter::new(&row, &options).get_values()
}
assert_eq!(get_formatted_values(100, 100, 0), vec!("1", "1", "0"));
assert_eq!(get_formatted_values(100, 99, 1), vec!("1", "1", "1"));
assert_eq!(get_formatted_values(1000, 1000, 0), vec!("1", "1", "0"));
assert_eq!(get_formatted_values(1001, 1000, 1), vec!("2", "1", "1"));
}
} }

View file

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

View file

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

View file

@ -884,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)
@ -913,6 +914,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::FILE) Arg::new(options::FILE)
.hide(true) .hide(true)
.value_hint(clap::ValueHint::AnyPath)
.multiple_occurrences(true) .multiple_occurrences(true)
) )
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,12 +13,16 @@ use uucore::display::{println_verbatim, Quotable};
use uucore::error::{FromIo, UError, UResult}; use uucore::error::{FromIo, UError, UResult};
use uucore::format_usage; use uucore::format_usage;
use std::env;
use std::error::Error; use std::error::Error;
use std::fmt::Display; use std::fmt::Display;
use std::iter; use std::iter;
use std::os::unix::prelude::PermissionsExt;
use std::path::{is_separator, Path, PathBuf}; use std::path::{is_separator, Path, PathBuf};
use std::{env, fs};
#[cfg(unix)]
use std::fs;
#[cfg(unix)]
use std::os::unix::prelude::PermissionsExt;
use rand::Rng; use rand::Rng;
use tempfile::Builder; use tempfile::Builder;
@ -42,7 +46,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),
} }
@ -57,7 +66,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",
@ -191,7 +207,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) \
@ -253,8 +270,12 @@ fn parse_template<'a>(
} }
}; };
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())); return Err(MkTempError::SuffixContainsDirSeparator(suf.into()));
} }
Ok((prefix, rand, suf)) Ok((prefix, rand, suf))
@ -358,11 +379,7 @@ mod tests {
#[test] #[test]
fn test_parse_template_errors() { fn test_parse_template_errors() {
// TODO This should be an error as well, but we are not assert!(parse_template("a/bXXX", None).is_err());
// catching it just yet. A future commit will correct this.
//
// assert!(parse_template("a/bXXX", None).is_err());
//
assert!(parse_template("XXXa/b", None).is_err()); assert!(parse_template("XXXa/b", None).is_err());
assert!(parse_template("XX", None).is_err()); assert!(parse_template("XX", None).is_err());
assert!(parse_template("XXXabc", Some("def")).is_err()); assert!(parse_template("XXXabc", Some("def")).is_err());

View file

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

View file

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

View file

@ -117,5 +117,9 @@ pub fn uu_app<'a>() -> Command<'a> {
.takes_value(true) .takes_value(true)
.allow_hyphen_values(true), .allow_hyphen_values(true),
) )
.arg(Arg::new(options::COMMAND).multiple_occurrences(true)) .arg(
Arg::new(options::COMMAND)
.multiple_occurrences(true)
.value_hint(clap::ValueHint::CommandName),
)
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -179,7 +179,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(options::USER) Arg::new(options::USER)
.takes_value(true) .takes_value(true)
.multiple_occurrences(true), .multiple_occurrences(true)
.value_hint(clap::ValueHint::Username),
) )
.arg( .arg(
// Redefine the help argument to not include the short flag // Redefine the help argument to not include the short flag

View file

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

View file

@ -754,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)
@ -826,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)
@ -849,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)
@ -857,7 +860,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::ONLY_FILE) .long(options::ONLY_FILE)
.help("read only word list from this FILE") .help("read only word list from this FILE")
.value_name("FILE") .value_name("FILE")
.takes_value(true), .takes_value(true)
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::REFERENCES) Arg::new(options::REFERENCES)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1397,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)
@ -1461,13 +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; PROG has to take input from stdin and output to stdout") .help("compress temporary files with PROG, decompress with PROG -d; 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)
@ -1482,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)
@ -1493,7 +1497,8 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(options::FILES) Arg::new(options::FILES)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.allow_invalid_utf8(true), .allow_invalid_utf8(true)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -74,7 +74,11 @@ pub fn uu_app<'a>() -> Command<'a> {
.short('i') .short('i')
.help("ignore interrupt signals (ignored on non-Unix platforms)"), .help("ignore interrupt signals (ignored on non-Unix platforms)"),
) )
.arg(Arg::new(options::FILE).multiple_occurrences(true)) .arg(
Arg::new(options::FILE)
.multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath),
)
} }
#[cfg(unix)] #[cfg(unix)]

View file

@ -170,6 +170,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.index(2) .index(2)
.required(true) .required(true)
.multiple_occurrences(true) .multiple_occurrences(true)
.value_hint(clap::ValueHint::CommandName)
) )
.trailing_var_arg(true) .trailing_var_arg(true)
.infer_long_args(true) .infer_long_args(true)

View file

@ -219,7 +219,8 @@ pub fn uu_app<'a>() -> Command<'a> {
.long(options::sources::REFERENCE) .long(options::sources::REFERENCE)
.help("use this file's times instead of the current time") .help("use this file's times instead of the current time")
.value_name("FILE") .value_name("FILE")
.allow_invalid_utf8(true), .allow_invalid_utf8(true)
.value_hint(clap::ValueHint::AnyPath),
) )
.arg( .arg(
Arg::new(options::TIME) Arg::new(options::TIME)
@ -238,7 +239,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::AnyPath),
) )
.group(ArgGroup::new(options::SOURCES).args(&[ .group(ArgGroup::new(options::SOURCES).args(&[
options::sources::CURRENT, options::sources::CURRENT,

View file

@ -143,7 +143,6 @@ pub fn uu_app<'a>() -> Command<'a> {
.about(ABOUT) .about(ABOUT)
.override_usage(format_usage(USAGE)) .override_usage(format_usage(USAGE))
.infer_long_args(true) .infer_long_args(true)
.infer_long_args(true)
.arg( .arg(
Arg::new(options::COMPLEMENT) Arg::new(options::COMPLEMENT)
.visible_short_alias('C') .visible_short_alias('C')

View file

@ -162,6 +162,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.required_unless_present(options::SIZE) .required_unless_present(options::SIZE)
.help("base the size of each file on the size of RFILE") .help("base the size of each file on the size of RFILE")
.value_name("RFILE") .value_name("RFILE")
.value_hint(clap::ValueHint::FilePath)
) )
.arg( .arg(
Arg::new(options::SIZE) Arg::new(options::SIZE)
@ -176,7 +177,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::FilePath))
} }
/// Truncate the named file to the specified size. /// Truncate the named file to the specified size.

View file

@ -99,7 +99,12 @@ pub fn uu_app<'a>() -> Command<'a> {
.override_usage(format_usage(USAGE)) .override_usage(format_usage(USAGE))
.about(SUMMARY) .about(SUMMARY)
.infer_long_args(true) .infer_long_args(true)
.arg(Arg::new(options::FILE).default_value("-").hide(true)) .arg(
Arg::new(options::FILE)
.default_value("-")
.hide(true)
.value_hint(clap::ValueHint::FilePath),
)
} }
// We use String as a representation of node here // We use String as a representation of node here

View file

@ -109,7 +109,12 @@ pub fn uu_app<'a>() -> Command<'a> {
.override_usage(format_usage(USAGE)) .override_usage(format_usage(USAGE))
.about(SUMMARY) .about(SUMMARY)
.infer_long_args(true) .infer_long_args(true)
.arg(Arg::new(options::FILE).hide(true).multiple_occurrences(true)) .arg(
Arg::new(options::FILE)
.hide(true)
.multiple_occurrences(true)
.value_hint(clap::ValueHint::FilePath)
)
.arg( .arg(
Arg::new(options::ALL) Arg::new(options::ALL)
.short('a') .short('a')

View file

@ -392,7 +392,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)
.max_values(2), .max_values(2)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

@ -36,6 +36,7 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(OPT_PATH) Arg::new(OPT_PATH)
.required(true) .required(true)
.hide(true) .hide(true)
.allow_invalid_utf8(true), .allow_invalid_utf8(true)
.value_hint(clap::ValueHint::AnyPath),
) )
} }

View file

@ -64,5 +64,10 @@ pub fn uu_app<'a>() -> Command<'a> {
.about(ABOUT) .about(ABOUT)
.override_usage(format_usage(USAGE)) .override_usage(format_usage(USAGE))
.infer_long_args(true) .infer_long_args(true)
.arg(Arg::new(ARG_FILES).takes_value(true).max_values(1)) .arg(
Arg::new(ARG_FILES)
.takes_value(true)
.max_values(1)
.value_hint(clap::ValueHint::FilePath),
)
} }

View file

@ -210,7 +210,8 @@ pub fn uu_app<'a>() -> Command<'a> {
"read input from the files specified by "read input from the files specified by
NUL-terminated names in file F; NUL-terminated names in file F;
If F is - then read names from standard input", If F is - then read names from standard input",
), )
.value_hint(clap::ValueHint::FilePath),
) )
.arg( .arg(
Arg::new(options::LINES) Arg::new(options::LINES)
@ -234,7 +235,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)
.allow_invalid_utf8(true), .allow_invalid_utf8(true)
.value_hint(clap::ValueHint::FilePath),
) )
} }

View file

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

View file

@ -85,6 +85,7 @@ use std::ffi::CString;
use std::io::Error as IOError; use std::io::Error as IOError;
#[cfg(unix)] #[cfg(unix)]
use std::mem; use std::mem;
#[cfg(not(unix))]
use std::path::Path; use std::path::Path;
use std::time::UNIX_EPOCH; use std::time::UNIX_EPOCH;
@ -709,9 +710,9 @@ impl FsMeta for StatFs {
} }
#[cfg(unix)] #[cfg(unix)]
pub fn statfs<P: AsRef<Path>>(path: P) -> Result<StatFs, String> pub fn statfs<P>(path: P) -> Result<StatFs, String>
where where
Vec<u8>: From<P>, P: Into<Vec<u8>>,
{ {
match CString::new(path) { match CString::new(path) {
Ok(p) => { Ok(p) => {

View file

@ -92,22 +92,25 @@ pub fn wrap_chown<P: AsRef<Path>>(
); );
if level == VerbosityLevel::Verbose { if level == VerbosityLevel::Verbose {
out = if verbosity.groups_only { out = if verbosity.groups_only {
let gid = meta.gid();
format!( format!(
"{}\nfailed to change group of {} from {} to {}", "{}\nfailed to change group of {} from {} to {}",
out, out,
path.quote(), path.quote(),
entries::gid2grp(meta.gid()).unwrap(), entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()),
entries::gid2grp(dest_gid).unwrap() entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
) )
} else { } else {
let uid = meta.uid();
let gid = meta.gid();
format!( format!(
"{}\nfailed to change ownership of {} from {}:{} to {}:{}", "{}\nfailed to change ownership of {} from {}:{} to {}:{}",
out, out,
path.quote(), path.quote(),
entries::uid2usr(meta.uid()).unwrap(), entries::uid2usr(uid).unwrap_or_else(|_| uid.to_string()),
entries::gid2grp(meta.gid()).unwrap(), entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()),
entries::uid2usr(dest_uid).unwrap(), entries::uid2usr(dest_uid).unwrap_or_else(|_| dest_uid.to_string()),
entries::gid2grp(dest_gid).unwrap() entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
) )
}; };
}; };
@ -119,21 +122,24 @@ pub fn wrap_chown<P: AsRef<Path>>(
if changed { if changed {
match verbosity.level { match verbosity.level {
VerbosityLevel::Changes | VerbosityLevel::Verbose => { VerbosityLevel::Changes | VerbosityLevel::Verbose => {
let gid = meta.gid();
out = if verbosity.groups_only { out = if verbosity.groups_only {
format!( format!(
"changed group of {} from {} to {}", "changed group of {} from {} to {}",
path.quote(), path.quote(),
entries::gid2grp(meta.gid()).unwrap(), entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()),
entries::gid2grp(dest_gid).unwrap() entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
) )
} else { } else {
let gid = meta.gid();
let uid = meta.uid();
format!( format!(
"changed ownership of {} from {}:{} to {}:{}", "changed ownership of {} from {}:{} to {}:{}",
path.quote(), path.quote(),
entries::uid2usr(meta.uid()).unwrap(), entries::uid2usr(uid).unwrap_or_else(|_| uid.to_string()),
entries::gid2grp(meta.gid()).unwrap(), entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()),
entries::uid2usr(dest_uid).unwrap(), entries::uid2usr(dest_uid).unwrap_or_else(|_| dest_uid.to_string()),
entries::gid2grp(dest_gid).unwrap() entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
) )
}; };
} }
@ -150,8 +156,8 @@ pub fn wrap_chown<P: AsRef<Path>>(
format!( format!(
"ownership of {} retained as {}:{}", "ownership of {} retained as {}:{}",
path.quote(), path.quote(),
entries::uid2usr(dest_uid).unwrap(), entries::uid2usr(dest_uid).unwrap_or_else(|_| dest_uid.to_string()),
entries::gid2grp(dest_gid).unwrap() entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
) )
}; };
} }
@ -456,6 +462,7 @@ pub fn chown_base<'a>(
command = command.arg( command = command.arg(
Arg::new(options::ARG_FILES) Arg::new(options::ARG_FILES)
.value_name(options::ARG_FILES) .value_name(options::ARG_FILES)
.value_hint(clap::ValueHint::FilePath)
.multiple_occurrences(true) .multiple_occurrences(true)
.takes_value(true) .takes_value(true)
.required(true) .required(true)

View file

@ -418,6 +418,29 @@ fn test_chown_only_user_id() {
.stderr_contains(&"failed to change"); .stderr_contains(&"failed to change");
} }
/// Test for setting the owner to a user ID for a user that does not exist.
///
/// For example:
///
/// $ touch f && chown 12345 f
///
/// succeeds with exit status 0 and outputs nothing. The owner of the
/// file is set to 12345, even though no user with that ID exists.
///
/// This test must be run as root, because only the root user can
/// transfer ownership of a file.
#[test]
fn test_chown_only_user_id_nonexistent_user() {
let ts = TestScenario::new(util_name!());
let at = ts.fixtures.clone();
at.touch("f");
if let Ok(result) = run_ucmd_as_root(&ts, &["12345", "f"]) {
result.success().no_stdout().no_stderr();
} else {
print!("Test skipped; requires root user");
}
}
#[test] #[test]
// FixME: stderr = chown: ownership of 'test_chown_file1' retained as cuuser:wheel // FixME: stderr = chown: ownership of 'test_chown_file1' retained as cuuser:wheel
#[cfg(not(target_os = "freebsd"))] #[cfg(not(target_os = "freebsd"))]
@ -461,6 +484,29 @@ fn test_chown_only_group_id() {
.stderr_contains(&"failed to change"); .stderr_contains(&"failed to change");
} }
/// Test for setting the group to a group ID for a group that does not exist.
///
/// For example:
///
/// $ touch f && chown :12345 f
///
/// succeeds with exit status 0 and outputs nothing. The group of the
/// file is set to 12345, even though no group with that ID exists.
///
/// This test must be run as root, because only the root user can
/// transfer ownership of a file.
#[test]
fn test_chown_only_group_id_nonexistent_group() {
let ts = TestScenario::new(util_name!());
let at = ts.fixtures.clone();
at.touch("f");
if let Ok(result) = run_ucmd_as_root(&ts, &[":12345", "f"]) {
result.success().no_stdout().no_stderr();
} else {
print!("Test skipped; requires root user");
}
}
#[test] #[test]
fn test_chown_owner_group_id() { fn test_chown_owner_group_id() {
// test chown 1111:1111 file.txt // test chown 1111:1111 file.txt

View file

@ -73,7 +73,7 @@ fn test_df_output() {
"Filesystem", "Filesystem",
"Size", "Size",
"Used", "Used",
"Available", "Avail",
"Capacity", "Capacity",
"Use%", "Use%",
"Mounted", "Mounted",
@ -84,7 +84,7 @@ fn test_df_output() {
"Filesystem", "Filesystem",
"Size", "Size",
"Used", "Used",
"Available", "Avail",
"Use%", "Use%",
"Mounted", "Mounted",
"on", "on",
@ -107,7 +107,7 @@ fn test_df_output_overridden() {
"Filesystem", "Filesystem",
"Size", "Size",
"Used", "Used",
"Available", "Avail",
"Capacity", "Capacity",
"Use%", "Use%",
"Mounted", "Mounted",
@ -118,7 +118,7 @@ fn test_df_output_overridden() {
"Filesystem", "Filesystem",
"Size", "Size",
"Used", "Used",
"Available", "Avail",
"Use%", "Use%",
"Mounted", "Mounted",
"on", "on",
@ -134,6 +134,46 @@ fn test_df_output_overridden() {
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test]
fn test_default_headers() {
let expected = if cfg!(target_os = "macos") {
vec![
"Filesystem",
"1K-blocks",
"Used",
"Available",
"Capacity",
"Use%",
"Mounted",
"on",
]
} else {
vec![
"Filesystem",
"1K-blocks",
"Used",
"Available",
"Use%",
"Mounted",
"on",
]
};
let output = new_ucmd!().succeeds().stdout_move_str();
let actual = output.lines().take(1).collect::<Vec<&str>>()[0];
let actual = actual.split_whitespace().collect::<Vec<_>>();
assert_eq!(actual, expected);
}
#[test]
fn test_precedence_of_human_readable_header_over_output_header() {
let output = new_ucmd!()
.args(&["-H", "--output=size"])
.succeeds()
.stdout_move_str();
let header = output.lines().next().unwrap().to_string();
assert_eq!(header.trim(), "Size");
}
#[test] #[test]
fn test_total_option_with_single_dash() { fn test_total_option_with_single_dash() {
// These should fail because `-total` should have two dashes, // These should fail because `-total` should have two dashes,
@ -395,6 +435,30 @@ fn test_default_block_size() {
assert_eq!(header, "512B-blocks"); assert_eq!(header, "512B-blocks");
} }
#[test]
fn test_default_block_size_in_posix_portability_mode() {
fn get_header(s: &str) -> String {
s.lines()
.next()
.unwrap()
.to_string()
.split_whitespace()
.nth(1)
.unwrap()
.to_string()
}
let output = new_ucmd!().arg("-P").succeeds().stdout_move_str();
assert_eq!(get_header(&output), "1024-blocks");
let output = new_ucmd!()
.arg("-P")
.env("POSIXLY_CORRECT", "1")
.succeeds()
.stdout_move_str();
assert_eq!(get_header(&output), "512-blocks");
}
#[test] #[test]
fn test_block_size_1024() { fn test_block_size_1024() {
fn get_header(block_size: u64) -> String { fn get_header(block_size: u64) -> String {
@ -443,6 +507,31 @@ fn test_block_size_with_suffix() {
assert_eq!(get_header("1GB"), "1GB-blocks"); assert_eq!(get_header("1GB"), "1GB-blocks");
} }
#[test]
fn test_block_size_in_posix_portability_mode() {
fn get_header(block_size: &str) -> String {
let output = new_ucmd!()
.args(&["-P", "-B", block_size])
.succeeds()
.stdout_move_str();
output
.lines()
.next()
.unwrap()
.to_string()
.split_whitespace()
.nth(1)
.unwrap()
.to_string()
}
assert_eq!(get_header("1024"), "1024-blocks");
assert_eq!(get_header("1K"), "1024-blocks");
assert_eq!(get_header("1KB"), "1000-blocks");
assert_eq!(get_header("1M"), "1048576-blocks");
assert_eq!(get_header("1MB"), "1000000-blocks");
}
#[test] #[test]
fn test_too_large_block_size() { fn test_too_large_block_size() {
fn run_command(size: &str) { fn run_command(size: &str) {
@ -465,6 +554,16 @@ fn test_invalid_block_size() {
.arg("--block-size=x") .arg("--block-size=x")
.fails() .fails()
.stderr_contains("invalid --block-size argument 'x'"); .stderr_contains("invalid --block-size argument 'x'");
new_ucmd!()
.arg("--block-size=0")
.fails()
.stderr_contains("invalid --block-size argument '0'");
new_ucmd!()
.arg("--block-size=0K")
.fails()
.stderr_contains("invalid --block-size argument '0K'");
} }
#[test] #[test]

View file

@ -2,6 +2,8 @@
use crate::common::util::*; use crate::common::util::*;
use uucore::display::Quotable;
use std::path::PathBuf; use std::path::PathBuf;
use tempfile::tempdir; use tempfile::tempdir;
@ -497,3 +499,15 @@ fn test_directory_permissions() {
assert!(metadata.is_dir()); assert!(metadata.is_dir());
assert_eq!(metadata.permissions().mode(), 0o40700); assert_eq!(metadata.permissions().mode(), 0o40700);
} }
/// Test that a template with a path separator is invalid.
#[test]
fn test_template_path_separator() {
new_ucmd!()
.args(&["-t", "a/bXXX"])
.fails()
.stderr_only(format!(
"mktemp: invalid template, {}, contains directory separator\n",
"a/bXXX".quote()
));
}

View file

@ -346,14 +346,25 @@ fn test_printf() {
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
} }
#[cfg(unix)]
#[test] #[test]
#[cfg(disable_until_fixed)] #[cfg(unix)]
fn test_pipe_fifo() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkfifo("FIFO");
ucmd.arg("FIFO")
.run()
.no_stderr()
.stdout_contains("fifo")
.stdout_contains("File: FIFO")
.succeeded();
}
#[test]
#[cfg(all(unix, not(target_os = "android")))]
fn test_stdin_pipe_fifo1() { fn test_stdin_pipe_fifo1() {
// $ echo | stat - // $ echo | stat -
// File: - // File: -
// Size: 0 Blocks: 0 IO Block: 4096 fifo // Size: 0 Blocks: 0 IO Block: 4096 fifo
// use std::process::{Command, Stdio};
new_ucmd!() new_ucmd!()
.arg("-") .arg("-")
.set_stdin(std::process::Stdio::piped()) .set_stdin(std::process::Stdio::piped())
@ -362,17 +373,25 @@ fn test_stdin_pipe_fifo1() {
.stdout_contains("fifo") .stdout_contains("fifo")
.stdout_contains("File: -") .stdout_contains("File: -")
.succeeded(); .succeeded();
new_ucmd!()
.args(&["-L", "-"])
.set_stdin(std::process::Stdio::piped())
.run()
.no_stderr()
.stdout_contains("fifo")
.stdout_contains("File: -")
.succeeded();
} }
#[cfg(unix)]
#[test] #[test]
#[cfg(disable_until_fixed)] #[cfg(all(unix, not(target_os = "android")))]
fn test_stdin_pipe_fifo2() { fn test_stdin_pipe_fifo2() {
// $ stat - // $ stat -
// File: - // File: -
// Size: 0 Blocks: 0 IO Block: 1024 character special file // Size: 0 Blocks: 0 IO Block: 1024 character special file
new_ucmd!() new_ucmd!()
.arg("-") .arg("-")
.set_stdin(std::process::Stdio::null())
.run() .run()
.no_stderr() .no_stderr()
.stdout_contains("character special file") .stdout_contains("character special file")
@ -380,20 +399,18 @@ fn test_stdin_pipe_fifo2() {
.succeeded(); .succeeded();
} }
#[cfg(unix)]
#[test] #[test]
#[cfg(disable_until_fixed)] #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
fn test_stdin_redirect() { fn test_stdin_redirect() {
// $ touch f && stat - < f // $ touch f && stat - < f
// File: - // File: -
// Size: 0 Blocks: 0 IO Block: 4096 regular empty file // Size: 0 Blocks: 0 IO Block: 4096 regular empty file
let ts = TestScenario::new(util_name!()); let ts = TestScenario::new(util_name!());
let at = &ts.fixtures; let at = &ts.fixtures;
at.touch("f"); at.touch("f");
new_ucmd!() ts.ucmd()
.arg("-") .arg("-")
.set_stdin(std::fs::File::open("f").unwrap()) .set_stdin(std::fs::File::open(at.plus("f")).unwrap())
.run() .run()
.no_stderr() .no_stderr()
.stdout_contains("regular empty file") .stdout_contains("regular empty file")