diff --git a/.cargo/config b/.cargo/config index 58e1381b1..0a8fd3d00 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,11 @@ [target.x86_64-unknown-redox] linker = "x86_64-unknown-redox-gcc" + +[target.'cfg(feature = "cargo-clippy")'] +rustflags = [ + "-Wclippy::use_self", + "-Wclippy::needless_pass_by_value", + "-Wclippy::semicolon_if_nothing_returned", + "-Wclippy::single_char_pattern", + "-Wclippy::explicit_iter_loop", +] diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index aec424312..81147c8dc 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1,7 +1,7 @@ name: CICD # spell-checker:ignore (acronyms) CICD MSVC musl -# spell-checker:ignore (env/flags) Awarnings Ccodegen Coverflow Cpanic RUSTDOCFLAGS RUSTFLAGS Zpanic +# spell-checker:ignore (env/flags) Awarnings Ccodegen Coverflow Cpanic Dwarnings RUSTDOCFLAGS RUSTFLAGS Zpanic # spell-checker:ignore (jargon) SHAs deps dequote softprops subshell toolchain # spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy # spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rustc rustfmt rustup shopt xargs @@ -340,6 +340,13 @@ jobs: ## Confirm MinSRV compatible 'Cargo.lock' # * 'Cargo.lock' is required to be in a format that `cargo` of MinSRV can interpret (eg, v1-format for MinSRV < v1.38) cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::Incompatible (or out-of-date) 'Cargo.lock' file; update using \`cargo +${{ env.RUST_MIN_SRV }} update\`" ; exit 1 ; } + - name: Confirm MinSRV equivalence for '.clippy.toml' + shell: bash + run: | + ## Confirm MinSRV equivalence for '.clippy.toml' + # * ensure '.clippy.toml' MSRV configuration setting is equal to ${{ env.RUST_MIN_SRV }} + CLIPPY_MSRV=$(grep -P "(?i)^\s*msrv\s*=\s*" .clippy.toml | grep -oP "\d+([.]\d+)+") + if [ "${CLIPPY_MSRV}" != "${{ env.RUST_MIN_SRV }}" ]; then { echo "::error file=.clippy.toml::Incorrect MSRV configuration for clippy (found '${CLIPPY_MSRV}'; should be '${{ env.RUST_MIN_SRV }}'); update '.clippy.toml' with 'msrv = \"${{ env.RUST_MIN_SRV }}\"'" ; exit 1 ; } ; fi - name: Info shell: bash run: | diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index b36bbcb33..69a26608c 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -1,6 +1,6 @@ name: GnuTests -# spell-checker:ignore (names) gnulib ; (utils) autopoint gperf pyinotify texinfo ; (vars) XPASS +# spell-checker:ignore (names) gnulib ; (people) Dawid Dziurla * dawidd6 ; (utils) autopoint chksum gperf pyinotify shopt texinfo ; (vars) FILESET XPASS on: [push, pull_request] @@ -9,23 +9,55 @@ jobs: name: Run GNU tests runs-on: ubuntu-latest steps: + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # * config + path_GNU="gnu" + path_GNULIB="gnulib" + path_GNU_tests="gnu/tests" + path_UUTILS="uutils" + path_reference="reference" + outputs path_GNU path_GNU_tests path_GNULIB path_reference path_UUTILS + # + repo_GNU_ref="v9.0" + repo_GNULIB_ref="8e99f24c0931a38880c6ee9b8287c7da80b0036b" + repo_reference_branch="${{ github.event.repository.default_branch }}" + outputs repo_GNU_ref repo_GNULIB_ref repo_reference_branch + # + SUITE_LOG_FILE="${path_GNU_tests}/test-suite.log" + TEST_LOGS_GLOB="${path_GNU_tests}/**/*.log" ## note: not usable at bash CLI; [why] double globstar not enabled by default b/c MacOS includes only bash v3 which doesn't have double globstar support + TEST_FILESET_PREFIX='test-fileset-IDs.sha1#' + TEST_FILESET_SUFFIX='.txt' + TEST_SUMMARY_FILE='gnu-result.json' + outputs SUITE_LOG_FILE TEST_FILESET_PREFIX TEST_FILESET_SUFFIX TEST_LOGS_GLOB TEST_SUMMARY_FILE - name: Checkout code uutil uses: actions/checkout@v2 with: - path: 'uutils' + path: '${{ steps.vars.outputs.path_UUTILS }}' - name: Checkout GNU coreutils uses: actions/checkout@v2 with: repository: 'coreutils/coreutils' - path: 'gnu' - ref: v9.0 + path: '${{ steps.vars.outputs.path_GNU }}' + ref: ${{ steps.vars.outputs.repo_GNU_ref }} - name: Checkout GNU coreutils library (gnulib) uses: actions/checkout@v2 with: repository: 'coreutils/gnulib' - path: 'gnulib' - ref: 8e99f24c0931a38880c6ee9b8287c7da80b0036b - fetch-depth: 0 # gnu gets upset if gnulib is a shallow checkout + path: '${{ steps.vars.outputs.path_GNULIB }}' + ref: ${{ steps.vars.outputs.repo_GNULIB_ref }} + fetch-depth: 0 # full depth checkout (o/w gnu gets upset if gnulib is a shallow checkout) + - name: Retrieve reference artifacts + uses: dawidd6/action-download-artifact@v2 + continue-on-error: true ## don't break the build for missing reference artifacts (may be expired or just not generated yet) + with: + workflow: GnuTests.yml + branch: "${{ steps.vars.outputs.repo_reference_branch }}" + path: "${{ steps.vars.outputs.path_reference }}" - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: @@ -43,28 +75,34 @@ jobs: shell: bash run: | ## Build binaries - cd uutils + cd '${{ steps.vars.outputs.path_UUTILS }}' bash util/build-gnu.sh - name: Run GNU tests shell: bash run: | - bash uutils/util/run-gnu-test.sh - - name: Extract testing info + path_GNU='${{ steps.vars.outputs.path_GNU }}' + path_GNULIB='${{ steps.vars.outputs.path_GNULIB }}' + path_UUTILS='${{ steps.vars.outputs.path_UUTILS }}' + bash "${path_UUTILS}/util/run-gnu-test.sh" + - name: Extract/summarize testing info + id: summary shell: bash run: | - ## Extract testing info - LOG_FILE=gnu/tests/test-suite.log - if test -f "$LOG_FILE" + ## Extract/summarize testing info + outputs() { step_id="summary"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } + # + SUITE_LOG_FILE='${{ steps.vars.outputs.SUITE_LOG_FILE }}' + if test -f "${SUITE_LOG_FILE}" then - TOTAL=$(sed -n "s/.*# TOTAL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - PASS=$(sed -n "s/.*# PASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - SKIP=$(sed -n "s/.*# SKIP: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) - ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) + TOTAL=$(sed -n "s/.*# TOTAL: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) + PASS=$(sed -n "s/.*# PASS: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) + SKIP=$(sed -n "s/.*# SKIP: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) + FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) + XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) + ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "${SUITE_LOG_FILE}" | tr -d '\r' | head -n1) if [[ "$TOTAL" -eq 0 || "$TOTAL" -eq 1 ]]; then - echo "Error in the execution, failing early" - exit 1 + echo "::error ::Failed to parse test results from '${SUITE_LOG_FILE}'; failing early" + exit 1 fi output="GNU tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR" echo "${output}" @@ -78,54 +116,61 @@ jobs: --arg fail "$FAIL" \ --arg xpass "$XPASS" \ --arg error "$ERROR" \ - '{($date): { sha: $sha, total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }}' > gnu-result.json + '{($date): { sha: $sha, total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }}' > '${{ steps.vars.outputs.TEST_SUMMARY_FILE }}' + HASH=$(sha1sum '${{ steps.vars.outputs.TEST_SUMMARY_FILE }}' | cut --delim=" " -f 1) + outputs HASH else - echo "::error ::Failed to get summary of test results" + echo "::error ::Failed to find summary of test results (missing '${SUITE_LOG_FILE}'); failing early" + exit 1 fi - - uses: actions/upload-artifact@v2 + - name: Reserve SHA1/ID of 'test-summary' + uses: actions/upload-artifact@v2 with: - name: test-report - path: gnu/tests/**/*.log - - uses: actions/upload-artifact@v2 + name: "${{ steps.summary.outputs.HASH }}" + path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" + - name: Reserve test results summary + uses: actions/upload-artifact@v2 with: - name: gnu-result - path: gnu-result.json - - name: Download the result - uses: dawidd6/action-download-artifact@v2 + name: test-summary + path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" + - name: Reserve test logs + uses: actions/upload-artifact@v2 with: - workflow: GnuTests.yml - name: gnu-result - repo: uutils/coreutils - branch: main - path: dl - - name: Download the log - uses: dawidd6/action-download-artifact@v2 - with: - workflow: GnuTests.yml - name: test-report - repo: uutils/coreutils - branch: main - path: dl - - name: Compare failing tests against main + name: test-logs + path: "${{ steps.vars.outputs.TEST_LOGS_GLOB }}" + - name: Compare test failures VS reference shell: bash run: | - OLD_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" dl/test-suite.log | sort) - NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" gnu/tests/test-suite.log | sort) - for LINE in $OLD_FAILING - do - if ! grep -Fxq $LINE<<<"$NEW_FAILING"; then - echo "::warning ::Congrats! The gnu test $LINE is now passing!" - fi - done - for LINE in $NEW_FAILING - do - if ! grep -Fxq $LINE<<<"$OLD_FAILING" - then - echo "::error ::GNU test failed: $LINE. $LINE is passing on 'main'. Maybe you have to rebase?" - fi - done - - name: Compare against main results + REF_LOG_FILE='${{ steps.vars.outputs.path_reference }}/test-logs/test-suite.log' + REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json' + if test -f "${REF_LOG_FILE}"; then + echo "Reference SHA1/ID (of '${REF_SUMMARY_FILE}'): $(sha1sum -- "${REF_SUMMARY_FILE}")" + 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) + for LINE in $REF_FAILING + do + if ! grep -Fxq $LINE<<<"$NEW_FAILING"; then + echo "::warning ::Congrats! The gnu test $LINE is now passing!" + fi + done + for LINE in $NEW_FAILING + do + if ! grep -Fxq $LINE<<<"$REF_FAILING" + then + echo "::error ::GNU test failed: $LINE. $LINE is passing on 'main'. Maybe you have to rebase?" + fi + done + else + echo "::warning ::Skipping test failure comparison; no prior reference test logs are available." + fi + - name: Compare test summary VS reference shell: bash run: | - mv dl/gnu-result.json main-gnu-result.json - python uutils/util/compare_gnu_result.py + REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json' + if test -f "${REF_SUMMARY_FILE}"; then + echo "Reference SHA1/ID (of '${REF_SUMMARY_FILE}'): $(sha1sum -- "${REF_SUMMARY_FILE}")" + mv "${REF_SUMMARY_FILE}" main-gnu-result.json + python uutils/util/compare_gnu_result.py + else + echo "::warning ::Skipping test summary comparison; no prior reference summary is available." + fi diff --git a/.travis/redox-toolchain.sh b/.travis/redox-toolchain.sh index 83bc8fc45..d8b43b489 100755 --- a/.travis/redox-toolchain.sh +++ b/.travis/redox-toolchain.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh rustup target add x86_64-unknown-redox sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys AA12E97F0881517F diff --git a/.vscode/.gitattributes b/.vscode/.gitattributes new file mode 100644 index 000000000..c050fbbf3 --- /dev/null +++ b/.vscode/.gitattributes @@ -0,0 +1,2 @@ +# Configure GitHub to not mark comments in configuration files as errors +*.json linguist-language=jsonc diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json index 95b6f0485..2ff4d4b7e 100644 --- a/.vscode/cSpell.json +++ b/.vscode/cSpell.json @@ -1,7 +1,12 @@ // `cspell` settings { - "version": "0.1", // Version of the setting file. Always 0.1 - "language": "en", // language - current active spelling language + // version of the setting file (always 0.1) + "version": "0.1", + + // spelling language + "language": "en", + + // custom dictionaries "dictionaries": ["acronyms+names", "jargon", "people", "shell", "workspace"], "dictionaryDefinitions": [ { "name": "acronyms+names", "path": "./cspell.dictionaries/acronyms+names.wordlist.txt" }, @@ -10,10 +15,19 @@ { "name": "shell", "path": "./cspell.dictionaries/shell.wordlist.txt" }, { "name": "workspace", "path": "./cspell.dictionaries/workspace.wordlist.txt" } ], - // ignorePaths - a list of globs to specify which files are to be ignored - "ignorePaths": ["Cargo.lock", "target/**", "tests/**/fixtures/**", "src/uu/dd/test-resources/**", "vendor/**"], - // ignoreWords - a list of words to be ignored (even if they are in the flagWords) + + // files to ignore (globs supported) + "ignorePaths": [ + "Cargo.lock", + "target/**", + "tests/**/fixtures/**", + "src/uu/dd/test-resources/**", + "vendor/**" + ], + + // words to ignore (even if they are in the flagWords) "ignoreWords": [], - // words - list of words to be always considered correct + + // words to always consider correct "words": [] } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 46b105d37..a02baee69 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,10 +1,8 @@ +// spell-checker:ignore (misc) matklad +// see for the documentation about the extensions.json format { - // spell-checker:ignore (misc) matklad - // see for the documentation about the extensions.json format "recommendations": [ - // Rust language support. - "rust-lang.rust", - // Provides support for rust-analyzer: novel LSP server for the Rust programming language. + // Rust language support "matklad.rust-analyzer", // `cspell` spell-checker support "streetsidesoftware.code-spell-checker" diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 901139edc..fcd86c93f 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -87,7 +87,7 @@ fn main() { }; if util == "completion" { - gen_completions(args, utils); + gen_completions(args, &utils); } match utils.get(util) { @@ -132,7 +132,7 @@ fn main() { /// Prints completions for the utility in the first parameter for the shell in the second parameter to stdout fn gen_completions( args: impl Iterator, - util_map: UtilityMap, + util_map: &UtilityMap, ) -> ! { let all_utilities: Vec<_> = std::iter::once("coreutils") .chain(util_map.keys().copied()) @@ -168,9 +168,9 @@ fn gen_completions( process::exit(0); } -fn gen_coreutils_app(util_map: UtilityMap) -> App<'static> { +fn gen_coreutils_app(util_map: &UtilityMap) -> App<'static> { let mut app = App::new("coreutils"); - for (_, (_, sub_app)) in &util_map { + for (_, (_, sub_app)) in util_map { app = app.subcommand(sub_app()); } app diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 6d9759fa4..006a796f0 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -12,14 +12,14 @@ use uucore::{encoding::Format, error::UResult}; pub mod base_common; -static ABOUT: &str = " - With no FILE, or when FILE is -, read standard input. +static ABOUT: &str = "\ +With no FILE, or when FILE is -, read standard input. - The data are encoded as described for the base32 alphabet in RFC - 4648. When decoding, the input may contain newlines in addition - to the bytes of the formal base32 alphabet. Use --ignore-garbage - to attempt to recover from any other non-alphabet bytes in the - encoded stream. +The data are encoded as described for the base32 alphabet in RFC +4648. When decoding, the input may contain newlines in addition +to the bytes of the formal base32 alphabet. Use --ignore-garbage +to attempt to recover from any other non-alphabet bytes in the +encoded stream. "; fn usage() -> String { diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 6d0192df9..20a9f55a5 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -13,14 +13,14 @@ use uucore::{encoding::Format, error::UResult}; use std::io::{stdin, Read}; -static ABOUT: &str = " - With no FILE, or when FILE is -, read standard input. +static ABOUT: &str = "\ +With no FILE, or when FILE is -, read standard input. - The data are encoded as described for the base64 alphabet in RFC - 3548. When decoding, the input may contain newlines in addition - to the bytes of the formal base64 alphabet. Use --ignore-garbage - to attempt to recover from any other non-alphabet bytes in the - encoded stream. +The data are encoded as described for the base64 alphabet in RFC +3548. When decoding, the input may contain newlines in addition +to the bytes of the formal base64 alphabet. Use --ignore-garbage +to attempt to recover from any other non-alphabet bytes in the +encoded stream. "; fn usage() -> String { diff --git a/src/uu/basenc/src/basenc.rs b/src/uu/basenc/src/basenc.rs index c21e224da..ef133b151 100644 --- a/src/uu/basenc/src/basenc.rs +++ b/src/uu/basenc/src/basenc.rs @@ -19,12 +19,12 @@ use uucore::{ use std::io::{stdin, Read}; -static ABOUT: &str = " - With no FILE, or when FILE is -, read standard input. +static ABOUT: &str = "\ +With no FILE, or when FILE is -, read standard input. - When decoding, the input may contain newlines in addition to the bytes of - the formal alphabet. Use --ignore-garbage to attempt to recover - from any other non-alphabet bytes in the encoded stream. +When decoding, the input may contain newlines in addition to the bytes of +the formal alphabet. Use --ignore-garbage to attempt to recover +from any other non-alphabet bytes in the encoded stream. "; const ENCODINGS: &[(&str, Format)] = &[ diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 179880b14..f656ed77d 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -201,12 +201,12 @@ fn set_main_group(group: &str) -> UResult<()> { } #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -fn set_groups(groups: Vec) -> libc::c_int { +fn set_groups(groups: &[libc::gid_t]) -> libc::c_int { unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) } } #[cfg(target_os = "linux")] -fn set_groups(groups: Vec) -> libc::c_int { +fn set_groups(groups: &[libc::gid_t]) -> libc::c_int { unsafe { setgroups(groups.len() as libc::size_t, groups.as_ptr()) } } @@ -220,7 +220,7 @@ fn set_groups_from_str(groups: &str) -> UResult<()> { }; groups_vec.push(gid); } - let err = set_groups(groups_vec); + let err = set_groups(&groups_vec); if err != 0 { return Err(ChrootError::SetGroupsFailed(Error::last_os_error()).into()); } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 938ecfe03..2082fecbd 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1035,7 +1035,10 @@ fn copy_directory( if is_symlink && !options.dereference { copy_link(&path, &local_to_target, symlinked_files)?; } else if path.is_dir() && !local_to_target.exists() { - or_continue!(fs::create_dir_all(local_to_target)); + if target.is_file() { + return Err("cannot overwrite non-directory with directory".into()); + } + fs::create_dir_all(local_to_target)?; } else if !path.is_dir() { if preserve_hard_links { let mut found_hard_link = false; diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 492ab70cb..6bc7bcfd9 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -4,7 +4,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore ctty, ctable, iconvflags, oconvflags +// spell-checker:ignore ctty, ctable, iconvflags, oconvflags parseargs #[cfg(test)] mod unit_tests; @@ -12,6 +12,8 @@ mod unit_tests; use super::*; use std::error::Error; use uucore::error::UError; +use uucore::parse_size::ParseSizeError; +use uucore::show_warning; pub type Matches = ArgMatches; @@ -31,6 +33,25 @@ pub enum ParseError { Unimplemented(String), } +impl ParseError { + /// Replace the argument, if any, with the given string, consuming self. + fn with_arg(self, s: String) -> Self { + match self { + Self::MultipleFmtTable => Self::MultipleFmtTable, + Self::MultipleUCaseLCase => Self::MultipleUCaseLCase, + Self::MultipleBlockUnblock => Self::MultipleBlockUnblock, + Self::MultipleExclNoCreate => Self::MultipleExclNoCreate, + Self::FlagNoMatch(_) => Self::FlagNoMatch(s), + Self::ConvFlagNoMatch(_) => Self::ConvFlagNoMatch(s), + Self::MultiplierStringParseFailure(_) => Self::MultiplierStringParseFailure(s), + Self::MultiplierStringOverflow(_) => Self::MultiplierStringOverflow(s), + Self::BlockUnblockWithoutCBS => Self::BlockUnblockWithoutCBS, + Self::StatusLevelNotRecognized(_) => Self::StatusLevelNotRecognized(s), + Self::Unimplemented(_) => Self::Unimplemented(s), + } + } +} + impl std::fmt::Display for ParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -310,28 +331,83 @@ fn parse_bytes_only(s: &str) -> Result { .map_err(|_| ParseError::MultiplierStringParseFailure(s.to_string())) } +/// Parse a number of bytes from the given string, assuming no `'x'` characters. +/// +/// The `'x'` character means "multiply the number before the `'x'` by +/// the number after the `'x'`". In order to compute the numbers +/// before and after the `'x'`, use this function, which assumes there +/// are no `'x'` characters in the string. +/// +/// A suffix `'c'` means multiply by 1, `'w'` by 2, and `'b'` by +/// 512. You can also use standard block size suffixes like `'k'` for +/// 1024. +/// +/// # Errors +/// +/// If a number cannot be parsed or if the multiplication would cause +/// an overflow. +/// +/// # Examples +/// +/// ```rust,ignore +/// assert_eq!(parse_bytes_no_x("123").unwrap(), 123); +/// assert_eq!(parse_bytes_no_x("2c").unwrap(), 2 * 1); +/// assert_eq!(parse_bytes_no_x("3w").unwrap(), 3 * 2); +/// assert_eq!(parse_bytes_no_x("2b").unwrap(), 2 * 512); +/// assert_eq!(parse_bytes_no_x("2k").unwrap(), 2 * 1024); +/// ``` +fn parse_bytes_no_x(s: &str) -> Result { + if s == "0" { + show_warning!( + "{} is a zero multiplier; use {} if that is intended", + "0x".quote(), + "00x".quote() + ); + } + let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) { + (None, None, None) => match uucore::parse_size::parse_size(s) { + Ok(n) => (n, 1), + Err(ParseSizeError::ParseFailure(s)) => { + return Err(ParseError::MultiplierStringParseFailure(s)) + } + Err(ParseSizeError::SizeTooBig(s)) => { + return Err(ParseError::MultiplierStringOverflow(s)) + } + }, + (Some(i), None, None) => (parse_bytes_only(&s[..i])?, 1), + (None, Some(i), None) => (parse_bytes_only(&s[..i])?, 2), + (None, None, Some(i)) => (parse_bytes_only(&s[..i])?, 512), + _ => return Err(ParseError::MultiplierStringParseFailure(s.to_string())), + }; + num.checked_mul(multiplier) + .ok_or_else(|| ParseError::MultiplierStringOverflow(s.to_string())) +} + /// Parse byte and multiplier like 512, 5KiB, or 1G. /// Uses uucore::parse_size, and adds the 'w' and 'c' suffixes which are mentioned /// in dd's info page. fn parse_bytes_with_opt_multiplier(s: &str) -> Result { - if let Some(idx) = s.rfind('c') { - parse_bytes_only(&s[..idx]) - } else if let Some(idx) = s.rfind('w') { - let partial = parse_bytes_only(&s[..idx])?; + // TODO On my Linux system, there seems to be a maximum block size of 4096 bytes: + // + // $ printf "%0.sa" {1..10000} | dd bs=4095 count=1 status=none | wc -c + // 4095 + // $ printf "%0.sa" {1..10000} | dd bs=4k count=1 status=none | wc -c + // 4096 + // $ printf "%0.sa" {1..10000} | dd bs=4097 count=1 status=none | wc -c + // 4096 + // $ printf "%0.sa" {1..10000} | dd bs=5k count=1 status=none | wc -c + // 4096 + // - partial - .checked_mul(2) - .ok_or_else(|| ParseError::MultiplierStringOverflow(s.to_string())) - } else { - uucore::parse_size::parse_size(s).map_err(|e| match e { - uucore::parse_size::ParseSizeError::ParseFailure(s) => { - ParseError::MultiplierStringParseFailure(s) - } - uucore::parse_size::ParseSizeError::SizeTooBig(s) => { - ParseError::MultiplierStringOverflow(s) - } - }) + // Split on the 'x' characters. Each component will be parsed + // individually, then multiplied together. + let mut total = 1; + for part in s.split('x') { + let num = parse_bytes_no_x(part).map_err(|e| e.with_arg(s.to_string()))?; + total *= num; } + + Ok(total) } pub fn parse_ibs(matches: &Matches) -> Result { @@ -689,3 +765,25 @@ pub fn parse_input_non_ascii(matches: &Matches) -> Result { Ok(false) } } + +#[cfg(test)] +mod tests { + + use crate::parseargs::parse_bytes_with_opt_multiplier; + + #[test] + fn test_parse_bytes_with_opt_multiplier() { + assert_eq!(parse_bytes_with_opt_multiplier("123").unwrap(), 123); + assert_eq!(parse_bytes_with_opt_multiplier("123c").unwrap(), 123 * 1); + assert_eq!(parse_bytes_with_opt_multiplier("123w").unwrap(), 123 * 2); + assert_eq!(parse_bytes_with_opt_multiplier("123b").unwrap(), 123 * 512); + assert_eq!(parse_bytes_with_opt_multiplier("123x3").unwrap(), 123 * 3); + assert_eq!(parse_bytes_with_opt_multiplier("123k").unwrap(), 123 * 1024); + assert_eq!(parse_bytes_with_opt_multiplier("1x2x3").unwrap(), 1 * 2 * 3); + assert_eq!( + parse_bytes_with_opt_multiplier("1wx2cx3w").unwrap(), + (1 * 2) * (2 * 1) * (3 * 2) + ); + assert!(parse_bytes_with_opt_multiplier("123asdf").is_err()); + } +} diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 77deeb6df..e856a6b1e 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -12,17 +12,17 @@ use uucore::error::UResult; use uucore::fsext::statfs_fn; use uucore::fsext::{read_fs_list, FsUsage, MountInfo}; -use clap::{crate_version, App, AppSettings, Arg}; +use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use number_prefix::NumberPrefix; use std::cell::Cell; use std::collections::HashMap; use std::collections::HashSet; - use std::error::Error; #[cfg(unix)] use std::ffi::CString; use std::fmt::Display; +use std::iter::FromIterator; #[cfg(unix)] use std::mem; @@ -69,6 +69,27 @@ struct Options { fs_selector: FsSelector, } +impl Options { + /// Convert command-line arguments into [`Options`]. + fn from(matches: &ArgMatches) -> Self { + Self { + show_local_fs: matches.is_present(OPT_LOCAL), + show_all_fs: matches.is_present(OPT_ALL), + show_listed_fs: false, + show_fs_type: matches.is_present(OPT_PRINT_TYPE), + show_inode_instead: matches.is_present(OPT_INODES), + human_readable_base: if matches.is_present(OPT_HUMAN_READABLE) { + 1024 + } else if matches.is_present(OPT_HUMAN_READABLE_2) { + 1000 + } else { + -1 + }, + fs_selector: FsSelector::from(matches), + } + } +} + #[derive(Debug, Clone)] struct Filesystem { mount_info: MountInfo, @@ -80,18 +101,19 @@ fn usage() -> String { } impl FsSelector { - fn new() -> Self { - Self::default() - } - - #[inline(always)] - fn include(&mut self, fs_type: String) { - self.include.insert(fs_type); - } - - #[inline(always)] - fn exclude(&mut self, fs_type: String) { - self.exclude.insert(fs_type); + /// Convert command-line arguments into a [`FsSelector`]. + /// + /// This function reads the include and exclude sets from + /// [`ArgMatches`] and returns the corresponding [`FsSelector`] + /// instance. + fn from(matches: &ArgMatches) -> Self { + let include = HashSet::from_iter(matches.values_of_lossy(OPT_TYPE).unwrap_or_default()); + let exclude = HashSet::from_iter( + matches + .values_of_lossy(OPT_EXCLUDE_TYPE) + .unwrap_or_default(), + ); + Self { include, exclude } } fn should_select(&self, fs_type: &str) -> bool { @@ -102,24 +124,6 @@ impl FsSelector { } } -impl Options { - fn new() -> Self { - Self { - show_local_fs: false, - show_all_fs: false, - show_listed_fs: false, - show_fs_type: false, - show_inode_instead: false, - // block_size: match env::var("BLOCKSIZE") { - // Ok(size) => size.parse().unwrap(), - // Err(_) => 512, - // }, - human_readable_base: -1, - fs_selector: FsSelector::new(), - } - } -} - impl Filesystem { // TODO: resolve uuid in `mount_info.dev_name` if exists fn new(mount_info: MountInfo) -> Option { @@ -157,64 +161,79 @@ impl Filesystem { } } +/// Keep only the specified subset of [`MountInfo`] instances. +/// +/// If `paths` is non-empty, this function excludes any [`MountInfo`] +/// that is not mounted at the specified path. +/// +/// The `opt` argument specifies a variety of ways of excluding +/// [`MountInfo`] instances; see [`Options`] for more information. +/// +/// Finally, if there are duplicate entries, the one with the shorter +/// path is kept. fn filter_mount_list(vmi: Vec, paths: &[String], opt: &Options) -> Vec { - vmi.into_iter() - .filter_map(|mi| { - if (mi.remote && opt.show_local_fs) - || (mi.dummy && !opt.show_all_fs && !opt.show_listed_fs) - || !opt.fs_selector.should_select(&mi.fs_type) - { - None - } else { - if paths.is_empty() { - // No path specified - return Some((mi.dev_id.clone(), mi)); - } - if paths.contains(&mi.mount_dir) { - // One or more paths have been provided - Some((mi.dev_id.clone(), mi)) - } else { - // Not a path we want to see - None - } - } - }) - .fold( - HashMap::>::new(), - |mut acc, (id, mi)| { - #[allow(clippy::map_entry)] - { - if acc.contains_key(&id) { - let seen = acc[&id].replace(mi.clone()); - let target_nearer_root = seen.mount_dir.len() > mi.mount_dir.len(); - // With bind mounts, prefer items nearer the root of the source - let source_below_root = !seen.mount_root.is_empty() - && !mi.mount_root.is_empty() - && seen.mount_root.len() < mi.mount_root.len(); - // let "real" devices with '/' in the name win. - if (!mi.dev_name.starts_with('/') || seen.dev_name.starts_with('/')) - // let points towards the root of the device win. - && (!target_nearer_root || source_below_root) - // let an entry over-mounted on a new device win... - && (seen.dev_name == mi.dev_name - /* ... but only when matching an existing mnt point, - to avoid problematic replacement when given - inaccurate mount lists, seen with some chroot - environments for example. */ - || seen.mount_dir != mi.mount_dir) - { - acc[&id].replace(seen); - } - } else { - acc.insert(id, Cell::new(mi)); - } - acc - } - }, - ) - .into_iter() - .map(|ent| ent.1.into_inner()) - .collect::>() + let mut mount_info_by_id = HashMap::>::new(); + for mi in vmi { + // Don't show remote filesystems if `--local` has been given. + if mi.remote && opt.show_local_fs { + continue; + } + + // Don't show pseudo filesystems unless `--all` has been given. + if mi.dummy && !opt.show_all_fs && !opt.show_listed_fs { + continue; + } + + // Don't show filesystems if they have been explicitly excluded. + if !opt.fs_selector.should_select(&mi.fs_type) { + continue; + } + + // Don't show filesystems other than the ones specified on the + // command line, if any. + if !paths.is_empty() && !paths.contains(&mi.mount_dir) { + continue; + } + + // If the device ID has not been encountered yet, just store it. + let id = mi.dev_id.clone(); + #[allow(clippy::map_entry)] + if !mount_info_by_id.contains_key(&id) { + mount_info_by_id.insert(id, Cell::new(mi)); + continue; + } + + // Otherwise, if we have seen the current device ID before, + // then check if we need to update it or keep the previously + // seen one. + let seen = mount_info_by_id[&id].replace(mi.clone()); + let target_nearer_root = seen.mount_dir.len() > mi.mount_dir.len(); + // With bind mounts, prefer items nearer the root of the source + let source_below_root = !seen.mount_root.is_empty() + && !mi.mount_root.is_empty() + && seen.mount_root.len() < mi.mount_root.len(); + // let "real" devices with '/' in the name win. + if (!mi.dev_name.starts_with('/') || seen.dev_name.starts_with('/')) + // let points towards the root of the device win. + && (!target_nearer_root || source_below_root) + // let an entry over-mounted on a new device win... + && (seen.dev_name == mi.dev_name + /* ... but only when matching an existing mnt point, + to avoid problematic replacement when given + inaccurate mount lists, seen with some chroot + environments for example. */ + || seen.mount_dir != mi.mount_dir) + { + mount_info_by_id[&id].replace(seen); + } + } + + // Take ownership of the `MountInfo` instances and collect them + // into a `Vec`. + mount_info_by_id + .into_values() + .map(|m| m.into_inner()) + .collect() } /// Convert `value` to a human readable string based on `base`. @@ -293,34 +312,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - let mut opt = Options::new(); - if matches.is_present(OPT_LOCAL) { - opt.show_local_fs = true; - } - if matches.is_present(OPT_ALL) { - opt.show_all_fs = true; - } - if matches.is_present(OPT_INODES) { - opt.show_inode_instead = true; - } - if matches.is_present(OPT_PRINT_TYPE) { - opt.show_fs_type = true; - } - if matches.is_present(OPT_HUMAN_READABLE) { - opt.human_readable_base = 1024; - } - if matches.is_present(OPT_HUMAN_READABLE_2) { - opt.human_readable_base = 1000; - } - for fs_type in matches.values_of_lossy(OPT_TYPE).unwrap_or_default() { - opt.fs_selector.include(fs_type.to_owned()); - } - for fs_type in matches - .values_of_lossy(OPT_EXCLUDE_TYPE) - .unwrap_or_default() - { - opt.fs_selector.exclude(fs_type.to_owned()); - } + let opt = Options::from(&matches); let fs_list = filter_mount_list(read_fs_list(), &paths, &opt) .into_iter() diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 7629aba69..e75210ef5 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -144,7 +144,7 @@ impl Stat { #[cfg(windows)] let file_info = get_file_info(&path); #[cfg(windows)] - Ok(Stat { + Ok(Self { path, is_dir: metadata.is_dir(), size: metadata.len(), diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 35606be71..1cb63fe93 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -88,10 +88,7 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { start = 0; next }), - '0' => parse_code(&mut iter, 8, 3, 3).unwrap_or_else(|| { - start = 0; - next - }), + '0' => parse_code(&mut iter, 8, 3, 3).unwrap_or('\0'), _ => { start = 0; next diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 677ffe1bf..c0e94a578 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -104,6 +104,7 @@ fn load_config_file(opts: &mut Options) -> UResult<()> { } #[cfg(not(windows))] +#[allow(clippy::ptr_arg)] fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) { let progname = Cow::from(args[0]); (progname, &args[1..]) diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index 324f90579..c6661dc35 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -4,16 +4,62 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +use clap::{App, AppSettings, Arg}; +use std::io::Write; +use uucore::error::{set_exit_code, UResult}; -use clap::App; -use uucore::error::UResult; +static ABOUT: &str = "\ +Returns false, an unsuccessful exit status. + +Immediately returns with the exit status `1`. When invoked with one of the recognized options it +will try to write the help or version text. Any IO error during this operation is diagnosed, yet +the program will also return `1`. +"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - uu_app().get_matches_from(args); - Err(1.into()) + let mut app = uu_app(); + + // Mirror GNU options, always return `1`. In particular even the 'successful' cases of no-op, + // and the interrupted display of help and version should return `1`. Also, we return Ok in all + // paths to avoid the allocation of an error object, an operation that could, in theory, fail + // and unwind through the standard library allocation handling machinery. + set_exit_code(1); + + if let Ok(matches) = app.try_get_matches_from_mut(args) { + let error = if matches.index_of("help").is_some() { + app.print_long_help() + } else if matches.index_of("version").is_some() { + writeln!(std::io::stdout(), "{}", app.render_version()) + } else { + Ok(()) + }; + + // Try to display this error. + if let Err(print_fail) = error { + // Completely ignore any error here, no more failover and we will fail in any case. + let _ = writeln!(std::io::stderr(), "{}: {}", uucore::util_name(), print_fail); + } + } + + Ok(()) } pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) + .version(clap::crate_version!()) + .about(ABOUT) + // We provide our own help and version options, to ensure maximum compatibility with GNU. + .setting(AppSettings::DisableHelpFlag | AppSettings::DisableVersionFlag) + .arg( + Arg::new("help") + .long("help") + .help("Print help information") + .exclusive(true), + ) + .arg( + Arg::new("version") + .long("version") + .help("Print version information"), + ) } diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 3f1d8ef42..b44a8b69d 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -20,7 +20,7 @@ pub fn parse_obsolete(src: &str) -> Option let mut has_num = false; let mut last_char = 0 as char; for (n, c) in &mut chars { - if c.is_numeric() { + if c.is_digit(10) { has_num = true; num_end = n; } else { diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index 764b7d279..99b800f14 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -12,6 +12,7 @@ use libc::c_long; use uucore::error::UResult; static SYNTAX: &str = "[options]"; +const SUMMARY: &str = "Print the numeric identifier (in hexadecimal) for the current host"; // currently rust libc interface doesn't include gethostid extern "C" { @@ -28,6 +29,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) + .about(SUMMARY) .override_usage(SYNTAX) .setting(AppSettings::InferLongArgs) } diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 02dc91dbf..413a183a8 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -74,7 +74,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { table(); Ok(()) } - Mode::List => list(pids_or_signals.get(0).cloned()), + Mode::List => list(pids_or_signals.get(0)), } } @@ -168,9 +168,9 @@ fn print_signals() { println!(); } -fn list(arg: Option) -> UResult<()> { +fn list(arg: Option<&String>) -> UResult<()> { match arg { - Some(ref x) => print_signal(x), + Some(x) => print_signal(x), None => { print_signals(); Ok(()) diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 1e6c5fbd3..a30d18c53 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -9,255 +9,248 @@ use uucore::InvalidEncodingHandling; const VERSION: &str = "version"; const HELP: &str = "help"; -static LONGHELP_LEAD: &str = "printf +const USAGE: &str = "printf FORMATSTRING [ARGUMENT]..."; +const ABOUT: &str = "Print output based off of the format string and proceeding arguments."; +const AFTER_HELP: &str = " +basic anonymous string templating: - USAGE: printf FORMATSTRING [ARGUMENT]... +prints format string at least once, repeating as long as there are remaining arguments +output prints escaped literals in the format string as character literals +output replaces anonymous fields with the next unused argument, formatted according to the field. - basic anonymous string templating: +Prints the , replacing escaped character sequences with character literals + and substitution field sequences with passed arguments - prints format string at least once, repeating as long as there are remaining arguments - output prints escaped literals in the format string as character literals - output replaces anonymous fields with the next unused argument, formatted according to the field. +literally, with the exception of the below + escaped character sequences, and the substitution sequences described further down. -Options: - --help display this help and exit - --version output version information and exit +ESCAPE SEQUENCES -"; -static LONGHELP_BODY: &str = " - Prints the , replacing escaped character sequences with character literals - and substitution field sequences with passed arguments +The following escape sequences, organized here in alphabetical order, +will print the corresponding character literal: - literally, with the exception of the below - escaped character sequences, and the substitution sequences described further down. +\" double quote - ESCAPE SEQUENCES +\\\\ backslash - The following escape sequences, organized here in alphabetical order, - will print the corresponding character literal: +\\a alert (BEL) - \" double quote +\\b backspace - \\\\ backslash +\\c End-of-Input - \\a alert (BEL) +\\e escape - \\b backspace +\\f form feed - \\c End-of-Input +\\n new line - \\e escape +\\r carriage return - \\f form feed +\\t horizontal tab - \\n new line +\\v vertical tab - \\r carriage return +\\NNN byte with value expressed in octal value NNN (1 to 3 digits) + values greater than 256 will be treated - \\t horizontal tab +\\xHH byte with value expressed in hexadecimal value NN (1 to 2 digits) - \\v vertical tab +\\uHHHH Unicode (IEC 10646) character with value expressed in hexadecimal value HHHH (4 digits) - \\NNN byte with value expressed in octal value NNN (1 to 3 digits) - values greater than 256 will be treated +\\uHHHH Unicode character with value expressed in hexadecimal value HHHH (8 digits) - \\xHH byte with value expressed in hexadecimal value NN (1 to 2 digits) +%% a single % - \\uHHHH Unicode (IEC 10646) character with value expressed in hexadecimal value HHHH (4 digits) +SUBSTITUTIONS - \\uHHHH Unicode character with value expressed in hexadecimal value HHHH (8 digits) +SUBSTITUTION QUICK REFERENCE - %% a single % +Fields - SUBSTITUTIONS +%s - string +%b - string parsed for literals + second parameter is max length - SUBSTITUTION QUICK REFERENCE +%c - char + no second parameter - Fields +%i or %d - 64-bit integer +%u - 64 bit unsigned integer +%x or %X - 64-bit unsigned integer as hex +%o - 64-bit unsigned integer as octal + second parameter is min-width, integer + output below that width is padded with leading zeroes - %s - string - %b - string parsed for literals - second parameter is max length +%f or %F - decimal floating point value +%e or %E - scientific notation floating point value +%g or %G - shorter of specially interpreted decimal or SciNote floating point value. + second parameter is + -max places after decimal point for floating point output + -max number of significant digits for scientific notation output - %c - char - no second parameter +parameterizing fields - %i or %d - 64-bit integer - %u - 64 bit unsigned integer - %x or %X - 64-bit unsigned integer as hex - %o - 64-bit unsigned integer as octal - second parameter is min-width, integer - output below that width is padded with leading zeroes +examples: - %f or %F - decimal floating point value - %e or %E - scientific notation floating point value - %g or %G - shorter of specially interpreted decimal or SciNote floating point value. - second parameter is - -max places after decimal point for floating point output - -max number of significant digits for scientific notation output +printf '%4.3i' 7 +has a first parameter of 4 + and a second parameter of 3 +will result in ' 007' - parameterizing fields +printf '%.1s' abcde +has no first parameter + and a second parameter of 1 +will result in 'a' - examples: +printf '%4c' q +has a first parameter of 4 + and no second parameter +will result in ' q' - printf '%4.3i' 7 - has a first parameter of 4 - and a second parameter of 3 - will result in ' 007' +The first parameter of a field is the minimum width to pad the output to + if the output is less than this absolute value of this width, + it will be padded with leading spaces, or, if the argument is negative, + with trailing spaces. the default is zero. - printf '%.1s' abcde - has no first parameter - and a second parameter of 1 - will result in 'a' +The second parameter of a field is particular to the output field type. + defaults can be found in the full substitution help below - printf '%4c' q - has a first parameter of 4 - and no second parameter - will result in ' q' +special prefixes to numeric arguments + 0 (e.g. 010) - interpret argument as octal (integer output fields only) + 0x (e.g. 0xABC) - interpret argument as hex (numeric output fields only) + \' (e.g. \'a) - interpret argument as a character constant - The first parameter of a field is the minimum width to pad the output to - if the output is less than this absolute value of this width, - it will be padded with leading spaces, or, if the argument is negative, - with trailing spaces. the default is zero. +HOW TO USE SUBSTITUTIONS - The second parameter of a field is particular to the output field type. - defaults can be found in the full substitution help below +Substitutions are used to pass additional argument(s) into the FORMAT string, to be formatted a +particular way. E.g. - special prefixes to numeric arguments - 0 (e.g. 010) - interpret argument as octal (integer output fields only) - 0x (e.g. 0xABC) - interpret argument as hex (numeric output fields only) - \' (e.g. \'a) - interpret argument as a character constant + printf 'the letter %X comes before the letter %X' 10 11 - HOW TO USE SUBSTITUTIONS +will print - Substitutions are used to pass additional argument(s) into the FORMAT string, to be formatted a - particular way. E.g. + 'the letter A comes before the letter B' - printf 'the letter %X comes before the letter %X' 10 11 +because the substitution field %X means +'take an integer argument and write it as a hexadecimal number' - will print +Passing more arguments than are in the format string will cause the format string to be + repeated for the remaining substitutions - 'the letter A comes before the letter B' + printf 'it is %i F in %s \n' 22 Portland 25 Boston 27 New York - because the substitution field %X means - 'take an integer argument and write it as a hexadecimal number' +will print - Passing more arguments than are in the format string will cause the format string to be - repeated for the remaining substitutions + 'it is 22 F in Portland + it is 25 F in Boston + it is 27 F in Boston + ' +If a format string is printed but there are less arguments remaining + than there are substitution fields, substitution fields without + an argument will default to empty strings, or for numeric fields + the value 0 - printf 'it is %i F in %s \n' 22 Portland 25 Boston 27 New York +AVAILABLE SUBSTITUTIONS - will print +This program, like GNU coreutils printf, +interprets a modified subset of the POSIX C printf spec, +a quick reference to substitutions is below. - 'it is 22 F in Portland - it is 25 F in Boston - it is 27 F in Boston - ' - If a format string is printed but there are less arguments remaining - than there are substitution fields, substitution fields without - an argument will default to empty strings, or for numeric fields - the value 0 + STRING SUBSTITUTIONS + All string fields have a 'max width' parameter + %.3s means 'print no more than three characters of the original input' - AVAILABLE SUBSTITUTIONS + %s - string - This program, like GNU coreutils printf, - interprets a modified subset of the POSIX C printf spec, - a quick reference to substitutions is below. + %b - escaped string - the string will be checked for any escaped literals from + the escaped literal list above, and translate them to literal characters. + e.g. \\n will be transformed into a newline character. - STRING SUBSTITUTIONS - All string fields have a 'max width' parameter - %.3s means 'print no more than three characters of the original input' + One special rule about %b mode is that octal literals are interpreted differently + In arguments passed by %b, pass octal-interpreted literals must be in the form of \\0NNN + instead of \\NNN. (Although, for legacy reasons, octal literals in the form of \\NNN will + still be interpreted and not throw a warning, you will have problems if you use this for a + literal whose code begins with zero, as it will be viewed as in \\0NNN form.) - %s - string + CHAR SUBSTITUTIONS + The character field does not have a secondary parameter. - %b - escaped string - the string will be checked for any escaped literals from - the escaped literal list above, and translate them to literal characters. - e.g. \\n will be transformed into a newline character. + %c - a single character - One special rule about %b mode is that octal literals are interpreted differently - In arguments passed by %b, pass octal-interpreted literals must be in the form of \\0NNN - instead of \\NNN. (Although, for legacy reasons, octal literals in the form of \\NNN will - still be interpreted and not throw a warning, you will have problems if you use this for a - literal whose code begins with zero, as it will be viewed as in \\0NNN form.) + INTEGER SUBSTITUTIONS + All integer fields have a 'pad with zero' parameter + %.4i means an integer which if it is less than 4 digits in length, + is padded with leading zeros until it is 4 digits in length. - CHAR SUBSTITUTIONS - The character field does not have a secondary parameter. + %d or %i - 64-bit integer - %c - a single character + %u - 64 bit unsigned integer - INTEGER SUBSTITUTIONS - All integer fields have a 'pad with zero' parameter - %.4i means an integer which if it is less than 4 digits in length, - is padded with leading zeros until it is 4 digits in length. + %x or %X - 64 bit unsigned integer printed in Hexadecimal (base 16) + %X instead of %x means to use uppercase letters for 'a' through 'f' - %d or %i - 64-bit integer + %o - 64 bit unsigned integer printed in octal (base 8) - %u - 64 bit unsigned integer + FLOATING POINT SUBSTITUTIONS - %x or %X - 64 bit unsigned integer printed in Hexadecimal (base 16) - %X instead of %x means to use uppercase letters for 'a' through 'f' + All floating point fields have a 'max decimal places / max significant digits' parameter + %.10f means a decimal floating point with 7 decimal places past 0 + %.10e means a scientific notation number with 10 significant digits + %.10g means the same behavior for decimal and Sci. Note, respectively, and provides the shorter + of each's output. - %o - 64 bit unsigned integer printed in octal (base 8) + Like with GNU coreutils, the value after the decimal point is these outputs is parsed as a + double first before being rendered to text. For both implementations do not expect meaningful + precision past the 18th decimal place. When using a number of decimal places that is 18 or + higher, you can expect variation in output between GNU coreutils printf and this printf at the + 18th decimal place of +/- 1 - FLOATING POINT SUBSTITUTIONS + %f - floating point value presented in decimal, truncated and displayed to 6 decimal places by + default. There is not past-double behavior parity with Coreutils printf, values are not + estimated or adjusted beyond input values. - All floating point fields have a 'max decimal places / max significant digits' parameter - %.10f means a decimal floating point with 7 decimal places past 0 - %.10e means a scientific notation number with 10 significant digits - %.10g means the same behavior for decimal and Sci. Note, respectively, and provides the shorter - of each's output. + %e or %E - floating point value presented in scientific notation + 7 significant digits by default + %E means use to use uppercase E for the mantissa. - Like with GNU coreutils, the value after the decimal point is these outputs is parsed as a - double first before being rendered to text. For both implementations do not expect meaningful - precision past the 18th decimal place. When using a number of decimal places that is 18 or - higher, you can expect variation in output between GNU coreutils printf and this printf at the - 18th decimal place of +/- 1 + %g or %G - floating point value presented in the shorter of decimal and scientific notation + behaves differently from %f and %E, please see posix printf spec for full details, + some examples of different behavior: - %f - floating point value presented in decimal, truncated and displayed to 6 decimal places by - default. There is not past-double behavior parity with Coreutils printf, values are not - estimated or adjusted beyond input values. + Sci Note has 6 significant digits by default + Trailing zeroes are removed + Instead of being truncated, digit after last is rounded - %e or %E - floating point value presented in scientific notation - 7 significant digits by default - %E means use to use uppercase E for the mantissa. + Like other behavior in this utility, the design choices of floating point + behavior in this utility is selected to reproduce in exact + the behavior of GNU coreutils' printf from an inputs and outputs standpoint. - %g or %G - floating point value presented in the shorter of decimal and scientific notation - behaves differently from %f and %E, please see posix printf spec for full details, - some examples of different behavior: +USING PARAMETERS + Most substitution fields can be parameterized using up to 2 numbers that can + be passed to the field, between the % sign and the field letter. - Sci Note has 6 significant digits by default - Trailing zeroes are removed - Instead of being truncated, digit after last is rounded + The 1st parameter always indicates the minimum width of output, it is useful for creating + columnar output. Any output that would be less than this minimum width is padded with + leading spaces + The 2nd parameter is proceeded by a dot. + You do not have to use parameters - Like other behavior in this utility, the design choices of floating point - behavior in this utility is selected to reproduce in exact - the behavior of GNU coreutils' printf from an inputs and outputs standpoint. +SPECIAL FORMS OF INPUT + For numeric input, the following additional forms of input are accepted besides decimal: - USING PARAMETERS - Most substitution fields can be parameterized using up to 2 numbers that can - be passed to the field, between the % sign and the field letter. + Octal (only with integer): if the argument begins with a 0 the proceeding characters + will be interpreted as octal (base 8) for integer fields - The 1st parameter always indicates the minimum width of output, it is useful for creating - columnar output. Any output that would be less than this minimum width is padded with - leading spaces - The 2nd parameter is proceeded by a dot. - You do not have to use parameters + Hexadecimal: if the argument begins with 0x the proceeding characters will be interpreted + will be interpreted as hex (base 16) for any numeric fields + for float fields, hexadecimal input results in a precision + limit (in converting input past the decimal point) of 10^-15 - SPECIAL FORMS OF INPUT - For numeric input, the following additional forms of input are accepted besides decimal: - - Octal (only with integer): if the argument begins with a 0 the proceeding characters - will be interpreted as octal (base 8) for integer fields - - Hexadecimal: if the argument begins with 0x the proceeding characters will be interpreted - will be interpreted as hex (base 16) for any numeric fields - for float fields, hexadecimal input results in a precision - limit (in converting input past the decimal point) of 10^-15 - - Character Constant: if the argument begins with a single quote character, the first byte - of the next character will be interpreted as an 8-bit unsigned integer. If there are - additional bytes, they will throw an error (unless the environment variable POSIXLY_CORRECT - is set) + Character Constant: if the argument begins with a single quote character, the first byte + of the next character will be interpreted as an 8-bit unsigned integer. If there are + additional bytes, they will throw an error (unless the environment variable POSIXLY_CORRECT + is set) WRITTEN BY : Nathan E. Ross, et al. for the uutils project @@ -271,31 +264,43 @@ COPYRIGHT : "; +mod options { + pub const FORMATSTRING: &str = "FORMATSTRING"; + pub const ARGUMENT: &str = "ARGUMENT"; +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); + let matches = uu_app().get_matches_from(args); - if args.len() <= 1 { - return Err(UUsageError::new(1, "missing operand")); - } - let formatstr = &args[1]; + let format_string = matches + .value_of(options::FORMATSTRING) + .ok_or_else(|| UUsageError::new(1, "missing operand"))?; + let values: Vec = match matches.values_of(options::ARGUMENT) { + Some(s) => s.map(|s| s.to_string()).collect(), + None => vec![], + }; - if formatstr == "--help" { - print!("{} {}", LONGHELP_LEAD, LONGHELP_BODY); - } else if formatstr == "--version" { - println!("{} {}", uucore::util_name(), crate_version!()); - } else { - let printf_args = &args[2..]; - memo::Memo::run_all(formatstr, printf_args); - } + memo::Memo::run_all(format_string, &values[..]); Ok(()) } pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) - .arg(Arg::new(VERSION).long(VERSION)) - .arg(Arg::new(HELP).long(HELP)) - .setting(AppSettings::InferLongArgs) + .setting(AppSettings::AllowHyphenValues) + .version(crate_version!()) + .about(ABOUT) + .after_help(AFTER_HELP) + .override_usage(USAGE) + .arg(Arg::new(HELP).long(HELP).help("Print help information")) + .arg( + Arg::new(VERSION) + .long(VERSION) + .help("Print version information"), + ) + .arg(Arg::new(options::FORMATSTRING)) + .arg(Arg::new(options::ARGUMENT).multiple_occurrences(true)) } diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index a7dcd48e9..da2dfff1b 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -31,6 +31,7 @@ Write a random permutation of the input lines to standard output. With no FILE, or when FILE is -, read standard input. "#; +static ABOUT: &str = "Shuffle the input by outputting a random permutation of input lines. Each output permutation is equally likely."; static TEMPLATE: &str = "Usage: {usage}\nMandatory arguments to long options are mandatory for short options too.\n{options}"; struct Options { @@ -121,6 +122,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .name(NAME) + .about(ABOUT) .version(crate_version!()) .help_template(TEMPLATE) .override_usage(USAGE) diff --git a/src/uu/split/src/number.rs b/src/uu/split/src/number.rs index b2c402716..ef3ccbc4b 100644 --- a/src/uu/split/src/number.rs +++ b/src/uu/split/src/number.rs @@ -96,8 +96,8 @@ impl Number { #[allow(dead_code)] fn digits(&self) -> &Vec { match self { - Number::FixedWidth(number) => &number.digits, - Number::DynamicWidth(number) => &number.digits, + Self::FixedWidth(number) => &number.digits, + Self::DynamicWidth(number) => &number.digits, } } @@ -136,8 +136,8 @@ impl Number { /// ``` pub fn increment(&mut self) -> Result<(), Overflow> { match self { - Number::FixedWidth(number) => number.increment(), - Number::DynamicWidth(number) => number.increment(), + Self::FixedWidth(number) => number.increment(), + Self::DynamicWidth(number) => number.increment(), } } } @@ -145,8 +145,8 @@ impl Number { impl Display for Number { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Number::FixedWidth(number) => number.fmt(f), - Number::DynamicWidth(number) => number.fmt(f), + Self::FixedWidth(number) => number.fmt(f), + Self::DynamicWidth(number) => number.fmt(f), } } } @@ -183,8 +183,8 @@ pub struct FixedWidthNumber { impl FixedWidthNumber { /// Instantiate a number of the given radix and width. - pub fn new(radix: u8, width: usize) -> FixedWidthNumber { - FixedWidthNumber { + pub fn new(radix: u8, width: usize) -> Self { + Self { radix, digits: vec![0; width], } @@ -286,8 +286,8 @@ impl DynamicWidthNumber { /// /// This associated function returns a new instance of the struct /// with the given radix and a width of two digits, both 0. - pub fn new(radix: u8) -> DynamicWidthNumber { - DynamicWidthNumber { + pub fn new(radix: u8) -> Self { + Self { radix, digits: vec![0, 0], } @@ -404,7 +404,7 @@ mod tests { fn num(n: usize) -> Number { let mut number = Number::DynamicWidth(DynamicWidthNumber::new(26)); for _ in 0..n { - number.increment().unwrap() + number.increment().unwrap(); } number } @@ -428,7 +428,7 @@ mod tests { fn num(n: usize) -> Number { let mut number = Number::DynamicWidth(DynamicWidthNumber::new(10)); for _ in 0..n { - number.increment().unwrap() + number.increment().unwrap(); } number } diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 23eb24768..a05959810 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -62,7 +62,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .override_usage(&usage[..]) .after_help(&long_usage[..]) .get_matches_from(args); - let settings = Settings::from(matches)?; + let settings = Settings::from(&matches)?; split(&settings) } @@ -232,7 +232,7 @@ struct Settings { impl Settings { /// Parse a strategy from the command-line arguments. - fn from(matches: ArgMatches) -> UResult { + fn from(matches: &ArgMatches) -> UResult { let result = Self { suffix_length: matches .value_of(OPT_SUFFIX_LENGTH) @@ -242,7 +242,7 @@ impl Settings { numeric_suffix: matches.occurrences_of(OPT_NUMERIC_SUFFIXES) > 0, additional_suffix: matches.value_of(OPT_ADDITIONAL_SUFFIX).unwrap().to_owned(), verbose: matches.occurrences_of("verbose") > 0, - strategy: Strategy::from(&matches)?, + strategy: Strategy::from(matches)?, input: matches.value_of(ARG_INPUT).unwrap().to_owned(), prefix: matches.value_of(ARG_PREFIX).unwrap().to_owned(), filter: matches.value_of(OPT_FILTER).map(|s| s.to_owned()), diff --git a/src/uu/tail/src/parse.rs b/src/uu/tail/src/parse.rs index 1c4f36bbd..ea9df9a02 100644 --- a/src/uu/tail/src/parse.rs +++ b/src/uu/tail/src/parse.rs @@ -19,7 +19,7 @@ pub fn parse_obsolete(src: &str) -> Option let mut has_num = false; let mut last_char = 0 as char; for (n, c) in &mut chars { - if c.is_numeric() { + if c.is_digit(10) { has_num = true; num_end = n; } else { diff --git a/src/uu/tail/src/platform/unix.rs b/src/uu/tail/src/platform/unix.rs index e7f75c31e..e01d5e444 100644 --- a/src/uu/tail/src/platform/unix.rs +++ b/src/uu/tail/src/platform/unix.rs @@ -30,6 +30,7 @@ impl ProcessChecker { } // Borrowing mutably to be aligned with Windows implementation + #[allow(clippy::wrong_self_convention)] pub fn is_dead(&mut self) -> bool { unsafe { libc::kill(self.pid, 0) != 0 && get_errno() != libc::EPERM } } diff --git a/src/uu/tail/src/platform/windows.rs b/src/uu/tail/src/platform/windows.rs index 7faa872e6..c63040a2a 100644 --- a/src/uu/tail/src/platform/windows.rs +++ b/src/uu/tail/src/platform/windows.rs @@ -24,11 +24,11 @@ pub struct ProcessChecker { } impl ProcessChecker { - pub fn new(process_id: self::Pid) -> ProcessChecker { + pub fn new(process_id: self::Pid) -> Self { #[allow(non_snake_case)] let FALSE = 0i32; let h = unsafe { OpenProcess(SYNCHRONIZE, FALSE, process_id as DWORD) }; - ProcessChecker { + Self { dead: h.is_null(), handle: h, } diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 1a3f4139e..cc7437bff 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -10,7 +10,7 @@ mod parser; -use clap::{crate_version, App, AppSettings}; +use clap::{crate_version, App}; use parser::{parse, Operator, Symbol, UnaryOperator}; use std::ffi::{OsStr, OsString}; use uucore::display::Quotable; @@ -86,10 +86,14 @@ NOTE: your shell may have its own version of test and/or [, which usually supers the version described here. Please refer to your shell's documentation for details about the options it supports."; +const ABOUT: &str = "Check file types and compare values."; + pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) - .setting(AppSettings::DisableHelpFlag) - .setting(AppSettings::DisableVersionFlag) + .version(crate_version!()) + .about(ABOUT) + .override_usage(USAGE) + .after_help(AFTER_HELP) } #[uucore::main] @@ -104,6 +108,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { // Let clap pretty-print help and version App::new(binary_name) .version(crate_version!()) + .about(ABOUT) .override_usage(USAGE) .after_help(AFTER_HELP) // Disable printing of -h and -v as valid alternatives for --help and --version, diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index b1df1aca4..e27dbfc18 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -58,7 +58,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); - let files = matches.values_of_os(ARG_FILES).unwrap(); + let files = matches.values_of_os(ARG_FILES).ok_or_else(|| { + USimpleError::new( + 1, + r##"missing file operand +Try 'touch --help' for more information."##, + ) + })?; let (mut atime, mut mtime) = if let Some(reference) = matches.value_of_os(options::sources::REFERENCE) { diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index ff5b08e85..4a8452db6 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -4,16 +4,59 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +use clap::{App, AppSettings, Arg}; +use std::io::Write; +use uucore::error::{set_exit_code, UResult}; -use clap::{App, AppSettings}; -use uucore::error::UResult; +static ABOUT: &str = "\ +Returns true, a successful exit status. + +Immediately returns with the exit status `0`, except when invoked with one of the recognized +options. In those cases it will try to write the help or version text. Any IO error during this +operation causes the program to return `1` instead. +"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - uu_app().get_matches_from(args); + let mut app = uu_app(); + + if let Ok(matches) = app.try_get_matches_from_mut(args) { + let error = if matches.index_of("help").is_some() { + app.print_long_help() + } else if matches.index_of("version").is_some() { + writeln!(std::io::stdout(), "{}", app.render_version()) + } else { + Ok(()) + }; + + if let Err(print_fail) = error { + // Try to display this error. + let _ = writeln!(std::io::stderr(), "{}: {}", uucore::util_name(), print_fail); + // Mirror GNU options. When failing to print warnings or version flags, then we exit + // with FAIL. This avoids allocation some error information which may result in yet + // other types of failure. + set_exit_code(1); + } + } + Ok(()) } pub fn uu_app<'a>() -> App<'a> { - App::new(uucore::util_name()).setting(AppSettings::InferLongArgs) + App::new(uucore::util_name()) + .version(clap::crate_version!()) + .about(ABOUT) + // We provide our own help and version options, to ensure maximum compatibility with GNU. + .setting(AppSettings::DisableHelpFlag | AppSettings::DisableVersionFlag) + .arg( + Arg::new("help") + .long("help") + .help("Print help information") + .exclusive(true), + ) + .arg( + Arg::new("version") + .long("version") + .help("Print version information"), + ) } diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 685363f8f..242dd416a 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -115,7 +115,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .override_usage(&usage[..]) .after_help(&long_usage[..]) - .get_matches_from(args); + .try_get_matches_from(args) + .map_err(|e| { + e.print().expect("Error writing clap::Error"); + match e.kind { + clap::ErrorKind::DisplayHelp | clap::ErrorKind::DisplayVersion => 0, + _ => 1, + } + })?; let files: Vec = matches .values_of(options::ARG_FILES) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index d1e623757..a3b05dff8 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -238,7 +238,7 @@ impl MountInfo { } } #[cfg(windows)] - fn new(mut volume_name: String) -> Option { + fn new(mut volume_name: String) -> Option { let mut dev_name_buf = [0u16; MAX_PATH]; volume_name.pop(); unsafe { @@ -289,7 +289,7 @@ impl MountInfo { } else { None }; - let mut mn_info = MountInfo { + let mut mn_info = Self { dev_id: volume_name, dev_name, fs_type: fs_type.unwrap_or_else(|| "".to_string()), @@ -319,7 +319,7 @@ use std::ffi::CStr; ))] impl From for MountInfo { fn from(statfs: StatFs) -> Self { - let mut info = MountInfo { + let mut info = Self { dev_id: "".to_string(), dev_name: unsafe { // spell-checker:disable-next-line @@ -553,7 +553,7 @@ impl FsUsage { } let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64; - FsUsage { + Self { // f_bsize File system block size. blocksize: bytes_per_cluster as u64, // f_blocks - Total number of blocks on the file system, in units of f_frsize. diff --git a/src/uucore/src/lib/features/tokenize/sub.rs b/src/uucore/src/lib/features/tokenize/sub.rs index 6f9196d93..ac471ae3e 100644 --- a/src/uucore/src/lib/features/tokenize/sub.rs +++ b/src/uucore/src/lib/features/tokenize/sub.rs @@ -60,6 +60,7 @@ pub struct Sub { field_char: char, field_type: FieldType, orig: String, + prefix_char: char, } impl Sub { pub fn new( @@ -67,6 +68,7 @@ impl Sub { second_field: CanAsterisk>, field_char: char, orig: String, + prefix_char: char, ) -> Self { // for more dry printing, field characters are grouped // in initialization of token. @@ -90,6 +92,7 @@ impl Sub { field_char, field_type, orig, + prefix_char, } } } @@ -126,6 +129,11 @@ impl SubParser { fn build_token(parser: Self) -> Box { // not a self method so as to allow move of sub-parser vals. // return new Sub struct as token + let prefix_char = match &parser.min_width_tmp { + Some(width) if width.starts_with('0') => '0', + _ => ' ', + }; + let t: Box = Box::new(Sub::new( if parser.min_width_is_asterisk { CanAsterisk::Asterisk @@ -139,6 +147,7 @@ impl SubParser { }, parser.field_char.unwrap(), parser.text_so_far, + prefix_char, )); t } @@ -394,7 +403,7 @@ impl token::Token for Sub { final_str.push_str(&pre_min_width); } for _ in 0..diff { - final_str.push(' '); + final_str.push(self.prefix_char); } if pad_before { final_str.push_str(&pre_min_width); diff --git a/src/uucore/src/lib/features/wide.rs b/src/uucore/src/lib/features/wide.rs index 49ce575a7..6b9368f50 100644 --- a/src/uucore/src/lib/features/wide.rs +++ b/src/uucore/src/lib/features/wide.rs @@ -27,10 +27,10 @@ pub trait FromWide { fn from_wide_null(wide: &[u16]) -> Self; } impl FromWide for String { - fn from_wide(wide: &[u16]) -> String { + fn from_wide(wide: &[u16]) -> Self { OsString::from_wide(wide).to_string_lossy().into_owned() } - fn from_wide_null(wide: &[u16]) -> String { + fn from_wide_null(wide: &[u16]) -> Self { let len = wide.iter().take_while(|&&c| c != 0).count(); OsString::from_wide(&wide[..len]) .to_string_lossy() diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index e14716591..a2753b964 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -231,6 +231,7 @@ pub mod arguments { .help("override the usual backup suffix") .takes_value(true) .value_name("SUFFIX") + .allow_hyphen_values(true) } } @@ -618,4 +619,13 @@ mod tests { assert_eq!(result, BackupMode::SimpleBackup); env::remove_var(ENV_VERSION_CONTROL); } + + #[test] + fn test_suffix_takes_hyphen_value() { + let _dummy = TEST_MUTEX.lock().unwrap(); + let matches = make_app().get_matches_from(vec!["app", "-b", "--suffix", "-v"]); + + let result = determine_backup_suffix(&matches); + assert_eq!(result, "-v"); + } } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 0a4dfd16d..cfa946d47 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -385,6 +385,24 @@ fn test_cp_arg_suffix() { ); } +#[test] +fn test_cp_arg_suffix_hyphen_value() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("-b") + .arg("--suffix") + .arg("-v") + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!( + at.read(&*format!("{}-v", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + #[test] fn test_cp_custom_backup_suffix_via_env() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1489,3 +1507,13 @@ fn test_dir_recursive_copy() { .fails() .stderr_contains("cannot copy a directory"); } + +#[test] +fn test_cp_dir_vs_file() { + new_ucmd!() + .arg("-R") + .arg(TEST_COPY_FROM_FOLDER) + .arg(TEST_EXISTING_FILE) + .fails() + .stderr_only("cp: cannot overwrite non-directory with directory"); +} diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index e73fe0673..d27122a75 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm +// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi use crate::common::util::*; @@ -178,6 +178,59 @@ fn test_stdin_stdout_count_w_multiplier() { .success(); } +#[test] +fn test_b_multiplier() { + // "2b" means 2 * 512, which is 1024. + new_ucmd!() + .args(&["bs=2b", "count=1"]) + .pipe_in("a".repeat(1025)) + .succeeds() + .stdout_is("a".repeat(1024)); +} + +#[test] +fn test_x_multiplier() { + // "2x3" means 2 * 3, which is 6. + new_ucmd!() + .args(&["bs=2x3", "count=1"]) + .pipe_in("abcdefghi") + .succeeds() + .stdout_is("abcdef"); +} + +#[test] +fn test_zero_multiplier_warning() { + for arg in ["count", "seek", "skip"] { + new_ucmd!() + .args(&[format!("{}=00x1", arg).as_str(), "status=none"]) + .pipe_in("") + .succeeds() + .no_stdout() + .no_stderr(); + + new_ucmd!() + .args(&[format!("{}=0x1", arg).as_str(), "status=none"]) + .pipe_in("") + .succeeds() + .no_stdout() + .stderr_contains("warning: '0x' is a zero multiplier; use '00x' if that is intended"); + + new_ucmd!() + .args(&[format!("{}=0x0x1", arg).as_str(), "status=none"]) + .pipe_in("") + .succeeds() + .no_stdout() + .stderr_is("dd: warning: '0x' is a zero multiplier; use '00x' if that is intended\ndd: warning: '0x' is a zero multiplier; use '00x' if that is intended\n"); + + new_ucmd!() + .args(&[format!("{}=1x0x1", arg).as_str(), "status=none"]) + .pipe_in("") + .succeeds() + .no_stdout() + .stderr_contains("warning: '0x' is a zero multiplier; use '00x' if that is intended"); + } +} + #[test] fn test_final_stats_noxfer() { new_ucmd!() diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index efc097773..b0506d071 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -179,15 +179,15 @@ fn test_du_hard_link() { #[cfg(target_vendor = "apple")] fn _du_hard_link(s: &str) { - assert_eq!(s, "12\tsubdir/links\n") + assert_eq!(s, "12\tsubdir/links\n"); } #[cfg(target_os = "windows")] fn _du_hard_link(s: &str) { - assert_eq!(s, "8\tsubdir/links\n") + assert_eq!(s, "8\tsubdir/links\n"); } #[cfg(target_os = "freebsd")] fn _du_hard_link(s: &str) { - assert_eq!(s, "16\tsubdir/links\n") + assert_eq!(s, "16\tsubdir/links\n"); } #[cfg(all( not(target_vendor = "apple"), diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 09ed9658f..401e16706 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -138,11 +138,19 @@ fn test_escape_short_octal() { } #[test] -fn test_escape_no_octal() { +fn test_escape_nul() { new_ucmd!() .args(&["-e", "foo\\0 bar"]) .succeeds() - .stdout_only("foo\\0 bar\n"); + .stdout_only("foo\0 bar\n"); +} + +#[test] +fn test_escape_octal_invalid_digit() { + new_ucmd!() + .args(&["-e", "foo\\08 bar"]) + .succeeds() + .stdout_only("foo\u{0}8 bar\n"); } #[test] diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index e1950f0df..3559e74cd 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -219,7 +219,7 @@ fn test_change_directory() { .args(&pwd) .succeeds() .stdout_move_str(); - assert_eq!(out.trim(), temporary_path.as_os_str()) + assert_eq!(out.trim(), temporary_path.as_os_str()); } #[test] diff --git a/tests/by-util/test_false.rs b/tests/by-util/test_false.rs index bbabc7a52..5ce64e7a8 100644 --- a/tests/by-util/test_false.rs +++ b/tests/by-util/test_false.rs @@ -1,6 +1,53 @@ use crate::common::util::*; +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +use std::fs::OpenOptions; #[test] fn test_exit_code() { new_ucmd!().fails(); } + +#[test] +fn test_version() { + new_ucmd!() + .args(&["--version"]) + .fails() + .stdout_contains("false"); +} + +#[test] +fn test_help() { + new_ucmd!() + .args(&["--help"]) + .fails() + .stdout_contains("false"); +} + +#[test] +fn test_short_options() { + for option in ["-h", "-V"] { + new_ucmd!().arg(option).fails().stdout_is(""); + } +} + +#[test] +fn test_conflict() { + new_ucmd!() + .args(&["--help", "--version"]) + .fails() + .stdout_is(""); +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +fn test_full() { + for option in ["--version", "--help"] { + let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); + + new_ucmd!() + .arg(option) + .set_stdout(dev_full) + .fails() + .stderr_contains("No space left on device"); + } +} diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 246f5b62a..25410d76f 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -306,6 +306,10 @@ fn test_head_invalid_num() { )); } } + new_ucmd!() + .args(&["-c", "-³"]) + .fails() + .stderr_is("head: invalid number of bytes: '³'"); } #[test] diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 97169f934..23bebf224 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -815,6 +815,31 @@ fn test_install_backup_short_custom_suffix() { assert!(at.file_exists(&format!("{}{}", file_b, suffix))); } +#[test] +fn test_install_backup_short_custom_suffix_hyphen_value() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_install_backup_custom_suffix_file_a"; + let file_b = "test_install_backup_custom_suffix_file_b"; + let suffix = "-v"; + + at.touch(file_a); + at.touch(file_b); + scene + .ucmd() + .arg("-b") + .arg(format!("--suffix={}", suffix)) + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}{}", file_b, suffix))); +} + #[test] fn test_install_backup_custom_suffix_via_env() { let scene = TestScenario::new(util_name!()); diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index 9fa73c0bc..a2a31464f 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -180,6 +180,33 @@ fn test_symlink_custom_backup_suffix() { assert_eq!(at.resolve_link(backup), file); } +#[test] +fn test_symlink_custom_backup_suffix_hyphen_value() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_symlink_custom_backup_suffix"; + let link = "test_symlink_custom_backup_suffix_link"; + let suffix = "-v"; + + at.touch(file); + at.symlink_file(file, link); + assert!(at.file_exists(file)); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); + + let arg = &format!("--suffix={}", suffix); + ucmd.args(&["-b", arg, "-s", file, link]) + .succeeds() + .no_stderr(); + assert!(at.file_exists(file)); + + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); + + let backup = &format!("{}{}", link, suffix); + assert!(at.is_symlink(backup)); + assert_eq!(at.resolve_link(backup), file); +} + #[test] fn test_symlink_backup_numbering() { let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index e3fd99e00..f61611390 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1327,17 +1327,14 @@ fn test_ls_order_time() { // So the order should be 2 3 4 1 for arg in &["-u", "--time=atime", "--time=access", "--time=use"] { let result = scene.ucmd().arg("-t").arg(arg).succeeds(); - let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap(); - let file4_access = at.open("test-4").metadata().unwrap().accessed().unwrap(); + at.open("test-3").metadata().unwrap().accessed().unwrap(); + at.open("test-4").metadata().unwrap().accessed().unwrap(); // It seems to be dependent on the platform whether the access time is actually set - if file3_access > file4_access { - result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n"); - } else { - // Access time does not seem to be set on Windows and some other - // systems so the order is 4 3 2 1 - result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); - } + #[cfg(unix)] + result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n"); + #[cfg(windows)] + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); } // test-2 had the last ctime change when the permissions were set diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 89f4043f8..a0bd0209d 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -340,6 +340,27 @@ fn test_mv_custom_backup_suffix() { assert!(at.file_exists(&format!("{}{}", file_b, suffix))); } +#[test] +fn test_mv_custom_backup_suffix_hyphen_value() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_custom_backup_suffix_file_a"; + let file_b = "test_mv_custom_backup_suffix_file_b"; + let suffix = "-v"; + + at.touch(file_a); + at.touch(file_b); + ucmd.arg("-b") + .arg(format!("--suffix={}", suffix)) + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}{}", file_b, suffix))); +} + #[test] fn test_mv_custom_backup_suffix_via_env() { let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index b5c9dc3ed..b3e608dc9 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -429,3 +429,19 @@ fn sub_any_specifiers_after_second_param() { .succeeds() .stdout_only("3"); } + +#[test] +fn stop_after_additional_escape() { + new_ucmd!() + .args(&["A%sC\\cD%sF", "B", "E"]) //spell-checker:disable-line + .succeeds() + .stdout_only("ABC"); +} + +#[test] +fn sub_float_leading_zeroes() { + new_ucmd!() + .args(&["%010f", "1"]) + .succeeds() + .stdout_only("001.000000"); +} diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 5cf622fb8..5ae56b29e 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1066,10 +1066,13 @@ fn test_separator_null() { #[test] fn test_output_is_input() { let input = "a\nb\nc\n"; - let (at, mut cmd) = at_and_ucmd!(); + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; at.touch("file"); at.append("file", input); - cmd.args(&["-m", "-u", "-o", "file", "file", "file", "file"]) + scene + .ucmd_keepenv() + .args(&["-m", "-u", "-o", "file", "file", "file", "file"]) .succeeds(); assert_eq!(at.read("file"), input); } diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 9bbb1c1ca..8c1255a88 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -36,6 +36,12 @@ fn test_group_num() { assert_eq!("", group_num("")); } +#[test] +#[should_panic] +fn test_group_num_panic_if_invalid_numeric_characters() { + group_num("³³³³³"); +} + #[cfg(test)] mod test_generate_tokens { use super::*; diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index dcdb2e9dc..ebcd29cf5 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -491,6 +491,10 @@ fn test_tail_invalid_num() { )); } } + new_ucmd!() + .args(&["-c", "-³"]) + .fails() + .stderr_is("tail: invalid number of bytes: '³'"); } #[test] diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index e661907cc..dd4a0b6cc 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -530,3 +530,12 @@ fn test_touch_permission_denied_error_msg() { &full_path )); } + +#[test] +fn test_touch_no_args() { + let mut ucmd = new_ucmd!(); + ucmd.fails().stderr_only( + r##"touch: missing file operand +Try 'touch --help' for more information."##, + ); +} diff --git a/tests/by-util/test_true.rs b/tests/by-util/test_true.rs index 1d8622c96..aba32578b 100644 --- a/tests/by-util/test_true.rs +++ b/tests/by-util/test_true.rs @@ -1,6 +1,53 @@ use crate::common::util::*; +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +use std::fs::OpenOptions; #[test] fn test_exit_code() { new_ucmd!().succeeds(); } + +#[test] +fn test_version() { + new_ucmd!() + .args(&["--version"]) + .succeeds() + .stdout_contains("true"); +} + +#[test] +fn test_help() { + new_ucmd!() + .args(&["--help"]) + .succeeds() + .stdout_contains("true"); +} + +#[test] +fn test_short_options() { + for option in ["-h", "-V"] { + new_ucmd!().arg(option).succeeds().stdout_is(""); + } +} + +#[test] +fn test_conflict() { + new_ucmd!() + .args(&["--help", "--version"]) + .succeeds() + .stdout_is(""); +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +fn test_full() { + for option in ["--version", "--help"] { + let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); + + new_ucmd!() + .arg(option) + .set_stdout(dev_full) + .fails() + .stderr_contains("No space left on device"); + } +} diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 0ef65ec16..c1e44f605 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -250,11 +250,24 @@ fn test_size_and_reference() { #[test] fn test_error_filename_only() { // truncate: you must specify either '--size' or '--reference' - new_ucmd!().args(&["file"]).fails().stderr_contains( - "error: The following required arguments were not provided: + new_ucmd!() + .args(&["file"]) + .fails() + .code_is(1) + .stderr_contains( + "error: The following required arguments were not provided: --reference --size ", - ); + ); +} + +#[test] +fn test_invalid_option() { + // truncate: cli parsing error returns 1 + new_ucmd!() + .args(&["--this-arg-does-not-exist"]) + .fails() + .code_is(1); } #[test] diff --git a/util/GHA-delete-GNU-workflow-logs.sh b/util/GHA-delete-GNU-workflow-logs.sh index 19e3311d4..f7406b831 100755 --- a/util/GHA-delete-GNU-workflow-logs.sh +++ b/util/GHA-delete-GNU-workflow-logs.sh @@ -15,21 +15,21 @@ ME_parent_dir_abs="$(realpath -mP -- "${ME_parent_dir}")" # * `gh` available? unset GH -gh --version 1>/dev/null 2>&1 -if [ $? -eq 0 ]; then export GH="gh"; fi +if gh --version 1>/dev/null 2>&1; then + export GH="gh" +else + echo "ERR!: missing \`gh\` (see install instructions at )" 1>&2 +fi # * `jq` available? unset JQ -jq --version 1>/dev/null 2>&1 -if [ $? -eq 0 ]; then export JQ="jq"; fi +if jq --version 1>/dev/null 2>&1; then + export JQ="jq" +else + echo "ERR!: missing \`jq\` (install with \`sudo apt install jq\`)" 1>&2 +fi if [ -z "${GH}" ] || [ -z "${JQ}" ]; then - if [ -z "${GH}" ]; then - echo 'ERR!: missing `gh` (see install instructions at )' 1>&2 - fi - if [ -z "${JQ}" ]; then - echo 'ERR!: missing `jq` (install with `sudo apt install jq`)' 1>&2 - fi exit 1 fi diff --git a/util/build-code_coverage.sh b/util/build-code_coverage.sh index 7ad3165fe..b92b7eb48 100755 --- a/util/build-code_coverage.sh +++ b/util/build-code_coverage.sh @@ -8,12 +8,13 @@ FEATURES_OPTION="--features feat_os_unix" -ME_dir="$(dirname -- $(readlink -fm -- "$0"))" +ME_dir="$(dirname -- "$(readlink -fm -- "$0")")" REPO_main_dir="$(dirname -- "${ME_dir}")" -cd "${REPO_main_dir}" +cd "${REPO_main_dir}" && echo "[ \"$PWD\" ]" +#shellcheck disable=SC2086 UTIL_LIST=$("${ME_dir}"/show-utils.sh ${FEATURES_OPTION}) CARGO_INDIVIDUAL_PACKAGE_OPTIONS="" for UTIL in ${UTIL_LIST}; do @@ -30,10 +31,12 @@ export RUSTC_WRAPPER="" ## NOTE: RUSTC_WRAPPER=='sccache' breaks code covera export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" export RUSTDOCFLAGS="-Cpanic=abort" export RUSTUP_TOOLCHAIN="nightly-gnu" -cargo build ${FEATURES_OPTION} -cargo test --no-run ${FEATURES_OPTION} -cargo test --quiet ${FEATURES_OPTION} -cargo test --quiet ${FEATURES_OPTION} ${CARGO_INDIVIDUAL_PACKAGE_OPTIONS} +#shellcheck disable=SC2086 +{ cargo build ${FEATURES_OPTION} + cargo test --no-run ${FEATURES_OPTION} + cargo test --quiet ${FEATURES_OPTION} + cargo test --quiet ${FEATURES_OPTION} ${CARGO_INDIVIDUAL_PACKAGE_OPTIONS} +} export COVERAGE_REPORT_DIR if [ -z "${COVERAGE_REPORT_DIR}" ]; then COVERAGE_REPORT_DIR="${REPO_main_dir}/target/debug/coverage-nix"; fi @@ -47,8 +50,7 @@ mkdir -p "${COVERAGE_REPORT_DIR}" grcov . --output-type lcov --output-path "${COVERAGE_REPORT_DIR}/../lcov.info" --branch --ignore build.rs --ignore '/*' --ignore '[A-Za-z]:/*' --ignore 'C:/Users/*' --excl-br-line '^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()' # * build HTML # -- use `genhtml` if available for display of additional branch coverage information -genhtml --version 2>/dev/null 1>&2 -if [ $? -eq 0 ]; then +if genhtml --version 2>/dev/null 1>&2; then genhtml "${COVERAGE_REPORT_DIR}/../lcov.info" --output-directory "${COVERAGE_REPORT_DIR}" --branch-coverage --function-coverage | grep ": [0-9]" else grcov . --output-type html --output-path "${COVERAGE_REPORT_DIR}" --branch --ignore build.rs --ignore '/*' --ignore '[A-Za-z]:/*' --ignore 'C:/Users/*' --excl-br-line '^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()' diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 8b1e4925b..a52d42107 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -15,7 +15,7 @@ if test ! -d ../gnulib; then fi -pushd $(pwd) +pushd "$PWD" make PROFILE=release BUILDDIR="$PWD/target/release/" cp "${BUILDDIR}/install" "${BUILDDIR}/ginstall" # The GNU tests rename this script before running, to avoid confusion with the make target @@ -49,7 +49,7 @@ make -j "$(nproc)" # Used to be 36. Reduced to 20 to decrease the log size for i in {00..20} do - make tests/factor/t${i}.sh + make "tests/factor/t${i}.sh" done # strip the long stuff diff --git a/util/publish.sh b/util/publish.sh index ae171e39c..6f4d9f237 100755 --- a/util/publish.sh +++ b/util/publish.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh set -e ARG="" @@ -6,26 +6,21 @@ if test "$1" != "--do-it"; then ARG="--dry-run --allow-dirty" fi -cd src/uucore/ -cargo publish $ARG -cd - -sleep 2s - -cd src/uucore_procs/ -cargo publish $ARG -cd - -sleep 2s - -cd src/uu/stdbuf/src/libstdbuf/ -cargo publish $ARG -cd - -sleep 2s +for dir in src/uucore/ src/uucore_procs/ src/uu/stdbuf/src/libstdbuf/ ; do + ( cd "$dir" + #shellcheck disable=SC2086 + cargo publish $ARG + ) + sleep 2s +done PROGS=$(ls -1d src/uu/*/) for p in $PROGS; do - cd $p - cargo publish $ARG - cd - + ( cd "$p" + #shellcheck disable=SC2086 + cargo publish $ARG + ) done +#shellcheck disable=SC2086 cargo publish $ARG diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh index 483fc1be9..123c4dab2 100755 --- a/util/run-gnu-test.sh +++ b/util/run-gnu-test.sh @@ -1,10 +1,28 @@ #!/bin/bash +# `$0 [TEST]` +# run GNU test (or all tests if TEST is missing/null) # spell-checker:ignore (env/vars) BUILDDIR GNULIB SUBDIRS -cd "$(dirname "${BASH_SOURCE[0]}")/../.." + +ME_dir="$(dirname -- "$(readlink -fm -- "$0")")" +REPO_main_dir="$(dirname -- "${ME_dir}")" + set -e -BUILDDIR="${PWD}/uutils/target/release" -GNULIB_DIR="${PWD}/gnulib" -pushd gnu + +### * config (from environment with fallback defaults) + +path_UUTILS=${path_UUTILS:-${REPO_main_dir}} +path_GNU=${path_GNU:-${path_UUTILS}/../gnu} +path_GNULIB=${path_GNULIB:-${path_UUTILS}/../gnulib} + +### + +BUILD_DIR="$(realpath -- "${path_UUTILS}/target/release")" +GNULIB_DIR="$(realpath -- "${path_GNULIB}")" + +export BUILD_DIR +export GNULIB_DIR + +pushd "$(realpath -- "${path_GNU}")" export RUST_BACKTRACE=1 @@ -13,4 +31,5 @@ if test -n "$1"; then export RUN_TEST="TESTS=$1" fi +#shellcheck disable=SC2086 timeout -sKILL 2h make -j "$(nproc)" check $RUN_TEST SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make diff --git a/util/show-code_coverage.sh b/util/show-code_coverage.sh index 365041434..6226d856b 100755 --- a/util/show-code_coverage.sh +++ b/util/show-code_coverage.sh @@ -2,15 +2,14 @@ # spell-checker:ignore (vars) OSID -ME_dir="$(dirname -- $(readlink -fm -- "$0"))" +ME_dir="$(dirname -- "$(readlink -fm -- "$0")")" REPO_main_dir="$(dirname -- "${ME_dir}")" export COVERAGE_REPORT_DIR="${REPO_main_dir}/target/debug/coverage-nix" -"${ME_dir}/build-code_coverage.sh" -if [ $? -ne 0 ]; then exit 1 ; fi +if ! "${ME_dir}/build-code_coverage.sh"; then exit 1 ; fi case ";$OSID_tags;" in - *";wsl;"* ) powershell.exe -c $(wslpath -w "${COVERAGE_REPORT_DIR}"/index.html) ;; + *";wsl;"* ) powershell.exe -c "$(wslpath -w "${COVERAGE_REPORT_DIR}"/index.html)" ;; * ) xdg-open --version >/dev/null 2>&1 && xdg-open "${COVERAGE_REPORT_DIR}"/index.html || echo "report available at '\"${COVERAGE_REPORT_DIR}\"/index.html'" ;; esac ; diff --git a/util/show-utils.sh b/util/show-utils.sh index b4a613d9b..f69b42678 100755 --- a/util/show-utils.sh +++ b/util/show-utils.sh @@ -15,17 +15,13 @@ default_utils="base32 base64 basename cat cksum comm cp cut date dircolors dirna project_main_dir="${ME_parent_dir_abs}" # printf 'project_main_dir="%s"\n' "${project_main_dir}" -cd "${project_main_dir}" +cd "${project_main_dir}" && # `jq` available? -unset JQ -jq --version 1>/dev/null 2>&1 -if [ $? -eq 0 ]; then export JQ="jq"; fi - -if [ -z "${JQ}" ]; then - echo 'WARN: missing `jq` (install with `sudo apt install jq`); falling back to default (only fully cross-platform) utility list' 1>&2 - echo $default_utils +if ! jq --version 1>/dev/null 2>&1; then + echo "WARN: missing \`jq\` (install with \`sudo apt install jq\`); falling back to default (only fully cross-platform) utility list" 1>&2 + echo "$default_utils" else - cargo metadata $* --format-version 1 | jq -r "[.resolve.nodes[] | { id: .id, deps: [.deps[] | { name:.name, pkg:.pkg }] }] | .[] | select(.id|startswith(\"coreutils\")) | [.deps[] | select((.name|startswith(\"uu_\")) or (.pkg|startswith(\"uu_\")))] | [.[].pkg | match(\"^\\\w+\";\"g\")] | [.[].string | sub(\"^uu_\"; \"\")] | sort | join(\" \")" - # cargo metadata $* --format-version 1 | jq -r "[.resolve.nodes[] | { id: .id, deps: [.deps[] | { name:.name, pkg:.pkg }] }] | .[] | select(.id|startswith(\"coreutils\")) | [.deps[] | select((.name|startswith(\"uu_\")) or (.pkg|startswith(\"uu_\")))] | [.[].pkg | match(\"^\\\w+\";\"g\")] | [.[].string] | sort | join(\" \")" + cargo metadata "$*" --format-version 1 | jq -r "[.resolve.nodes[] | { id: .id, deps: [.deps[] | { name:.name, pkg:.pkg }] }] | .[] | select(.id|startswith(\"coreutils\")) | [.deps[] | select((.name|startswith(\"uu_\")) or (.pkg|startswith(\"uu_\")))] | [.[].pkg | match(\"^\\\w+\";\"g\")] | [.[].string | sub(\"^uu_\"; \"\")] | sort | join(\" \")" + # cargo metadata "$*" --format-version 1 | jq -r "[.resolve.nodes[] | { id: .id, deps: [.deps[] | { name:.name, pkg:.pkg }] }] | .[] | select(.id|startswith(\"coreutils\")) | [.deps[] | select((.name|startswith(\"uu_\")) or (.pkg|startswith(\"uu_\")))] | [.[].pkg | match(\"^\\\w+\";\"g\")] | [.[].string] | sort | join(\" \")" fi diff --git a/util/update-version.sh b/util/update-version.sh index 503f65e52..62b130bda 100755 --- a/util/update-version.sh +++ b/util/update-version.sh @@ -1,10 +1,10 @@ -#!/bin/bash +#!/bin/sh # This is a stupid helper. I will mass replace all versions (including other crates) # So, it should be triple-checked # How to ship a new release: # 1) update this script -# 2) run it: bash util/update-version.sh +# 2) run it: sh util/update-version.sh # 3) Do a spot check with "git diff" # 4) cargo test --release --features unix # 5) Run util/publish.sh in dry mode (it will fail as packages needs more recent version of uucore) @@ -23,6 +23,7 @@ UUCORE_TO="0.0.11" PROGS=$(ls -1d src/uu/*/Cargo.toml src/uu/stdbuf/src/libstdbuf/Cargo.toml Cargo.toml src/uu/base64/Cargo.toml) # update the version of all programs +#shellcheck disable=SC2086 sed -i -e "s|version = \"$FROM\"|version = \"$TO\"|" $PROGS # Update uucore_procs @@ -35,6 +36,8 @@ sed -i -e "s|= { optional=true, version=\"$FROM\", package=\"uu_|= { optional=tr # Update uucore itself sed -i -e "s|version = \"$UUCORE_FROM\"|version = \"$UUCORE_TO\"|" src/uucore/Cargo.toml # Update crates using uucore +#shellcheck disable=SC2086 sed -i -e "s|uucore = { version=\">=$UUCORE_FROM\",|uucore = { version=\">=$UUCORE_TO\",|" $PROGS # Update crates using uucore_procs +#shellcheck disable=SC2086 sed -i -e "s|uucore_procs = { version=\">=$UUCORE_PROCS_FROM\",|uucore_procs = { version=\">=$UUCORE_PROCS_TO\",|" $PROGS