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

Fix merge conflict in tests/by-util/test_cp.rs

This commit is contained in:
Narasimha Prasanna HN 2022-02-07 23:26:06 +05:30
commit 0379fca260
63 changed files with 1178 additions and 554 deletions

View file

@ -1,2 +1,11 @@
[target.x86_64-unknown-redox] [target.x86_64-unknown-redox]
linker = "x86_64-unknown-redox-gcc" 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",
]

View file

@ -1,7 +1,7 @@
name: CICD name: CICD
# spell-checker:ignore (acronyms) CICD MSVC musl # 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 (jargon) SHAs deps dequote softprops subshell toolchain
# spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy # 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 # 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' ## 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.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 ; } 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 - name: Info
shell: bash shell: bash
run: | run: |

View file

@ -1,6 +1,6 @@
name: GnuTests 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] on: [push, pull_request]
@ -9,23 +9,55 @@ jobs:
name: Run GNU tests name: Run GNU tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: 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 - name: Checkout code uutil
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
path: 'uutils' path: '${{ steps.vars.outputs.path_UUTILS }}'
- name: Checkout GNU coreutils - name: Checkout GNU coreutils
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: 'coreutils/coreutils' repository: 'coreutils/coreutils'
path: 'gnu' path: '${{ steps.vars.outputs.path_GNU }}'
ref: v9.0 ref: ${{ steps.vars.outputs.repo_GNU_ref }}
- name: Checkout GNU coreutils library (gnulib) - name: Checkout GNU coreutils library (gnulib)
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: 'coreutils/gnulib' repository: 'coreutils/gnulib'
path: 'gnulib' path: '${{ steps.vars.outputs.path_GNULIB }}'
ref: 8e99f24c0931a38880c6ee9b8287c7da80b0036b ref: ${{ steps.vars.outputs.repo_GNULIB_ref }}
fetch-depth: 0 # gnu gets upset if gnulib is a shallow checkout 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 - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -43,28 +75,34 @@ jobs:
shell: bash shell: bash
run: | run: |
## Build binaries ## Build binaries
cd uutils cd '${{ steps.vars.outputs.path_UUTILS }}'
bash util/build-gnu.sh bash util/build-gnu.sh
- name: Run GNU tests - name: Run GNU tests
shell: bash shell: bash
run: | run: |
bash uutils/util/run-gnu-test.sh path_GNU='${{ steps.vars.outputs.path_GNU }}'
- name: Extract testing info 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 shell: bash
run: | run: |
## Extract testing info ## Extract/summarize testing info
LOG_FILE=gnu/tests/test-suite.log outputs() { step_id="summary"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
if test -f "$LOG_FILE" #
SUITE_LOG_FILE='${{ steps.vars.outputs.SUITE_LOG_FILE }}'
if test -f "${SUITE_LOG_FILE}"
then then
TOTAL=$(sed -n "s/.*# TOTAL: \(.*\)/\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" "$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" "$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" "$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" "$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" "$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 if [[ "$TOTAL" -eq 0 || "$TOTAL" -eq 1 ]]; then
echo "Error in the execution, failing early" echo "::error ::Failed to parse test results from '${SUITE_LOG_FILE}'; failing early"
exit 1 exit 1
fi fi
output="GNU tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR" output="GNU tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR"
echo "${output}" echo "${output}"
@ -78,54 +116,61 @@ jobs:
--arg fail "$FAIL" \ --arg fail "$FAIL" \
--arg xpass "$XPASS" \ --arg xpass "$XPASS" \
--arg error "$ERROR" \ --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 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 fi
- uses: actions/upload-artifact@v2 - name: Reserve SHA1/ID of 'test-summary'
uses: actions/upload-artifact@v2
with: with:
name: test-report name: "${{ steps.summary.outputs.HASH }}"
path: gnu/tests/**/*.log path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
- uses: actions/upload-artifact@v2 - name: Reserve test results summary
uses: actions/upload-artifact@v2
with: with:
name: gnu-result name: test-summary
path: gnu-result.json path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
- name: Download the result - name: Reserve test logs
uses: dawidd6/action-download-artifact@v2 uses: actions/upload-artifact@v2
with: with:
workflow: GnuTests.yml name: test-logs
name: gnu-result path: "${{ steps.vars.outputs.TEST_LOGS_GLOB }}"
repo: uutils/coreutils - name: Compare test failures VS reference
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
shell: bash shell: bash
run: | run: |
OLD_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" dl/test-suite.log | sort) REF_LOG_FILE='${{ steps.vars.outputs.path_reference }}/test-logs/test-suite.log'
NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" gnu/tests/test-suite.log | sort) REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json'
for LINE in $OLD_FAILING if test -f "${REF_LOG_FILE}"; then
do echo "Reference SHA1/ID (of '${REF_SUMMARY_FILE}'): $(sha1sum -- "${REF_SUMMARY_FILE}")"
if ! grep -Fxq $LINE<<<"$NEW_FAILING"; then REF_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort)
echo "::warning ::Congrats! The gnu test $LINE is now passing!" NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | sort)
fi for LINE in $REF_FAILING
done do
for LINE in $NEW_FAILING if ! grep -Fxq $LINE<<<"$NEW_FAILING"; then
do echo "::warning ::Congrats! The gnu test $LINE is now passing!"
if ! grep -Fxq $LINE<<<"$OLD_FAILING" fi
then done
echo "::error ::GNU test failed: $LINE. $LINE is passing on 'main'. Maybe you have to rebase?" for LINE in $NEW_FAILING
fi do
done if ! grep -Fxq $LINE<<<"$REF_FAILING"
- name: Compare against main results 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 shell: bash
run: | run: |
mv dl/gnu-result.json main-gnu-result.json REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json'
python uutils/util/compare_gnu_result.py 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

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/bin/sh
rustup target add x86_64-unknown-redox rustup target add x86_64-unknown-redox
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys AA12E97F0881517F sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys AA12E97F0881517F

2
.vscode/.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Configure GitHub to not mark comments in configuration files as errors
*.json linguist-language=jsonc

26
.vscode/cSpell.json vendored
View file

