mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 03:57: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:
|
permissions:
|
||||||
contents: read # to fetch code (actions/checkout)
|
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:
|
jobs:
|
||||||
cargo-deny:
|
cargo-deny:
|
||||||
name: Style/cargo-deny
|
name: Style/cargo-deny
|
||||||
|
|
16
.github/workflows/GnuTests.yml
vendored
16
.github/workflows/GnuTests.yml
vendored
|
@ -14,6 +14,11 @@ on: [push, pull_request]
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
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:
|
jobs:
|
||||||
gnu:
|
gnu:
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -196,6 +201,9 @@ jobs:
|
||||||
REF_LOG_FILE='${{ steps.vars.outputs.path_reference }}/test-logs/test-suite.log'
|
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'
|
REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json'
|
||||||
REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}'
|
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 }}
|
mkdir -p ${{ steps.vars.outputs.path_reference }}
|
||||||
|
|
||||||
|
@ -226,11 +234,19 @@ jobs:
|
||||||
for LINE in ${NEW_FAILING}
|
for LINE in ${NEW_FAILING}
|
||||||
do
|
do
|
||||||
if ! grep -Fxq ${LINE}<<<"${REF_FAILING}"
|
if ! grep -Fxq ${LINE}<<<"${REF_FAILING}"
|
||||||
|
then
|
||||||
|
if ! grep ${LINE} ${IGNORE_INTERMITTENT}
|
||||||
then
|
then
|
||||||
MSG="GNU test failed: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
|
MSG="GNU test failed: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
|
||||||
echo "::error ::$MSG"
|
echo "::error ::$MSG"
|
||||||
echo $MSG >> ${COMMENT_LOG}
|
echo $MSG >> ${COMMENT_LOG}
|
||||||
have_new_failures="true"
|
have_new_failures="true"
|
||||||
|
else
|
||||||
|
MSG="Skip an intermittent issue ${LINE}"
|
||||||
|
echo "::warning ::$MSG"
|
||||||
|
echo $MSG >> ${COMMENT_LOG}
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
for LINE in ${REF_ERROR}
|
for LINE in ${REF_ERROR}
|
||||||
|
|
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -878,6 +878,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fundu"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "925250bc259498d4008ee072bf16586083ab2c491aa4b06b3c4d0a6556cebd74"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.25"
|
version = "0.3.25"
|
||||||
|
@ -2807,7 +2813,6 @@ version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"libc",
|
"libc",
|
||||||
"num_cpus",
|
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3094,9 +3099,9 @@ version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"clap",
|
"clap",
|
||||||
|
"fundu",
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"nix",
|
|
||||||
"notify",
|
"notify",
|
||||||
"same-file",
|
"same-file",
|
||||||
"uucore",
|
"uucore",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# coreutils (uutils)
|
# coreutils (uutils)
|
||||||
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
|
# * 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]
|
[package]
|
||||||
name = "coreutils"
|
name = "coreutils"
|
||||||
|
@ -282,6 +282,7 @@ filetime = "0.2"
|
||||||
fnv = "1.0.7"
|
fnv = "1.0.7"
|
||||||
fs_extra = "1.1.0"
|
fs_extra = "1.1.0"
|
||||||
fts-sys = "0.2"
|
fts-sys = "0.2"
|
||||||
|
fundu = "0.3.0"
|
||||||
gcd = "2.2"
|
gcd = "2.2"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
half = "2.1"
|
half = "2.1"
|
||||||
|
|
|
@ -43,13 +43,23 @@ pacman -S uutils-coreutils
|
||||||
|
|
||||||
### Debian
|
### Debian
|
||||||
|
|
||||||
[](https://packages.debian.org/sid/source/rust-coreutils)
|
[](https://packages.debian.org/sid/source/rust-coreutils)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
apt install rust-coreutils
|
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
|
### Manjaro
|
||||||

|

|
||||||
|
@ -69,6 +79,18 @@ pamac install uutils-coreutils
|
||||||
nix-env -iA nixos.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
|
## MacOS
|
||||||
|
|
||||||
### Homebrew
|
### 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;
|
use std::os::unix::fs::FileTypeExt;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use uucore::format_usage;
|
use uucore::{format_usage, help_section, help_usage};
|
||||||
|
|
||||||
static USAGE: &str = "{} [OPTION]... [FILE]...";
|
const USAGE: &str = help_usage!("cat.md");
|
||||||
static ABOUT: &str = "Concatenate FILE(s), or standard input, to standard output
|
const ABOUT: &str = help_section!("about", "cat.md");
|
||||||
With no FILE, or when FILE is -, read standard input.";
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
enum CatError {
|
enum CatError {
|
||||||
|
|
|
@ -12,15 +12,21 @@ use std::fs;
|
||||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::display::Quotable;
|
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::fs::display_permissions_unix;
|
||||||
use uucore::libc::mode_t;
|
use uucore::libc::mode_t;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use uucore::mode;
|
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.
|
const ABOUT: &str = "Change the mode of each FILE to MODE.\n\
|
||||||
With --reference, change the mode of each FILE to that of RFILE.";
|
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 {
|
mod options {
|
||||||
pub const CHANGES: &str = "changes";
|
pub const CHANGES: &str = "changes";
|
||||||
|
@ -34,15 +40,6 @@ mod options {
|
||||||
pub const FILE: &str = "FILE";
|
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]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let mut args = args.collect_lossy();
|
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").
|
// 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 mode_had_minus_prefix = mode::strip_minus_from_mode(&mut args);
|
||||||
|
|
||||||
let after_help = get_long_usage();
|
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||||
|
|
||||||
let matches = uu_app().after_help(after_help).try_get_matches_from(args)?;
|
|
||||||
|
|
||||||
let changes = matches.get_flag(options::CHANGES);
|
let changes = matches.get_flag(options::CHANGES);
|
||||||
let quiet = matches.get_flag(options::QUIET);
|
let quiet = matches.get_flag(options::QUIET);
|
||||||
|
@ -200,21 +195,24 @@ impl Chmoder {
|
||||||
filename.quote()
|
filename.quote()
|
||||||
);
|
);
|
||||||
if !self.quiet {
|
if !self.quiet {
|
||||||
return Err(USimpleError::new(
|
show!(USimpleError::new(
|
||||||
1,
|
1,
|
||||||
format!("cannot operate on dangling symlink {}", filename.quote()),
|
format!("cannot operate on dangling symlink {}", filename.quote()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if !self.quiet {
|
} else if !self.quiet {
|
||||||
return Err(USimpleError::new(
|
show!(USimpleError::new(
|
||||||
1,
|
1,
|
||||||
format!(
|
format!(
|
||||||
"cannot access {}: No such file or directory",
|
"cannot access {}: No such file or directory",
|
||||||
filename.quote()
|
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 == "/" {
|
if self.recursive && self.preserve_root && filename == "/" {
|
||||||
return Err(USimpleError::new(
|
return Err(USimpleError::new(
|
||||||
|
|
|
@ -32,21 +32,12 @@ mod options {
|
||||||
pub const TOTAL: &str = "total";
|
pub const TOTAL: &str = "total";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mkdelim(col: usize, opts: &ArgMatches) -> String {
|
fn column_width(col: &str, opts: &ArgMatches) -> usize {
|
||||||
let mut s = String::new();
|
if opts.get_flag(col) {
|
||||||
let delim = match opts.get_one::<String>(options::DELIMITER).unwrap().as_str() {
|
0
|
||||||
"" => "\0",
|
} else {
|
||||||
delim => delim,
|
1
|
||||||
};
|
|
||||||
|
|
||||||
if col > 1 && !opts.get_flag(options::COLUMN_1) {
|
|
||||||
s.push_str(delim.as_ref());
|
|
||||||
}
|
}
|
||||||
if col > 2 && !opts.get_flag(options::COLUMN_2) {
|
|
||||||
s.push_str(delim.as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_nl(line: &mut String) {
|
fn ensure_nl(line: &mut String) {
|
||||||
|
@ -70,7 +61,16 @@ impl LineReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
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 ra = &mut String::new();
|
||||||
let mut na = a.read_line(ra);
|
let mut na = a.read_line(ra);
|
||||||
|
@ -98,7 +98,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
if !opts.get_flag(options::COLUMN_1) {
|
if !opts.get_flag(options::COLUMN_1) {
|
||||||
ensure_nl(ra);
|
ensure_nl(ra);
|
||||||
print!("{}{}", delim[1], ra);
|
print!("{ra}");
|
||||||
}
|
}
|
||||||
ra.clear();
|
ra.clear();
|
||||||
na = a.read_line(ra);
|
na = a.read_line(ra);
|
||||||
|
@ -107,7 +107,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
if !opts.get_flag(options::COLUMN_2) {
|
if !opts.get_flag(options::COLUMN_2) {
|
||||||
ensure_nl(rb);
|
ensure_nl(rb);
|
||||||
print!("{}{}", delim[2], rb);
|
print!("{delim_col_2}{rb}");
|
||||||
}
|
}
|
||||||
rb.clear();
|
rb.clear();
|
||||||
nb = b.read_line(rb);
|
nb = b.read_line(rb);
|
||||||
|
@ -116,7 +116,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
if !opts.get_flag(options::COLUMN_3) {
|
if !opts.get_flag(options::COLUMN_3) {
|
||||||
ensure_nl(ra);
|
ensure_nl(ra);
|
||||||
print!("{}{}", delim[3], ra);
|
print!("{delim_col_3}{ra}");
|
||||||
}
|
}
|
||||||
ra.clear();
|
ra.clear();
|
||||||
rb.clear();
|
rb.clear();
|
||||||
|
@ -128,7 +128,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.get_flag(options::TOTAL) {
|
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::{
|
use uucore::fs::{
|
||||||
canonicalize, paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode,
|
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;
|
use crate::copydir::copy_directory;
|
||||||
|
|
||||||
|
@ -228,13 +228,10 @@ pub struct Options {
|
||||||
progress_bar: bool,
|
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;
|
static EXIT_ERR: i32 = 1;
|
||||||
|
|
||||||
const USAGE: &str = "\
|
const USAGE: &str = help_usage!("cp.md");
|
||||||
{} [OPTION]... [-T] SOURCE DEST
|
|
||||||
{} [OPTION]... SOURCE... DIRECTORY
|
|
||||||
{} [OPTION]... -t DIRECTORY SOURCE...";
|
|
||||||
|
|
||||||
// Argument constants
|
// Argument constants
|
||||||
mod options {
|
mod options {
|
||||||
|
|
|
@ -11,26 +11,22 @@ use uucore::display::print_verbatim;
|
||||||
use uucore::error::{UResult, UUsageError};
|
use uucore::error::{UResult, UUsageError};
|
||||||
use uucore::format_usage;
|
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 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 {
|
mod options {
|
||||||
pub const ZERO: &str = "zero";
|
pub const ZERO: &str = "zero";
|
||||||
pub const DIR: &str = "dir";
|
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]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let args = args.collect_lossy();
|
let args = args.collect_lossy();
|
||||||
|
|
||||||
let matches = uu_app()
|
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||||
.after_help(get_long_usage())
|
|
||||||
.try_get_matches_from(args)?;
|
|
||||||
|
|
||||||
let separator = if matches.get_flag(options::ZERO) {
|
let separator = if matches.get_flag(options::ZERO) {
|
||||||
"\0"
|
"\0"
|
||||||
|
|
|
@ -22,8 +22,10 @@ use uucore::{format_usage, show, show_if_err};
|
||||||
|
|
||||||
static DEFAULT_PERM: u32 = 0o755;
|
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 USAGE: &str = "{} [OPTION]... [USER]";
|
||||||
|
const LONG_USAGE: &str =
|
||||||
|
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.";
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const MODE: &str = "mode";
|
pub const MODE: &str = "mode";
|
||||||
|
@ -32,10 +34,6 @@ mod options {
|
||||||
pub const DIRS: &str = "dirs";
|
pub const DIRS: &str = "dirs";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_long_usage() -> &'static str {
|
|
||||||
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'."
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn get_mode(_matches: &ArgMatches, _mode_had_minus_prefix: bool) -> Result<u32, String> {
|
fn get_mode(_matches: &ArgMatches, _mode_had_minus_prefix: bool) -> Result<u32, String> {
|
||||||
Ok(DEFAULT_PERM)
|
Ok(DEFAULT_PERM)
|
||||||
|
@ -92,9 +90,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
// Linux-specific options, not implemented
|
// Linux-specific options, not implemented
|
||||||
// opts.optflag("Z", "context", "set SELinux security context" +
|
// opts.optflag("Z", "context", "set SELinux security context" +
|
||||||
// " of each created directory to CTX"),
|
// " of each created directory to CTX"),
|
||||||
let matches = uu_app()
|
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||||
.after_help(get_long_usage())
|
|
||||||
.try_get_matches_from(args)?;
|
|
||||||
|
|
||||||
let dirs = matches
|
let dirs = matches
|
||||||
.get_many::<OsString>(options::DIRS)
|
.get_many::<OsString>(options::DIRS)
|
||||||
|
|
|
@ -16,7 +16,6 @@ path = "src/nproc.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = { workspace=true }
|
libc = { workspace=true }
|
||||||
num_cpus = { workspace=true }
|
|
||||||
clap = { workspace=true }
|
clap = { workspace=true }
|
||||||
uucore = { workspace=true, features=["fs"] }
|
uucore = { workspace=true, features=["fs"] }
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
// spell-checker:ignore (ToDO) NPROCESSORS nprocs numstr threadstr sysconf
|
// spell-checker:ignore (ToDO) NPROCESSORS nprocs numstr threadstr sysconf
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
use std::env;
|
use std::{env, thread};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::error::{UResult, USimpleError};
|
||||||
use uucore::format_usage;
|
use uucore::format_usage;
|
||||||
|
@ -73,16 +73,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
// If OMP_NUM_THREADS=0, rejects the value
|
// If OMP_NUM_THREADS=0, rejects the value
|
||||||
let thread: Vec<&str> = threadstr.split_terminator(',').collect();
|
let thread: Vec<&str> = threadstr.split_terminator(',').collect();
|
||||||
match &thread[..] {
|
match &thread[..] {
|
||||||
[] => num_cpus::get(),
|
[] => available_parallelism(),
|
||||||
[s, ..] => match s.parse() {
|
[s, ..] => match s.parse() {
|
||||||
Ok(0) | Err(_) => num_cpus::get(),
|
Ok(0) | Err(_) => available_parallelism(),
|
||||||
Ok(n) => n,
|
Ok(n) => n,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// the variable 'OMP_NUM_THREADS' doesn't exist
|
// the variable 'OMP_NUM_THREADS' doesn't exist
|
||||||
// fallback to the regular CPU detection
|
// 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 {
|
if nprocs == 1 {
|
||||||
// In some situation, /proc and /sys are not mounted, and sysconf returns 1.
|
// In some situation, /proc and /sys are not mounted, and sysconf returns 1.
|
||||||
// However, we want to guarantee that `nproc --all` >= `nproc`.
|
// However, we want to guarantee that `nproc --all` >= `nproc`.
|
||||||
num_cpus::get()
|
available_parallelism()
|
||||||
} else if nprocs > 0 {
|
} else if nprocs > 0 {
|
||||||
nprocs as usize
|
nprocs as usize
|
||||||
} else {
|
} 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(
|
#[cfg(not(any(
|
||||||
target_os = "linux",
|
target_os = "linux",
|
||||||
target_vendor = "apple",
|
target_vendor = "apple",
|
||||||
|
@ -143,5 +143,14 @@ fn num_cpus_all() -> usize {
|
||||||
target_os = "netbsd"
|
target_os = "netbsd"
|
||||||
)))]
|
)))]
|
||||||
fn num_cpus_all() -> usize {
|
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,
|
verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
static ABOUT: &str = "Remove (unlink) the FILE(s)";
|
const ABOUT: &str = "Remove (unlink) the FILE(s)";
|
||||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
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_DIR: &str = "dir";
|
||||||
static OPT_INTERACTIVE: &str = "interactive";
|
static OPT_INTERACTIVE: &str = "interactive";
|
||||||
static OPT_FORCE: &str = "force";
|
static OPT_FORCE: &str = "force";
|
||||||
|
@ -53,28 +67,9 @@ static PRESUME_INPUT_TTY: &str = "-presume-input-tty";
|
||||||
|
|
||||||
static ARG_FILES: &str = "files";
|
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]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let matches = uu_app()
|
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||||
.after_help(get_long_usage())
|
|
||||||
.try_get_matches_from(args)?;
|
|
||||||
|
|
||||||
let files: Vec<String> = matches
|
let files: Vec<String> = matches
|
||||||
.get_many::<String>(ARG_FILES)
|
.get_many::<String>(ARG_FILES)
|
||||||
|
|
|
@ -13,7 +13,7 @@ use uucore::fsext::{
|
||||||
pretty_filetype, pretty_fstype, pretty_time, read_fs_list, statfs, BirthTime, FsMeta,
|
pretty_filetype, pretty_fstype, pretty_time, read_fs_list, statfs, BirthTime, FsMeta,
|
||||||
};
|
};
|
||||||
use uucore::libc::mode_t;
|
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 clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
@ -24,8 +24,9 @@ use std::os::unix::fs::{FileTypeExt, MetadataExt};
|
||||||
use std::os::unix::prelude::OsStrExt;
|
use std::os::unix::prelude::OsStrExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
const ABOUT: &str = "Display file or file system status.";
|
const ABOUT: &str = help_section!("about", "stat.md");
|
||||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
const USAGE: &str = help_usage!("stat.md");
|
||||||
|
const LONG_USAGE: &str = help_section!("long usage", "stat.md");
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const DEREFERENCE: &str = "dereference";
|
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]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let matches = uu_app()
|
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||||
.after_help(get_long_usage())
|
|
||||||
.try_get_matches_from(args)?;
|
|
||||||
|
|
||||||
let stater = Stater::new(&matches)?;
|
let stater = Stater::new(&matches)?;
|
||||||
let exit_status = stater.exec();
|
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]
|
[package]
|
||||||
name = "uu_tail"
|
name = "uu_tail"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
|
@ -19,17 +20,15 @@ clap = { workspace=true }
|
||||||
libc = { workspace=true }
|
libc = { workspace=true }
|
||||||
memchr = { workspace=true }
|
memchr = { workspace=true }
|
||||||
notify = { workspace=true }
|
notify = { workspace=true }
|
||||||
uucore = { workspace=true, features=["ringbuffer", "lines"] }
|
uucore = { workspace=true }
|
||||||
same-file = { workspace=true }
|
same-file = { workspace=true }
|
||||||
atty = { workspace=true }
|
atty = { workspace=true }
|
||||||
|
fundu = { workspace=true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows-sys = { workspace=true, features = ["Win32_System_Threading", "Win32_Foundation"] }
|
windows-sys = { workspace=true, features = ["Win32_System_Threading", "Win32_Foundation"] }
|
||||||
winapi-util = { workspace=true }
|
winapi-util = { workspace=true }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
|
||||||
nix = { workspace=true, features = ["fs"] }
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "tail"
|
name = "tail"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
// * file that was distributed with this source code.
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) kqueue Signum
|
// spell-checker:ignore (ToDO) kqueue Signum fundu
|
||||||
|
|
||||||
use crate::paths::Input;
|
use crate::paths::Input;
|
||||||
use crate::{parse, platform, Quotable};
|
use crate::{parse, platform, Quotable};
|
||||||
use atty::Stream;
|
use atty::Stream;
|
||||||
use clap::crate_version;
|
use clap::crate_version;
|
||||||
use clap::{parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
|
use clap::{parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
|
||||||
|
use fundu::DurationParser;
|
||||||
use same_file::Handle;
|
use same_file::Handle;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
@ -148,16 +149,20 @@ impl Settings {
|
||||||
settings.retry =
|
settings.retry =
|
||||||
matches.get_flag(options::RETRY) || matches.get_flag(options::FOLLOW_RETRY);
|
matches.get_flag(options::RETRY) || matches.get_flag(options::FOLLOW_RETRY);
|
||||||
|
|
||||||
if let Some(s) = matches.get_one::<String>(options::SLEEP_INT) {
|
if let Some(source) = matches.get_one::<String>(options::SLEEP_INT) {
|
||||||
settings.sleep_sec = match s.parse::<f32>() {
|
// Advantage of `fundu` over `Duration::(try_)from_secs_f64(source.parse().unwrap())`:
|
||||||
Ok(s) => Duration::from_secs_f32(s),
|
// * doesn't panic on errors like `Duration::from_secs_f64` would.
|
||||||
Err(_) => {
|
// * no precision loss, rounding errors or other floating point problems.
|
||||||
return Err(UUsageError::new(
|
// * evaluates to `Duration::MAX` if the parsed number would have exceeded
|
||||||
1,
|
// `DURATION::MAX` or `infinity` was given
|
||||||
format!("invalid number of seconds: {}", s.quote()),
|
// * 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);
|
settings.use_polling = matches.get_flag(options::USE_POLLING);
|
||||||
|
|
|
@ -37,21 +37,21 @@ pub enum BadSequence {
|
||||||
impl Display for BadSequence {
|
impl Display for BadSequence {
|
||||||
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 {
|
||||||
Self::MissingCharClassName => writeln!(f, "missing character class name '[::]'"),
|
Self::MissingCharClassName => write!(f, "missing character class name '[::]'"),
|
||||||
Self::MissingEquivalentClassChar => {
|
Self::MissingEquivalentClassChar => {
|
||||||
writeln!(f, "missing equivalence class character '[==]'")
|
write!(f, "missing equivalence class character '[==]'")
|
||||||
}
|
}
|
||||||
Self::MultipleCharRepeatInSet2 => {
|
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 => {
|
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) => {
|
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 => {
|
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::display::Quotable;
|
||||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
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 USAGE: &str = "{} [OPTION]... SET1 [SET2]";
|
||||||
|
const LONG_USAGE: &str = "\
|
||||||
|
Translate, squeeze, and/or delete characters from standard input, \
|
||||||
|
writing to standard output.";
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const COMPLEMENT: &str = "complement";
|
pub const COMPLEMENT: &str = "complement";
|
||||||
|
@ -30,19 +33,11 @@ mod options {
|
||||||
pub const SETS: &str = "sets";
|
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]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let args = args.collect_lossy();
|
let args = args.collect_lossy();
|
||||||
|
|
||||||
let matches = uu_app()
|
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||||
.after_help(get_long_usage())
|
|
||||||
.try_get_matches_from(args)?;
|
|
||||||
|
|
||||||
let delete_flag = matches.get_flag(options::DELETE);
|
let delete_flag = matches.get_flag(options::DELETE);
|
||||||
let complement_flag = matches.get_flag(options::COMPLEMENT);
|
let complement_flag = matches.get_flag(options::COMPLEMENT);
|
||||||
|
|
|
@ -73,20 +73,9 @@ 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 USAGE: &str = "{} [OPTION]... [FILE]...";
|
||||||
|
const LONG_USAGE: &str = "\
|
||||||
pub mod options {
|
|
||||||
pub static IO_BLOCKS: &str = "io-blocks";
|
|
||||||
pub static NO_CREATE: &str = "no-create";
|
|
||||||
pub static REFERENCE: &str = "reference";
|
|
||||||
pub static SIZE: &str = "size";
|
|
||||||
pub static ARG_FILES: &str = "files";
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_long_usage() -> String {
|
|
||||||
String::from(
|
|
||||||
"
|
|
||||||
SIZE is an integer with an optional prefix and optional unit.
|
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:
|
The available units (K, M, G, T, P, E, Z, and Y) use the following format:
|
||||||
'KB' => 1000 (kilobytes)
|
'KB' => 1000 (kilobytes)
|
||||||
|
@ -102,14 +91,20 @@ fn get_long_usage() -> String {
|
||||||
'<' => at most
|
'<' => at most
|
||||||
'>' => at least
|
'>' => at least
|
||||||
'/' => round down to multiple of
|
'/' => round down to multiple of
|
||||||
'%' => round up to multiple of",
|
'%' => round up to multiple of";
|
||||||
)
|
|
||||||
|
pub mod options {
|
||||||
|
pub static IO_BLOCKS: &str = "io-blocks";
|
||||||
|
pub static NO_CREATE: &str = "no-create";
|
||||||
|
pub static REFERENCE: &str = "reference";
|
||||||
|
pub static SIZE: &str = "size";
|
||||||
|
pub static ARG_FILES: &str = "files";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let matches = uu_app()
|
let matches = uu_app()
|
||||||
.after_help(get_long_usage())
|
.after_help(LONG_USAGE)
|
||||||
.try_get_matches_from(args)
|
.try_get_matches_from(args)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
e.print().expect("Error writing clap::Error");
|
e.print().expect("Error writing clap::Error");
|
||||||
|
|
|
@ -15,8 +15,14 @@ use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
||||||
use uucore::format_usage;
|
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 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 mod options {
|
||||||
pub static ALL_REPEATED: &str = "all-repeated";
|
pub static ALL_REPEATED: &str = "all-repeated";
|
||||||
pub static CHECK_CHARS: &str = "check-chars";
|
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]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let matches = uu_app()
|
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||||
.after_help(get_long_usage())
|
|
||||||
.try_get_matches_from(args)?;
|
|
||||||
|
|
||||||
let files: Vec<String> = matches
|
let files: Vec<String> = matches
|
||||||
.get_many::<String>(ARG_FILES)
|
.get_many::<String>(ARG_FILES)
|
||||||
|
|
|
@ -574,3 +574,71 @@ fn test_mode_after_dash_dash() {
|
||||||
ucmd,
|
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");
|
.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]
|
#[test]
|
||||||
fn output_delimiter() {
|
fn output_delimiter() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
|
|
@ -68,10 +68,10 @@ fn test_date_utc() {
|
||||||
fn test_date_format_y() {
|
fn test_date_format_y() {
|
||||||
let scene = TestScenario::new(util_name!());
|
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);
|
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);
|
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();
|
let mut re = Regex::new(r"\S+").unwrap();
|
||||||
scene.ucmd().arg("+%b").succeeds().stdout_matches(&re);
|
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);
|
scene.ucmd().arg("+%m").succeeds().stdout_matches(&re);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ fn test_date_format_day() {
|
||||||
re = Regex::new(r"\S+").unwrap();
|
re = Regex::new(r"\S+").unwrap();
|
||||||
scene.ucmd().arg("+%A").succeeds().stdout_matches(&re);
|
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);
|
scene.ucmd().arg("+%u").succeeds().stdout_matches(&re);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ fn test_date_issue_3780() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_nano_seconds() {
|
fn test_date_nano_seconds() {
|
||||||
// %N nanoseconds (000000000..999999999)
|
// %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);
|
new_ucmd!().arg("+%N").succeeds().stdout_matches(&re);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,5 +49,5 @@ fn test_long_output() {
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-l")
|
.arg("-l")
|
||||||
.succeeds()
|
.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-1")
|
||||||
.arg("./test-commas-trailing-2")
|
.arg("./test-commas-trailing-2")
|
||||||
.succeeds()
|
.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]
|
#[test]
|
||||||
|
|
|
@ -300,7 +300,7 @@ fn test_relative_base_not_prefix_of_relative_to() {
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[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))]
|
#[cfg(not(windows))]
|
||||||
result.stdout_is("/usr\n/usr/local\n");
|
result.stdout_is("/usr\n/usr/local\n");
|
||||||
|
@ -344,7 +344,7 @@ fn test_relative() {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
result.stdout_is("/tmp\n.\n");
|
result.stdout_is("/tmp\n.\n");
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
result.stdout_matches(&Regex::new(r"^.*:\\tmp\n\.$").unwrap());
|
result.stdout_matches(&Regex::new(r"^.*:\\tmp\n\.\n$").unwrap());
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["-sm", "--relative-base=/", "--relative-to=/", "/", "/usr"])
|
.args(&["-sm", "--relative-base=/", "--relative-to=/", "/", "/usr"])
|
||||||
|
@ -357,7 +357,7 @@ fn test_relative() {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
result.stdout_is("/tmp\n.\n");
|
result.stdout_is("/tmp\n.\n");
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
result.stdout_matches(&Regex::new(r"^.*:\\tmp\n\.$").unwrap());
|
result.stdout_matches(&Regex::new(r"^.*:\\tmp\n\.\n$").unwrap());
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["-sm", "--relative-base=/", "/", "/usr"])
|
.args(&["-sm", "--relative-base=/", "/", "/usr"])
|
||||||
|
|
|
@ -13,6 +13,7 @@ use crate::common::random::*;
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::Alphanumeric;
|
||||||
|
use rstest::rstest;
|
||||||
use std::char::from_digit;
|
use std::char::from_digit;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
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);
|
.stdout_only(expected_stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[rstest]
|
||||||
#[cfg(disable_until_fixed)]
|
#[case::exponent_exceed_float_max("1.0e2048")]
|
||||||
fn test_args_sleep_interval_when_illegal_argument_then_usage_error() {
|
#[case::underscore_delimiter("1_000")]
|
||||||
let scene = TestScenario::new(util_name!());
|
#[case::only_point(".")]
|
||||||
for interval in [
|
#[case::space_in_primes("' '")]
|
||||||
&format!("{}0", f64::MAX),
|
#[case::space(" ")]
|
||||||
&format!("{}0.0", f64::MAX),
|
#[case::empty("")]
|
||||||
"1_000",
|
#[case::comma_separator("0,0")]
|
||||||
".",
|
#[case::words_nominator_fract("one.zero")]
|
||||||
"' '",
|
#[case::words_fract(".zero")]
|
||||||
"",
|
#[case::words_nominator("one.")]
|
||||||
" ",
|
#[case::two_points("0..0")]
|
||||||
"0,0",
|
#[case::seconds_unit("1.0s")]
|
||||||
"one.zero",
|
#[case::circumflex_exponent("1.0e^1000")]
|
||||||
".zero",
|
fn test_args_sleep_interval_when_illegal_argument_then_usage_error(#[case] sleep_interval: &str) {
|
||||||
"one.",
|
new_ucmd!()
|
||||||
"0..0",
|
.args(&["--sleep-interval", sleep_interval])
|
||||||
] {
|
|
||||||
scene
|
|
||||||
.ucmd()
|
|
||||||
.args(&["--sleep-interval", interval])
|
|
||||||
.run()
|
.run()
|
||||||
.usage_error(format!("invalid number of seconds: '{}'", interval))
|
.usage_error(format!("invalid number of seconds: '{sleep_interval}'"))
|
||||||
.code_is(1);
|
.code_is(1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -777,10 +777,7 @@ fn check_against_gnu_tr_tests_range_a_a() {
|
||||||
.stdout_is("zbc");
|
.stdout_is("zbc");
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Since pr https://github.com/uutils/coreutils/pull/4261:
|
|
||||||
// stderr ends with 2 newlines but expected is only 1.
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(disabled_until_fixed)]
|
|
||||||
fn check_against_gnu_tr_tests_null() {
|
fn check_against_gnu_tr_tests_null() {
|
||||||
// ['null', qw(a ''), {IN=>''}, {OUT=>''}, {EXIT=>1},
|
// ['null', qw(a ''), {IN=>''}, {OUT=>''}, {EXIT=>1},
|
||||||
// {ERR=>"$prog: when not truncating set1, string2 must be non-empty\n"}],
|
// {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");
|
.stdout_is("1x2");
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Since pr https://github.com/uutils/coreutils/pull/4261:
|
|
||||||
// stderr ends with 2 newlines but expected is only 1.
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(disabled_until_fixed)]
|
|
||||||
fn check_against_gnu_tr_tests_o_rep_1() {
|
fn check_against_gnu_tr_tests_o_rep_1() {
|
||||||
// # Another couple octal repeat count tests.
|
// # Another couple octal repeat count tests.
|
||||||
// ['o-rep-1', qw('[b*08]' '[x*]'), {IN=>''}, {OUT=>''}, {EXIT=>1},
|
// ['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("");
|
.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]
|
#[test]
|
||||||
fn check_against_gnu_tr_tests_empty_eq() {
|
fn check_against_gnu_tr_tests_empty_eq() {
|
||||||
// # Ensure that these fail.
|
// # Ensure that these fail.
|
||||||
|
@ -1049,10 +1039,6 @@ fn check_against_gnu_tr_tests_empty_eq() {
|
||||||
.stderr_is("tr: missing equivalence class character '[==]'\n");
|
.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]
|
#[test]
|
||||||
fn check_against_gnu_tr_tests_empty_cc() {
|
fn check_against_gnu_tr_tests_empty_cc() {
|
||||||
// ['empty-cc', qw('[::]' x), {IN=>''}, {OUT=>''}, {EXIT=>1},
|
// ['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");
|
.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]
|
#[test]
|
||||||
fn check_against_gnu_tr_tests_repeat_bs_9() {
|
fn check_against_gnu_tr_tests_repeat_bs_9() {
|
||||||
// # Weird repeat counts.
|
// # Weird repeat counts.
|
||||||
|
|
|
@ -29,7 +29,7 @@ fn test_default_output() {
|
||||||
scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_matches(&Regex::new("[rwx-]{10}.*some-file1$").unwrap());
|
.stdout_matches(&Regex::new("[rwx-]{10}.*some-file1\n$").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -36,11 +36,6 @@ use std::{env, hint, thread};
|
||||||
use tempfile::{Builder, TempDir};
|
use tempfile::{Builder, TempDir};
|
||||||
use uucore::Args;
|
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 TESTS_DIR: &str = "tests";
|
||||||
static FIXTURES_DIR: &str = "fixtures";
|
static FIXTURES_DIR: &str = "fixtures";
|
||||||
|
|
||||||
|
@ -603,7 +598,7 @@ impl CmdResult {
|
||||||
|
|
||||||
/// asserts that
|
/// asserts that
|
||||||
/// 1. the command resulted in stderr stream output that equals the
|
/// 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
|
/// 2. the command resulted in empty (zero-length) stdout stream output
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn stderr_only<T: AsRef<str>>(&self, msg: T) -> &Self {
|
pub fn stderr_only<T: AsRef<str>>(&self, msg: T) -> &Self {
|
||||||
|
@ -628,7 +623,7 @@ impl CmdResult {
|
||||||
|
|
||||||
/// asserts that
|
/// asserts that
|
||||||
/// 1. the command resulted in stderr stream output that equals the
|
/// 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."`
|
/// `"{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
|
/// 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!`
|
/// `msg` should be the same as the one provided to `UUsageError::new` or `show_error!`
|
||||||
|
@ -686,7 +681,7 @@ impl CmdResult {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn stdout_matches(&self, regex: ®ex::Regex) -> &Self {
|
pub fn stdout_matches(&self, regex: ®ex::Regex) -> &Self {
|
||||||
assert!(
|
assert!(
|
||||||
regex.is_match(self.stdout_str().trim()),
|
regex.is_match(self.stdout_str()),
|
||||||
"Stdout does not match regex:\n{}",
|
"Stdout does not match regex:\n{}",
|
||||||
self.stdout_str()
|
self.stdout_str()
|
||||||
);
|
);
|
||||||
|
@ -696,7 +691,7 @@ impl CmdResult {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn stdout_does_not_match(&self, regex: ®ex::Regex) -> &Self {
|
pub fn stdout_does_not_match(&self, regex: ®ex::Regex) -> &Self {
|
||||||
assert!(
|
assert!(
|
||||||
!regex.is_match(self.stdout_str().trim()),
|
!regex.is_match(self.stdout_str()),
|
||||||
"Stdout matches regex:\n{}",
|
"Stdout matches regex:\n{}",
|
||||||
self.stdout_str()
|
self.stdout_str()
|
||||||
);
|
);
|
||||||
|
@ -1101,13 +1096,7 @@ impl TestScenario {
|
||||||
pub fn new(util_name: &str) -> Self {
|
pub fn new(util_name: &str) -> Self {
|
||||||
let tmpd = Rc::new(TempDir::new().unwrap());
|
let tmpd = Rc::new(TempDir::new().unwrap());
|
||||||
let ts = Self {
|
let ts = Self {
|
||||||
bin_path: {
|
bin_path: PathBuf::from(env!("CARGO_BIN_EXE_coreutils")),
|
||||||
// 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())
|
|
||||||
},
|
|
||||||
util_name: String::from(util_name),
|
util_name: String::from(util_name),
|
||||||
fixtures: AtPath::new(tmpd.as_ref().path()),
|
fixtures: AtPath::new(tmpd.as_ref().path()),
|
||||||
tmpd,
|
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