mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +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='_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
|
||||
# * CARGO_FEATURES_OPTION
|
||||
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
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use std::fs;
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
use std::path::Path;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{ExitCode, UResult, USimpleError, UUsageError};
|
||||
use uucore::fs::display_permissions_unix;
|
||||
use uucore::libc::mode_t;
|
||||
#[cfg(not(windows))]
|
||||
use uucore::mode;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
use uucore::{show_error, InvalidEncodingHandling};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
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]+'.")
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let mut args = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
|
@ -72,12 +71,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let verbose = matches.is_present(options::VERBOSE);
|
||||
let preserve_root = matches.is_present(options::PRESERVE_ROOT);
|
||||
let recursive = matches.is_present(options::RECURSIVE);
|
||||
let fmode = matches
|
||||
.value_of(options::REFERENCE)
|
||||
.and_then(|fref| match fs::metadata(fref) {
|
||||
let fmode = match matches.value_of(options::REFERENCE) {
|
||||
Some(fref) => match fs::metadata(fref) {
|
||||
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 cmode = if mode_had_minus_prefix {
|
||||
// clap parsing is finished, now put prefix back
|
||||
|
@ -100,7 +105,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
};
|
||||
|
||||
if files.is_empty() {
|
||||
crash!(1, "missing operand");
|
||||
return Err(UUsageError::new(1, "missing operand".to_string()));
|
||||
}
|
||||
|
||||
let chmoder = Chmoder {
|
||||
|
@ -112,12 +117,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
fmode,
|
||||
cmode,
|
||||
};
|
||||
match chmoder.chmod(files) {
|
||||
Ok(()) => {}
|
||||
Err(e) => return e,
|
||||
}
|
||||
|
||||
0
|
||||
chmoder.chmod(files)
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
@ -191,7 +192,7 @@ struct Chmoder {
|
|||
}
|
||||
|
||||
impl Chmoder {
|
||||
fn chmod(&self, files: Vec<String>) -> Result<(), i32> {
|
||||
fn chmod(&self, files: Vec<String>) -> UResult<()> {
|
||||
let mut r = Ok(());
|
||||
|
||||
for filename in &files {
|
||||
|
@ -204,22 +205,30 @@ impl Chmoder {
|
|||
filename.quote()
|
||||
);
|
||||
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 {
|
||||
show_error!(
|
||||
"cannot access {}: No such file or directory",
|
||||
filename.quote()
|
||||
);
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
format!(
|
||||
"cannot access {}: No such file or directory",
|
||||
filename.quote()
|
||||
),
|
||||
));
|
||||
}
|
||||
return Err(1);
|
||||
return Err(ExitCode::new(1));
|
||||
}
|
||||
if self.recursive && self.preserve_root && filename == "/" {
|
||||
show_error!(
|
||||
"it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe",
|
||||
filename.quote()
|
||||
);
|
||||
return Err(1);
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
format!(
|
||||
"it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe",
|
||||
filename.quote()
|
||||
)
|
||||
));
|
||||
}
|
||||
if !self.recursive {
|
||||
r = self.chmod_file(file).and(r);
|
||||
|
@ -234,14 +243,14 @@ impl Chmoder {
|
|||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
|
||||
fn chmod_file(&self, file: &Path) -> UResult<()> {
|
||||
// chmod is useless on Windows
|
||||
// it doesn't set any permissions at all
|
||||
// instead it just sets the readonly attribute on the file
|
||||
Err(0)
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(unix)]
|
||||
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
|
||||
fn chmod_file(&self, file: &Path) -> UResult<()> {
|
||||
use uucore::mode::get_umask;
|
||||
|
||||
let fperm = match fs::metadata(file) {
|
||||
|
@ -258,11 +267,13 @@ impl Chmoder {
|
|||
} else if err.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
// These two filenames would normally be conditionally
|
||||
// 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 {
|
||||
show_error!("{}: {}", file.quote(), err);
|
||||
return Err(USimpleError::new(1, format!("{}: {}", file.quote(), err)));
|
||||
}
|
||||
return Err(1);
|
||||
}
|
||||
};
|
||||
match self.fmode {
|
||||
|
@ -296,22 +307,25 @@ impl Chmoder {
|
|||
}
|
||||
Err(f) => {
|
||||
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)?;
|
||||
// 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 {
|
||||
show_error!(
|
||||
"{}: new permissions are {}, not {}",
|
||||
file.maybe_quote(),
|
||||
display_permissions_unix(new_mode as mode_t, false),
|
||||
display_permissions_unix(naively_expected_new_mode as mode_t, false)
|
||||
);
|
||||
return Err(1);
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
format!(
|
||||
"{}: new permissions are {}, not {}",
|
||||
file.maybe_quote(),
|
||||
display_permissions_unix(new_mode as mode_t, false),
|
||||
display_permissions_unix(naively_expected_new_mode as mode_t, false)
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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.
|
||||
//
|
||||
// (c) Jordi Boggiano <j.boggiano@seld.be>
|
||||
// (c) Thomas Queiroz <thomasqueirozb@gmail.com>
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
@ -23,7 +24,7 @@ use std::io::{self, Write};
|
|||
use std::iter::Iterator;
|
||||
use std::process::Command;
|
||||
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 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 ?
|
||||
if let Some(idx) = opt.find('=') {
|
||||
// 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 {
|
||||
eprintln!(
|
||||
"{}: cannot specify --null (-0) with command",
|
||||
uucore::util_name()
|
||||
);
|
||||
eprintln!(
|
||||
"Type \"{} --help\" for detailed information",
|
||||
uucore::execution_phrase()
|
||||
);
|
||||
Err(1)
|
||||
Err(UUsageError::new(
|
||||
125,
|
||||
"cannot specify --null (-0) with command".to_string(),
|
||||
))
|
||||
} else {
|
||||
opts.program.push(opt);
|
||||
Ok(())
|
||||
|
@ -93,10 +89,8 @@ fn load_config_file(opts: &mut Options) -> UResult<()> {
|
|||
Ini::load_from_file(file)
|
||||
};
|
||||
|
||||
let conf = conf.map_err(|error| {
|
||||
show_error!("{}: {}", file.maybe_quote(), error);
|
||||
1
|
||||
})?;
|
||||
let conf =
|
||||
conf.map_err(|e| USimpleError::new(1, format!("{}: {}", file.maybe_quote(), e)))?;
|
||||
|
||||
for (_, prop) in &conf {
|
||||
// ignore all INI section lines (treat them as comments)
|
||||
|
@ -138,7 +132,7 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.long("ignore-environment")
|
||||
.help("start with an empty environment"))
|
||||
.arg(Arg::with_name("chdir")
|
||||
.short("c")
|
||||
.short("C") // GNU env compatibility
|
||||
.long("chdir")
|
||||
.takes_value(true)
|
||||
.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
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
||||
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 call into other components ;
|
||||
- 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.
|
||||
|
||||
2. Benchmarks are immutable (once merged in `uutils`)
|
||||
|
||||
Modifying a benchmark means previously-collected values cannot meaningfully
|
||||
be compared, silently giving nonsensical results. If you must modify an
|
||||
existing benchmark, rename it.
|
||||
|
||||
3. Test common cases
|
||||
|
||||
We are interested in overall performance, rather than specific edge-cases;
|
||||
use **reproducibly-randomized inputs**, sampling from either all possible
|
||||
input values or some subset of interest.
|
||||
|
||||
4. Use [`criterion`], `criterion::black_box`, ...
|
||||
|
||||
`criterion` isn't perfect, but it is also much better than ad-hoc
|
||||
solutions in each benchmark.
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ pub fn gcd(mut u: u64, mut v: u64) -> u64 {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use quickcheck::quickcheck;
|
||||
use quickcheck::{quickcheck, TestResult};
|
||||
|
||||
quickcheck! {
|
||||
fn euclidean(a: u64, b: u64) -> bool {
|
||||
|
@ -76,13 +76,12 @@ mod tests {
|
|||
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
|
||||
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);
|
||||
assert_eq!(a % g, 0);
|
||||
assert_eq!(b % g, 0);
|
||||
TestResult::from_bool( g != 0 && a % g == 0 && b % g == 0 )
|
||||
}
|
||||
|
||||
fn commutative(a: u64, b: u64) -> bool {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
use clap::{crate_version, App, Arg};
|
||||
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.";
|
||||
|
||||
|
@ -20,7 +21,8 @@ fn usage() -> String {
|
|||
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 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() {
|
||||
print!("{}={}{}", env_var, value, separator);
|
||||
}
|
||||
return 0;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut not_found = false;
|
||||
for env_var in variables {
|
||||
if let Ok(var) = env::var(env_var) {
|
||||
print!("{}{}", var, separator);
|
||||
} else {
|
||||
not_found = true;
|
||||
}
|
||||
}
|
||||
0
|
||||
|
||||
if not_found {
|
||||
Err(1.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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_R: &str = "recursive_R";
|
||||
static OPT_VERBOSE: &str = "verbose";
|
||||
static PRESUME_INPUT_TTY: &str = "presume-input-tty";
|
||||
|
||||
static ARG_FILES: &str = "files";
|
||||
|
||||
|
@ -208,6 +209,17 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.long(OPT_VERBOSE)
|
||||
.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::with_name(ARG_FILES)
|
||||
.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;
|
||||
|
||||
mod chunks;
|
||||
mod parse;
|
||||
mod platform;
|
||||
use chunks::ReverseChunks;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt;
|
||||
use std::fs::{File, Metadata};
|
||||
use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||
use uucore::ringbuffer::RingBuffer;
|
||||
|
||||
|
@ -58,105 +62,122 @@ pub mod options {
|
|||
pub static ARG_FILES: &str = "files";
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FilterMode {
|
||||
Bytes(usize),
|
||||
Lines(usize, u8), // (number of lines, delimiter)
|
||||
}
|
||||
|
||||
impl Default for FilterMode {
|
||||
fn default() -> Self {
|
||||
FilterMode::Lines(10, b'\n')
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Settings {
|
||||
quiet: bool,
|
||||
verbose: bool,
|
||||
mode: FilterMode,
|
||||
sleep_msec: u32,
|
||||
beginning: bool,
|
||||
follow: bool,
|
||||
pid: platform::Pid,
|
||||
files: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
mode: FilterMode::Lines(10, b'\n'),
|
||||
impl Settings {
|
||||
pub fn get_from(args: impl uucore::Args) -> Result<Self, String> {
|
||||
let matches = uu_app().get_matches_from(arg_iterate(args)?);
|
||||
|
||||
let mut settings: Settings = Settings {
|
||||
sleep_msec: 1000,
|
||||
beginning: false,
|
||||
follow: false,
|
||||
pid: 0,
|
||||
follow: matches.is_present(options::FOLLOW),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if settings.follow {
|
||||
if let Some(n) = matches.value_of(options::SLEEP_INT) {
|
||||
let parsed: Option<u32> = n.parse().ok();
|
||||
if let Some(m) = parsed {
|
||||
settings.sleep_msec = m * 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pid_str) = matches.value_of(options::PID) {
|
||||
if let Ok(pid) = pid_str.parse() {
|
||||
settings.pid = pid;
|
||||
if pid != 0 {
|
||||
if !settings.follow {
|
||||
show_warning!("PID ignored; --pid=PID is useful only when following");
|
||||
}
|
||||
|
||||
if !platform::supports_pid_checks(pid) {
|
||||
show_warning!("--pid=PID is not supported on this system");
|
||||
settings.pid = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) {
|
||||
match parse_num(arg) {
|
||||
Ok((n, beginning)) => (FilterMode::Bytes(n), beginning),
|
||||
Err(e) => return Err(format!("invalid number of bytes: {}", e)),
|
||||
}
|
||||
} else if let Some(arg) = matches.value_of(options::LINES) {
|
||||
match parse_num(arg) {
|
||||
Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning),
|
||||
Err(e) => return Err(format!("invalid number of lines: {}", e)),
|
||||
}
|
||||
} else {
|
||||
(FilterMode::Lines(10, b'\n'), false)
|
||||
};
|
||||
settings.mode = mode_and_beginning.0;
|
||||
settings.beginning = mode_and_beginning.1;
|
||||
|
||||
if matches.is_present(options::ZERO_TERM) {
|
||||
if let FilterMode::Lines(count, _) = settings.mode {
|
||||
settings.mode = FilterMode::Lines(count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
settings.verbose = matches.is_present(options::verbosity::VERBOSE);
|
||||
settings.quiet = matches.is_present(options::verbosity::QUIET);
|
||||
|
||||
settings.files = match matches.values_of(options::ARG_FILES) {
|
||||
Some(v) => v.map(|s| s.to_owned()).collect(),
|
||||
None => vec!["-".to_owned()],
|
||||
};
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
}
|
||||
|
||||
#[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 let Some(n) = matches.value_of(options::SLEEP_INT) {
|
||||
let parsed: Option<u32> = n.parse().ok();
|
||||
if let Some(m) = parsed {
|
||||
settings.sleep_msec = m * 1000
|
||||
}
|
||||
#[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));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pid_str) = matches.value_of(options::PID) {
|
||||
if let Ok(pid) = pid_str.parse() {
|
||||
settings.pid = pid;
|
||||
if pid != 0 {
|
||||
if !settings.follow {
|
||||
show_warning!("PID ignored; --pid=PID is useful only when following");
|
||||
}
|
||||
|
||||
if !platform::supports_pid_checks(pid) {
|
||||
show_warning!("--pid=PID is not supported on this system");
|
||||
settings.pid = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) {
|
||||
match parse_num(arg) {
|
||||
Ok((n, beginning)) => (FilterMode::Bytes(n), beginning),
|
||||
Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()),
|
||||
}
|
||||
} else if let Some(arg) = matches.value_of(options::LINES) {
|
||||
match parse_num(arg) {
|
||||
Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning),
|
||||
Err(e) => crash!(1, "invalid number of lines: {}", e.to_string()),
|
||||
}
|
||||
} else {
|
||||
(FilterMode::Lines(10, b'\n'), false)
|
||||
};
|
||||
settings.mode = mode_and_beginning.0;
|
||||
settings.beginning = mode_and_beginning.1;
|
||||
uu_tail(&args)
|
||||
}
|
||||
|
||||
if matches.is_present(options::ZERO_TERM) {
|
||||
if let FilterMode::Lines(count, _) = settings.mode {
|
||||
settings.mode = FilterMode::Lines(count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
let verbose = matches.is_present(options::verbosity::VERBOSE);
|
||||
let quiet = matches.is_present(options::verbosity::QUIET);
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.values_of(options::ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_else(|| vec![String::from("-")]);
|
||||
|
||||
let multiple = files.len() > 1;
|
||||
fn uu_tail(settings: &Settings) -> UResult<()> {
|
||||
let multiple = settings.files.len() > 1;
|
||||
let mut first_header = true;
|
||||
let mut readers: Vec<(Box<dyn BufRead>, &String)> = Vec::new();
|
||||
|
||||
#[cfg(unix)]
|
||||
let stdin_string = String::from("standard input");
|
||||
|
||||
for filename in &files {
|
||||
for filename in &settings.files {
|
||||
let use_stdin = filename.as_str() == "-";
|
||||
if (multiple || verbose) && !quiet {
|
||||
if (multiple || settings.verbose) && !settings.quiet {
|
||||
if !first_header {
|
||||
println!();
|
||||
}
|
||||
|
@ -170,7 +191,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
if use_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
|
||||
//
|
||||
|
@ -202,14 +223,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let mut file = File::open(&path).unwrap();
|
||||
let md = file.metadata().unwrap();
|
||||
if is_seekable(&mut file) && get_block_size(&md) > 0 {
|
||||
bounded_tail(&mut file, &settings);
|
||||
bounded_tail(&mut file, settings);
|
||||
if settings.follow {
|
||||
let reader = BufReader::new(file);
|
||||
readers.push((Box::new(reader), filename));
|
||||
}
|
||||
} else {
|
||||
let mut reader = BufReader::new(file);
|
||||
unbounded_tail(&mut reader, &settings);
|
||||
unbounded_tail(&mut reader, settings);
|
||||
if settings.follow {
|
||||
readers.push((Box::new(reader), filename));
|
||||
}
|
||||
|
@ -218,10 +239,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
|
||||
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> {
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) ttyname hostnames runlevel mesg wtmp statted boottime deadprocs initspawn clockchange curr runlvline pidstr exitstr hoststr
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP};
|
||||
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
|
||||
.collect_str(InvalidEncodingHandling::Ignore)
|
||||
.accept_any();
|
||||
|
@ -157,9 +158,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
args: files,
|
||||
};
|
||||
|
||||
who.exec();
|
||||
|
||||
0
|
||||
who.exec()
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
@ -326,7 +325,7 @@ fn current_tty() -> String {
|
|||
}
|
||||
|
||||
impl Who {
|
||||
fn exec(&mut self) {
|
||||
fn exec(&mut self) -> UResult<()> {
|
||||
let run_level_chk = |_record: i16| {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return false;
|
||||
|
@ -362,7 +361,7 @@ impl Who {
|
|||
for ut in records {
|
||||
if !self.my_line_only || cur_tty == ut.tty_device() {
|
||||
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()) {
|
||||
if cfg!(target_os = "linux") {
|
||||
self.print_runlevel(&ut);
|
||||
|
@ -383,6 +382,7 @@ impl Who {
|
|||
if ut.record_type() == utmpx::BOOT_TIME {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -464,7 +464,7 @@ impl Who {
|
|||
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");
|
||||
p.push(ut.tty_device().as_str());
|
||||
let mesg;
|
||||
|
@ -491,7 +491,13 @@ impl Who {
|
|||
};
|
||||
|
||||
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 {
|
||||
ut.host()
|
||||
};
|
||||
|
@ -507,6 +513,8 @@ impl Who {
|
|||
hoststr.as_str(),
|
||||
"",
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
|
@ -236,17 +236,20 @@ impl Utmpx {
|
|||
flags: AI_CANONNAME,
|
||||
..AddrInfoHints::default()
|
||||
};
|
||||
let sockets = getaddrinfo(Some(hostname), None, Some(hints))
|
||||
.unwrap()
|
||||
.collect::<IOResult<Vec<_>>>()?;
|
||||
for socket in sockets {
|
||||
if let Some(ai_canonname) = socket.canonname {
|
||||
return Ok(if display.is_empty() {
|
||||
ai_canonname
|
||||
} else {
|
||||
format!("{}:{}", ai_canonname, display)
|
||||
});
|
||||
if let Ok(sockets) = getaddrinfo(Some(hostname), None, Some(hints)) {
|
||||
let sockets = sockets.collect::<IOResult<Vec<_>>>()?;
|
||||
for socket in sockets {
|
||||
if let Some(ai_canonname) = socket.canonname {
|
||||
return Ok(if display.is_empty() {
|
||||
ai_canonname
|
||||
} else {
|
||||
format!("{}:{}", ai_canonname, display)
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// GNU coreutils has this behavior
|
||||
return Ok(hostname.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;
|
||||
|
||||
// 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_rules! show(
|
||||
($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_rules! show_if_err(
|
||||
($res:expr) => ({
|
||||
|
@ -31,6 +134,19 @@ macro_rules! show_if_err(
|
|||
);
|
||||
|
||||
/// 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_rules! show_error(
|
||||
($($args:tt)+) => ({
|
||||
|
@ -40,6 +156,17 @@ macro_rules! show_error(
|
|||
);
|
||||
|
||||
/// 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_rules! show_error_custom_description (
|
||||
($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_rules! show_warning(
|
||||
($($args:tt)+) => ({
|
||||
|
@ -57,6 +199,21 @@ macro_rules! show_warning(
|
|||
);
|
||||
|
||||
/// 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_rules! show_usage_error(
|
||||
($($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_rules! exit(
|
||||
($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_rules! crash(
|
||||
($exit_code:expr, $($args:tt)+) => ({
|
||||
|
@ -85,8 +259,26 @@ macro_rules! crash(
|
|||
})
|
||||
);
|
||||
|
||||
/// Unwraps the Result. Instead of panicking, it exists the program with the
|
||||
/// provided exit code.
|
||||
/// Unwrap a [`std::result::Result`], crashing instead of panicking.
|
||||
///
|
||||
/// 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_rules! crash_if_err(
|
||||
($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_rules! safe_write(
|
||||
($fd:expr, $($args:tt)+) => (
|
||||
match write!($fd, $($args)+) {
|
||||
Ok(_) => {}
|
||||
Err(f) => panic!("{}", f)
|
||||
macro_rules! safe_unwrap(
|
||||
($exp:expr) => (
|
||||
match $exp {
|
||||
Ok(m) => m,
|
||||
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_rules! safe_writeln(
|
||||
($fd:expr, $($args:tt)+) => (
|
||||
|
@ -123,6 +339,7 @@ macro_rules! safe_writeln(
|
|||
|
||||
//-- message templates : (join utility sub-macros)
|
||||
|
||||
// used only by "cut"
|
||||
#[macro_export]
|
||||
macro_rules! snippet_list_join_oxford_comma {
|
||||
($conjunction:expr, $valOne:expr, $valTwo:expr) => (
|
||||
|
@ -133,6 +350,7 @@ macro_rules! snippet_list_join_oxford_comma {
|
|||
);
|
||||
}
|
||||
|
||||
// used only by "cut"
|
||||
#[macro_export]
|
||||
macro_rules! snippet_list_join {
|
||||
($conjunction:expr, $valOne:expr, $valTwo:expr) => (
|
||||
|
@ -167,6 +385,7 @@ macro_rules! msg_invalid_opt_use {
|
|||
};
|
||||
}
|
||||
|
||||
// Only used by "cut"
|
||||
#[macro_export]
|
||||
macro_rules! msg_opt_only_usable_if {
|
||||
($clause:expr, $flag:expr) => {
|
||||
|
@ -181,6 +400,7 @@ macro_rules! msg_opt_only_usable_if {
|
|||
};
|
||||
}
|
||||
|
||||
// Used only by "cut"
|
||||
#[macro_export]
|
||||
macro_rules! msg_opt_invalid_should_be {
|
||||
($expects:expr, $received:expr, $flag:expr) => {
|
||||
|
@ -200,6 +420,7 @@ macro_rules! msg_opt_invalid_should_be {
|
|||
|
||||
// -- message templates : invalid input : input combinations
|
||||
|
||||
// UNUSED!
|
||||
#[macro_export]
|
||||
macro_rules! msg_expects_one_of {
|
||||
($valOne:expr $(, $remaining_values:expr)*) => (
|
||||
|
@ -207,6 +428,7 @@ macro_rules! msg_expects_one_of {
|
|||
);
|
||||
}
|
||||
|
||||
// Used only by "cut"
|
||||
#[macro_export]
|
||||
macro_rules! msg_expects_no_more_than_one_of {
|
||||
($valOne:expr $(, $remaining_values:expr)*) => (
|
||||
|
|
|
@ -541,7 +541,7 @@ fn test_no_operands() {
|
|||
.arg("777")
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_is("chmod: missing operand");
|
||||
.usage_error("missing operand");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
// spell-checker:ignore (words) bamf chdir
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use std::fs;
|
||||
// spell-checker:ignore (words) bamf chdir rlimit prlimit COMSPEC
|
||||
|
||||
use crate::common::util::*;
|
||||
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]
|
||||
fn test_single_name_value_pair() {
|
||||
let out = new_ucmd!().arg("FOO=bar").run();
|
||||
|
@ -163,7 +174,7 @@ fn test_fail_null_with_program() {
|
|||
fn test_change_directory() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
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);
|
||||
|
||||
// command to print out current working directory
|
||||
|
@ -179,27 +190,36 @@ fn test_change_directory() {
|
|||
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)]
|
||||
#[test]
|
||||
fn test_change_directory() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
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
|
||||
.ucmd()
|
||||
.arg("--chdir")
|
||||
.arg(&temporary_path)
|
||||
.args(&pwd)
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
|
||||
assert!(!out
|
||||
.lines()
|
||||
.any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap())));
|
||||
assert_eq!(out.trim(), temporary_path.as_os_str())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -314,3 +314,39 @@ fn test_rm_verbose_slash() {
|
|||
assert!(!at.dir_exists(dir));
|
||||
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");
|
||||
}
|
||||
|
||||
/// 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]
|
||||
fn test_positive_zero_lines() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue