mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 12:37:49 +00:00
Merge branch 'master' of github.com:uutils/coreutils into hbina-tr-reimplement-expansion
This commit is contained in:
commit
38659de66d
16 changed files with 764 additions and 194 deletions
5
.github/workflows/CICD.yml
vendored
5
.github/workflows/CICD.yml
vendored
|
@ -615,11 +615,6 @@ jobs:
|
||||||
# staging directory
|
# staging directory
|
||||||
STAGING='_staging'
|
STAGING='_staging'
|
||||||
outputs STAGING
|
outputs STAGING
|
||||||
## # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>)
|
|
||||||
## # note: CODECOV_TOKEN / HAS_CODECOV_TOKEN is not needed for public repositories when using AppVeyor, Azure Pipelines, CircleCI, GitHub Actions, Travis (see <https://docs.codecov.io/docs/about-the-codecov-bash-uploader#section-upload-token>)
|
|
||||||
## unset HAS_CODECOV_TOKEN
|
|
||||||
## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi
|
|
||||||
## outputs HAS_CODECOV_TOKEN
|
|
||||||
# target-specific options
|
# target-specific options
|
||||||
# * CARGO_FEATURES_OPTION
|
# * CARGO_FEATURES_OPTION
|
||||||
CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage
|
CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage
|
||||||
|
|
|
@ -7,19 +7,17 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's
|
// spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
use std::fs;
|
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::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::InvalidEncodingHandling;
|
use uucore::{show_error, InvalidEncodingHandling};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
static ABOUT: &str = "Change the mode of each FILE to MODE.
|
static ABOUT: &str = "Change the mode of each FILE to MODE.
|
||||||
|
@ -50,7 +48,8 @@ fn get_long_usage() -> String {
|
||||||
String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.")
|
String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
#[uucore_procs::gen_uumain]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let mut args = args
|
let mut args = args
|
||||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||||
.accept_any();
|
.accept_any();
|
||||||
|
@ -72,12 +71,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
let verbose = matches.is_present(options::VERBOSE);
|
let verbose = matches.is_present(options::VERBOSE);
|
||||||
let preserve_root = matches.is_present(options::PRESERVE_ROOT);
|
let preserve_root = matches.is_present(options::PRESERVE_ROOT);
|
||||||
let recursive = matches.is_present(options::RECURSIVE);
|
let recursive = matches.is_present(options::RECURSIVE);
|
||||||
let fmode = matches
|
let fmode = match matches.value_of(options::REFERENCE) {
|
||||||
.value_of(options::REFERENCE)
|
Some(fref) => match fs::metadata(fref) {
|
||||||
.and_then(|fref| match fs::metadata(fref) {
|
|
||||||
Ok(meta) => Some(meta.mode()),
|
Ok(meta) => Some(meta.mode()),
|
||||||
Err(err) => crash!(1, "cannot stat attributes of {}: {}", fref.quote(), err),
|
Err(err) => {
|
||||||
});
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("cannot stat attributes of {}: {}", fref.quote(), err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
|
let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
|
||||||
let cmode = if mode_had_minus_prefix {
|
let cmode = if mode_had_minus_prefix {
|
||||||
// clap parsing is finished, now put prefix back
|
// clap parsing is finished, now put prefix back
|
||||||
|
@ -100,7 +105,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
};
|
};
|
||||||
|
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
crash!(1, "missing operand");
|
return Err(UUsageError::new(1, "missing operand".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let chmoder = Chmoder {
|
let chmoder = Chmoder {
|
||||||
|
@ -112,12 +117,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
fmode,
|
fmode,
|
||||||
cmode,
|
cmode,
|
||||||
};
|
};
|
||||||
match chmoder.chmod(files) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(e) => return e,
|
|
||||||
}
|
|
||||||
|
|
||||||
0
|
chmoder.chmod(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> App<'static, 'static> {
|
pub fn uu_app() -> App<'static, 'static> {
|
||||||
|
@ -191,7 +192,7 @@ struct Chmoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chmoder {
|
impl Chmoder {
|
||||||
fn chmod(&self, files: Vec<String>) -> Result<(), i32> {
|
fn chmod(&self, files: Vec<String>) -> UResult<()> {
|
||||||
let mut r = Ok(());
|
let mut r = Ok(());
|
||||||
|
|
||||||
for filename in &files {
|
for filename in &files {
|
||||||
|
@ -204,22 +205,30 @@ impl Chmoder {
|
||||||
filename.quote()
|
filename.quote()
|
||||||
);
|
);
|
||||||
if !self.quiet {
|
if !self.quiet {
|
||||||
show_error!("cannot operate on dangling symlink {}", filename.quote());
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("cannot operate on dangling symlink {}", filename.quote()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
} else if !self.quiet {
|
} else if !self.quiet {
|
||||||
show_error!(
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!(
|
||||||
"cannot access {}: No such file or directory",
|
"cannot access {}: No such file or directory",
|
||||||
filename.quote()
|
filename.quote()
|
||||||
);
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
return Err(1);
|
return Err(ExitCode::new(1));
|
||||||
}
|
}
|
||||||
if self.recursive && self.preserve_root && filename == "/" {
|
if self.recursive && self.preserve_root && filename == "/" {
|
||||||
show_error!(
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!(
|
||||||
"it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe",
|
"it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe",
|
||||||
filename.quote()
|
filename.quote()
|
||||||
);
|
)
|
||||||
return Err(1);
|
));
|
||||||
}
|
}
|
||||||
if !self.recursive {
|
if !self.recursive {
|
||||||
r = self.chmod_file(file).and(r);
|
r = self.chmod_file(file).and(r);
|
||||||
|
@ -234,14 +243,14 @@ impl Chmoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
|
fn chmod_file(&self, file: &Path) -> UResult<()> {
|
||||||
// chmod is useless on Windows
|
// chmod is useless on Windows
|
||||||
// it doesn't set any permissions at all
|
// it doesn't set any permissions at all
|
||||||
// instead it just sets the readonly attribute on the file
|
// instead it just sets the readonly attribute on the file
|
||||||
Err(0)
|
Ok(())
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
|
fn chmod_file(&self, file: &Path) -> UResult<()> {
|
||||||
use uucore::mode::get_umask;
|
use uucore::mode::get_umask;
|
||||||
|
|
||||||
let fperm = match fs::metadata(file) {
|
let fperm = match fs::metadata(file) {
|
||||||
|
@ -258,11 +267,13 @@ impl Chmoder {
|
||||||
} else if err.kind() == std::io::ErrorKind::PermissionDenied {
|
} else if err.kind() == std::io::ErrorKind::PermissionDenied {
|
||||||
// These two filenames would normally be conditionally
|
// These two filenames would normally be conditionally
|
||||||
// quoted, but GNU's tests expect them to always be quoted
|
// quoted, but GNU's tests expect them to always be quoted
|
||||||
show_error!("{}: Permission denied", file.quote());
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("{}: Permission denied", file.quote()),
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
show_error!("{}: {}", file.quote(), err);
|
return Err(USimpleError::new(1, format!("{}: {}", file.quote(), err)));
|
||||||
}
|
}
|
||||||
return Err(1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match self.fmode {
|
match self.fmode {
|
||||||
|
@ -296,22 +307,25 @@ impl Chmoder {
|
||||||
}
|
}
|
||||||
Err(f) => {
|
Err(f) => {
|
||||||
if !self.quiet {
|
if !self.quiet {
|
||||||
show_error!("{}", f);
|
return Err(USimpleError::new(1, f));
|
||||||
|
} else {
|
||||||
|
return Err(ExitCode::new(1));
|
||||||
}
|
}
|
||||||
return Err(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.change_file(fperm, new_mode, file)?;
|
self.change_file(fperm, new_mode, file)?;
|
||||||
// if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail
|
// if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail
|
||||||
if (new_mode & !naively_expected_new_mode) != 0 {
|
if (new_mode & !naively_expected_new_mode) != 0 {
|
||||||
show_error!(
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!(
|
||||||
"{}: new permissions are {}, not {}",
|
"{}: new permissions are {}, not {}",
|
||||||
file.maybe_quote(),
|
file.maybe_quote(),
|
||||||
display_permissions_unix(new_mode as mode_t, false),
|
display_permissions_unix(new_mode as mode_t, false),
|
||||||
display_permissions_unix(naively_expected_new_mode as mode_t, false)
|
display_permissions_unix(naively_expected_new_mode as mode_t, false)
|
||||||
);
|
),
|
||||||
return Err(1);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
43
src/uu/env/src/env.rs
vendored
43
src/uu/env/src/env.rs
vendored
|
@ -1,6 +1,7 @@
|
||||||
// This file is part of the uutils coreutils package.
|
// This file is part of the uutils coreutils package.
|
||||||
//
|
//
|
||||||
// (c) Jordi Boggiano <j.boggiano@seld.be>
|
// (c) Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
// (c) Thomas Queiroz <thomasqueirozb@gmail.com>
|
||||||
//
|
//
|
||||||
// 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.
|
||||||
|
@ -23,7 +24,7 @@ use std::io::{self, Write};
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||||
|
|
||||||
const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]";
|
const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]";
|
||||||
const AFTER_HELP: &str = "\
|
const AFTER_HELP: &str = "\
|
||||||
|
@ -50,7 +51,7 @@ fn print_env(null: bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<bool, i32> {
|
fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> UResult<bool> {
|
||||||
// is it a NAME=VALUE like opt ?
|
// is it a NAME=VALUE like opt ?
|
||||||
if let Some(idx) = opt.find('=') {
|
if let Some(idx) = opt.find('=') {
|
||||||
// yes, so push name, value pair
|
// yes, so push name, value pair
|
||||||
|
@ -64,17 +65,12 @@ fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32> {
|
fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> UResult<()> {
|
||||||
if opts.null {
|
if opts.null {
|
||||||
eprintln!(
|
Err(UUsageError::new(
|
||||||
"{}: cannot specify --null (-0) with command",
|
125,
|
||||||
uucore::util_name()
|
"cannot specify --null (-0) with command".to_string(),
|
||||||
);
|
))
|
||||||
eprintln!(
|
|
||||||
"Type \"{} --help\" for detailed information",
|
|
||||||
uucore::execution_phrase()
|
|
||||||
);
|
|
||||||
Err(1)
|
|
||||||
} else {
|
} else {
|
||||||
opts.program.push(opt);
|
opts.program.push(opt);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -93,10 +89,8 @@ fn load_config_file(opts: &mut Options) -> UResult<()> {
|
||||||
Ini::load_from_file(file)
|
Ini::load_from_file(file)
|
||||||
};
|
};
|
||||||
|
|
||||||
let conf = conf.map_err(|error| {
|
let conf =
|
||||||
show_error!("{}: {}", file.maybe_quote(), error);
|
conf.map_err(|e| USimpleError::new(1, format!("{}: {}", file.maybe_quote(), e)))?;
|
||||||
1
|
|
||||||
})?;
|
|
||||||
|
|
||||||
for (_, prop) in &conf {
|
for (_, prop) in &conf {
|
||||||
// ignore all INI section lines (treat them as comments)
|
// ignore all INI section lines (treat them as comments)
|
||||||
|
@ -138,7 +132,7 @@ pub fn uu_app() -> App<'static, 'static> {
|
||||||
.long("ignore-environment")
|
.long("ignore-environment")
|
||||||
.help("start with an empty environment"))
|
.help("start with an empty environment"))
|
||||||
.arg(Arg::with_name("chdir")
|
.arg(Arg::with_name("chdir")
|
||||||
.short("c")
|
.short("C") // GNU env compatibility
|
||||||
.long("chdir")
|
.long("chdir")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.number_of_values(1)
|
.number_of_values(1)
|
||||||
|
@ -236,6 +230,14 @@ fn run_env(args: impl uucore::Args) -> UResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GNU env tests this behavior
|
||||||
|
if opts.program.is_empty() && running_directory.is_some() {
|
||||||
|
return Err(UUsageError::new(
|
||||||
|
125,
|
||||||
|
"must specify command with --chdir (-C)".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: we manually set and unset the env vars below rather than using Command::env() to more
|
// NOTE: we manually set and unset the env vars below rather than using Command::env() to more
|
||||||
// easily handle the case where no command is given
|
// easily handle the case where no command is given
|
||||||
|
|
||||||
|
@ -251,6 +253,13 @@ fn run_env(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
|
||||||
// unset specified env vars
|
// unset specified env vars
|
||||||
for name in &opts.unsets {
|
for name in &opts.unsets {
|
||||||
|
if name.is_empty() || name.contains(0 as char) || name.contains('=') {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
125,
|
||||||
|
format!("cannot unset {}: Invalid argument", name.quote()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
env::remove_var(name);
|
env::remove_var(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,8 @@ on Daniel Lemire's [*Microbenchmarking calls for idealized conditions*][lemire],
|
||||||
which I recommend reading if you want to add benchmarks to `factor`.
|
which I recommend reading if you want to add benchmarks to `factor`.
|
||||||
|
|
||||||
1. Select a small, self-contained, deterministic component
|
1. Select a small, self-contained, deterministic component
|
||||||
`gcd` and `table::factor` are good example of such:
|
(`gcd` and `table::factor` are good examples):
|
||||||
|
|
||||||
- no I/O or access to external data structures ;
|
- no I/O or access to external data structures ;
|
||||||
- no call into other components ;
|
- no call into other components ;
|
||||||
- behavior is deterministic: no RNG, no concurrency, ... ;
|
- behavior is deterministic: no RNG, no concurrency, ... ;
|
||||||
|
@ -53,16 +54,19 @@ which I recommend reading if you want to add benchmarks to `factor`.
|
||||||
maximizing the numbers of samples we can take in a given time.
|
maximizing the numbers of samples we can take in a given time.
|
||||||
|
|
||||||
2. Benchmarks are immutable (once merged in `uutils`)
|
2. Benchmarks are immutable (once merged in `uutils`)
|
||||||
|
|
||||||
Modifying a benchmark means previously-collected values cannot meaningfully
|
Modifying a benchmark means previously-collected values cannot meaningfully
|
||||||
be compared, silently giving nonsensical results. If you must modify an
|
be compared, silently giving nonsensical results. If you must modify an
|
||||||
existing benchmark, rename it.
|
existing benchmark, rename it.
|
||||||
|
|
||||||
3. Test common cases
|
3. Test common cases
|
||||||
|
|
||||||
We are interested in overall performance, rather than specific edge-cases;
|
We are interested in overall performance, rather than specific edge-cases;
|
||||||
use **reproducibly-randomized inputs**, sampling from either all possible
|
use **reproducibly-randomized inputs**, sampling from either all possible
|
||||||
input values or some subset of interest.
|
input values or some subset of interest.
|
||||||
|
|
||||||
4. Use [`criterion`], `criterion::black_box`, ...
|
4. Use [`criterion`], `criterion::black_box`, ...
|
||||||
|
|
||||||
`criterion` isn't perfect, but it is also much better than ad-hoc
|
`criterion` isn't perfect, but it is also much better than ad-hoc
|
||||||
solutions in each benchmark.
|
solutions in each benchmark.
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub fn gcd(mut u: u64, mut v: u64) -> u64 {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use quickcheck::quickcheck;
|
use quickcheck::{quickcheck, TestResult};
|
||||||
|
|
||||||
quickcheck! {
|
quickcheck! {
|
||||||
fn euclidean(a: u64, b: u64) -> bool {
|
fn euclidean(a: u64, b: u64) -> bool {
|
||||||
|
@ -76,13 +76,12 @@ mod tests {
|
||||||
gcd(0, a) == a
|
gcd(0, a) == a
|
||||||
}
|
}
|
||||||
|
|
||||||
fn divisor(a: u64, b: u64) -> () {
|
fn divisor(a: u64, b: u64) -> TestResult {
|
||||||
// Test that gcd(a, b) divides a and b, unless a == b == 0
|
// Test that gcd(a, b) divides a and b, unless a == b == 0
|
||||||
if a == 0 && b == 0 { return; }
|
if a == 0 && b == 0 { return TestResult::discard(); } // restrict test domain to !(a == b == 0)
|
||||||
|
|
||||||
let g = gcd(a, b);
|
let g = gcd(a, b);
|
||||||
assert_eq!(a % g, 0);
|
TestResult::from_bool( g != 0 && a % g == 0 && b % g == 0 )
|
||||||
assert_eq!(b % g, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commutative(a: u64, b: u64) -> bool {
|
fn commutative(a: u64, b: u64) -> bool {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use uucore::error::UResult;
|
||||||
|
|
||||||
static ABOUT: &str = "Display the values of the specified environment VARIABLE(s), or (with no VARIABLE) display name and value pairs for them all.";
|
static ABOUT: &str = "Display the values of the specified environment VARIABLE(s), or (with no VARIABLE) display name and value pairs for them all.";
|
||||||
|
|
||||||
|
@ -20,7 +21,8 @@ fn usage() -> String {
|
||||||
format!("{0} [VARIABLE]... [OPTION]...", uucore::execution_phrase())
|
format!("{0} [VARIABLE]... [OPTION]...", uucore::execution_phrase())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
#[uucore_procs::gen_uumain]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let usage = usage();
|
let usage = usage();
|
||||||
|
|
||||||
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
||||||
|
@ -40,15 +42,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
for (env_var, value) in env::vars() {
|
for (env_var, value) in env::vars() {
|
||||||
print!("{}={}{}", env_var, value, separator);
|
print!("{}={}{}", env_var, value, separator);
|
||||||
}
|
}
|
||||||
return 0;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut not_found = false;
|
||||||
for env_var in variables {
|
for env_var in variables {
|
||||||
if let Ok(var) = env::var(env_var) {
|
if let Ok(var) = env::var(env_var) {
|
||||||
print!("{}{}", var, separator);
|
print!("{}{}", var, separator);
|
||||||
|
} else {
|
||||||
|
not_found = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0
|
|
||||||
|
if not_found {
|
||||||
|
Err(1.into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> App<'static, 'static> {
|
pub fn uu_app() -> App<'static, 'static> {
|
||||||
|
|
|
@ -50,6 +50,7 @@ static OPT_PROMPT_MORE: &str = "prompt-more";
|
||||||
static OPT_RECURSIVE: &str = "recursive";
|
static OPT_RECURSIVE: &str = "recursive";
|
||||||
static OPT_RECURSIVE_R: &str = "recursive_R";
|
static OPT_RECURSIVE_R: &str = "recursive_R";
|
||||||
static OPT_VERBOSE: &str = "verbose";
|
static OPT_VERBOSE: &str = "verbose";
|
||||||
|
static PRESUME_INPUT_TTY: &str = "presume-input-tty";
|
||||||
|
|
||||||
static ARG_FILES: &str = "files";
|
static ARG_FILES: &str = "files";
|
||||||
|
|
||||||
|
@ -208,6 +209,17 @@ pub fn uu_app() -> App<'static, 'static> {
|
||||||
.long(OPT_VERBOSE)
|
.long(OPT_VERBOSE)
|
||||||
.help("explain what is being done")
|
.help("explain what is being done")
|
||||||
)
|
)
|
||||||
|
// From the GNU source code:
|
||||||
|
// This is solely for testing.
|
||||||
|
// Do not document.
|
||||||
|
// It is relatively difficult to ensure that there is a tty on stdin.
|
||||||
|
// Since rm acts differently depending on that, without this option,
|
||||||
|
// it'd be harder to test the parts of rm that depend on that setting.
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(PRESUME_INPUT_TTY)
|
||||||
|
.long(PRESUME_INPUT_TTY)
|
||||||
|
.hidden(true)
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(ARG_FILES)
|
Arg::with_name(ARG_FILES)
|
||||||
.multiple(true)
|
.multiple(true)
|
||||||
|
|
161
src/uu/tail/src/parse.rs
Normal file
161
src/uu/tail/src/parse.rs
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
// * This file is part of the uutils coreutils package.
|
||||||
|
// *
|
||||||
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum ParseError {
|
||||||
|
Syntax,
|
||||||
|
Overflow,
|
||||||
|
}
|
||||||
|
/// Parses obsolete syntax
|
||||||
|
/// tail -NUM[kmzv] // spell-checker:disable-line
|
||||||
|
pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>, ParseError>> {
|
||||||
|
let mut chars = src.char_indices();
|
||||||
|
if let Some((_, '-')) = chars.next() {
|
||||||
|
let mut num_end = 0usize;
|
||||||
|
let mut has_num = false;
|
||||||
|
let mut last_char = 0 as char;
|
||||||
|
for (n, c) in &mut chars {
|
||||||
|
if c.is_numeric() {
|
||||||
|
has_num = true;
|
||||||
|
num_end = n;
|
||||||
|
} else {
|
||||||
|
last_char = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if has_num {
|
||||||
|
match src[1..=num_end].parse::<usize>() {
|
||||||
|
Ok(num) => {
|
||||||
|
let mut quiet = false;
|
||||||
|
let mut verbose = false;
|
||||||
|
let mut zero_terminated = false;
|
||||||
|
let mut multiplier = None;
|
||||||
|
let mut c = last_char;
|
||||||
|
loop {
|
||||||
|
// not that here, we only match lower case 'k', 'c', and 'm'
|
||||||
|
match c {
|
||||||
|
// we want to preserve order
|
||||||
|
// this also saves us 1 heap allocation
|
||||||
|
'q' => {
|
||||||
|
quiet = true;
|
||||||
|
verbose = false
|
||||||
|
}
|
||||||
|
'v' => {
|
||||||
|
verbose = true;
|
||||||
|
quiet = false
|
||||||
|
}
|
||||||
|
'z' => zero_terminated = true,
|
||||||
|
'c' => multiplier = Some(1),
|
||||||
|
'b' => multiplier = Some(512),
|
||||||
|
'k' => multiplier = Some(1024),
|
||||||
|
'm' => multiplier = Some(1024 * 1024),
|
||||||
|
'\0' => {}
|
||||||
|
_ => return Some(Err(ParseError::Syntax)),
|
||||||
|
}
|
||||||
|
if let Some((_, next)) = chars.next() {
|
||||||
|
c = next
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut options = Vec::new();
|
||||||
|
if quiet {
|
||||||
|
options.push(OsString::from("-q"))
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
options.push(OsString::from("-v"))
|
||||||
|
}
|
||||||
|
if zero_terminated {
|
||||||
|
options.push(OsString::from("-z"))
|
||||||
|
}
|
||||||
|
if let Some(n) = multiplier {
|
||||||
|
options.push(OsString::from("-c"));
|
||||||
|
let num = match num.checked_mul(n) {
|
||||||
|
Some(n) => n,
|
||||||
|
None => return Some(Err(ParseError::Overflow)),
|
||||||
|
};
|
||||||
|
options.push(OsString::from(format!("{}", num)));
|
||||||
|
} else {
|
||||||
|
options.push(OsString::from("-n"));
|
||||||
|
options.push(OsString::from(format!("{}", num)));
|
||||||
|
}
|
||||||
|
Some(Ok(options.into_iter()))
|
||||||
|
}
|
||||||
|
Err(_) => Some(Err(ParseError::Overflow)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
fn obsolete(src: &str) -> Option<Result<Vec<String>, ParseError>> {
|
||||||
|
let r = parse_obsolete(src);
|
||||||
|
match r {
|
||||||
|
Some(s) => match s {
|
||||||
|
Ok(v) => Some(Ok(v.map(|s| s.to_str().unwrap().to_owned()).collect())),
|
||||||
|
Err(e) => Some(Err(e)),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn obsolete_result(src: &[&str]) -> Option<Result<Vec<String>, ParseError>> {
|
||||||
|
Some(Ok(src.iter().map(|s| s.to_string()).collect()))
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_parse_numbers_obsolete() {
|
||||||
|
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
|
||||||
|
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));
|
||||||
|
assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "5242880"]));
|
||||||
|
assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "1024"]));
|
||||||
|
assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "1024"]));
|
||||||
|
assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "1024"]));
|
||||||
|
assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "1"]));
|
||||||
|
assert_eq!(
|
||||||
|
obsolete("-1vzqvq"), // spell-checker:disable-line
|
||||||
|
obsolete_result(&["-q", "-z", "-n", "1"])
|
||||||
|
);
|
||||||
|
assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "1"]));
|
||||||
|
assert_eq!(
|
||||||
|
obsolete("-105kzm"),
|
||||||
|
obsolete_result(&["-z", "-c", "110100480"])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_parse_errors_obsolete() {
|
||||||
|
assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax)));
|
||||||
|
assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax)));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_parse_obsolete_no_match() {
|
||||||
|
assert_eq!(obsolete("-k"), None);
|
||||||
|
assert_eq!(obsolete("asd"), None);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
fn test_parse_obsolete_overflow_x64() {
|
||||||
|
assert_eq!(
|
||||||
|
obsolete("-1000000000000000m"),
|
||||||
|
Some(Err(ParseError::Overflow))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
obsolete("-10000000000000000000000"),
|
||||||
|
Some(Err(ParseError::Overflow))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
fn test_parse_obsolete_overflow_x32() {
|
||||||
|
assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow)));
|
||||||
|
assert_eq!(obsolete("-42949672k"), Some(Err(ParseError::Overflow)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,17 +16,21 @@ extern crate clap;
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
mod chunks;
|
mod chunks;
|
||||||
|
mod parse;
|
||||||
mod platform;
|
mod platform;
|
||||||
use chunks::ReverseChunks;
|
use chunks::ReverseChunks;
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::ffi::OsString;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{File, Metadata};
|
||||||
use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
|
use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use uucore::display::Quotable;
|
||||||
|
use uucore::error::{UResult, USimpleError};
|
||||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||||
use uucore::ringbuffer::RingBuffer;
|
use uucore::ringbuffer::RingBuffer;
|
||||||
|
|
||||||
|
@ -58,40 +62,40 @@ pub mod options {
|
||||||
pub static ARG_FILES: &str = "files";
|
pub static ARG_FILES: &str = "files";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
enum FilterMode {
|
enum FilterMode {
|
||||||
Bytes(usize),
|
Bytes(usize),
|
||||||
Lines(usize, u8), // (number of lines, delimiter)
|
Lines(usize, u8), // (number of lines, delimiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for FilterMode {
|
||||||
|
fn default() -> Self {
|
||||||
|
FilterMode::Lines(10, b'\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
struct Settings {
|
struct Settings {
|
||||||
|
quiet: bool,
|
||||||
|
verbose: bool,
|
||||||
mode: FilterMode,
|
mode: FilterMode,
|
||||||
sleep_msec: u32,
|
sleep_msec: u32,
|
||||||
beginning: bool,
|
beginning: bool,
|
||||||
follow: bool,
|
follow: bool,
|
||||||
pid: platform::Pid,
|
pid: platform::Pid,
|
||||||
|
files: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Settings {
|
||||||
fn default() -> Settings {
|
pub fn get_from(args: impl uucore::Args) -> Result<Self, String> {
|
||||||
Settings {
|
let matches = uu_app().get_matches_from(arg_iterate(args)?);
|
||||||
mode: FilterMode::Lines(10, b'\n'),
|
|
||||||
|
let mut settings: Settings = Settings {
|
||||||
sleep_msec: 1000,
|
sleep_msec: 1000,
|
||||||
beginning: false,
|
follow: matches.is_present(options::FOLLOW),
|
||||||
follow: false,
|
..Default::default()
|
||||||
pid: 0,
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
|
||||||
let mut settings: Settings = Default::default();
|
|
||||||
|
|
||||||
let app = uu_app();
|
|
||||||
|
|
||||||
let matches = app.get_matches_from(args);
|
|
||||||
|
|
||||||
settings.follow = matches.is_present(options::FOLLOW);
|
|
||||||
if settings.follow {
|
if settings.follow {
|
||||||
if let Some(n) = matches.value_of(options::SLEEP_INT) {
|
if let Some(n) = matches.value_of(options::SLEEP_INT) {
|
||||||
let parsed: Option<u32> = n.parse().ok();
|
let parsed: Option<u32> = n.parse().ok();
|
||||||
|
@ -120,12 +124,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) {
|
let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) {
|
||||||
match parse_num(arg) {
|
match parse_num(arg) {
|
||||||
Ok((n, beginning)) => (FilterMode::Bytes(n), beginning),
|
Ok((n, beginning)) => (FilterMode::Bytes(n), beginning),
|
||||||
Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()),
|
Err(e) => return Err(format!("invalid number of bytes: {}", e)),
|
||||||
}
|
}
|
||||||
} else if let Some(arg) = matches.value_of(options::LINES) {
|
} else if let Some(arg) = matches.value_of(options::LINES) {
|
||||||
match parse_num(arg) {
|
match parse_num(arg) {
|
||||||
Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning),
|
Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning),
|
||||||
Err(e) => crash!(1, "invalid number of lines: {}", e.to_string()),
|
Err(e) => return Err(format!("invalid number of lines: {}", e)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(FilterMode::Lines(10, b'\n'), false)
|
(FilterMode::Lines(10, b'\n'), false)
|
||||||
|
@ -139,24 +143,41 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let verbose = matches.is_present(options::verbosity::VERBOSE);
|
settings.verbose = matches.is_present(options::verbosity::VERBOSE);
|
||||||
let quiet = matches.is_present(options::verbosity::QUIET);
|
settings.quiet = matches.is_present(options::verbosity::QUIET);
|
||||||
|
|
||||||
let files: Vec<String> = matches
|
settings.files = match matches.values_of(options::ARG_FILES) {
|
||||||
.values_of(options::ARG_FILES)
|
Some(v) => v.map(|s| s.to_owned()).collect(),
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
None => vec!["-".to_owned()],
|
||||||
.unwrap_or_else(|| vec![String::from("-")]);
|
};
|
||||||
|
|
||||||
let multiple = files.len() > 1;
|
Ok(settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
|
#[uucore_procs::gen_uumain]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
let args = match Settings::get_from(args) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(s) => {
|
||||||
|
return Err(USimpleError::new(1, s));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
uu_tail(&args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uu_tail(settings: &Settings) -> UResult<()> {
|
||||||
|
let multiple = settings.files.len() > 1;
|
||||||
let mut first_header = true;
|
let mut first_header = true;
|
||||||
let mut readers: Vec<(Box<dyn BufRead>, &String)> = Vec::new();
|
let mut readers: Vec<(Box<dyn BufRead>, &String)> = Vec::new();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let stdin_string = String::from("standard input");
|
let stdin_string = String::from("standard input");
|
||||||
|
|
||||||
for filename in &files {
|
for filename in &settings.files {
|
||||||
let use_stdin = filename.as_str() == "-";
|
let use_stdin = filename.as_str() == "-";
|
||||||
if (multiple || verbose) && !quiet {
|
if (multiple || settings.verbose) && !settings.quiet {
|
||||||
if !first_header {
|
if !first_header {
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
@ -170,7 +191,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
|
|
||||||
if use_stdin {
|
if use_stdin {
|
||||||
let mut reader = BufReader::new(stdin());
|
let mut reader = BufReader::new(stdin());
|
||||||
unbounded_tail(&mut reader, &settings);
|
unbounded_tail(&mut reader, settings);
|
||||||
|
|
||||||
// Don't follow stdin since there are no checks for pipes/FIFOs
|
// Don't follow stdin since there are no checks for pipes/FIFOs
|
||||||
//
|
//
|
||||||
|
@ -202,14 +223,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
let mut file = File::open(&path).unwrap();
|
let mut file = File::open(&path).unwrap();
|
||||||
let md = file.metadata().unwrap();
|
let md = file.metadata().unwrap();
|
||||||
if is_seekable(&mut file) && get_block_size(&md) > 0 {
|
if is_seekable(&mut file) && get_block_size(&md) > 0 {
|
||||||
bounded_tail(&mut file, &settings);
|
bounded_tail(&mut file, settings);
|
||||||
if settings.follow {
|
if settings.follow {
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
readers.push((Box::new(reader), filename));
|
readers.push((Box::new(reader), filename));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut reader = BufReader::new(file);
|
let mut reader = BufReader::new(file);
|
||||||
unbounded_tail(&mut reader, &settings);
|
unbounded_tail(&mut reader, settings);
|
||||||
if settings.follow {
|
if settings.follow {
|
||||||
readers.push((Box::new(reader), filename));
|
readers.push((Box::new(reader), filename));
|
||||||
}
|
}
|
||||||
|
@ -218,10 +239,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.follow {
|
if settings.follow {
|
||||||
follow(&mut readers[..], &settings);
|
follow(&mut readers[..], settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
0
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arg_iterate<'a>(
|
||||||
|
mut args: impl uucore::Args + 'a,
|
||||||
|
) -> Result<Box<dyn Iterator<Item = OsString> + 'a>, String> {
|
||||||
|
// argv[0] is always present
|
||||||
|
let first = args.next().unwrap();
|
||||||
|
if let Some(second) = args.next() {
|
||||||
|
if let Some(s) = second.to_str() {
|
||||||
|
match parse::parse_obsolete(s) {
|
||||||
|
Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
|
||||||
|
Some(Err(e)) => match e {
|
||||||
|
parse::ParseError::Syntax => Err(format!("bad argument format: {}", s.quote())),
|
||||||
|
parse::ParseError::Overflow => Err(format!(
|
||||||
|
"invalid argument: {} Value too large for defined datatype",
|
||||||
|
s.quote()
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
None => Ok(Box::new(vec![first, second].into_iter().chain(args))),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("bad argument encoding".to_owned())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Box::new(vec![first].into_iter()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> App<'static, 'static> {
|
pub fn uu_app() -> App<'static, 'static> {
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) ttyname hostnames runlevel mesg wtmp statted boottime deadprocs initspawn clockchange curr runlvline pidstr exitstr hoststr
|
// spell-checker:ignore (ToDO) ttyname hostnames runlevel mesg wtmp statted boottime deadprocs initspawn clockchange curr runlvline pidstr exitstr hoststr
|
||||||
|
|
||||||
#[macro_use]
|
use uucore::display::Quotable;
|
||||||
extern crate uucore;
|
use uucore::error::{FromIo, UResult};
|
||||||
use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP};
|
use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP};
|
||||||
use uucore::utmpx::{self, time, Utmpx};
|
use uucore::utmpx::{self, time, Utmpx};
|
||||||
|
|
||||||
|
@ -59,7 +59,8 @@ fn get_long_usage() -> String {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
#[uucore_procs::gen_uumain]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let args = args
|
let args = args
|
||||||
.collect_str(InvalidEncodingHandling::Ignore)
|
.collect_str(InvalidEncodingHandling::Ignore)
|
||||||
.accept_any();
|
.accept_any();
|
||||||
|
@ -157,9 +158,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
args: files,
|
args: files,
|
||||||
};
|
};
|
||||||
|
|
||||||
who.exec();
|
who.exec()
|
||||||
|
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> App<'static, 'static> {
|
pub fn uu_app() -> App<'static, 'static> {
|
||||||
|
@ -326,7 +325,7 @@ fn current_tty() -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Who {
|
impl Who {
|
||||||
fn exec(&mut self) {
|
fn exec(&mut self) -> UResult<()> {
|
||||||
let run_level_chk = |_record: i16| {
|
let run_level_chk = |_record: i16| {
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
return false;
|
return false;
|
||||||
|
@ -362,7 +361,7 @@ impl Who {
|
||||||
for ut in records {
|
for ut in records {
|
||||||
if !self.my_line_only || cur_tty == ut.tty_device() {
|
if !self.my_line_only || cur_tty == ut.tty_device() {
|
||||||
if self.need_users && ut.is_user_process() {
|
if self.need_users && ut.is_user_process() {
|
||||||
self.print_user(&ut);
|
self.print_user(&ut)?;
|
||||||
} else if self.need_runlevel && run_level_chk(ut.record_type()) {
|
} else if self.need_runlevel && run_level_chk(ut.record_type()) {
|
||||||
if cfg!(target_os = "linux") {
|
if cfg!(target_os = "linux") {
|
||||||
self.print_runlevel(&ut);
|
self.print_runlevel(&ut);
|
||||||
|
@ -383,6 +382,7 @@ impl Who {
|
||||||
if ut.record_type() == utmpx::BOOT_TIME {}
|
if ut.record_type() == utmpx::BOOT_TIME {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -464,7 +464,7 @@ impl Who {
|
||||||
self.print_line("", ' ', "system boot", &time_string(ut), "", "", "", "");
|
self.print_line("", ' ', "system boot", &time_string(ut), "", "", "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_user(&self, ut: &Utmpx) {
|
fn print_user(&self, ut: &Utmpx) -> UResult<()> {
|
||||||
let mut p = PathBuf::from("/dev");
|
let mut p = PathBuf::from("/dev");
|
||||||
p.push(ut.tty_device().as_str());
|
p.push(ut.tty_device().as_str());
|
||||||
let mesg;
|
let mesg;
|
||||||
|
@ -491,7 +491,13 @@ impl Who {
|
||||||
};
|
};
|
||||||
|
|
||||||
let s = if self.do_lookup {
|
let s = if self.do_lookup {
|
||||||
crash_if_err!(1, ut.canon_host())
|
ut.canon_host().map_err_context(|| {
|
||||||
|
let host = ut.host();
|
||||||
|
format!(
|
||||||
|
"failed to canonicalize {}",
|
||||||
|
host.split(':').next().unwrap_or(&host).quote()
|
||||||
|
)
|
||||||
|
})?
|
||||||
} else {
|
} else {
|
||||||
ut.host()
|
ut.host()
|
||||||
};
|
};
|
||||||
|
@ -507,6 +513,8 @@ impl Who {
|
||||||
hoststr.as_str(),
|
hoststr.as_str(),
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
|
|
@ -236,9 +236,8 @@ impl Utmpx {
|
||||||
flags: AI_CANONNAME,
|
flags: AI_CANONNAME,
|
||||||
..AddrInfoHints::default()
|
..AddrInfoHints::default()
|
||||||
};
|
};
|
||||||
let sockets = getaddrinfo(Some(hostname), None, Some(hints))
|
if let Ok(sockets) = getaddrinfo(Some(hostname), None, Some(hints)) {
|
||||||
.unwrap()
|
let sockets = sockets.collect::<IOResult<Vec<_>>>()?;
|
||||||
.collect::<IOResult<Vec<_>>>()?;
|
|
||||||
for socket in sockets {
|
for socket in sockets {
|
||||||
if let Some(ai_canonname) = socket.canonname {
|
if let Some(ai_canonname) = socket.canonname {
|
||||||
return Ok(if display.is_empty() {
|
return Ok(if display.is_empty() {
|
||||||
|
@ -248,6 +247,10 @@ impl Utmpx {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// GNU coreutils has this behavior
|
||||||
|
return Ok(hostname.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(host.to_string())
|
Ok(host.to_string())
|
||||||
|
|
|
@ -1,3 +1,37 @@
|
||||||
|
//! Macros for the uucore utilities.
|
||||||
|
//!
|
||||||
|
//! This module bundles all macros used across the uucore utilities. These
|
||||||
|
//! include macros for reporting errors in various formats, aborting program
|
||||||
|
//! execution and more.
|
||||||
|
//!
|
||||||
|
//! To make use of all macros in this module, they must be imported like so:
|
||||||
|
//!
|
||||||
|
//! ```ignore
|
||||||
|
//! #[macro_use]
|
||||||
|
//! extern crate uucore;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Alternatively, you can import single macros by importing them through their
|
||||||
|
//! fully qualified name like this:
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! use uucore::{show, crash};
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Here's an overview of the macros sorted by purpose
|
||||||
|
//!
|
||||||
|
//! - Print errors
|
||||||
|
//! - From types implementing [`crate::error::UError`]: [`show!`],
|
||||||
|
//! [`show_if_err!`]
|
||||||
|
//! - From custom messages: [`show_error!`], [`show_usage_error!`]
|
||||||
|
//! - Print warnings: [`show_warning!`]
|
||||||
|
//! - Terminate util execution
|
||||||
|
//! - Terminate regularly: [`exit!`], [`return_if_err!`]
|
||||||
|
//! - Crash program: [`crash!`], [`crash_if_err!`], [`safe_unwrap!`]
|
||||||
|
//! - Unwrapping result types: [`safe_unwrap!`]
|
||||||
|
|
||||||
|
// spell-checker:ignore sourcepath targetpath
|
||||||
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
// This file is part of the uutils coreutils package.
|
// This file is part of the uutils coreutils package.
|
||||||
|
@ -12,6 +46,45 @@ pub static UTILITY_IS_SECOND_ARG: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
//====
|
//====
|
||||||
|
|
||||||
|
/// Display a [`crate::error::UError`] and set global exit code.
|
||||||
|
///
|
||||||
|
/// Prints the error message contained in an [`crate::error::UError`] to stderr
|
||||||
|
/// and sets the exit code through [`crate::error::set_exit_code`]. The printed
|
||||||
|
/// error message is prepended with the calling utility's name. A call to this
|
||||||
|
/// macro will not finish program execution.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// The following example would print a message "Some error occurred" and set
|
||||||
|
/// the utility's exit code to 2.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[macro_use]
|
||||||
|
/// # extern crate uucore;
|
||||||
|
///
|
||||||
|
/// use uucore::error::{self, USimpleError};
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let err = USimpleError::new(2, "Some error occurred.");
|
||||||
|
/// show!(err);
|
||||||
|
/// assert_eq!(error::get_exit_code(), 2);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If not using [`crate::error::UError`], one may achieve the same behavior
|
||||||
|
/// like this:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[macro_use]
|
||||||
|
/// # extern crate uucore;
|
||||||
|
///
|
||||||
|
/// use uucore::error::set_exit_code;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// set_exit_code(2);
|
||||||
|
/// show_error!("Some error occurred.");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! show(
|
macro_rules! show(
|
||||||
($err:expr) => ({
|
($err:expr) => ({
|
||||||
|
@ -21,6 +94,36 @@ macro_rules! show(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Display an error and set global exit code in error case.
|
||||||
|
///
|
||||||
|
/// Wraps around [`show!`] and takes a [`crate::error::UResult`] instead of a
|
||||||
|
/// [`crate::error::UError`] type. This macro invokes [`show!`] if the
|
||||||
|
/// [`crate::error::UResult`] is an `Err`-variant. This can be invoked directly
|
||||||
|
/// on the result of a function call, like in the `install` utility:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// show_if_err!(copy(sourcepath, &targetpath, b));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// # #[macro_use]
|
||||||
|
/// # extern crate uucore;
|
||||||
|
/// # use uucore::error::{UError, UIoError, UResult, USimpleError};
|
||||||
|
///
|
||||||
|
/// # fn main() {
|
||||||
|
/// let is_ok = Ok(1);
|
||||||
|
/// // This does nothing at all
|
||||||
|
/// show_if_err!(is_ok);
|
||||||
|
///
|
||||||
|
/// let is_err = Err(USimpleError::new(1, "I'm an error").into());
|
||||||
|
/// // Calls `show!` on the contained USimpleError
|
||||||
|
/// show_if_err!(is_err);
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
///
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! show_if_err(
|
macro_rules! show_if_err(
|
||||||
($res:expr) => ({
|
($res:expr) => ({
|
||||||
|
@ -31,6 +134,19 @@ macro_rules! show_if_err(
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Show an error to stderr in a similar style to GNU coreutils.
|
/// Show an error to stderr in a similar style to GNU coreutils.
|
||||||
|
///
|
||||||
|
/// Takes a [`format!`]-like input and prints it to stderr. The output is
|
||||||
|
/// prepended with the current utility's name.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[macro_use]
|
||||||
|
/// # extern crate uucore;
|
||||||
|
/// # fn main() {
|
||||||
|
/// show_error!("Couldn't apply {} to {}", "foo", "bar");
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! show_error(
|
macro_rules! show_error(
|
||||||
($($args:tt)+) => ({
|
($($args:tt)+) => ({
|
||||||
|
@ -40,6 +156,17 @@ macro_rules! show_error(
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Show a warning to stderr in a similar style to GNU coreutils.
|
/// Show a warning to stderr in a similar style to GNU coreutils.
|
||||||
|
///
|
||||||
|
/// Is this really required? Used in the following locations:
|
||||||
|
///
|
||||||
|
/// ./src/uu/head/src/head.rs:12
|
||||||
|
/// ./src/uu/head/src/head.rs:424
|
||||||
|
/// ./src/uu/head/src/head.rs:427
|
||||||
|
/// ./src/uu/head/src/head.rs:430
|
||||||
|
/// ./src/uu/head/src/head.rs:453
|
||||||
|
/// ./src/uu/du/src/du.rs:339
|
||||||
|
/// ./src/uu/wc/src/wc.rs:270
|
||||||
|
/// ./src/uu/wc/src/wc.rs:273
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! show_error_custom_description (
|
macro_rules! show_error_custom_description (
|
||||||
($err:expr,$($args:tt)+) => ({
|
($err:expr,$($args:tt)+) => ({
|
||||||
|
@ -48,6 +175,21 @@ macro_rules! show_error_custom_description (
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Print a warning message to stderr.
|
||||||
|
///
|
||||||
|
/// Takes [`format!`]-compatible input and prepends it with the current
|
||||||
|
/// utility's name and "warning: " before printing to stderr.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[macro_use]
|
||||||
|
/// # extern crate uucore;
|
||||||
|
/// # fn main() {
|
||||||
|
/// // outputs <name>: warning: Couldn't apply foo to bar
|
||||||
|
/// show_warning!("Couldn't apply {} to {}", "foo", "bar");
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! show_warning(
|
macro_rules! show_warning(
|
||||||
($($args:tt)+) => ({
|
($($args:tt)+) => ({
|
||||||
|
@ -57,6 +199,21 @@ macro_rules! show_warning(
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Show a bad invocation help message in a similar style to GNU coreutils.
|
/// Show a bad invocation help message in a similar style to GNU coreutils.
|
||||||
|
///
|
||||||
|
/// Takes a [`format!`]-compatible input and prepends it with the current
|
||||||
|
/// utility's name before printing to stderr.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[macro_use]
|
||||||
|
/// # extern crate uucore;
|
||||||
|
/// # fn main() {
|
||||||
|
/// // outputs <name>: Couldn't apply foo to bar
|
||||||
|
/// // Try '<name> --help' for more information.
|
||||||
|
/// show_usage_error!("Couldn't apply {} to {}", "foo", "bar");
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! show_usage_error(
|
macro_rules! show_usage_error(
|
||||||
($($args:tt)+) => ({
|
($($args:tt)+) => ({
|
||||||
|
@ -68,7 +225,9 @@ macro_rules! show_usage_error(
|
||||||
|
|
||||||
//====
|
//====
|
||||||
|
|
||||||
/// Calls `exit()` with the provided exit code.
|
/// Calls [`std::process::exit`] with the provided exit code.
|
||||||
|
///
|
||||||
|
/// Why not call exit directly?
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! exit(
|
macro_rules! exit(
|
||||||
($exit_code:expr) => ({
|
($exit_code:expr) => ({
|
||||||
|
@ -76,7 +235,22 @@ macro_rules! exit(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Display the provided error message, then `exit()` with the provided exit code
|
/// Display an error and [`exit!`]
|
||||||
|
///
|
||||||
|
/// Displays the provided error message using [`show_error!`], then invokes
|
||||||
|
/// [`exit!`] with the provided exit code.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # #[macro_use]
|
||||||
|
/// # extern crate uucore;
|
||||||
|
/// # fn main() {
|
||||||
|
/// // outputs <name>: Couldn't apply foo to bar
|
||||||
|
/// // and terminates execution
|
||||||
|
/// crash!(1, "Couldn't apply {} to {}", "foo", "bar");
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! crash(
|
macro_rules! crash(
|
||||||
($exit_code:expr, $($args:tt)+) => ({
|
($exit_code:expr, $($args:tt)+) => ({
|
||||||
|
@ -85,8 +259,26 @@ macro_rules! crash(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Unwraps the Result. Instead of panicking, it exists the program with the
|
/// Unwrap a [`std::result::Result`], crashing instead of panicking.
|
||||||
/// provided exit code.
|
///
|
||||||
|
/// If the result is an `Ok`-variant, returns the value contained inside. If it
|
||||||
|
/// is an `Err`-variant, invokes [`crash!`] with the formatted error instead.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # #[macro_use]
|
||||||
|
/// # extern crate uucore;
|
||||||
|
/// # fn main() {
|
||||||
|
/// let is_ok: Result<u32, &str> = Ok(1);
|
||||||
|
/// // Does nothing
|
||||||
|
/// crash_if_err!(1, is_ok);
|
||||||
|
///
|
||||||
|
/// let is_err: Result<u32, &str> = Err("This didn't work...");
|
||||||
|
/// // Calls `crash!`
|
||||||
|
/// crash_if_err!(1, is_err);
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! crash_if_err(
|
macro_rules! crash_if_err(
|
||||||
($exit_code:expr, $exp:expr) => (
|
($exit_code:expr, $exp:expr) => (
|
||||||
|
@ -97,18 +289,42 @@ macro_rules! crash_if_err(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
//====
|
/// Unwrap some Result, crashing instead of panicking.
|
||||||
|
///
|
||||||
|
/// Drop this in favor of `crash_if_err!`
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! safe_write(
|
macro_rules! safe_unwrap(
|
||||||
($fd:expr, $($args:tt)+) => (
|
($exp:expr) => (
|
||||||
match write!($fd, $($args)+) {
|
match $exp {
|
||||||
Ok(_) => {}
|
Ok(m) => m,
|
||||||
Err(f) => panic!("{}", f)
|
Err(f) => $crate::crash!(1, "{}", f.to_string())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//====
|
||||||
|
|
||||||
|
/// Unwraps the Result. Instead of panicking, it shows the error and then
|
||||||
|
/// returns from the function with the provided exit code.
|
||||||
|
/// Assumes the current function returns an i32 value.
|
||||||
|
///
|
||||||
|
/// Replace with `crash_if_err`?
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! return_if_err(
|
||||||
|
($exit_code:expr, $exp:expr) => (
|
||||||
|
match $exp {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(f) => {
|
||||||
|
$crate::show_error!("{}", f);
|
||||||
|
return $exit_code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
//====
|
||||||
|
|
||||||
|
/// This is used exclusively by du...
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! safe_writeln(
|
macro_rules! safe_writeln(
|
||||||
($fd:expr, $($args:tt)+) => (
|
($fd:expr, $($args:tt)+) => (
|
||||||
|
@ -123,6 +339,7 @@ macro_rules! safe_writeln(
|
||||||
|
|
||||||
//-- message templates : (join utility sub-macros)
|
//-- message templates : (join utility sub-macros)
|
||||||
|
|
||||||
|
// used only by "cut"
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! snippet_list_join_oxford_comma {
|
macro_rules! snippet_list_join_oxford_comma {
|
||||||
($conjunction:expr, $valOne:expr, $valTwo:expr) => (
|
($conjunction:expr, $valOne:expr, $valTwo:expr) => (
|
||||||
|
@ -133,6 +350,7 @@ macro_rules! snippet_list_join_oxford_comma {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used only by "cut"
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! snippet_list_join {
|
macro_rules! snippet_list_join {
|
||||||
($conjunction:expr, $valOne:expr, $valTwo:expr) => (
|
($conjunction:expr, $valOne:expr, $valTwo:expr) => (
|
||||||
|
@ -167,6 +385,7 @@ macro_rules! msg_invalid_opt_use {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only used by "cut"
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! msg_opt_only_usable_if {
|
macro_rules! msg_opt_only_usable_if {
|
||||||
($clause:expr, $flag:expr) => {
|
($clause:expr, $flag:expr) => {
|
||||||
|
@ -181,6 +400,7 @@ macro_rules! msg_opt_only_usable_if {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used only by "cut"
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! msg_opt_invalid_should_be {
|
macro_rules! msg_opt_invalid_should_be {
|
||||||
($expects:expr, $received:expr, $flag:expr) => {
|
($expects:expr, $received:expr, $flag:expr) => {
|
||||||
|
@ -200,6 +420,7 @@ macro_rules! msg_opt_invalid_should_be {
|
||||||
|
|
||||||
// -- message templates : invalid input : input combinations
|
// -- message templates : invalid input : input combinations
|
||||||
|
|
||||||
|
// UNUSED!
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! msg_expects_one_of {
|
macro_rules! msg_expects_one_of {
|
||||||
($valOne:expr $(, $remaining_values:expr)*) => (
|
($valOne:expr $(, $remaining_values:expr)*) => (
|
||||||
|
@ -207,6 +428,7 @@ macro_rules! msg_expects_one_of {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used only by "cut"
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! msg_expects_no_more_than_one_of {
|
macro_rules! msg_expects_no_more_than_one_of {
|
||||||
($valOne:expr $(, $remaining_values:expr)*) => (
|
($valOne:expr $(, $remaining_values:expr)*) => (
|
||||||
|
|
|
@ -541,7 +541,7 @@ fn test_no_operands() {
|
||||||
.arg("777")
|
.arg("777")
|
||||||
.fails()
|
.fails()
|
||||||
.code_is(1)
|
.code_is(1)
|
||||||
.stderr_is("chmod: missing operand");
|
.usage_error("missing operand");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
// spell-checker:ignore (words) bamf chdir
|
// spell-checker:ignore (words) bamf chdir rlimit prlimit COMSPEC
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -80,6 +77,20 @@ fn test_combined_file_set_unset() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unset_invalid_variables() {
|
||||||
|
use uucore::display::Quotable;
|
||||||
|
|
||||||
|
// Cannot test input with \0 in it, since output will also contain \0. rlimit::prlimit fails
|
||||||
|
// with this error: Error { kind: InvalidInput, message: "nul byte found in provided data" }
|
||||||
|
for var in &["", "a=b"] {
|
||||||
|
new_ucmd!().arg("-u").arg(var).run().stderr_only(format!(
|
||||||
|
"env: cannot unset {}: Invalid argument",
|
||||||
|
var.quote()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_name_value_pair() {
|
fn test_single_name_value_pair() {
|
||||||
let out = new_ucmd!().arg("FOO=bar").run();
|
let out = new_ucmd!().arg("FOO=bar").run();
|
||||||
|
@ -163,7 +174,7 @@ fn test_fail_null_with_program() {
|
||||||
fn test_change_directory() {
|
fn test_change_directory() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let temporary_directory = tempdir().unwrap();
|
let temporary_directory = tempdir().unwrap();
|
||||||
let temporary_path = fs::canonicalize(temporary_directory.path()).unwrap();
|
let temporary_path = std::fs::canonicalize(temporary_directory.path()).unwrap();
|
||||||
assert_ne!(env::current_dir().unwrap(), temporary_path);
|
assert_ne!(env::current_dir().unwrap(), temporary_path);
|
||||||
|
|
||||||
// command to print out current working directory
|
// command to print out current working directory
|
||||||
|
@ -179,27 +190,36 @@ fn test_change_directory() {
|
||||||
assert_eq!(out.trim(), temporary_path.as_os_str())
|
assert_eq!(out.trim(), temporary_path.as_os_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
// no way to consistently get "current working directory", `cd` doesn't work @ CI
|
|
||||||
// instead, we test that the unique temporary directory appears somewhere in the printed variables
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_change_directory() {
|
fn test_change_directory() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let temporary_directory = tempdir().unwrap();
|
let temporary_directory = tempdir().unwrap();
|
||||||
let temporary_path = temporary_directory.path();
|
|
||||||
|
|
||||||
assert_ne!(env::current_dir().unwrap(), temporary_path);
|
let temporary_path = temporary_directory.path();
|
||||||
|
let temporary_path = temporary_path
|
||||||
|
.strip_prefix(r"\\?\")
|
||||||
|
.unwrap_or(temporary_path);
|
||||||
|
|
||||||
|
let env_cd = env::current_dir().unwrap();
|
||||||
|
let env_cd = env_cd.strip_prefix(r"\\?\").unwrap_or(&env_cd);
|
||||||
|
|
||||||
|
assert_ne!(env_cd, temporary_path);
|
||||||
|
|
||||||
|
// COMSPEC is a variable that contains the full path to cmd.exe
|
||||||
|
let cmd_path = env::var("COMSPEC").unwrap();
|
||||||
|
|
||||||
|
// command to print out current working directory
|
||||||
|
let pwd = [&*cmd_path, "/C", "cd"];
|
||||||
|
|
||||||
let out = scene
|
let out = scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("--chdir")
|
.arg("--chdir")
|
||||||
.arg(&temporary_path)
|
.arg(&temporary_path)
|
||||||
|
.args(&pwd)
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_move_str();
|
.stdout_move_str();
|
||||||
|
assert_eq!(out.trim(), temporary_path.as_os_str())
|
||||||
assert!(!out
|
|
||||||
.lines()
|
|
||||||
.any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -314,3 +314,39 @@ fn test_rm_verbose_slash() {
|
||||||
assert!(!at.dir_exists(dir));
|
assert!(!at.dir_exists(dir));
|
||||||
assert!(!at.file_exists(file_a));
|
assert!(!at.file_exists(file_a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rm_silently_accepts_presume_input_tty1() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file_1 = "test_rm_silently_accepts_presume_input_tty1";
|
||||||
|
|
||||||
|
at.touch(file_1);
|
||||||
|
|
||||||
|
ucmd.arg("--presume-input-tty").arg(file_1).succeeds();
|
||||||
|
|
||||||
|
assert!(!at.file_exists(file_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rm_silently_accepts_presume_input_tty2() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file_2 = "test_rm_silently_accepts_presume_input_tty2";
|
||||||
|
|
||||||
|
at.touch(file_2);
|
||||||
|
|
||||||
|
ucmd.arg("---presume-input-tty").arg(file_2).succeeds();
|
||||||
|
|
||||||
|
assert!(!at.file_exists(file_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rm_silently_accepts_presume_input_tty3() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file_3 = "test_rm_silently_accepts_presume_input_tty3";
|
||||||
|
|
||||||
|
at.touch(file_3);
|
||||||
|
|
||||||
|
ucmd.arg("----presume-input-tty").arg(file_3).succeeds();
|
||||||
|
|
||||||
|
assert!(!at.file_exists(file_3));
|
||||||
|
}
|
||||||
|
|
|
@ -358,6 +358,36 @@ fn test_positive_lines() {
|
||||||
.stdout_is("c\nd\ne\n");
|
.stdout_is("c\nd\ne\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test for reading all but the first NUM lines: `tail -3`.
|
||||||
|
#[test]
|
||||||
|
fn test_obsolete_syntax_positive_lines() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-3"])
|
||||||
|
.pipe_in("a\nb\nc\nd\ne\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("c\nd\ne\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test for reading all but the first NUM lines: `tail -n -10`.
|
||||||
|
#[test]
|
||||||
|
fn test_small_file() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-n -10"])
|
||||||
|
.pipe_in("a\nb\nc\nd\ne\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("a\nb\nc\nd\ne\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test for reading all but the first NUM lines: `tail -10`.
|
||||||
|
#[test]
|
||||||
|
fn test_obsolete_syntax_small_file() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-10"])
|
||||||
|
.pipe_in("a\nb\nc\nd\ne\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("a\nb\nc\nd\ne\n");
|
||||||
|
}
|
||||||
|
|
||||||
/// Test for reading all lines, specified by `tail -n +0`.
|
/// Test for reading all lines, specified by `tail -n +0`.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_positive_zero_lines() {
|
fn test_positive_zero_lines() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue