mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge branch 'main' into fs_extra
This commit is contained in:
commit
f88b4f4109
35 changed files with 426 additions and 300 deletions
5
.github/workflows/CICD.yml
vendored
5
.github/workflows/CICD.yml
vendored
|
@ -20,6 +20,11 @@ on: [push, pull_request]
|
|||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
# End the current execution if there is a new changeset in the PR.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
cargo-deny:
|
||||
name: Style/cargo-deny
|
||||
|
|
24
.github/workflows/GnuTests.yml
vendored
24
.github/workflows/GnuTests.yml
vendored
|
@ -14,6 +14,11 @@ on: [push, pull_request]
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
# End the current execution if there is a new changeset in the PR.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
gnu:
|
||||
permissions:
|
||||
|
@ -196,6 +201,9 @@ jobs:
|
|||
REF_LOG_FILE='${{ steps.vars.outputs.path_reference }}/test-logs/test-suite.log'
|
||||
REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json'
|
||||
REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}'
|
||||
# https://github.com/uutils/coreutils/issues/4294
|
||||
# https://github.com/uutils/coreutils/issues/4295
|
||||
IGNORE_INTERMITTENT='tests/tail-2/inotify-dir-recreate tests/misc/timeout tests/rm/rm1'
|
||||
|
||||
mkdir -p ${{ steps.vars.outputs.path_reference }}
|
||||
|
||||
|
@ -227,10 +235,18 @@ jobs:
|
|||
do
|
||||
if ! grep -Fxq ${LINE}<<<"${REF_FAILING}"
|
||||
then
|
||||
MSG="GNU test failed: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
|
||||
echo "::error ::$MSG"
|
||||
echo $MSG >> ${COMMENT_LOG}
|
||||
have_new_failures="true"
|
||||
if ! grep ${LINE} ${IGNORE_INTERMITTENT}
|
||||
then
|
||||
MSG="GNU test failed: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
|
||||
echo "::error ::$MSG"
|
||||
echo $MSG >> ${COMMENT_LOG}
|
||||
have_new_failures="true"
|
||||
else
|
||||
MSG="Skip an intermittent issue ${LINE}"
|
||||
echo "::warning ::$MSG"
|
||||
echo $MSG >> ${COMMENT_LOG}
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
done
|
||||
for LINE in ${REF_ERROR}
|
||||
|
|
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -878,6 +878,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fundu"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925250bc259498d4008ee072bf16586083ab2c491aa4b06b3c4d0a6556cebd74"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.25"
|
||||
|
@ -2807,7 +2813,6 @@ version = "0.0.17"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"libc",
|
||||
"num_cpus",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
|
@ -3094,9 +3099,9 @@ version = "0.0.17"
|
|||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"fundu",
|
||||
"libc",
|
||||
"memchr",
|
||||
"nix",
|
||||
"notify",
|
||||
"same-file",
|
||||
"uucore",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# coreutils (uutils)
|
||||
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
|
||||
|
||||
# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue
|
||||
# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu
|
||||
|
||||
[package]
|
||||
name = "coreutils"
|
||||
|
@ -282,6 +282,7 @@ filetime = "0.2"
|
|||
fnv = "1.0.7"
|
||||
fs_extra = "1.1.0"
|
||||
fts-sys = "0.2"
|
||||
fundu = "0.3.0"
|
||||
gcd = "2.2"
|
||||
glob = "0.3.0"
|
||||
half = "2.1"
|
||||
|
|
|
@ -43,13 +43,23 @@ pacman -S uutils-coreutils
|
|||
|
||||
### Debian
|
||||
|
||||
[](https://packages.debian.org/sid/source/rust-coreutils)
|
||||
[](https://packages.debian.org/sid/source/rust-coreutils)
|
||||
|
||||
```bash
|
||||
apt install rust-coreutils
|
||||
# To use it:
|
||||
export PATH=/usr/lib/cargo/bin/coreutils:$PATH
|
||||
```
|
||||
|
||||
> **Note**: Requires the `unstable` repository.
|
||||
> **Note**: Only available from Bookworm (Debian 12)
|
||||
|
||||
### Gentoo
|
||||
|
||||
[](https://packages.gentoo.org/packages/sys-apps/uutils)
|
||||
|
||||
```bash
|
||||
emerge -pv sys-apps/uutils
|
||||
```
|
||||
|
||||
### Manjaro
|
||||

|
||||
|
@ -69,6 +79,18 @@ pamac install uutils-coreutils
|
|||
nix-env -iA nixos.uutils-coreutils
|
||||
```
|
||||
|
||||
### Ubuntu
|
||||
|
||||
[](https://packages.ubuntu.com/source/lunar/rust-coreutils)
|
||||
|
||||
```bash
|
||||
apt install rust-coreutils
|
||||
# To use it:
|
||||
export PATH=/usr/lib/cargo/bin/coreutils:$PATH
|
||||
```
|
||||
|
||||
> **Note**: Only available from Kinetic (Ubuntu 22.10)
|
||||
|
||||
## MacOS
|
||||
|
||||
### Homebrew
|
||||
|
|
11
src/uu/cat/cat.md
Normal file
11
src/uu/cat/cat.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# cat
|
||||
|
||||
## Usage
|
||||
```
|
||||
cat [OPTION]... [FILE]...
|
||||
```
|
||||
|
||||
## About
|
||||
|
||||
Concatenate FILE(s), or standard input, to standard output
|
||||
With no FILE, or when FILE is -, read standard input.
|
|
@ -33,11 +33,10 @@ use std::net::Shutdown;
|
|||
use std::os::unix::fs::FileTypeExt;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::net::UnixStream;
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_section, help_usage};
|
||||
|
||||
static USAGE: &str = "{} [OPTION]... [FILE]...";
|
||||
static ABOUT: &str = "Concatenate FILE(s), or standard input, to standard output
|
||||
With no FILE, or when FILE is -, read standard input.";
|
||||
const USAGE: &str = help_usage!("cat.md");
|
||||
const ABOUT: &str = help_section!("about", "cat.md");
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum CatError {
|
||||
|
|
|
@ -12,15 +12,21 @@ use std::fs;
|
|||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
use std::path::Path;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{ExitCode, UResult, USimpleError, UUsageError};
|
||||
use uucore::error::{set_exit_code, ExitCode, UResult, USimpleError, UUsageError};
|
||||
use uucore::fs::display_permissions_unix;
|
||||
use uucore::libc::mode_t;
|
||||
#[cfg(not(windows))]
|
||||
use uucore::mode;
|
||||
use uucore::{format_usage, show_error};
|
||||
use uucore::{format_usage, show, show_error};
|
||||
|
||||
static ABOUT: &str = "Change the mode of each FILE to MODE.
|
||||
With --reference, change the mode of each FILE to that of RFILE.";
|
||||
const ABOUT: &str = "Change the mode of each FILE to MODE.\n\
|
||||
With --reference, change the mode of each FILE to that of RFILE.";
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... MODE[,MODE]... FILE...
|
||||
{} [OPTION]... OCTAL-MODE FILE...
|
||||
{} [OPTION]... --reference=RFILE FILE...";
|
||||
const LONG_USAGE: &str =
|
||||
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.";
|
||||
|
||||
mod options {
|
||||
pub const CHANGES: &str = "changes";
|
||||
|
@ -34,15 +40,6 @@ mod options {
|
|||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... MODE[,MODE]... FILE...
|
||||
{} [OPTION]... OCTAL-MODE FILE...
|
||||
{} [OPTION]... --reference=RFILE FILE...";
|
||||
|
||||
fn get_long_usage() -> &'static str {
|
||||
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'."
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let mut args = args.collect_lossy();
|
||||
|
@ -51,9 +48,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
|
||||
let mode_had_minus_prefix = mode::strip_minus_from_mode(&mut args);
|
||||
|
||||
let after_help = get_long_usage();
|
||||
|
||||
let matches = uu_app().after_help(after_help).try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
|
||||
let changes = matches.get_flag(options::CHANGES);
|
||||
let quiet = matches.get_flag(options::QUIET);
|
||||
|
@ -200,21 +195,24 @@ impl Chmoder {
|
|||
filename.quote()
|
||||
);
|
||||
if !self.quiet {
|
||||
return Err(USimpleError::new(
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
format!("cannot operate on dangling symlink {}", filename.quote()),
|
||||
));
|
||||
}
|
||||
} else if !self.quiet {
|
||||
return Err(USimpleError::new(
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
format!(
|
||||
"cannot access {}: No such file or directory",
|
||||
filename.quote()
|
||||
),
|
||||
)
|
||||
));
|
||||
}
|
||||
return Err(ExitCode::new(1));
|
||||
// GNU exits with exit code 1 even if -q or --quiet are passed
|
||||
// So we set the exit code, because it hasn't been set yet if `self.quiet` is true.
|
||||
set_exit_code(1);
|
||||
continue;
|
||||
}
|
||||
if self.recursive && self.preserve_root && filename == "/" {
|
||||
return Err(USimpleError::new(
|
||||
|
|
|
@ -32,21 +32,12 @@ mod options {
|
|||
pub const TOTAL: &str = "total";
|
||||
}
|
||||
|
||||
fn mkdelim(col: usize, opts: &ArgMatches) -> String {
|
||||
let mut s = String::new();
|
||||
let delim = match opts.get_one::<String>(options::DELIMITER).unwrap().as_str() {
|
||||
"" => "\0",
|
||||
delim => delim,
|
||||
};
|
||||
|
||||
if col > 1 && !opts.get_flag(options::COLUMN_1) {
|
||||
s.push_str(delim.as_ref());
|
||||
fn column_width(col: &str, opts: &ArgMatches) -> usize {
|
||||
if opts.get_flag(col) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
if col > 2 && !opts.get_flag(options::COLUMN_2) {
|
||||
s.push_str(delim.as_ref());
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
fn ensure_nl(line: &mut String) {
|
||||
|
@ -70,7 +61,16 @@ impl LineReader {
|
|||
}
|
||||
|
||||
fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
||||
let delim: Vec<String> = (0..4).map(|col| mkdelim(col, opts)).collect();
|
||||
let delim = match opts.get_one::<String>(options::DELIMITER).unwrap().as_str() {
|
||||
"" => "\0",
|
||||
delim => delim,
|
||||
};
|
||||
|
||||
let width_col_1 = column_width(options::COLUMN_1, opts);
|
||||
let width_col_2 = column_width(options::COLUMN_2, opts);
|
||||
|
||||
let delim_col_2 = delim.repeat(width_col_1);
|
||||
let delim_col_3 = delim.repeat(width_col_1 + width_col_2);
|
||||
|
||||
let ra = &mut String::new();
|
||||
let mut na = a.read_line(ra);
|
||||
|
@ -98,7 +98,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
Ordering::Less => {
|
||||
if !opts.get_flag(options::COLUMN_1) {
|
||||
ensure_nl(ra);
|
||||
print!("{}{}", delim[1], ra);
|
||||
print!("{ra}");
|
||||
}
|
||||
ra.clear();
|
||||
na = a.read_line(ra);
|
||||
|
@ -107,7 +107,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
Ordering::Greater => {
|
||||
if !opts.get_flag(options::COLUMN_2) {
|
||||
ensure_nl(rb);
|
||||
print!("{}{}", delim[2], rb);
|
||||
print!("{delim_col_2}{rb}");
|
||||
}
|
||||
rb.clear();
|
||||
nb = b.read_line(rb);
|
||||
|
@ -116,7 +116,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
Ordering::Equal => {
|
||||
if !opts.get_flag(options::COLUMN_3) {
|
||||
ensure_nl(ra);
|
||||
print!("{}{}", delim[3], ra);
|
||||
print!("{delim_col_3}{ra}");
|
||||
}
|
||||
ra.clear();
|
||||
rb.clear();
|
||||
|
@ -128,7 +128,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
}
|
||||
|
||||
if opts.get_flag(options::TOTAL) {
|
||||
println!("{total_col_1}\t{total_col_2}\t{total_col_3}\ttotal");
|
||||
println!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
12
src/uu/cp/cp.md
Normal file
12
src/uu/cp/cp.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# cp
|
||||
|
||||
## Usage
|
||||
```
|
||||
cp [OPTION]... [-T] SOURCE DEST
|
||||
cp [OPTION]... SOURCE... DIRECTORY
|
||||
cp [OPTION]... -t DIRECTORY SOURCE...
|
||||
```
|
||||
|
||||
## About
|
||||
|
||||
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.
|
|
@ -40,7 +40,7 @@ use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError};
|
|||
use uucore::fs::{
|
||||
canonicalize, paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode,
|
||||
};
|
||||
use uucore::{crash, format_usage, prompt_yes, show_error, show_warning};
|
||||
use uucore::{crash, format_usage, help_section, help_usage, prompt_yes, show_error, show_warning};
|
||||
|
||||
use crate::copydir::copy_directory;
|
||||
|
||||
|
@ -228,13 +228,10 @@ pub struct Options {
|
|||
progress_bar: bool,
|
||||
}
|
||||
|
||||
static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
|
||||
const ABOUT: &str = help_section!("about", "cp.md");
|
||||
static EXIT_ERR: i32 = 1;
|
||||
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... [-T] SOURCE DEST
|
||||
{} [OPTION]... SOURCE... DIRECTORY
|
||||
{} [OPTION]... -t DIRECTORY SOURCE...";
|
||||
const USAGE: &str = help_usage!("cp.md");
|
||||
|
||||
// Argument constants
|
||||
mod options {
|
||||
|
|
|
@ -11,26 +11,22 @@ use uucore::display::print_verbatim;
|
|||
use uucore::error::{UResult, UUsageError};
|
||||
use uucore::format_usage;
|
||||
|
||||
static ABOUT: &str = "Strip last component from file name";
|
||||
const ABOUT: &str = "Strip last component from file name";
|
||||
const USAGE: &str = "{} [OPTION] NAME...";
|
||||
const LONG_USAGE: &str = "\
|
||||
Output each NAME with its last non-slash component and trailing slashes \n\
|
||||
removed; if NAME contains no /'s, output '.' (meaning the current directory).";
|
||||
|
||||
mod options {
|
||||
pub const ZERO: &str = "zero";
|
||||
pub const DIR: &str = "dir";
|
||||
}
|
||||
|
||||
fn get_long_usage() -> &'static str {
|
||||
"Output each NAME with its last non-slash component and trailing slashes \n\
|
||||
removed; if NAME contains no /'s, output '.' (meaning the current directory)."
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args.collect_lossy();
|
||||
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
|
||||
let separator = if matches.get_flag(options::ZERO) {
|
||||
"\0"
|
||||
|
|
|
@ -22,8 +22,10 @@ use uucore::{format_usage, show, show_if_err};
|
|||
|
||||
static DEFAULT_PERM: u32 = 0o755;
|
||||
|
||||
static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
|
||||
const ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
|
||||
const USAGE: &str = "{} [OPTION]... [USER]";
|
||||
const LONG_USAGE: &str =
|
||||
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.";
|
||||
|
||||
mod options {
|
||||
pub const MODE: &str = "mode";
|
||||
|
@ -32,10 +34,6 @@ mod options {
|
|||
pub const DIRS: &str = "dirs";
|
||||
}
|
||||
|
||||
fn get_long_usage() -> &'static str {
|
||||
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'."
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_mode(_matches: &ArgMatches, _mode_had_minus_prefix: bool) -> Result<u32, String> {
|
||||
Ok(DEFAULT_PERM)
|
||||
|
@ -92,9 +90,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
// Linux-specific options, not implemented
|
||||
// opts.optflag("Z", "context", "set SELinux security context" +
|
||||
// " of each created directory to CTX"),
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
|
||||
let dirs = matches
|
||||
.get_many::<OsString>(options::DIRS)
|
||||
|
|
|
@ -16,7 +16,6 @@ path = "src/nproc.rs"
|
|||
|
||||
[dependencies]
|
||||
libc = { workspace=true }
|
||||
num_cpus = { workspace=true }
|
||||
clap = { workspace=true }
|
||||
uucore = { workspace=true, features=["fs"] }
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// spell-checker:ignore (ToDO) NPROCESSORS nprocs numstr threadstr sysconf
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use std::env;
|
||||
use std::{env, thread};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::format_usage;
|
||||
|
@ -73,16 +73,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
// If OMP_NUM_THREADS=0, rejects the value
|
||||
let thread: Vec<&str> = threadstr.split_terminator(',').collect();
|
||||
match &thread[..] {
|
||||
[] => num_cpus::get(),
|
||||
[] => available_parallelism(),
|
||||
[s, ..] => match s.parse() {
|
||||
Ok(0) | Err(_) => num_cpus::get(),
|
||||
Ok(0) | Err(_) => available_parallelism(),
|
||||
Ok(n) => n,
|
||||
},
|
||||
}
|
||||
}
|
||||
// the variable 'OMP_NUM_THREADS' doesn't exist
|
||||
// fallback to the regular CPU detection
|
||||
Err(_) => num_cpus::get(),
|
||||
Err(_) => available_parallelism(),
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -127,7 +127,7 @@ fn num_cpus_all() -> usize {
|
|||
if nprocs == 1 {
|
||||
// In some situation, /proc and /sys are not mounted, and sysconf returns 1.
|
||||
// However, we want to guarantee that `nproc --all` >= `nproc`.
|
||||
num_cpus::get()
|
||||
available_parallelism()
|
||||
} else if nprocs > 0 {
|
||||
nprocs as usize
|
||||
} else {
|
||||
|
@ -135,7 +135,7 @@ fn num_cpus_all() -> usize {
|
|||
}
|
||||
}
|
||||
|
||||
// Other platforms (e.g., windows), num_cpus::get() directly.
|
||||
// Other platforms (e.g., windows), available_parallelism() directly.
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_vendor = "apple",
|
||||
|
@ -143,5 +143,14 @@ fn num_cpus_all() -> usize {
|
|||
target_os = "netbsd"
|
||||
)))]
|
||||
fn num_cpus_all() -> usize {
|
||||
num_cpus::get()
|
||||
available_parallelism()
|
||||
}
|
||||
|
||||
// In some cases, thread::available_parallelism() may return an Err
|
||||
// In this case, we will return 1 (like GNU)
|
||||
fn available_parallelism() -> usize {
|
||||
match thread::available_parallelism() {
|
||||
Ok(n) => n.get(),
|
||||
Err(_) => 1,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,8 +37,22 @@ struct Options {
|
|||
verbose: bool,
|
||||
}
|
||||
|
||||
static ABOUT: &str = "Remove (unlink) the FILE(s)";
|
||||
const ABOUT: &str = "Remove (unlink) the FILE(s)";
|
||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
||||
const LONG_USAGE: &str = "\
|
||||
By default, rm does not remove directories. Use the --recursive (-r or -R)
|
||||
option to remove each listed directory, too, along with all of its contents
|
||||
|
||||
To remove a file whose name starts with a '-', for example '-foo',
|
||||
use one of these commands:
|
||||
rm -- -foo
|
||||
|
||||
rm ./-foo
|
||||
|
||||
Note that if you use rm to remove a file, it might be possible to recover
|
||||
some of its contents, given sufficient expertise and/or time. For greater
|
||||
assurance that the contents are truly unrecoverable, consider using shred.";
|
||||
|
||||
static OPT_DIR: &str = "dir";
|
||||
static OPT_INTERACTIVE: &str = "interactive";
|
||||
static OPT_FORCE: &str = "force";
|
||||
|
@ -53,28 +67,9 @@ static PRESUME_INPUT_TTY: &str = "-presume-input-tty";
|
|||
|
||||
static ARG_FILES: &str = "files";
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
String::from(
|
||||
"By default, rm does not remove directories. Use the --recursive (-r or -R)
|
||||
option to remove each listed directory, too, along with all of its contents
|
||||
|
||||
To remove a file whose name starts with a '-', for example '-foo',
|
||||
use one of these commands:
|
||||
rm -- -foo
|
||||
|
||||
rm ./-foo
|
||||
|
||||
Note that if you use rm to remove a file, it might be possible to recover
|
||||
some of its contents, given sufficient expertise and/or time. For greater
|
||||
assurance that the contents are truly unrecoverable, consider using shred.",
|
||||
)
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.get_many::<String>(ARG_FILES)
|
||||
|
|
|
@ -13,7 +13,7 @@ use uucore::fsext::{
|
|||
pretty_filetype, pretty_fstype, pretty_time, read_fs_list, statfs, BirthTime, FsMeta,
|
||||
};
|
||||
use uucore::libc::mode_t;
|
||||
use uucore::{entries, format_usage, show_error, show_warning};
|
||||
use uucore::{entries, format_usage, help_section, help_usage, show_error, show_warning};
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||
use std::borrow::Cow;
|
||||
|
@ -24,8 +24,9 @@ use std::os::unix::fs::{FileTypeExt, MetadataExt};
|
|||
use std::os::unix::prelude::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
const ABOUT: &str = "Display file or file system status.";
|
||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
||||
const ABOUT: &str = help_section!("about", "stat.md");
|
||||
const USAGE: &str = help_usage!("stat.md");
|
||||
const LONG_USAGE: &str = help_section!("long usage", "stat.md");
|
||||
|
||||
mod options {
|
||||
pub const DEREFERENCE: &str = "dereference";
|
||||
|
@ -751,67 +752,9 @@ impl Stater {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_long_usage() -> &'static str {
|
||||
"
|
||||
The valid format sequences for files (without --file-system):
|
||||
|
||||
%a access rights in octal (note '#' and '0' printf flags)
|
||||
%A access rights in human readable form
|
||||
%b number of blocks allocated (see %B)
|
||||
%B the size in bytes of each block reported by %b
|
||||
%C SELinux security context string
|
||||
%d device number in decimal
|
||||
%D device number in hex
|
||||
%f raw mode in hex
|
||||
%F file type
|
||||
%g group ID of owner
|
||||
%G group name of owner
|
||||
%h number of hard links
|
||||
%i inode number
|
||||
%m mount point
|
||||
%n file name
|
||||
%N quoted file name with dereference if symbolic link
|
||||
%o optimal I/O transfer size hint
|
||||
%s total size, in bytes
|
||||
%t major device type in hex, for character/block device special files
|
||||
%T minor device type in hex, for character/block device special files
|
||||
%u user ID of owner
|
||||
%U user name of owner
|
||||
%w time of file birth, human-readable; - if unknown
|
||||
%W time of file birth, seconds since Epoch; 0 if unknown
|
||||
%x time of last access, human-readable
|
||||
%X time of last access, seconds since Epoch
|
||||
%y time of last data modification, human-readable
|
||||
%Y time of last data modification, seconds since Epoch
|
||||
%z time of last status change, human-readable
|
||||
%Z time of last status change, seconds since Epoch
|
||||
|
||||
Valid format sequences for file systems:
|
||||
|
||||
%a free blocks available to non-superuser
|
||||
%b total data blocks in file system
|
||||
%c total file nodes in file system
|
||||
%d free file nodes in file system
|
||||
%f free blocks in file system
|
||||
%i file system ID in hex
|
||||
%l maximum length of filenames
|
||||
%n file name
|
||||
%s block size (for faster transfers)
|
||||
%S fundamental block size (for block counts)
|
||||
%t file system type in hex
|
||||
%T file system type in human readable form
|
||||
|
||||
NOTE: your shell may have its own version of stat, which usually supersedes
|
||||
the version described here. Please refer to your shell's documentation
|
||||
for details about the options it supports.
|
||||
"
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
|
||||
let stater = Stater::new(&matches)?;
|
||||
let exit_status = stater.exec();
|
||||
|
|
64
src/uu/stat/stat.md
Normal file
64
src/uu/stat/stat.md
Normal file
|
@ -0,0 +1,64 @@
|
|||
# stat
|
||||
|
||||
## About
|
||||
|
||||
Display file or file system status.
|
||||
|
||||
## Usage
|
||||
```
|
||||
stat [OPTION]... FILE...
|
||||
```
|
||||
|
||||
## Long Usage
|
||||
|
||||
The valid format sequences for files (without `--file-system`):
|
||||
|
||||
%a access rights in octal (note '#' and '0' printf flags)
|
||||
%A access rights in human readable form
|
||||
%b number of blocks allocated (see %B)
|
||||
%B the size in bytes of each block reported by %b
|
||||
%C SELinux security context string
|
||||
%d device number in decimal
|
||||
%D device number in hex
|
||||
%f raw mode in hex
|
||||
%F file type
|
||||
%g group ID of owner
|
||||
%G group name of owner
|
||||
%h number of hard links
|
||||
%i inode number
|
||||
%m mount point
|
||||
%n file name
|
||||
%N quoted file name with dereference if symbolic link
|
||||
%o optimal I/O transfer size hint
|
||||
%s total size, in bytes
|
||||
%t major device type in hex, for character/block device special files
|
||||
%T minor device type in hex, for character/block device special files
|
||||
%u user ID of owner
|
||||
%U user name of owner
|
||||
%w time of file birth, human-readable; - if unknown
|
||||
%W time of file birth, seconds since Epoch; 0 if unknown
|
||||
%x time of last access, human-readable
|
||||
%X time of last access, seconds since Epoch
|
||||
%y time of last data modification, human-readable
|
||||
%Y time of last data modification, seconds since Epoch
|
||||
%z time of last status change, human-readable
|
||||
%Z time of last status change, seconds since Epoch
|
||||
|
||||
Valid format sequences for file systems:
|
||||
|
||||
%a free blocks available to non-superuser
|
||||
%b total data blocks in file system
|
||||
%c total file nodes in file system
|
||||
%d free file nodes in file system
|
||||
%f free blocks in file system
|
||||
%i file system ID in hex
|
||||
%l maximum length of filenames
|
||||
%n file name
|
||||
%s block size (for faster transfers)
|
||||
%S fundamental block size (for block counts)
|
||||
%t file system type in hex
|
||||
%T file system type in human readable form
|
||||
|
||||
NOTE: your shell may have its own version of stat, which usually supersedes
|
||||
the version described here. Please refer to your shell's documentation
|
||||
for details about the options it supports.
|
|
@ -1,3 +1,4 @@
|
|||
# spell-checker:ignore (libs) kqueue fundu
|
||||
[package]
|
||||
name = "uu_tail"
|
||||
version = "0.0.17"
|
||||
|
@ -19,17 +20,15 @@ clap = { workspace=true }
|
|||
libc = { workspace=true }
|
||||
memchr = { workspace=true }
|
||||
notify = { workspace=true }
|
||||
uucore = { workspace=true, features=["ringbuffer", "lines"] }
|
||||
uucore = { workspace=true }
|
||||
same-file = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
fundu = { workspace=true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { workspace=true, features = ["Win32_System_Threading", "Win32_Foundation"] }
|
||||
winapi-util = { workspace=true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { workspace=true, features = ["fs"] }
|
||||
|
||||
[[bin]]
|
||||
name = "tail"
|
||||
path = "src/main.rs"
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) kqueue Signum
|
||||
// spell-checker:ignore (ToDO) kqueue Signum fundu
|
||||
|
||||
use crate::paths::Input;
|
||||
use crate::{parse, platform, Quotable};
|
||||
use atty::Stream;
|
||||
use clap::crate_version;
|
||||
use clap::{parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
|
||||
use fundu::DurationParser;
|
||||
use same_file::Handle;
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::OsString;
|
||||
|
@ -148,16 +149,20 @@ impl Settings {
|
|||
settings.retry =
|
||||
matches.get_flag(options::RETRY) || matches.get_flag(options::FOLLOW_RETRY);
|
||||
|
||||
if let Some(s) = matches.get_one::<String>(options::SLEEP_INT) {
|
||||
settings.sleep_sec = match s.parse::<f32>() {
|
||||
Ok(s) => Duration::from_secs_f32(s),
|
||||
Err(_) => {
|
||||
return Err(UUsageError::new(
|
||||
1,
|
||||
format!("invalid number of seconds: {}", s.quote()),
|
||||
))
|
||||
}
|
||||
}
|
||||
if let Some(source) = matches.get_one::<String>(options::SLEEP_INT) {
|
||||
// Advantage of `fundu` over `Duration::(try_)from_secs_f64(source.parse().unwrap())`:
|
||||
// * doesn't panic on errors like `Duration::from_secs_f64` would.
|
||||
// * no precision loss, rounding errors or other floating point problems.
|
||||
// * evaluates to `Duration::MAX` if the parsed number would have exceeded
|
||||
// `DURATION::MAX` or `infinity` was given
|
||||
// * not applied here but it supports customizable time units and provides better error
|
||||
// messages
|
||||
settings.sleep_sec =
|
||||
DurationParser::without_time_units()
|
||||
.parse(source)
|
||||
.map_err(|_| {
|
||||
UUsageError::new(1, format!("invalid number of seconds: '{source}'"))
|
||||
})?;
|
||||
}
|
||||
|
||||
settings.use_polling = matches.get_flag(options::USE_POLLING);
|
||||
|
|
|
@ -37,21 +37,21 @@ pub enum BadSequence {
|
|||
impl Display for BadSequence {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MissingCharClassName => writeln!(f, "missing character class name '[::]'"),
|
||||
Self::MissingCharClassName => write!(f, "missing character class name '[::]'"),
|
||||
Self::MissingEquivalentClassChar => {
|
||||
writeln!(f, "missing equivalence class character '[==]'")
|
||||
write!(f, "missing equivalence class character '[==]'")
|
||||
}
|
||||
Self::MultipleCharRepeatInSet2 => {
|
||||
writeln!(f, "only one [c*] repeat construct may appear in string2")
|
||||
write!(f, "only one [c*] repeat construct may appear in string2")
|
||||
}
|
||||
Self::CharRepeatInSet1 => {
|
||||
writeln!(f, "the [c*] repeat construct may not appear in string1")
|
||||
write!(f, "the [c*] repeat construct may not appear in string1")
|
||||
}
|
||||
Self::InvalidRepeatCount(count) => {
|
||||
writeln!(f, "invalid repeat count '{count}' in [c*n] construct")
|
||||
write!(f, "invalid repeat count '{count}' in [c*n] construct")
|
||||
}
|
||||
Self::EmptySet2WhenNotTruncatingSet1 => {
|
||||
writeln!(f, "when not truncating set1, string2 must be non-empty")
|
||||
write!(f, "when not truncating set1, string2 must be non-empty")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,11 @@ use crate::operation::DeleteOperation;
|
|||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||
|
||||
static ABOUT: &str = "Translate or delete characters";
|
||||
const ABOUT: &str = "Translate or delete characters";
|
||||
const USAGE: &str = "{} [OPTION]... SET1 [SET2]";
|
||||
const LONG_USAGE: &str = "\
|
||||
Translate, squeeze, and/or delete characters from standard input, \
|
||||
writing to standard output.";
|
||||
|
||||
mod options {
|
||||
pub const COMPLEMENT: &str = "complement";
|
||||
|
@ -30,19 +33,11 @@ mod options {
|
|||
pub const SETS: &str = "sets";
|
||||
}
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
"Translate, squeeze, and/or delete characters from standard input, \
|
||||
writing to standard output."
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args.collect_lossy();
|
||||
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
|
||||
let delete_flag = matches.get_flag(options::DELETE);
|
||||
let complement_flag = matches.get_flag(options::COMPLEMENT);
|
||||
|
|
|
@ -73,8 +73,25 @@ impl TruncateMode {
|
|||
}
|
||||
}
|
||||
|
||||
static ABOUT: &str = "Shrink or extend the size of each file to the specified size.";
|
||||
const ABOUT: &str = "Shrink or extend the size of each file to the specified size.";
|
||||
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
||||
const LONG_USAGE: &str = "\
|
||||
SIZE is an integer with an optional prefix and optional unit.
|
||||
The available units (K, M, G, T, P, E, Z, and Y) use the following format:
|
||||
'KB' => 1000 (kilobytes)
|
||||
'K' => 1024 (kibibytes)
|
||||
'MB' => 1000*1000 (megabytes)
|
||||
'M' => 1024*1024 (mebibytes)
|
||||
'GB' => 1000*1000*1000 (gigabytes)
|
||||
'G' => 1024*1024*1024 (gibibytes)
|
||||
SIZE may also be prefixed by one of the following to adjust the size of each
|
||||
file based on its current size:
|
||||
'+' => extend by
|
||||
'-' => reduce by
|
||||
'<' => at most
|
||||
'>' => at least
|
||||
'/' => round down to multiple of
|
||||
'%' => round up to multiple of";
|
||||
|
||||
pub mod options {
|
||||
pub static IO_BLOCKS: &str = "io-blocks";
|
||||
|
@ -84,32 +101,10 @@ pub mod options {
|
|||
pub static ARG_FILES: &str = "files";
|
||||
}
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
String::from(
|
||||
"
|
||||
SIZE is an integer with an optional prefix and optional unit.
|
||||
The available units (K, M, G, T, P, E, Z, and Y) use the following format:
|
||||
'KB' => 1000 (kilobytes)
|
||||
'K' => 1024 (kibibytes)
|
||||
'MB' => 1000*1000 (megabytes)
|
||||
'M' => 1024*1024 (mebibytes)
|
||||
'GB' => 1000*1000*1000 (gigabytes)
|
||||
'G' => 1024*1024*1024 (gibibytes)
|
||||
SIZE may also be prefixed by one of the following to adjust the size of each
|
||||
file based on its current size:
|
||||
'+' => extend by
|
||||
'-' => reduce by
|
||||
'<' => at most
|
||||
'>' => at least
|
||||
'/' => round down to multiple of
|
||||
'%' => round up to multiple of",
|
||||
)
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.after_help(LONG_USAGE)
|
||||
.try_get_matches_from(args)
|
||||
.map_err(|e| {
|
||||
e.print().expect("Error writing clap::Error");
|
||||
|
|
|
@ -15,8 +15,14 @@ use uucore::display::Quotable;
|
|||
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
||||
use uucore::format_usage;
|
||||
|
||||
static ABOUT: &str = "Report or omit repeated lines.";
|
||||
const ABOUT: &str = "Report or omit repeated lines.";
|
||||
const USAGE: &str = "{} [OPTION]... [INPUT [OUTPUT]]...";
|
||||
const LONG_USAGE: &str = "\
|
||||
Filter adjacent matching lines from INPUT (or standard input),\n\
|
||||
writing to OUTPUT (or standard output).\n\n\
|
||||
Note: 'uniq' does not detect repeated lines unless they are adjacent.\n\
|
||||
You may want to sort the input first, or use 'sort -u' without 'uniq'.";
|
||||
|
||||
pub mod options {
|
||||
pub static ALL_REPEATED: &str = "all-repeated";
|
||||
pub static CHECK_CHARS: &str = "check-chars";
|
||||
|
@ -241,20 +247,9 @@ fn opt_parsed<T: FromStr>(opt_name: &str, matches: &ArgMatches) -> UResult<Optio
|
|||
})
|
||||
}
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
String::from(
|
||||
"Filter adjacent matching lines from INPUT (or standard input),\n\
|
||||
writing to OUTPUT (or standard output).
|
||||
Note: 'uniq' does not detect repeated lines unless they are adjacent.\n\
|
||||
You may want to sort the input first, or use 'sort -u' without 'uniq'.\n",
|
||||
)
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.get_many::<String>(ARG_FILES)
|
||||
|
|
|
@ -574,3 +574,71 @@ fn test_mode_after_dash_dash() {
|
|||
ucmd,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chmod_file_after_non_existing_file() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch(TEST_FILE);
|
||||
at.touch("file2");
|
||||
set_permissions(at.plus(TEST_FILE), Permissions::from_mode(0o664)).unwrap();
|
||||
set_permissions(at.plus("file2"), Permissions::from_mode(0o664)).unwrap();
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("u+x")
|
||||
.arg("does-not-exist")
|
||||
.arg(TEST_FILE)
|
||||
.fails()
|
||||
.stderr_contains("chmod: cannot access 'does-not-exist': No such file or directory")
|
||||
.code_is(1);
|
||||
|
||||
assert_eq!(at.metadata(TEST_FILE).permissions().mode(), 0o100764);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("u+x")
|
||||
.arg("--q")
|
||||
.arg("does-not-exist")
|
||||
.arg("file2")
|
||||
.fails()
|
||||
.no_stderr()
|
||||
.code_is(1);
|
||||
assert_eq!(at.metadata("file2").permissions().mode(), 0o100764);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chmod_file_symlink_after_non_existing_file() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
let existing = "file";
|
||||
let test_existing_symlink = "file_symlink";
|
||||
|
||||
let non_existing = "test_chmod_symlink_non_existing_file";
|
||||
let test_dangling_symlink = "test_chmod_symlink_non_existing_file_symlink";
|
||||
let expected_stdout = &format!(
|
||||
"failed to change mode of '{test_dangling_symlink}' from 0000 (---------) to 0000 (---------)"
|
||||
);
|
||||
let expected_stderr = &format!("cannot operate on dangling symlink '{test_dangling_symlink}'");
|
||||
|
||||
at.touch(existing);
|
||||
set_permissions(at.plus(existing), Permissions::from_mode(0o664)).unwrap();
|
||||
at.symlink_file(non_existing, test_dangling_symlink);
|
||||
at.symlink_file(existing, test_existing_symlink);
|
||||
|
||||
// this cannot succeed since the symbolic link dangles
|
||||
// but the metadata for the existing target should change
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("u+x")
|
||||
.arg("-v")
|
||||
.arg(test_dangling_symlink)
|
||||
.arg(test_existing_symlink)
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stdout_contains(expected_stdout)
|
||||
.stderr_contains(expected_stderr);
|
||||
assert_eq!(
|
||||
at.metadata(test_existing_symlink).permissions().mode(),
|
||||
0o100764
|
||||
);
|
||||
}
|
||||
|
|
|
@ -71,6 +71,14 @@ fn total_with_suppressed_regular_output() {
|
|||
.stdout_is_fixture("ab_total_suppressed_regular_output.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn total_with_output_delimiter() {
|
||||
new_ucmd!()
|
||||
.args(&["--total", "--output-delimiter=word", "a", "b"])
|
||||
.succeeds()
|
||||
.stdout_is_fixture("ab_total_delimiter_word.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_delimiter() {
|
||||
new_ucmd!()
|
||||
|
|
|
@ -68,10 +68,10 @@ fn test_date_utc() {
|
|||
fn test_date_format_y() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
|
||||
let mut re = Regex::new(r"^\d{4}$").unwrap();
|
||||
let mut re = Regex::new(r"^\d{4}\n$").unwrap();
|
||||
scene.ucmd().arg("+%Y").succeeds().stdout_matches(&re);
|
||||
|
||||
re = Regex::new(r"^\d{2}$").unwrap();
|
||||
re = Regex::new(r"^\d{2}\n$").unwrap();
|
||||
scene.ucmd().arg("+%y").succeeds().stdout_matches(&re);
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ fn test_date_format_m() {
|
|||
let mut re = Regex::new(r"\S+").unwrap();
|
||||
scene.ucmd().arg("+%b").succeeds().stdout_matches(&re);
|
||||
|
||||
re = Regex::new(r"^\d{2}$").unwrap();
|
||||
re = Regex::new(r"^\d{2}\n$").unwrap();
|
||||
scene.ucmd().arg("+%m").succeeds().stdout_matches(&re);
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ fn test_date_format_day() {
|
|||
re = Regex::new(r"\S+").unwrap();
|
||||
scene.ucmd().arg("+%A").succeeds().stdout_matches(&re);
|
||||
|
||||
re = Regex::new(r"^\d{1}$").unwrap();
|
||||
re = Regex::new(r"^\d{1}\n$").unwrap();
|
||||
scene.ucmd().arg("+%u").succeeds().stdout_matches(&re);
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ fn test_date_issue_3780() {
|
|||
#[test]
|
||||
fn test_date_nano_seconds() {
|
||||
// %N nanoseconds (000000000..999999999)
|
||||
let re = Regex::new(r"^\d{1,9}$").unwrap();
|
||||
let re = Regex::new(r"^\d{1,9}\n$").unwrap();
|
||||
new_ucmd!().arg("+%N").succeeds().stdout_matches(&re);
|
||||
}
|
||||
|
||||
|
|
|
@ -49,5 +49,5 @@ fn test_long_output() {
|
|||
.ucmd()
|
||||
.arg("-l")
|
||||
.succeeds()
|
||||
.stdout_matches(&Regex::new("[rwx-]{10}.*some-file1$").unwrap());
|
||||
.stdout_matches(&Regex::new("[rwx-]{10}.*some-file1\n$").unwrap());
|
||||
}
|
||||
|
|
|
@ -950,7 +950,7 @@ fn test_ls_commas_trailing() {
|
|||
.arg("./test-commas-trailing-1")
|
||||
.arg("./test-commas-trailing-2")
|
||||
.succeeds()
|
||||
.stdout_matches(&Regex::new(r"\S$").unwrap()); // matches if there is no whitespace at the end of stdout.
|
||||
.stdout_matches(&Regex::new(r"\S\n$").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -300,7 +300,7 @@ fn test_relative_base_not_prefix_of_relative_to() {
|
|||
.succeeds();
|
||||
|
||||
#[cfg(windows)]
|
||||
result.stdout_matches(&Regex::new(r"^.*:\\usr\n.*:\\usr\\local$").unwrap());
|
||||
result.stdout_matches(&Regex::new(r"^.*:\\usr\n.*:\\usr\\local\n$").unwrap());
|
||||
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_is("/usr\n/usr/local\n");
|
||||
|
@ -344,7 +344,7 @@ fn test_relative() {
|
|||
#[cfg(not(windows))]
|
||||
result.stdout_is("/tmp\n.\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_matches(&Regex::new(r"^.*:\\tmp\n\.$").unwrap());
|
||||
result.stdout_matches(&Regex::new(r"^.*:\\tmp\n\.\n$").unwrap());
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-sm", "--relative-base=/", "--relative-to=/", "/", "/usr"])
|
||||
|
@ -357,7 +357,7 @@ fn test_relative() {
|
|||
#[cfg(not(windows))]
|
||||
result.stdout_is("/tmp\n.\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_matches(&Regex::new(r"^.*:\\tmp\n\.$").unwrap());
|
||||
result.stdout_matches(&Regex::new(r"^.*:\\tmp\n\.\n$").unwrap());
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-sm", "--relative-base=/", "/", "/usr"])
|
||||
|
|
|
@ -13,6 +13,7 @@ use crate::common::random::*;
|
|||
use crate::common::util::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rstest::rstest;
|
||||
use std::char::from_digit;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
@ -4453,29 +4454,24 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_file_stays_same
|
|||
.stdout_only(expected_stdout);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(disable_until_fixed)]
|
||||
fn test_args_sleep_interval_when_illegal_argument_then_usage_error() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
for interval in [
|
||||
&format!("{}0", f64::MAX),
|
||||
&format!("{}0.0", f64::MAX),
|
||||
"1_000",
|
||||
".",
|
||||
"' '",
|
||||
"",
|
||||
" ",
|
||||
"0,0",
|
||||
"one.zero",
|
||||
".zero",
|
||||
"one.",
|
||||
"0..0",
|
||||
] {
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["--sleep-interval", interval])
|
||||
.run()
|
||||
.usage_error(format!("invalid number of seconds: '{}'", interval))
|
||||
.code_is(1);
|
||||
}
|
||||
#[rstest]
|
||||
#[case::exponent_exceed_float_max("1.0e2048")]
|
||||
#[case::underscore_delimiter("1_000")]
|
||||
#[case::only_point(".")]
|
||||
#[case::space_in_primes("' '")]
|
||||
#[case::space(" ")]
|
||||
#[case::empty("")]
|
||||
#[case::comma_separator("0,0")]
|
||||
#[case::words_nominator_fract("one.zero")]
|
||||
#[case::words_fract(".zero")]
|
||||
#[case::words_nominator("one.")]
|
||||
#[case::two_points("0..0")]
|
||||
#[case::seconds_unit("1.0s")]
|
||||
#[case::circumflex_exponent("1.0e^1000")]
|
||||
fn test_args_sleep_interval_when_illegal_argument_then_usage_error(#[case] sleep_interval: &str) {
|
||||
new_ucmd!()
|
||||
.args(&["--sleep-interval", sleep_interval])
|
||||
.run()
|
||||
.usage_error(format!("invalid number of seconds: '{sleep_interval}'"))
|
||||
.code_is(1);
|
||||
}
|
||||
|
|
|
@ -777,10 +777,7 @@ fn check_against_gnu_tr_tests_range_a_a() {
|
|||
.stdout_is("zbc");
|
||||
}
|
||||
|
||||
// FIXME: Since pr https://github.com/uutils/coreutils/pull/4261:
|
||||
// stderr ends with 2 newlines but expected is only 1.
|
||||
#[test]
|
||||
#[cfg(disabled_until_fixed)]
|
||||
fn check_against_gnu_tr_tests_null() {
|
||||
// ['null', qw(a ''), {IN=>''}, {OUT=>''}, {EXIT=>1},
|
||||
// {ERR=>"$prog: when not truncating set1, string2 must be non-empty\n"}],
|
||||
|
@ -855,10 +852,7 @@ fn check_against_gnu_tr_tests_rep_3() {
|
|||
.stdout_is("1x2");
|
||||
}
|
||||
|
||||
// FIXME: Since pr https://github.com/uutils/coreutils/pull/4261:
|
||||
// stderr ends with 2 newlines but expected is only 1.
|
||||
#[test]
|
||||
#[cfg(disabled_until_fixed)]
|
||||
fn check_against_gnu_tr_tests_o_rep_1() {
|
||||
// # Another couple octal repeat count tests.
|
||||
// ['o-rep-1', qw('[b*08]' '[x*]'), {IN=>''}, {OUT=>''}, {EXIT=>1},
|
||||
|
@ -1032,10 +1026,6 @@ fn check_against_gnu_tr_tests_ross_6() {
|
|||
.stdout_is("");
|
||||
}
|
||||
|
||||
// FIXME: Since pr https://github.com/uutils/coreutils/pull/4261:
|
||||
// stderr ends with 2 newlines but expected is only 1.
|
||||
#[test]
|
||||
#[cfg(disabled_until_fixed)]
|
||||
#[test]
|
||||
fn check_against_gnu_tr_tests_empty_eq() {
|
||||
// # Ensure that these fail.
|
||||
|
@ -1049,10 +1039,6 @@ fn check_against_gnu_tr_tests_empty_eq() {
|
|||
.stderr_is("tr: missing equivalence class character '[==]'\n");
|
||||
}
|
||||
|
||||
// FIXME: Since pr https://github.com/uutils/coreutils/pull/4261:
|
||||
// stderr ends with 2 newlines but expected is only 1.
|
||||
#[test]
|
||||
#[cfg(disabled_until_fixed)]
|
||||
#[test]
|
||||
fn check_against_gnu_tr_tests_empty_cc() {
|
||||
// ['empty-cc', qw('[::]' x), {IN=>''}, {OUT=>''}, {EXIT=>1},
|
||||
|
@ -1064,6 +1050,24 @@ fn check_against_gnu_tr_tests_empty_cc() {
|
|||
.stderr_is("tr: missing character class name '[::]'\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_against_gnu_tr_tests_repeat_set1() {
|
||||
new_ucmd!()
|
||||
.args(&["[a*]", "a"])
|
||||
.pipe_in("")
|
||||
.fails()
|
||||
.stderr_is("tr: the [c*] repeat construct may not appear in string1\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_against_gnu_tr_tests_repeat_set2() {
|
||||
new_ucmd!()
|
||||
.args(&["a", "[a*][a*]"])
|
||||
.pipe_in("")
|
||||
.fails()
|
||||
.stderr_is("tr: only one [c*] repeat construct may appear in string2\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_against_gnu_tr_tests_repeat_bs_9() {
|
||||
// # Weird repeat counts.
|
||||
|
|
|
@ -29,7 +29,7 @@ fn test_default_output() {
|
|||
scene
|
||||
.ucmd()
|
||||
.succeeds()
|
||||
.stdout_matches(&Regex::new("[rwx-]{10}.*some-file1$").unwrap());
|
||||
.stdout_matches(&Regex::new("[rwx-]{10}.*some-file1\n$").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -36,11 +36,6 @@ use std::{env, hint, thread};
|
|||
use tempfile::{Builder, TempDir};
|
||||
use uucore::Args;
|
||||
|
||||
#[cfg(windows)]
|
||||
static PROGNAME: &str = concat!(env!("CARGO_PKG_NAME"), ".exe");
|
||||
#[cfg(not(windows))]
|
||||
static PROGNAME: &str = env!("CARGO_PKG_NAME");
|
||||
|
||||
static TESTS_DIR: &str = "tests";
|
||||
static FIXTURES_DIR: &str = "fixtures";
|
||||
|
||||
|
@ -603,7 +598,7 @@ impl CmdResult {
|
|||
|
||||
/// asserts that
|
||||
/// 1. the command resulted in stderr stream output that equals the
|
||||
/// passed in value, when both are trimmed of trailing whitespace
|
||||
/// passed in value
|
||||
/// 2. the command resulted in empty (zero-length) stdout stream output
|
||||
#[track_caller]
|
||||
pub fn stderr_only<T: AsRef<str>>(&self, msg: T) -> &Self {
|
||||
|
@ -628,7 +623,7 @@ impl CmdResult {
|
|||
|
||||
/// asserts that
|
||||
/// 1. the command resulted in stderr stream output that equals the
|
||||
/// the following format when both are trimmed of trailing whitespace
|
||||
/// the following format
|
||||
/// `"{util_name}: {msg}\nTry '{bin_path} {util_name} --help' for more information."`
|
||||
/// This the expected format when a `UUsageError` is returned or when `show_error!` is called
|
||||
/// `msg` should be the same as the one provided to `UUsageError::new` or `show_error!`
|
||||
|
@ -686,7 +681,7 @@ impl CmdResult {
|
|||
#[track_caller]
|
||||
pub fn stdout_matches(&self, regex: ®ex::Regex) -> &Self {
|
||||
assert!(
|
||||
regex.is_match(self.stdout_str().trim()),
|
||||
regex.is_match(self.stdout_str()),
|
||||
"Stdout does not match regex:\n{}",
|
||||
self.stdout_str()
|
||||
);
|
||||
|
@ -696,7 +691,7 @@ impl CmdResult {
|
|||
#[track_caller]
|
||||
pub fn stdout_does_not_match(&self, regex: ®ex::Regex) -> &Self {
|
||||
assert!(
|
||||
!regex.is_match(self.stdout_str().trim()),
|
||||
!regex.is_match(self.stdout_str()),
|
||||
"Stdout matches regex:\n{}",
|
||||
self.stdout_str()
|
||||
);
|
||||
|
@ -1101,13 +1096,7 @@ impl TestScenario {
|
|||
pub fn new(util_name: &str) -> Self {
|
||||
let tmpd = Rc::new(TempDir::new().unwrap());
|
||||
let ts = Self {
|
||||
bin_path: {
|
||||
// Instead of hard coding the path relative to the current
|
||||
// directory, use Cargo's OUT_DIR to find path to executable.
|
||||
// This allows tests to be run using profiles other than debug.
|
||||
let target_dir = path_concat!(env!("OUT_DIR"), "..", "..", "..", PROGNAME);
|
||||
PathBuf::from(AtPath::new(Path::new(&target_dir)).root_dir_resolved())
|
||||
},
|
||||
bin_path: PathBuf::from(env!("CARGO_BIN_EXE_coreutils")),
|
||||
util_name: String::from(util_name),
|
||||
fixtures: AtPath::new(tmpd.as_ref().path()),
|
||||
tmpd,
|
||||
|
|
4
tests/fixtures/comm/ab_total_delimiter_word.expected
vendored
Normal file
4
tests/fixtures/comm/ab_total_delimiter_word.expected
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
a
|
||||
wordb
|
||||
wordwordz
|
||||
1word1word1wordtotal
|
Loading…
Add table
Add a link
Reference in a new issue