@ -1,7 +1,12 @@
// `cspell` settings // `cspell` settings
{ {
"version": "0.1", // Version of the setting file. Always 0.1 // version of the setting file (always 0.1)
"language": "en", // language - current active spelling language "version": "0.1",
// spelling language
"language": "en",
// custom dictionaries
"dictionaries": ["acronyms+names", "jargon", "people", "shell", "workspace"], "dictionaries": ["acronyms+names", "jargon", "people", "shell", "workspace"],
"dictionaryDefinitions": [ "dictionaryDefinitions": [
{ "name": "acronyms+names", "path": "./cspell.dictionaries/acronyms+names.wordlist.txt" }, { "name": "acronyms+names", "path": "./cspell.dictionaries/acronyms+names.wordlist.txt" },
@ -10,10 +15,19 @@
{ "name": "shell", "path": "./cspell.dictionaries/shell.wordlist.txt" }, { "name": "shell", "path": "./cspell.dictionaries/shell.wordlist.txt" },
{ "name": "workspace", "path": "./cspell.dictionaries/workspace.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/**"], // files to ignore (globs supported)
// ignoreWords - a list of words to be ignored (even if they are in the flagWords) "ignorePaths": [
"Cargo.lock",
"target/**",
"tests/**/fixtures/**",
"src/uu/dd/test-resources/**",
"vendor/**"
],
// words to ignore (even if they are in the flagWords)
"ignoreWords": [], "ignoreWords": [],
// words - list of words to be always considered correct
// words to always consider correct
"words": [] "words": []
} }

View file

@ -1,10 +1,8 @@
// spell-checker:ignore (misc) matklad
// see <http://go.microsoft.com/fwlink/?LinkId=827846> for the documentation about the extensions.json format
{ {
// spell-checker:ignore (misc) matklad
// see <http://go.microsoft.com/fwlink/?LinkId=827846> for the documentation about the extensions.json format
"recommendations": [ "recommendations": [
// Rust language support. // Rust language support
"rust-lang.rust",
// Provides support for rust-analyzer: novel LSP server for the Rust programming language.
"matklad.rust-analyzer", "matklad.rust-analyzer",
// `cspell` spell-checker support // `cspell` spell-checker support
"streetsidesoftware.code-spell-checker" "streetsidesoftware.code-spell-checker"

View file

@ -87,7 +87,7 @@ fn main() {
}; };
if util == "completion" { if util == "completion" {
gen_completions(args, utils); gen_completions(args, &utils);
} }
match utils.get(util) { 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 /// Prints completions for the utility in the first parameter for the shell in the second parameter to stdout
fn gen_completions<T: uucore::Args>( fn gen_completions<T: uucore::Args>(
args: impl Iterator<Item = OsString>, args: impl Iterator<Item = OsString>,
util_map: UtilityMap<T>, util_map: &UtilityMap<T>,
) -> ! { ) -> ! {
let all_utilities: Vec<_> = std::iter::once("coreutils") let all_utilities: Vec<_> = std::iter::once("coreutils")
.chain(util_map.keys().copied()) .chain(util_map.keys().copied())
@ -168,9 +168,9 @@ fn gen_completions<T: uucore::Args>(
process::exit(0); process::exit(0);
} }
fn gen_coreutils_app<T: uucore::Args>(util_map: UtilityMap<T>) -> App<'static> { fn gen_coreutils_app<T: uucore::Args>(util_map: &UtilityMap<T>) -> App<'static> {
let mut app = App::new("coreutils"); let mut app = App::new("coreutils");
for (_, (_, sub_app)) in &util_map { for (_, (_, sub_app)) in util_map {
app = app.subcommand(sub_app()); app = app.subcommand(sub_app());
} }
app app

View file

@ -12,14 +12,14 @@ use uucore::{encoding::Format, error::UResult};
pub mod base_common; pub mod base_common;
static ABOUT: &str = " static ABOUT: &str = "\
With no FILE, or when FILE is -, read standard input. With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base32 alphabet in RFC The data are encoded as described for the base32 alphabet in RFC
4648. When decoding, the input may contain newlines in addition 4648. When decoding, the input may contain newlines in addition
to the bytes of the formal base32 alphabet. Use --ignore-garbage to the bytes of the formal base32 alphabet. Use --ignore-garbage
to attempt to recover from any other non-alphabet bytes in the to attempt to recover from any other non-alphabet bytes in the
encoded stream. encoded stream.
"; ";
fn usage() -> String { fn usage() -> String {

View file

@ -13,14 +13,14 @@ use uucore::{encoding::Format, error::UResult};
use std::io::{stdin, Read}; use std::io::{stdin, Read};
static ABOUT: &str = " static ABOUT: &str = "\
With no FILE, or when FILE is -, read standard input. With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base64 alphabet in RFC The data are encoded as described for the base64 alphabet in RFC
3548. When decoding, the input may contain newlines in addition 3548. When decoding, the input may contain newlines in addition
to the bytes of the formal base64 alphabet. Use --ignore-garbage to the bytes of the formal base64 alphabet. Use --ignore-garbage
to attempt to recover from any other non-alphabet bytes in the to attempt to recover from any other non-alphabet bytes in the
encoded stream. encoded stream.
"; ";
fn usage() -> String { fn usage() -> String {

View file

@ -19,12 +19,12 @@ use uucore::{
use std::io::{stdin, Read}; use std::io::{stdin, Read};
static ABOUT: &str = " static ABOUT: &str = "\
With no FILE, or when FILE is -, read standard input. With no FILE, or when FILE is -, read standard input.
When decoding, the input may contain newlines in addition to the bytes of When decoding, the input may contain newlines in addition to the bytes of
the formal alphabet. Use --ignore-garbage to attempt to recover the formal alphabet. Use --ignore-garbage to attempt to recover
from any other non-alphabet bytes in the encoded stream. from any other non-alphabet bytes in the encoded stream.
"; ";
const ENCODINGS: &[(&str, Format)] = &[ const ENCODINGS: &[(&str, Format)] = &[

View file

@ -201,12 +201,12 @@ fn set_main_group(group: &str) -> UResult<()> {
} }
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] #[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
fn set_groups(groups: Vec<libc::gid_t>) -> libc::c_int { fn set_groups(groups: &[libc::gid_t]) -> libc::c_int {
unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) } unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) }
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn set_groups(groups: Vec<libc::gid_t>) -> libc::c_int { fn set_groups(groups: &[libc::gid_t]) -> libc::c_int {
unsafe { setgroups(groups.len() as libc::size_t, groups.as_ptr()) } 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); groups_vec.push(gid);
} }
let err = set_groups(groups_vec); let err = set_groups(&groups_vec);
if err != 0 { if err != 0 {
return Err(ChrootError::SetGroupsFailed(Error::last_os_error()).into()); return Err(ChrootError::SetGroupsFailed(Error::last_os_error()).into());
} }

View file

@ -1035,7 +1035,10 @@ fn copy_directory(
if is_symlink && !options.dereference { if is_symlink && !options.dereference {
copy_link(&path, &local_to_target, symlinked_files)?; copy_link(&path, &local_to_target, symlinked_files)?;
} else if path.is_dir() && !local_to_target.exists() { } 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() { } else if !path.is_dir() {
if preserve_hard_links { if preserve_hard_links {
let mut found_hard_link = false; let mut found_hard_link = false;

View file

@ -4,7 +4,7 @@
// //
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore ctty, ctable, iconvflags, oconvflags // spell-checker:ignore ctty, ctable, iconvflags, oconvflags parseargs
#[cfg(test)] #[cfg(test)]
mod unit_tests; mod unit_tests;
@ -12,6 +12,8 @@ mod unit_tests;
use super::*; use super::*;
use std::error::Error; use std::error::Error;
use uucore::error::UError; use uucore::error::UError;
use uucore::parse_size::ParseSizeError;
use uucore::show_warning;
pub type Matches = ArgMatches; pub type Matches = ArgMatches;
@ -31,6 +33,25 @@ pub enum ParseError {
Unimplemented(String), 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 { impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
@ -310,28 +331,83 @@ fn parse_bytes_only(s: &str) -> Result<usize, ParseError> {
.map_err(|_| ParseError::MultiplierStringParseFailure(s.to_string())) .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<usize, ParseError> {
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. /// Parse byte and multiplier like 512, 5KiB, or 1G.
/// Uses uucore::parse_size, and adds the 'w' and 'c' suffixes which are mentioned /// Uses uucore::parse_size, and adds the 'w' and 'c' suffixes which are mentioned
/// in dd's info page. /// in dd's info page.
fn parse_bytes_with_opt_multiplier(s: &str) -> Result<usize, ParseError> { fn parse_bytes_with_opt_multiplier(s: &str) -> Result<usize, ParseError> {
if let Some(idx) = s.rfind('c') { // TODO On my Linux system, there seems to be a maximum block size of 4096 bytes:
parse_bytes_only(&s[..idx]) //
} else if let Some(idx) = s.rfind('w') { // $ printf "%0.sa" {1..10000} | dd bs=4095 count=1 status=none | wc -c
let partial = parse_bytes_only(&s[..idx])?; // 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 // Split on the 'x' characters. Each component will be parsed
.checked_mul(2) // individually, then multiplied together.
.ok_or_else(|| ParseError::MultiplierStringOverflow(s.to_string())) let mut total = 1;
} else { for part in s.split('x') {
uucore::parse_size::parse_size(s).map_err(|e| match e { let num = parse_bytes_no_x(part).map_err(|e| e.with_arg(s.to_string()))?;
uucore::parse_size::ParseSizeError::ParseFailure(s) => { total *= num;
ParseError::MultiplierStringParseFailure(s)
}
uucore::parse_size::ParseSizeError::SizeTooBig(s) => {
ParseError::MultiplierStringOverflow(s)
}
})
} }
Ok(total)
} }
pub fn parse_ibs(matches: &Matches) -> Result<usize, ParseError> { pub fn parse_ibs(matches: &Matches) -> Result<usize, ParseError> {
@ -689,3 +765,25 @@ pub fn parse_input_non_ascii(matches: &Matches) -> Result<bool, ParseError> {
Ok(false) 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());
}
}

View file

@ -12,17 +12,17 @@ use uucore::error::UResult;
use uucore::fsext::statfs_fn; use uucore::fsext::statfs_fn;
use uucore::fsext::{read_fs_list, FsUsage, MountInfo}; 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 number_prefix::NumberPrefix;
use std::cell::Cell; use std::cell::Cell;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::error::Error; use std::error::Error;
#[cfg(unix)] #[cfg(unix)]
use std::ffi::CString; use std::ffi::CString;
use std::fmt::Display; use std::fmt::Display;
use std::iter::FromIterator;
#[cfg(unix)] #[cfg(unix)]
use std::mem; use std::mem;
@ -69,6 +69,27 @@ struct Options {
fs_selector: FsSelector, 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)] #[derive(Debug, Clone)]
struct Filesystem { struct Filesystem {
mount_info: MountInfo, mount_info: MountInfo,
@ -80,18 +101,19 @@ fn usage() -> String {
} }
impl FsSelector { impl FsSelector {
fn new() -> Self { /// Convert command-line arguments into a [`FsSelector`].
Self::default() ///
} /// This function reads the include and exclude sets from
/// [`ArgMatches`] and returns the corresponding [`FsSelector`]
#[inline(always)] /// instance.
fn include(&mut self, fs_type: String) { fn from(matches: &ArgMatches) -> Self {
self.include.insert(fs_type); let include = HashSet::from_iter(matches.values_of_lossy(OPT_TYPE).unwrap_or_default());
} let exclude = HashSet::from_iter(
matches
#[inline(always)] .values_of_lossy(OPT_EXCLUDE_TYPE)
fn exclude(&mut self, fs_type: String) { .unwrap_or_default(),
self.exclude.insert(fs_type); );
Self { include, exclude }
} }
fn should_select(&self, fs_type: &str) -> bool { 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 { impl Filesystem {
// TODO: resolve uuid in `mount_info.dev_name` if exists // TODO: resolve uuid in `mount_info.dev_name` if exists
fn new(mount_info: MountInfo) -> Option<Self> { fn new(mount_info: MountInfo) -> Option<Self> {
@ -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<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> { fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> {
vmi.into_iter() let mut mount_info_by_id = HashMap::<String, Cell<MountInfo>>::new();
.filter_map(|mi| { for mi in vmi {
if (mi.remote && opt.show_local_fs) // Don't show remote filesystems if `--local` has been given.
|| (mi.dummy && !opt.show_all_fs && !opt.show_listed_fs) if mi.remote && opt.show_local_fs {
|| !opt.fs_selector.should_select(&mi.fs_type) continue;
{ }
None
} else { // Don't show pseudo filesystems unless `--all` has been given.
if paths.is_empty() { if mi.dummy && !opt.show_all_fs && !opt.show_listed_fs {
// No path specified continue;
return Some((mi.dev_id.clone(), mi)); }
}
if paths.contains(&mi.mount_dir) { // Don't show filesystems if they have been explicitly excluded.
// One or more paths have been provided if !opt.fs_selector.should_select(&mi.fs_type) {
Some((mi.dev_id.clone(), mi)) continue;
} else { }
// Not a path we want to see
None // 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;
.fold( }
HashMap::<String, Cell<MountInfo>>::new(),
|mut acc, (id, mi)| { // If the device ID has not been encountered yet, just store it.
#[allow(clippy::map_entry)] let id = mi.dev_id.clone();
{ #[allow(clippy::map_entry)]
if acc.contains_key(&id) { if !mount_info_by_id.contains_key(&id) {
let seen = acc[&id].replace(mi.clone()); mount_info_by_id.insert(id, Cell::new(mi));
let target_nearer_root = seen.mount_dir.len() > mi.mount_dir.len(); continue;
// 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() // Otherwise, if we have seen the current device ID before,
&& seen.mount_root.len() < mi.mount_root.len(); // then check if we need to update it or keep the previously
// let "real" devices with '/' in the name win. // seen one.
if (!mi.dev_name.starts_with('/') || seen.dev_name.starts_with('/')) let seen = mount_info_by_id[&id].replace(mi.clone());
// let points towards the root of the device win. let target_nearer_root = seen.mount_dir.len() > mi.mount_dir.len();
&& (!target_nearer_root || source_below_root) // With bind mounts, prefer items nearer the root of the source
// let an entry over-mounted on a new device win... let source_below_root = !seen.mount_root.is_empty()
&& (seen.dev_name == mi.dev_name && !mi.mount_root.is_empty()
/* ... but only when matching an existing mnt point, && seen.mount_root.len() < mi.mount_root.len();
to avoid problematic replacement when given // let "real" devices with '/' in the name win.
inaccurate mount lists, seen with some chroot if (!mi.dev_name.starts_with('/') || seen.dev_name.starts_with('/'))
environments for example. */ // let points towards the root of the device win.
|| seen.mount_dir != mi.mount_dir) && (!target_nearer_root || source_below_root)
{ // let an entry over-mounted on a new device win...
acc[&id].replace(seen); && (seen.dev_name == mi.dev_name
} /* ... but only when matching an existing mnt point,
} else { to avoid problematic replacement when given
acc.insert(id, Cell::new(mi)); inaccurate mount lists, seen with some chroot
} environments for example. */
acc || seen.mount_dir != mi.mount_dir)
} {
}, mount_info_by_id[&id].replace(seen);
) }
.into_iter() }
.map(|ent| ent.1.into_inner())
.collect::<Vec<_>>() // 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`. /// 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(); let opt = Options::from(&matches);
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 fs_list = filter_mount_list(read_fs_list(), &paths, &opt) let fs_list = filter_mount_list(read_fs_list(), &paths, &opt)
.into_iter() .into_iter()

View file

@ -144,7 +144,7 @@ impl Stat {
#[cfg(windows)] #[cfg(windows)]
let file_info = get_file_info(&path); let file_info = get_file_info(&path);
#[cfg(windows)] #[cfg(windows)]
Ok(Stat { Ok(Self {
path, path,
is_dir: metadata.is_dir(), is_dir: metadata.is_dir(),
size: metadata.len(), size: metadata.len(),

View file

@ -88,10 +88,7 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result<bool> {
start = 0; start = 0;
next next
}), }),
'0' => parse_code(&mut iter, 8, 3, 3).unwrap_or_else(|| { '0' => parse_code(&mut iter, 8, 3, 3).unwrap_or('\0'),
start = 0;
next
}),
_ => { _ => {
start = 0; start = 0;
next next

View file

@ -104,6 +104,7 @@ fn load_config_file(opts: &mut Options) -> UResult<()> {
} }
#[cfg(not(windows))] #[cfg(not(windows))]
#[allow(clippy::ptr_arg)]
fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) { fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) {
let progname = Cow::from(args[0]); let progname = Cow::from(args[0]);
(progname, &args[1..]) (progname, &args[1..])

View file

@ -4,16 +4,62 @@
// * // *
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
use clap::{App, AppSettings, Arg};
use std::io::Write;
use uucore::error::{set_exit_code, UResult};
use clap::App; static ABOUT: &str = "\
use uucore::error::UResult; 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] #[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uu_app().get_matches_from(args); let mut app = uu_app();
Err(1.into())
// 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> { pub fn uu_app<'a>() -> App<'a> {
App::new(uucore::util_name()) 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"),
)
} }

View file

@ -20,7 +20,7 @@ pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>
let mut has_num = false; let mut has_num = false;
let mut last_char = 0 as char; let mut last_char = 0 as char;
for (n, c) in &mut chars { for (n, c) in &mut chars {
if c.is_numeric() { if c.is_digit(10) {
has_num = true; has_num = true;
num_end = n; num_end = n;
} else { } else {

View file

@ -12,6 +12,7 @@ use libc::c_long;
use uucore::error::UResult; use uucore::error::UResult;
static SYNTAX: &str = "[options]"; 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 // currently rust libc interface doesn't include gethostid
extern "C" { extern "C" {
@ -28,6 +29,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
pub fn uu_app<'a>() -> App<'a> { pub fn uu_app<'a>() -> App<'a> {
App::new(uucore::util_name()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(SUMMARY)
.override_usage(SYNTAX) .override_usage(SYNTAX)
.setting(AppSettings::InferLongArgs) .setting(AppSettings::InferLongArgs)
} }

View file

@ -74,7 +74,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
table(); table();
Ok(()) 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!(); println!();
} }
fn list(arg: Option<String>) -> UResult<()> { fn list(arg: Option<&String>) -> UResult<()> {
match arg { match arg {
Some(ref x) => print_signal(x), Some(x) => print_signal(x),
None => { None => {
print_signals(); print_signals();
Ok(()) Ok(())

View file

@ -9,255 +9,248 @@ use uucore::InvalidEncodingHandling;
const VERSION: &str = "version"; const VERSION: &str = "version";
const HELP: &str = "help"; 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 literally, with the exception of the below
output prints escaped literals in the format string as character literals escaped character sequences, and the substitution sequences described further down.
output replaces anonymous fields with the next unused argument, formatted according to the field.
Options: ESCAPE SEQUENCES
--help display this help and exit
--version output version information and exit
"; The following escape sequences, organized here in alphabetical order,
static LONGHELP_BODY: &str = " will print the corresponding character literal:
Prints the , replacing escaped character sequences with character literals
and substitution field sequences with passed arguments
literally, with the exception of the below \" double quote
escaped character sequences, and the substitution sequences described further down.
ESCAPE SEQUENCES \\\\ backslash
The following escape sequences, organized here in alphabetical order, \\a alert (BEL)
will print the corresponding character literal:
\" 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) \\uHHHH Unicode character with value expressed in hexadecimal value HHHH (8 digits)
values greater than 256 will be treated
\\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 %f or %F - decimal floating point value
%b - string parsed for literals %e or %E - scientific notation floating point value
second parameter is max length %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 parameterizing fields
no second parameter
%i or %d - 64-bit integer examples:
%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
%f or %F - decimal floating point value printf '%4.3i' 7
%e or %E - scientific notation floating point value has a first parameter of 4
%g or %G - shorter of specially interpreted decimal or SciNote floating point value. and a second parameter of 3
second parameter is will result in ' 007'
-max places after decimal point for floating point output
-max number of significant digits for scientific notation output
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 The first parameter of a field is the minimum width to pad the output to
has a first parameter of 4 if the output is less than this absolute value of this width,
and a second parameter of 3 it will be padded with leading spaces, or, if the argument is negative,
will result in ' 007' with trailing spaces. the default is zero.
printf '%.1s' abcde The second parameter of a field is particular to the output field type.
has no first parameter defaults can be found in the full substitution help below
and a second parameter of 1
will result in 'a'
printf '%4c' q special prefixes to numeric arguments
has a first parameter of 4 0 (e.g. 010) - interpret argument as octal (integer output fields only)
and no second parameter 0x (e.g. 0xABC) - interpret argument as hex (numeric output fields only)
will result in ' q' \' (e.g. \'a) - interpret argument as a character constant
The first parameter of a field is the minimum width to pad the output to HOW TO USE SUBSTITUTIONS
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.
The second parameter of a field is particular to the output field type. Substitutions are used to pass additional argument(s) into the FORMAT string, to be formatted a
defaults can be found in the full substitution help below particular way. E.g.
special prefixes to numeric arguments printf 'the letter %X comes before the letter %X' 10 11
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
HOW TO USE SUBSTITUTIONS will print
Substitutions are used to pass additional argument(s) into the FORMAT string, to be formatted a 'the letter A comes before the letter B'
particular way. E.g.
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 will print
'take an integer argument and write it as a hexadecimal number'
Passing more arguments than are in the format string will cause the format string to be 'it is 22 F in Portland
repeated for the remaining substitutions 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 STRING SUBSTITUTIONS
it is 25 F in Boston All string fields have a 'max width' parameter
it is 27 F in Boston %.3s means 'print no more than three characters of the original input'
'
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
AVAILABLE SUBSTITUTIONS %s - string
This program, like GNU coreutils printf, %b - escaped string - the string will be checked for any escaped literals from
interprets a modified subset of the POSIX C printf spec, the escaped literal list above, and translate them to literal characters.
a quick reference to substitutions is below. e.g. \\n will be transformed into a newline character.
STRING SUBSTITUTIONS One special rule about %b mode is that octal literals are interpreted differently
All string fields have a 'max width' parameter In arguments passed by %b, pass octal-interpreted literals must be in the form of \\0NNN
%.3s means 'print no more than three characters of the original input' 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 %c - a single character
the escaped literal list above, and translate them to literal characters.
e.g. \\n will be transformed into a newline character.
One special rule about %b mode is that octal literals are interpreted differently INTEGER SUBSTITUTIONS
In arguments passed by %b, pass octal-interpreted literals must be in the form of \\0NNN All integer fields have a 'pad with zero' parameter
instead of \\NNN. (Although, for legacy reasons, octal literals in the form of \\NNN will %.4i means an integer which if it is less than 4 digits in length,
still be interpreted and not throw a warning, you will have problems if you use this for a is padded with leading zeros until it is 4 digits in length.
literal whose code begins with zero, as it will be viewed as in \\0NNN form.)
CHAR SUBSTITUTIONS %d or %i - 64-bit integer
The character field does not have a secondary parameter.
%c - a single character %u - 64 bit unsigned integer
INTEGER SUBSTITUTIONS %x or %X - 64 bit unsigned integer printed in Hexadecimal (base 16)
All integer fields have a 'pad with zero' parameter %X instead of %x means to use uppercase letters for 'a' through 'f'
%.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.
%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) All floating point fields have a 'max decimal places / max significant digits' parameter
%X instead of %x means to use uppercase letters for 'a' through 'f' %.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 %e or %E - floating point value presented in scientific notation
%.10f means a decimal floating point with 7 decimal places past 0 7 significant digits by default
%.10e means a scientific notation number with 10 significant digits %E means use to use uppercase E for the mantissa.
%.10g means the same behavior for decimal and Sci. Note, respectively, and provides the shorter
of each's output.
Like with GNU coreutils, the value after the decimal point is these outputs is parsed as a %g or %G - floating point value presented in the shorter of decimal and scientific notation
double first before being rendered to text. For both implementations do not expect meaningful behaves differently from %f and %E, please see posix printf spec for full details,
precision past the 18th decimal place. When using a number of decimal places that is 18 or some examples of different behavior:
higher, you can expect variation in output between GNU coreutils printf and this printf at the
18th decimal place of +/- 1
%f - floating point value presented in decimal, truncated and displayed to 6 decimal places by Sci Note has 6 significant digits by default
default. There is not past-double behavior parity with Coreutils printf, values are not Trailing zeroes are removed
estimated or adjusted beyond input values. Instead of being truncated, digit after last is rounded
%e or %E - floating point value presented in scientific notation Like other behavior in this utility, the design choices of floating point
7 significant digits by default behavior in this utility is selected to reproduce in exact
%E means use to use uppercase E for the mantissa. 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 USING PARAMETERS
behaves differently from %f and %E, please see posix printf spec for full details, Most substitution fields can be parameterized using up to 2 numbers that can
some examples of different behavior: be passed to the field, between the % sign and the field letter.
Sci Note has 6 significant digits by default The 1st parameter always indicates the minimum width of output, it is useful for creating
Trailing zeroes are removed columnar output. Any output that would be less than this minimum width is padded with
Instead of being truncated, digit after last is rounded 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 SPECIAL FORMS OF INPUT
behavior in this utility is selected to reproduce in exact For numeric input, the following additional forms of input are accepted besides decimal:
the behavior of GNU coreutils' printf from an inputs and outputs standpoint.
USING PARAMETERS Octal (only with integer): if the argument begins with a 0 the proceeding characters
Most substitution fields can be parameterized using up to 2 numbers that can will be interpreted as octal (base 8) for integer fields
be passed to the field, between the % sign and the field letter.
The 1st parameter always indicates the minimum width of output, it is useful for creating Hexadecimal: if the argument begins with 0x the proceeding characters will be interpreted
columnar output. Any output that would be less than this minimum width is padded with will be interpreted as hex (base 16) for any numeric fields
leading spaces for float fields, hexadecimal input results in a precision
The 2nd parameter is proceeded by a dot. limit (in converting input past the decimal point) of 10^-15
You do not have to use parameters
SPECIAL FORMS OF INPUT Character Constant: if the argument begins with a single quote character, the first byte
For numeric input, the following additional forms of input are accepted besides decimal: 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
Octal (only with integer): if the argument begins with a 0 the proceeding characters is set)
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)
WRITTEN BY : WRITTEN BY :
Nathan E. Ross, et al. for the uutils project 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] #[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args let args = args
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let matches = uu_app().get_matches_from(args);
if args.len() <= 1 { let format_string = matches
return Err(UUsageError::new(1, "missing operand")); .value_of(options::FORMATSTRING)
} .ok_or_else(|| UUsageError::new(1, "missing operand"))?;
let formatstr = &args[1]; let values: Vec<String> = match matches.values_of(options::ARGUMENT) {
Some(s) => s.map(|s| s.to_string()).collect(),
None => vec![],
};
if formatstr == "--help" { memo::Memo::run_all(format_string, &values[..]);
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);
}
Ok(()) Ok(())
} }
pub fn uu_app<'a>() -> App<'a> { pub fn uu_app<'a>() -> App<'a> {
App::new(uucore::util_name()) App::new(uucore::util_name())
.arg(Arg::new(VERSION).long(VERSION)) .setting(AppSettings::AllowHyphenValues)
.arg(Arg::new(HELP).long(HELP)) .version(crate_version!())
.setting(AppSettings::InferLongArgs) .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))
} }

View file

@ -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. 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}"; static TEMPLATE: &str = "Usage: {usage}\nMandatory arguments to long options are mandatory for short options too.\n{options}";
struct Options { struct Options {
@ -121,6 +122,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
pub fn uu_app<'a>() -> App<'a> { pub fn uu_app<'a>() -> App<'a> {
App::new(uucore::util_name()) App::new(uucore::util_name())
.name(NAME) .name(NAME)
.about(ABOUT)
.version(crate_version!()) .version(crate_version!())
.help_template(TEMPLATE) .help_template(TEMPLATE)
.override_usage(USAGE) .override_usage(USAGE)

View file

@ -96,8 +96,8 @@ impl Number {
#[allow(dead_code)] #[allow(dead_code)]
fn digits(&self) -> &Vec<u8> { fn digits(&self) -> &Vec<u8> {
match self { match self {
Number::FixedWidth(number) => &number.digits, Self::FixedWidth(number) => &number.digits,
Number::DynamicWidth(number) => &number.digits, Self::DynamicWidth(number) => &number.digits,
} }
} }
@ -136,8 +136,8 @@ impl Number {
/// ``` /// ```
pub fn increment(&mut self) -> Result<(), Overflow> { pub fn increment(&mut self) -> Result<(), Overflow> {
match self { match self {
Number::FixedWidth(number) => number.increment(), Self::FixedWidth(number) => number.increment(),
Number::DynamicWidth(number) => number.increment(), Self::DynamicWidth(number) => number.increment(),
} }
} }
} }
@ -145,8 +145,8 @@ impl Number {
impl Display for Number { impl Display for Number {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Number::FixedWidth(number) => number.fmt(f), Self::FixedWidth(number) => number.fmt(f),
Number::DynamicWidth(number) => number.fmt(f), Self::DynamicWidth(number) => number.fmt(f),
} }
} }
} }
@ -183,8 +183,8 @@ pub struct FixedWidthNumber {
impl FixedWidthNumber { impl FixedWidthNumber {
/// Instantiate a number of the given radix and width. /// Instantiate a number of the given radix and width.
pub fn new(radix: u8, width: usize) -> FixedWidthNumber { pub fn new(radix: u8, width: usize) -> Self {
FixedWidthNumber { Self {
radix, radix,
digits: vec![0; width], digits: vec![0; width],
} }
@ -286,8 +286,8 @@ impl DynamicWidthNumber {
/// ///
/// This associated function returns a new instance of the struct /// This associated function returns a new instance of the struct
/// with the given radix and a width of two digits, both 0. /// with the given radix and a width of two digits, both 0.
pub fn new(radix: u8) -> DynamicWidthNumber { pub fn new(radix: u8) -> Self {
DynamicWidthNumber { Self {
radix, radix,
digits: vec![0, 0], digits: vec![0, 0],
} }
@ -404,7 +404,7 @@ mod tests {
fn num(n: usize) -> Number { fn num(n: usize) -> Number {
let mut number = Number::DynamicWidth(DynamicWidthNumber::new(26)); let mut number = Number::DynamicWidth(DynamicWidthNumber::new(26));
for _ in 0..n { for _ in 0..n {
number.increment().unwrap() number.increment().unwrap();
} }
number number
} }
@ -428,7 +428,7 @@ mod tests {
fn num(n: usize) -> Number { fn num(n: usize) -> Number {
let mut number = Number::DynamicWidth(DynamicWidthNumber::new(10)); let mut number = Number::DynamicWidth(DynamicWidthNumber::new(10));
for _ in 0..n { for _ in 0..n {
number.increment().unwrap() number.increment().unwrap();
} }
number number
} }

View file

@ -62,7 +62,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.override_usage(&usage[..]) .override_usage(&usage[..])
.after_help(&long_usage[..]) .after_help(&long_usage[..])
.get_matches_from(args); .get_matches_from(args);
let settings = Settings::from(matches)?; let settings = Settings::from(&matches)?;
split(&settings) split(&settings)
} }
@ -232,7 +232,7 @@ struct Settings {
impl Settings { impl Settings {
/// Parse a strategy from the command-line arguments. /// Parse a strategy from the command-line arguments.
fn from(matches: ArgMatches) -> UResult<Self> { fn from(matches: &ArgMatches) -> UResult<Self> {
let result = Self { let result = Self {
suffix_length: matches suffix_length: matches
.value_of(OPT_SUFFIX_LENGTH) .value_of(OPT_SUFFIX_LENGTH)
@ -242,7 +242,7 @@ impl Settings {
numeric_suffix: matches.occurrences_of(OPT_NUMERIC_SUFFIXES) > 0, numeric_suffix: matches.occurrences_of(OPT_NUMERIC_SUFFIXES) > 0,
additional_suffix: matches.value_of(OPT_ADDITIONAL_SUFFIX).unwrap().to_owned(), additional_suffix: matches.value_of(OPT_ADDITIONAL_SUFFIX).unwrap().to_owned(),
verbose: matches.occurrences_of("verbose") > 0, verbose: matches.occurrences_of("verbose") > 0,
strategy: Strategy::from(&matches)?, strategy: Strategy::from(matches)?,
input: matches.value_of(ARG_INPUT).unwrap().to_owned(), input: matches.value_of(ARG_INPUT).unwrap().to_owned(),
prefix: matches.value_of(ARG_PREFIX).unwrap().to_owned(), prefix: matches.value_of(ARG_PREFIX).unwrap().to_owned(),
filter: matches.value_of(OPT_FILTER).map(|s| s.to_owned()), filter: matches.value_of(OPT_FILTER).map(|s| s.to_owned()),

View file

@ -19,7 +19,7 @@ pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>
let mut has_num = false; let mut has_num = false;
let mut last_char = 0 as char; let mut last_char = 0 as char;
for (n, c) in &mut chars { for (n, c) in &mut chars {
if c.is_numeric() { if c.is_digit(10) {
has_num = true; has_num = true;
num_end = n; num_end = n;
} else { } else {

View file

@ -30,6 +30,7 @@ impl ProcessChecker {
} }
// Borrowing mutably to be aligned with Windows implementation // Borrowing mutably to be aligned with Windows implementation
#[allow(clippy::wrong_self_convention)]
pub fn is_dead(&mut self) -> bool { pub fn is_dead(&mut self) -> bool {
unsafe { libc::kill(self.pid, 0) != 0 && get_errno() != libc::EPERM } unsafe { libc::kill(self.pid, 0) != 0 && get_errno() != libc::EPERM }
} }

View file

@ -24,11 +24,11 @@ pub struct ProcessChecker {
} }
impl ProcessChecker { impl ProcessChecker {
pub fn new(process_id: self::Pid) -> ProcessChecker { pub fn new(process_id: self::Pid) -> Self {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let FALSE = 0i32; let FALSE = 0i32;
let h = unsafe { OpenProcess(SYNCHRONIZE, FALSE, process_id as DWORD) }; let h = unsafe { OpenProcess(SYNCHRONIZE, FALSE, process_id as DWORD) };
ProcessChecker { Self {
dead: h.is_null(), dead: h.is_null(),
handle: h, handle: h,
} }

View file

@ -10,7 +10,7 @@
mod parser; mod parser;
use clap::{crate_version, App, AppSettings}; use clap::{crate_version, App};
use parser::{parse, Operator, Symbol, UnaryOperator}; use parser::{parse, Operator, Symbol, UnaryOperator};
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use uucore::display::Quotable; 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 the version described here. Please refer to your shell's documentation
for details about the options it supports."; for details about the options it supports.";
const ABOUT: &str = "Check file types and compare values.";
pub fn uu_app<'a>() -> App<'a> { pub fn uu_app<'a>() -> App<'a> {
App::new(uucore::util_name()) App::new(uucore::util_name())
.setting(AppSettings::DisableHelpFlag) .version(crate_version!())
.setting(AppSettings::DisableVersionFlag) .about(ABOUT)
.override_usage(USAGE)
.after_help(AFTER_HELP)
} }
#[uucore::main] #[uucore::main]
@ -104,6 +108,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
// Let clap pretty-print help and version // Let clap pretty-print help and version
App::new(binary_name) App::new(binary_name)
.version(crate_version!()) .version(crate_version!())
.about(ABOUT)
.override_usage(USAGE) .override_usage(USAGE)
.after_help(AFTER_HELP) .after_help(AFTER_HELP)
// Disable printing of -h and -v as valid alternatives for --help and --version, // Disable printing of -h and -v as valid alternatives for --help and --version,

View file

@ -58,7 +58,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); 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) = let (mut atime, mut mtime) =
if let Some(reference) = matches.value_of_os(options::sources::REFERENCE) { if let Some(reference) = matches.value_of_os(options::sources::REFERENCE) {

View file

@ -4,16 +4,59 @@
// * // *
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
use clap::{App, AppSettings, Arg};
use std::io::Write;
use uucore::error::{set_exit_code, UResult};
use clap::{App, AppSettings}; static ABOUT: &str = "\
use uucore::error::UResult; 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] #[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { 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(()) Ok(())
} }
pub fn uu_app<'a>() -> App<'a> { 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"),
)
} }

View file

@ -115,7 +115,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app() let matches = uu_app()
.override_usage(&usage[..]) .override_usage(&usage[..])
.after_help(&long_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<String> = matches let files: Vec<String> = matches
.values_of(options::ARG_FILES) .values_of(options::ARG_FILES)

View file

@ -238,7 +238,7 @@ impl MountInfo {
} }
} }
#[cfg(windows)] #[cfg(windows)]
fn new(mut volume_name: String) -> Option<MountInfo> { fn new(mut volume_name: String) -> Option<Self> {
let mut dev_name_buf = [0u16; MAX_PATH]; let mut dev_name_buf = [0u16; MAX_PATH];
volume_name.pop(); volume_name.pop();
unsafe { unsafe {
@ -289,7 +289,7 @@ impl MountInfo {
} else { } else {
None None
}; };
let mut mn_info = MountInfo { let mut mn_info = Self {
dev_id: volume_name, dev_id: volume_name,
dev_name, dev_name,
fs_type: fs_type.unwrap_or_else(|| "".to_string()), fs_type: fs_type.unwrap_or_else(|| "".to_string()),
@ -319,7 +319,7 @@ use std::ffi::CStr;
))] ))]
impl From<StatFs> for MountInfo { impl From<StatFs> for MountInfo {
fn from(statfs: StatFs) -> Self { fn from(statfs: StatFs) -> Self {
let mut info = MountInfo { let mut info = Self {
dev_id: "".to_string(), dev_id: "".to_string(),
dev_name: unsafe { dev_name: unsafe {
// spell-checker:disable-next-line // 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; let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
FsUsage { Self {
// f_bsize File system block size. // f_bsize File system block size.
blocksize: bytes_per_cluster as u64, blocksize: bytes_per_cluster as u64,
// f_blocks - Total number of blocks on the file system, in units of f_frsize. // f_blocks - Total number of blocks on the file system, in units of f_frsize.

View file

@ -60,6 +60,7 @@ pub struct Sub {
field_char: char, field_char: char,
field_type: FieldType, field_type: FieldType,
orig: String, orig: String,
prefix_char: char,
} }
impl Sub { impl Sub {
pub fn new( pub fn new(
@ -67,6 +68,7 @@ impl Sub {
second_field: CanAsterisk<Option<u32>>, second_field: CanAsterisk<Option<u32>>,
field_char: char, field_char: char,
orig: String, orig: String,
prefix_char: char,
) -> Self { ) -> Self {
// for more dry printing, field characters are grouped // for more dry printing, field characters are grouped
// in initialization of token. // in initialization of token.
@ -90,6 +92,7 @@ impl Sub {
field_char, field_char,
field_type, field_type,
orig, orig,
prefix_char,
} }
} }
} }
@ -126,6 +129,11 @@ impl SubParser {
fn build_token(parser: Self) -> Box<dyn token::Token> { fn build_token(parser: Self) -> Box<dyn token::Token> {
// not a self method so as to allow move of sub-parser vals. // not a self method so as to allow move of sub-parser vals.
// return new Sub struct as token // 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<dyn token::Token> = Box::new(Sub::new( let t: Box<dyn token::Token> = Box::new(Sub::new(
if parser.min_width_is_asterisk { if parser.min_width_is_asterisk {
CanAsterisk::Asterisk CanAsterisk::Asterisk
@ -139,6 +147,7 @@ impl SubParser {
}, },
parser.field_char.unwrap(), parser.field_char.unwrap(),
parser.text_so_far, parser.text_so_far,
prefix_char,
)); ));
t t
} }
@ -394,7 +403,7 @@ impl token::Token for Sub {
final_str.push_str(&pre_min_width); final_str.push_str(&pre_min_width);
} }
for _ in 0..diff { for _ in 0..diff {
final_str.push(' '); final_str.push(self.prefix_char);
} }
if pad_before { if pad_before {
final_str.push_str(&pre_min_width); final_str.push_str(&pre_min_width);

View file

@ -27,10 +27,10 @@ pub trait FromWide {
fn from_wide_null(wide: &[u16]) -> Self; fn from_wide_null(wide: &[u16]) -> Self;
} }
impl FromWide for String { 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() 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(); let len = wide.iter().take_while(|&&c| c != 0).count();
OsString::from_wide(&wide[..len]) OsString::from_wide(&wide[..len])
.to_string_lossy() .to_string_lossy()

View file

@ -231,6 +231,7 @@ pub mod arguments {
.help("override the usual backup suffix") .help("override the usual backup suffix")
.takes_value(true) .takes_value(true)
.value_name("SUFFIX") .value_name("SUFFIX")
.allow_hyphen_values(true)
} }
} }
@ -618,4 +619,13 @@ mod tests {
assert_eq!(result, BackupMode::SimpleBackup); assert_eq!(result, BackupMode::SimpleBackup);
env::remove_var(ENV_VERSION_CONTROL); 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");
}
} }

View file

@ -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] #[test]
fn test_cp_custom_backup_suffix_via_env() { fn test_cp_custom_backup_suffix_via_env() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
@ -1489,3 +1507,13 @@ fn test_dir_recursive_copy() {
.fails() .fails()
.stderr_contains("cannot copy a directory"); .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");
}

View file

@ -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::*; use crate::common::util::*;
@ -178,6 +178,59 @@ fn test_stdin_stdout_count_w_multiplier() {
.success(); .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] #[test]
fn test_final_stats_noxfer() { fn test_final_stats_noxfer() {
new_ucmd!() new_ucmd!()

View file

@ -179,15 +179,15 @@ fn test_du_hard_link() {
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
fn _du_hard_link(s: &str) { fn _du_hard_link(s: &str) {
assert_eq!(s, "12\tsubdir/links\n") assert_eq!(s, "12\tsubdir/links\n");
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn _du_hard_link(s: &str) { fn _du_hard_link(s: &str) {
assert_eq!(s, "8\tsubdir/links\n") assert_eq!(s, "8\tsubdir/links\n");
} }
#[cfg(target_os = "freebsd")] #[cfg(target_os = "freebsd")]
fn _du_hard_link(s: &str) { fn _du_hard_link(s: &str) {
assert_eq!(s, "16\tsubdir/links\n") assert_eq!(s, "16\tsubdir/links\n");
} }
#[cfg(all( #[cfg(all(
not(target_vendor = "apple"), not(target_vendor = "apple"),

View file

@ -138,11 +138,19 @@ fn test_escape_short_octal() {
} }
#[test] #[test]
fn test_escape_no_octal() { fn test_escape_nul() {
new_ucmd!() new_ucmd!()
.args(&["-e", "foo\\0 bar"]) .args(&["-e", "foo\\0 bar"])
.succeeds() .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] #[test]

View file

@ -219,7 +219,7 @@ fn test_change_directory() {
.args(&pwd) .args(&pwd)
.succeeds() .succeeds()
.stdout_move_str(); .stdout_move_str();
assert_eq!(out.trim(), temporary_path.as_os_str()) assert_eq!(out.trim(), temporary_path.as_os_str());
} }
#[test] #[test]

View file

@ -1,6 +1,53 @@
use crate::common::util::*; use crate::common::util::*;
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
use std::fs::OpenOptions;
#[test] #[test]
fn test_exit_code() { fn test_exit_code() {
new_ucmd!().fails(); 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");
}
}

View file

@ -306,6 +306,10 @@ fn test_head_invalid_num() {
)); ));
} }
} }
new_ucmd!()
.args(&["-c", ""])
.fails()
.stderr_is("head: invalid number of bytes: '³'");
} }
#[test] #[test]

View file

@ -815,6 +815,31 @@ fn test_install_backup_short_custom_suffix() {
assert!(at.file_exists(&format!("{}{}", file_b, 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] #[test]
fn test_install_backup_custom_suffix_via_env() { fn test_install_backup_custom_suffix_via_env() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());

View file

@ -180,6 +180,33 @@ fn test_symlink_custom_backup_suffix() {
assert_eq!(at.resolve_link(backup), file); 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] #[test]
fn test_symlink_backup_numbering() { fn test_symlink_backup_numbering() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();

View file

@ -1327,17 +1327,14 @@ fn test_ls_order_time() {
// So the order should be 2 3 4 1 // So the order should be 2 3 4 1
for arg in &["-u", "--time=atime", "--time=access", "--time=use"] { for arg in &["-u", "--time=atime", "--time=access", "--time=use"] {
let result = scene.ucmd().arg("-t").arg(arg).succeeds(); let result = scene.ucmd().arg("-t").arg(arg).succeeds();
let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap(); at.open("test-3").metadata().unwrap().accessed().unwrap();
let file4_access = at.open("test-4").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 // It seems to be dependent on the platform whether the access time is actually set
if file3_access > file4_access { #[cfg(unix)]
result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n"); result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n");
} else { #[cfg(windows)]
// Access time does not seem to be set on Windows and some other result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n");
// systems so the order is 4 3 2 1
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n");
}
} }
// test-2 had the last ctime change when the permissions were set // test-2 had the last ctime change when the permissions were set

View file

@ -340,6 +340,27 @@ fn test_mv_custom_backup_suffix() {
assert!(at.file_exists(&format!("{}{}", file_b, 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] #[test]
fn test_mv_custom_backup_suffix_via_env() { fn test_mv_custom_backup_suffix_via_env() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();

View file

@ -429,3 +429,19 @@ fn sub_any_specifiers_after_second_param() {
.succeeds() .succeeds()
.stdout_only("3"); .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");
}

View file

@ -1066,10 +1066,13 @@ fn test_separator_null() {
#[test] #[test]
fn test_output_is_input() { fn test_output_is_input() {
let input = "a\nb\nc\n"; 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.touch("file");
at.append("file", input); 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(); .succeeds();
assert_eq!(at.read("file"), input); assert_eq!(at.read("file"), input);
} }

View file

@ -36,6 +36,12 @@ fn test_group_num() {
assert_eq!("", group_num("")); assert_eq!("", group_num(""));
} }
#[test]
#[should_panic]
fn test_group_num_panic_if_invalid_numeric_characters() {
group_num("³³³³³");
}
#[cfg(test)] #[cfg(test)]
mod test_generate_tokens { mod test_generate_tokens {
use super::*; use super::*;

View file

@ -491,6 +491,10 @@ fn test_tail_invalid_num() {
)); ));
} }
} }
new_ucmd!()
.args(&["-c", ""])
.fails()
.stderr_is("tail: invalid number of bytes: '³'");
} }
#[test] #[test]

View file

@ -530,3 +530,12 @@ fn test_touch_permission_denied_error_msg() {
&full_path &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."##,
);
}

View file

@ -1,6 +1,53 @@
use crate::common::util::*; use crate::common::util::*;
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
use std::fs::OpenOptions;
#[test] #[test]
fn test_exit_code() { fn test_exit_code() {
new_ucmd!().succeeds(); 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");
}
}

View file

@ -250,11 +250,24 @@ fn test_size_and_reference() {
#[test] #[test]
fn test_error_filename_only() { fn test_error_filename_only() {
// truncate: you must specify either '--size' or '--reference' // truncate: you must specify either '--size' or '--reference'
new_ucmd!().args(&["file"]).fails().stderr_contains( new_ucmd!()
"error: The following required arguments were not provided: .args(&["file"])
.fails()
.code_is(1)
.stderr_contains(
"error: The following required arguments were not provided:
--reference <RFILE> --reference <RFILE>
--size <SIZE>", --size <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] #[test]

View file

@ -15,21 +15,21 @@ ME_parent_dir_abs="$(realpath -mP -- "${ME_parent_dir}")"
# * `gh` available? # * `gh` available?
unset GH unset GH
gh --version 1>/dev/null 2>&1 if gh --version 1>/dev/null 2>&1; then
if [ $? -eq 0 ]; then export GH="gh"; fi export GH="gh"
else
echo "ERR!: missing \`gh\` (see install instructions at <https://github.com/cli/cli>)" 1>&2
fi
# * `jq` available? # * `jq` available?
unset JQ unset JQ
jq --version 1>/dev/null 2>&1 if jq --version 1>/dev/null 2>&1; then
if [ $? -eq 0 ]; then export JQ="jq"; fi 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}" ] || [ -z "${JQ}" ]; then
if [ -z "${GH}" ]; then
echo 'ERR!: missing `gh` (see install instructions at <https://github.com/cli/cli>)' 1>&2
fi
if [ -z "${JQ}" ]; then
echo 'ERR!: missing `jq` (install with `sudo apt install jq`)' 1>&2
fi
exit 1 exit 1
fi fi

View file

@ -8,12 +8,13 @@
FEATURES_OPTION="--features feat_os_unix" FEATURES_OPTION="--features feat_os_unix"
ME_dir="$(dirname -- $(readlink -fm -- "$0"))" ME_dir="$(dirname -- "$(readlink -fm -- "$0")")"
REPO_main_dir="$(dirname -- "${ME_dir}")" REPO_main_dir="$(dirname -- "${ME_dir}")"
cd "${REPO_main_dir}" cd "${REPO_main_dir}" &&
echo "[ \"$PWD\" ]" echo "[ \"$PWD\" ]"
#shellcheck disable=SC2086
UTIL_LIST=$("${ME_dir}"/show-utils.sh ${FEATURES_OPTION}) UTIL_LIST=$("${ME_dir}"/show-utils.sh ${FEATURES_OPTION})
CARGO_INDIVIDUAL_PACKAGE_OPTIONS="" CARGO_INDIVIDUAL_PACKAGE_OPTIONS=""
for UTIL in ${UTIL_LIST}; do 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 RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
export RUSTDOCFLAGS="-Cpanic=abort" export RUSTDOCFLAGS="-Cpanic=abort"
export RUSTUP_TOOLCHAIN="nightly-gnu" export RUSTUP_TOOLCHAIN="nightly-gnu"
cargo build ${FEATURES_OPTION} #shellcheck disable=SC2086
cargo test --no-run ${FEATURES_OPTION} { cargo build ${FEATURES_OPTION}
cargo test --quiet ${FEATURES_OPTION} cargo test --no-run ${FEATURES_OPTION}
cargo test --quiet ${FEATURES_OPTION} ${CARGO_INDIVIDUAL_PACKAGE_OPTIONS} cargo test --quiet ${FEATURES_OPTION}
cargo test --quiet ${FEATURES_OPTION} ${CARGO_INDIVIDUAL_PACKAGE_OPTIONS}
}
export COVERAGE_REPORT_DIR export COVERAGE_REPORT_DIR
if [ -z "${COVERAGE_REPORT_DIR}" ]; then COVERAGE_REPORT_DIR="${REPO_main_dir}/target/debug/coverage-nix"; fi 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\()' 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 # * build HTML
# -- use `genhtml` if available for display of additional branch coverage information # -- use `genhtml` if available for display of additional branch coverage information
genhtml --version 2>/dev/null 1>&2 if genhtml --version 2>/dev/null 1>&2; then
if [ $? -eq 0 ]; then
genhtml "${COVERAGE_REPORT_DIR}/../lcov.info" --output-directory "${COVERAGE_REPORT_DIR}" --branch-coverage --function-coverage | grep ": [0-9]" genhtml "${COVERAGE_REPORT_DIR}/../lcov.info" --output-directory "${COVERAGE_REPORT_DIR}" --branch-coverage --function-coverage | grep ": [0-9]"
else 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\()' 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\()'

View file

@ -15,7 +15,7 @@ if test ! -d ../gnulib; then
fi fi
pushd $(pwd) pushd "$PWD"
make PROFILE=release make PROFILE=release
BUILDDIR="$PWD/target/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 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 # Used to be 36. Reduced to 20 to decrease the log size
for i in {00..20} for i in {00..20}
do do
make tests/factor/t${i}.sh make "tests/factor/t${i}.sh"
done done
# strip the long stuff # strip the long stuff

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/bin/sh
set -e set -e
ARG="" ARG=""
@ -6,26 +6,21 @@ if test "$1" != "--do-it"; then
ARG="--dry-run --allow-dirty" ARG="--dry-run --allow-dirty"
fi fi
cd src/uucore/ for dir in src/uucore/ src/uucore_procs/ src/uu/stdbuf/src/libstdbuf/ ; do
cargo publish $ARG ( cd "$dir"
cd - #shellcheck disable=SC2086
sleep 2s cargo publish $ARG
)
cd src/uucore_procs/ sleep 2s
cargo publish $ARG done
cd -
sleep 2s
cd src/uu/stdbuf/src/libstdbuf/
cargo publish $ARG
cd -
sleep 2s
PROGS=$(ls -1d src/uu/*/) PROGS=$(ls -1d src/uu/*/)
for p in $PROGS; do for p in $PROGS; do
cd $p ( cd "$p"
cargo publish $ARG #shellcheck disable=SC2086
cd - cargo publish $ARG
)
done done
#shellcheck disable=SC2086
cargo publish $ARG cargo publish $ARG

View file

@ -1,10 +1,28 @@
#!/bin/bash #!/bin/bash
# `$0 [TEST]`
# run GNU test (or all tests if TEST is missing/null)
# spell-checker:ignore (env/vars) BUILDDIR GNULIB SUBDIRS # 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 set -e
BUILDDIR="${PWD}/uutils/target/release"
GNULIB_DIR="${PWD}/gnulib" ### * config (from environment with fallback defaults)
pushd gnu
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 export RUST_BACKTRACE=1
@ -13,4 +31,5 @@ if test -n "$1"; then
export RUN_TEST="TESTS=$1" export RUN_TEST="TESTS=$1"
fi 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 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

View file

@ -2,15 +2,14 @@
# spell-checker:ignore (vars) OSID # spell-checker:ignore (vars) OSID
ME_dir="$(dirname -- $(readlink -fm -- "$0"))" ME_dir="$(dirname -- "$(readlink -fm -- "$0")")"
REPO_main_dir="$(dirname -- "${ME_dir}")" REPO_main_dir="$(dirname -- "${ME_dir}")"
export COVERAGE_REPORT_DIR="${REPO_main_dir}/target/debug/coverage-nix" export COVERAGE_REPORT_DIR="${REPO_main_dir}/target/debug/coverage-nix"
"${ME_dir}/build-code_coverage.sh" if ! "${ME_dir}/build-code_coverage.sh"; then exit 1 ; fi
if [ $? -ne 0 ]; then exit 1 ; fi
case ";$OSID_tags;" in 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'" ;; * ) xdg-open --version >/dev/null 2>&1 && xdg-open "${COVERAGE_REPORT_DIR}"/index.html || echo "report available at '\"${COVERAGE_REPORT_DIR}\"/index.html'" ;;
esac ; esac ;

View file

@ -15,17 +15,13 @@ default_utils="base32 base64 basename cat cksum comm cp cut date dircolors dirna
project_main_dir="${ME_parent_dir_abs}" project_main_dir="${ME_parent_dir_abs}"
# printf 'project_main_dir="%s"\n' "${project_main_dir}" # printf 'project_main_dir="%s"\n' "${project_main_dir}"
cd "${project_main_dir}" cd "${project_main_dir}" &&
# `jq` available? # `jq` available?
unset JQ if ! jq --version 1>/dev/null 2>&1; then
jq --version 1>/dev/null 2>&1 echo "WARN: missing \`jq\` (install with \`sudo apt install jq\`); falling back to default (only fully cross-platform) utility list" 1>&2
if [ $? -eq 0 ]; then export JQ="jq"; fi echo "$default_utils"
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
else 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 | 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] | sort | join(\" \")"
fi fi

View file

@ -1,10 +1,10 @@
#!/bin/bash #!/bin/sh
# This is a stupid helper. I will mass replace all versions (including other crates) # This is a stupid helper. I will mass replace all versions (including other crates)
# So, it should be triple-checked # So, it should be triple-checked
# How to ship a new release: # How to ship a new release:
# 1) update this script # 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" # 3) Do a spot check with "git diff"
# 4) cargo test --release --features unix # 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) # 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) 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 # update the version of all programs
#shellcheck disable=SC2086
sed -i -e "s|version = \"$FROM\"|version = \"$TO\"|" $PROGS sed -i -e "s|version = \"$FROM\"|version = \"$TO\"|" $PROGS
# Update uucore_procs # Update uucore_procs
@ -35,6 +36,8 @@ sed -i -e "s|= { optional=true, version=\"$FROM\", package=\"uu_|= { optional=tr
# Update uucore itself # Update uucore itself
sed -i -e "s|version = \"$UUCORE_FROM\"|version = \"$UUCORE_TO\"|" src/uucore/Cargo.toml sed -i -e "s|version = \"$UUCORE_FROM\"|version = \"$UUCORE_TO\"|" src/uucore/Cargo.toml
# Update crates using uucore # Update crates using uucore
#shellcheck disable=SC2086
sed -i -e "s|uucore = { version=\">=$UUCORE_FROM\",|uucore = { version=\">=$UUCORE_TO\",|" $PROGS sed -i -e "s|uucore = { version=\">=$UUCORE_FROM\",|uucore = { version=\">=$UUCORE_TO\",|" $PROGS
# Update crates using uucore_procs # 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 sed -i -e "s|uucore_procs = { version=\">=$UUCORE_PROCS_FROM\",|uucore_procs = { version=\">=$UUCORE_PROCS_TO\",|" $PROGS