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

Merge branch 'master' of github.com:backwaterred/coreutils

This commit is contained in:
Tyler 2021-07-21 17:33:18 -07:00
commit 95d9f03cff
18 changed files with 724 additions and 184 deletions

View file

@ -17,7 +17,7 @@ search the issues to make sure no one else is working on it.
## Best practices ## Best practices
1. Follow what GNU is doing in term of options and behavior. 1. Follow what GNU is doing in terms of options and behavior. It is recommended to look at the GNU Coreutils manual ([on the web](https://www.gnu.org/software/coreutils/manual/html_node/index.html), or locally using `info <utility>`). It is more in depth than the man pages and provides a good description of available features and their implementation details.
1. If possible, look at the GNU test suite execution in the CI and make the test work if failing. 1. If possible, look at the GNU test suite execution in the CI and make the test work if failing.
1. Use clap for argument management. 1. Use clap for argument management.
1. Make sure that the code coverage is covering all of the cases, including errors. 1. Make sure that the code coverage is covering all of the cases, including errors.

3
Cargo.lock generated
View file

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3
[[package]] [[package]]
name = "Inflector" name = "Inflector"
version = "0.11.4" version = "0.11.4"
@ -2811,6 +2813,7 @@ dependencies = [
name = "uucore" name = "uucore"
version = "0.0.9" version = "0.0.9"
dependencies = [ dependencies = [
"clap",
"data-encoding", "data-encoding",
"dns-lookup", "dns-lookup",
"dunce", "dunce",

View file

@ -14,6 +14,8 @@ use uucore::fs::resolve_relative_path;
use uucore::libc::{gid_t, uid_t}; use uucore::libc::{gid_t, uid_t};
use uucore::perms::{wrap_chown, Verbosity}; use uucore::perms::{wrap_chown, Verbosity};
use uucore::error::{FromIo, UError, UResult, USimpleError};
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use walkdir::WalkDir; use walkdir::WalkDir;
@ -66,7 +68,8 @@ fn get_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();
@ -104,8 +107,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if recursive { if recursive {
if bit_flag == FTS_PHYSICAL { if bit_flag == FTS_PHYSICAL {
if derefer == 1 { if derefer == 1 {
show_error!("-R --dereference requires -H or -L"); return Err(USimpleError::new(
return 1; 1,
"-R --dereference requires -H or -L".to_string(),
));
} }
derefer = 0; derefer = 0;
} }
@ -126,15 +131,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}; };
let filter = if let Some(spec) = matches.value_of(options::FROM) { let filter = if let Some(spec) = matches.value_of(options::FROM) {
match parse_spec(spec) { match parse_spec(spec)? {
Ok((Some(uid), None)) => IfFrom::User(uid), (Some(uid), None) => IfFrom::User(uid),
Ok((None, Some(gid))) => IfFrom::Group(gid), (None, Some(gid)) => IfFrom::Group(gid),
Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid), (Some(uid), Some(gid)) => IfFrom::UserGroup(uid, gid),
Ok((None, None)) => IfFrom::All, (None, None) => IfFrom::All,
Err(e) => {
show_error!("{}", e);
return 1;
}
} }
} else { } else {
IfFrom::All IfFrom::All
@ -143,27 +144,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let dest_uid: Option<u32>; let dest_uid: Option<u32>;
let dest_gid: Option<u32>; let dest_gid: Option<u32>;
if let Some(file) = matches.value_of(options::REFERENCE) { if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) { let meta = fs::metadata(&file)
Ok(meta) => { .map_err_context(|| format!("failed to get attributes of '{}'", file))?;
dest_gid = Some(meta.gid()); dest_gid = Some(meta.gid());
dest_uid = Some(meta.uid()); dest_uid = Some(meta.uid());
}
Err(e) => {
show_error!("failed to get attributes of '{}': {}", file, e);
return 1;
}
}
} else { } else {
match parse_spec(owner) { let (u, g) = parse_spec(owner)?;
Ok((u, g)) => { dest_uid = u;
dest_uid = u; dest_gid = g;
dest_gid = g;
}
Err(e) => {
show_error!("{}", e);
return 1;
}
}
} }
let executor = Chowner { let executor = Chowner {
bit_flag, bit_flag,
@ -275,7 +263,7 @@ pub fn uu_app() -> App<'static, 'static> {
) )
} }
fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> { fn parse_spec(spec: &str) -> UResult<(Option<u32>, Option<u32>)> {
let args = spec.split_terminator(':').collect::<Vec<_>>(); let args = spec.split_terminator(':').collect::<Vec<_>>();
let usr_only = args.len() == 1 && !args[0].is_empty(); let usr_only = args.len() == 1 && !args[0].is_empty();
let grp_only = args.len() == 2 && args[0].is_empty(); let grp_only = args.len() == 2 && args[0].is_empty();
@ -283,7 +271,7 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
let uid = if usr_only || usr_grp { let uid = if usr_only || usr_grp {
Some( Some(
Passwd::locate(args[0]) Passwd::locate(args[0])
.map_err(|_| format!("invalid user: '{}'", spec))? .map_err(|_| USimpleError::new(1, format!("invalid user: '{}'", spec)))?
.uid(), .uid(),
) )
} else { } else {
@ -292,7 +280,7 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
let gid = if grp_only || usr_grp { let gid = if grp_only || usr_grp {
Some( Some(
Group::locate(args[1]) Group::locate(args[1])
.map_err(|_| format!("invalid group: '{}'", spec))? .map_err(|_| USimpleError::new(1, format!("invalid group: '{}'", spec)))?
.gid(), .gid(),
) )
} else { } else {
@ -330,12 +318,15 @@ macro_rules! unwrap {
} }
impl Chowner { impl Chowner {
fn exec(&self) -> i32 { fn exec(&self) -> UResult<()> {
let mut ret = 0; let mut ret = 0;
for f in &self.files { for f in &self.files {
ret |= self.traverse(f); ret |= self.traverse(f);
} }
ret if ret != 0 {
return Err(UError::from(ret));
}
Ok(())
} }
fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 { fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 {

View file

@ -98,6 +98,9 @@ quick_error! {
/// path, but those that are not implemented yet should return /// path, but those that are not implemented yet should return
/// a NotImplemented error. /// a NotImplemented error.
NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) } NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) }
/// Invalid arguments to backup
Backup(description: String) { display("{}\nTry 'cp --help' for more information.", description) }
} }
} }
@ -359,7 +362,6 @@ pub fn uu_app() -> App<'static, 'static> {
.takes_value(true) .takes_value(true)
.require_equals(true) .require_equals(true)
.min_values(0) .min_values(0)
.possible_values(backup_control::BACKUP_CONTROL_VALUES)
.value_name("CONTROL") .value_name("CONTROL")
) )
.arg(Arg::with_name(options::BACKUP_NO_ARG) .arg(Arg::with_name(options::BACKUP_NO_ARG)
@ -604,9 +606,17 @@ impl Options {
|| matches.is_present(options::ARCHIVE); || matches.is_present(options::ARCHIVE);
let backup_mode = backup_control::determine_backup_mode( let backup_mode = backup_control::determine_backup_mode(
matches.is_present(options::BACKUP_NO_ARG) || matches.is_present(options::BACKUP), matches.is_present(options::BACKUP_NO_ARG),
matches.is_present(options::BACKUP),
matches.value_of(options::BACKUP), matches.value_of(options::BACKUP),
); );
let backup_mode = match backup_mode {
Err(err) => {
return Err(Error::Backup(err));
}
Ok(mode) => mode,
};
let backup_suffix = let backup_suffix =
backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX)); backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX));

View file

@ -6,6 +6,9 @@
// 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.
// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
@ -13,6 +16,7 @@ use clap::{crate_version, App, Arg};
use std::io::{self, Write}; use std::io::{self, Write};
use std::iter::Peekable; use std::iter::Peekable;
use std::str::Chars; use std::str::Chars;
use uucore::error::{FromIo, UResult};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
const NAME: &str = "echo"; const NAME: &str = "echo";
@ -113,7 +117,8 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result<bool> {
Ok(should_stop) Ok(should_stop)
} }
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::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
@ -126,13 +131,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
None => vec!["".to_string()], None => vec!["".to_string()],
}; };
match execute(no_newline, escaped, values) { execute(no_newline, escaped, values).map_err_context(|| "could not write to stdout".to_string())
Ok(_) => 0,
Err(f) => {
show_error!("{}", f);
1
}
}
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {

View file

@ -5,12 +5,20 @@
// * 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.
// Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
#[macro_use]
extern crate uucore;
use clap::App; use clap::App;
use uucore::error::{UError, UResult};
use uucore::executable; use uucore::executable;
pub fn uumain(args: impl uucore::Args) -> i32 { #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uu_app().get_matches_from(args); uu_app().get_matches_from(args);
1 Err(UError::from(1))
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {

View file

@ -308,15 +308,25 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
None None
}; };
let backup_mode = backup_control::determine_backup_mode(
matches.is_present(OPT_BACKUP_NO_ARG),
matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP),
);
let backup_mode = match backup_mode {
Err(err) => {
show_usage_error!("{}", err);
return Err(1);
}
Ok(mode) => mode,
};
let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned()); let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned());
Ok(Behavior { Ok(Behavior {
main_function, main_function,
specified_mode, specified_mode,
backup_mode: backup_control::determine_backup_mode( backup_mode,
matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP),
),
suffix: backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)), suffix: backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)),
owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(), owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(),
group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(), group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(),

View file

@ -7,20 +7,21 @@
// spell-checker:ignore (ToDO) signalname pids // spell-checker:ignore (ToDO) signalname pids
// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use libc::{c_int, pid_t}; use libc::{c_int, pid_t};
use std::io::Error; use std::io::Error;
use uucore::error::{UResult, USimpleError};
use uucore::signals::ALL_SIGNALS; use uucore::signals::ALL_SIGNALS;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static ABOUT: &str = "Send signal to processes or list information about signals."; static ABOUT: &str = "Send signal to processes or list information about signals.";
static EXIT_OK: i32 = 0;
static EXIT_ERR: i32 = 1;
pub mod options { pub mod options {
pub static PIDS_OR_SIGNALS: &str = "pids_of_signals"; pub static PIDS_OR_SIGNALS: &str = "pids_of_signals";
pub static LIST: &str = "list"; pub static LIST: &str = "list";
@ -36,7 +37,8 @@ pub enum Mode {
List, List,
} }
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();
@ -66,13 +68,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
(None, Some(s)) => s.to_owned(), (None, Some(s)) => s.to_owned(),
(None, None) => "TERM".to_owned(), (None, None) => "TERM".to_owned(),
}; };
return kill(&sig, &pids_or_signals); kill(&sig, &pids_or_signals)
}
Mode::Table => {
table();
Ok(())
} }
Mode::Table => table(),
Mode::List => list(pids_or_signals.get(0).cloned()), Mode::List => list(pids_or_signals.get(0).cloned()),
} }
EXIT_OK
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
@ -139,20 +142,23 @@ fn table() {
println!(); println!();
} }
} }
println!() println!();
} }
fn print_signal(signal_name_or_value: &str) { fn print_signal(signal_name_or_value: &str) -> UResult<()> {
for (value, &signal) in ALL_SIGNALS.iter().enumerate() { for (value, &signal) in ALL_SIGNALS.iter().enumerate() {
if signal == signal_name_or_value || (format!("SIG{}", signal)) == signal_name_or_value { if signal == signal_name_or_value || (format!("SIG{}", signal)) == signal_name_or_value {
println!("{}", value); println!("{}", value);
exit!(EXIT_OK as i32) return Ok(());
} else if signal_name_or_value == value.to_string() { } else if signal_name_or_value == value.to_string() {
println!("{}", signal); println!("{}", signal);
exit!(EXIT_OK as i32) return Ok(());
} }
} }
crash!(EXIT_ERR, "unknown signal name {}", signal_name_or_value) Err(USimpleError::new(
1,
format!("unknown signal name {}", signal_name_or_value),
))
} }
fn print_signals() { fn print_signals() {
@ -170,30 +176,41 @@ fn print_signals() {
} }
} }
fn list(arg: Option<String>) { fn list(arg: Option<String>) -> UResult<()> {
match arg { match arg {
Some(ref x) => print_signal(x), Some(ref x) => print_signal(x),
None => print_signals(), None => {
}; print_signals();
Ok(())
}
}
} }
fn kill(signalname: &str, pids: &[String]) -> i32 { fn kill(signalname: &str, pids: &[String]) -> UResult<()> {
let mut status = 0;
let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname); let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname);
let signal_value = match optional_signal_value { let signal_value = match optional_signal_value {
Some(x) => x, Some(x) => x,
None => crash!(EXIT_ERR, "unknown signal name {}", signalname), None => {
return Err(USimpleError::new(
1,
format!("unknown signal name {}", signalname),
));
}
}; };
for pid in pids { for pid in pids {
match pid.parse::<usize>() { match pid.parse::<usize>() {
Ok(x) => { Ok(x) => {
if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 { if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 {
show_error!("{}", Error::last_os_error()); show!(USimpleError::new(1, format!("{}", Error::last_os_error())));
status = 1;
} }
} }
Err(e) => crash!(EXIT_ERR, "failed to parse argument {}: {}", pid, e), Err(e) => {
return Err(USimpleError::new(
1,
format!("failed to parse argument {}: {}", pid, e),
));
}
}; };
} }
status Ok(())
} }

View file

@ -22,6 +22,7 @@ use std::os::unix::fs::symlink;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file}; use std::os::windows::fs::{symlink_dir, symlink_file};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uucore::backup_control::{self, BackupMode};
use uucore::fs::{canonicalize, CanonicalizeMode}; use uucore::fs::{canonicalize, CanonicalizeMode};
pub struct Settings { pub struct Settings {
@ -43,14 +44,6 @@ pub enum OverwriteMode {
Force, Force,
} }
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BackupMode {
NoBackup,
SimpleBackup,
NumberedBackup,
ExistingBackup,
}
fn get_usage() -> String { fn get_usage() -> String {
format!( format!(
"{0} [OPTION]... [-T] TARGET LINK_NAME (1st form) "{0} [OPTION]... [-T] TARGET LINK_NAME (1st form)
@ -78,7 +71,7 @@ fn get_long_usage() -> String {
static ABOUT: &str = "change file owner and group"; static ABOUT: &str = "change file owner and group";
mod options { mod options {
pub const B: &str = "b"; pub const BACKUP_NO_ARG: &str = "b";
pub const BACKUP: &str = "backup"; pub const BACKUP: &str = "backup";
pub const FORCE: &str = "force"; pub const FORCE: &str = "force";
pub const INTERACTIVE: &str = "interactive"; pub const INTERACTIVE: &str = "interactive";
@ -99,7 +92,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = uu_app() let matches = uu_app()
.usage(&usage[..]) .usage(&usage[..])
.after_help(&long_usage[..]) .after_help(&*format!(
"{}\n{}",
long_usage,
backup_control::BACKUP_CONTROL_LONG_HELP
))
.get_matches_from(args); .get_matches_from(args);
/* the list of files */ /* the list of files */
@ -118,33 +115,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
OverwriteMode::NoClobber OverwriteMode::NoClobber
}; };
let backup_mode = if matches.is_present(options::B) { let backup_mode = backup_control::determine_backup_mode(
BackupMode::ExistingBackup matches.is_present(options::BACKUP_NO_ARG),
} else if matches.is_present(options::BACKUP) { matches.is_present(options::BACKUP),
match matches.value_of(options::BACKUP) { matches.value_of(options::BACKUP),
None => BackupMode::ExistingBackup, );
Some(mode) => match mode { let backup_mode = match backup_mode {
"simple" | "never" => BackupMode::SimpleBackup, Err(err) => {
"numbered" | "t" => BackupMode::NumberedBackup, show_usage_error!("{}", err);
"existing" | "nil" => BackupMode::ExistingBackup, return 1;
"none" | "off" => BackupMode::NoBackup,
_ => panic!(), // cannot happen as it is managed by clap
},
} }
} else { Ok(mode) => mode,
BackupMode::NoBackup
}; };
let backup_suffix = if matches.is_present(options::SUFFIX) { let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX));
matches.value_of(options::SUFFIX).unwrap()
} else {
"~"
};
let settings = Settings { let settings = Settings {
overwrite: overwrite_mode, overwrite: overwrite_mode,
backup: backup_mode, backup: backup_mode,
suffix: backup_suffix.to_string(), suffix: backup_suffix,
symbolic: matches.is_present(options::SYMBOLIC), symbolic: matches.is_present(options::SYMBOLIC),
relative: matches.is_present(options::RELATIVE), relative: matches.is_present(options::RELATIVE),
target_dir: matches target_dir: matches
@ -162,22 +151,19 @@ pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg(Arg::with_name(options::B).short(options::B).help(
"make a backup of each file that would otherwise be overwritten or \
removed",
))
.arg( .arg(
Arg::with_name(options::BACKUP) Arg::with_name(options::BACKUP)
.long(options::BACKUP) .long(options::BACKUP)
.help( .help("make a backup of each existing destination file")
"make a backup of each file that would otherwise be overwritten \
or removed",
)
.takes_value(true) .takes_value(true)
.possible_values(&[ .require_equals(true)
"simple", "never", "numbered", "t", "existing", "nil", "none", "off", .min_values(0)
]) .value_name("CONTROL"),
.value_name("METHOD"), )
.arg(
Arg::with_name(options::BACKUP_NO_ARG)
.short(options::BACKUP_NO_ARG)
.help("like --backup but does not accept an argument"),
) )
// TODO: opts.arg( // TODO: opts.arg(
// Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \ // Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \

View file

@ -86,9 +86,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let overwrite_mode = determine_overwrite_mode(&matches); let overwrite_mode = determine_overwrite_mode(&matches);
let backup_mode = backup_control::determine_backup_mode( let backup_mode = backup_control::determine_backup_mode(
matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP), matches.is_present(OPT_BACKUP_NO_ARG),
matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP), matches.value_of(OPT_BACKUP),
); );
let backup_mode = match backup_mode {
Err(err) => {
show_usage_error!("{}", err);
return 1;
}
Ok(mode) => mode,
};
if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup {
show_usage_error!("options --backup and --no-clobber are mutually exclusive"); show_usage_error!("options --backup and --no-clobber are mutually exclusive");
@ -135,7 +143,6 @@ pub fn uu_app() -> App<'static, 'static> {
.takes_value(true) .takes_value(true)
.require_equals(true) .require_equals(true)
.min_values(0) .min_values(0)
.possible_values(backup_control::BACKUP_CONTROL_VALUES)
.value_name("CONTROL") .value_name("CONTROL")
) )
.arg( .arg(

View file

@ -5,6 +5,9 @@
// * 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.
// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
@ -13,6 +16,8 @@ use std::env;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uucore::error::{FromIo, UResult, USimpleError};
static ABOUT: &str = "Display the full filename of the current working directory."; static ABOUT: &str = "Display the full filename of the current working directory.";
static OPT_LOGICAL: &str = "logical"; static OPT_LOGICAL: &str = "logical";
static OPT_PHYSICAL: &str = "physical"; static OPT_PHYSICAL: &str = "physical";
@ -36,7 +41,8 @@ fn get_usage() -> String {
format!("{0} [OPTION]... FILE...", executable!()) format!("{0} [OPTION]... FILE...", executable!())
} }
pub fn uumain(args: impl uucore::Args) -> i32 { #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = get_usage(); let usage = get_usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
@ -46,16 +52,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if matches.is_present(OPT_LOGICAL) { if matches.is_present(OPT_LOGICAL) {
println!("{}", logical_path.display()); println!("{}", logical_path.display());
} else { } else {
match absolute_path(&logical_path) { let physical_path = absolute_path(&logical_path)
Ok(physical_path) => println!("{}", physical_path.display()), .map_err_context(|| "failed to get absolute path".to_string())?;
Err(e) => crash!(1, "failed to get absolute path {}", e), println!("{}", physical_path.display());
};
} }
} }
Err(e) => crash!(1, "failed to get current directory {}", e), Err(e) => {
return Err(USimpleError::new(
1,
format!("failed to get current directory {}", e),
))
}
}; };
0 Ok(())
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {

View file

@ -5,12 +5,17 @@
// * 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.
// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use uucore::error::{UResult, USimpleError};
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
static ABOUT: &str = "Pause for NUMBER seconds."; static ABOUT: &str = "Pause for NUMBER seconds.";
@ -32,17 +37,18 @@ fn get_usage() -> String {
) )
} }
pub fn uumain(args: impl uucore::Args) -> i32 { #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = get_usage(); let usage = get_usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
if let Some(values) = matches.values_of(options::NUMBER) { if let Some(values) = matches.values_of(options::NUMBER) {
let numbers = values.collect(); let numbers = values.collect();
sleep(numbers); return sleep(numbers);
} }
0 Ok(())
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
@ -61,15 +67,15 @@ pub fn uu_app() -> App<'static, 'static> {
) )
} }
fn sleep(args: Vec<&str>) { fn sleep(args: Vec<&str>) -> UResult<()> {
let sleep_dur = let sleep_dur =
args.iter().fold( args.iter().try_fold(
Duration::new(0, 0), Duration::new(0, 0),
|result, arg| match uucore::parse_time::from_str(&arg[..]) { |result, arg| match uucore::parse_time::from_str(&arg[..]) {
Ok(m) => m + result, Ok(m) => Ok(m + result),
Err(f) => crash!(1, "{}", f), Err(f) => Err(USimpleError::new(1, f)),
}, },
); )?;
thread::sleep(sleep_dur); thread::sleep(sleep_dur);
Ok(())
} }

View file

@ -5,12 +5,20 @@
// * 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.
// Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]
#[macro_use]
extern crate uucore;
use clap::App; use clap::App;
use uucore::error::UResult;
use uucore::executable; use uucore::executable;
pub fn uumain(args: impl uucore::Args) -> i32 { #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uu_app().get_matches_from(args); uu_app().get_matches_from(args);
0 Ok(())
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {

View file

@ -30,6 +30,10 @@ time = { version="<= 0.1.43", optional=true }
data-encoding = { version="~2.1", optional=true } ## data-encoding: require v2.1; but v2.2.0 breaks the build for MinSRV v1.31.0 data-encoding = { version="~2.1", optional=true } ## data-encoding: require v2.1; but v2.2.0 breaks the build for MinSRV v1.31.0
libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0 libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0
[dev-dependencies]
clap = "2.33.3"
lazy_static = "1.3"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] } winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }

View file

@ -7,19 +7,15 @@ pub static BACKUP_CONTROL_VALUES: &[&str] = &[
"simple", "never", "numbered", "t", "existing", "nil", "none", "off", "simple", "never", "numbered", "t", "existing", "nil", "none", "off",
]; ];
pub static BACKUP_CONTROL_LONG_HELP: &str = "The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX. Here are the version control values: pub static BACKUP_CONTROL_LONG_HELP: &str =
"The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.
The version control method may be selected via the --backup option or through
the VERSION_CONTROL environment variable. Here are the values:
none, off none, off never make backups (even if --backup is given)
never make backups (even if --backup is given) numbered, t make numbered backups
existing, nil numbered if numbered backups exist, simple otherwise
numbered, t simple, never always make simple backups";
make numbered backups
existing, nil
numbered if numbered backups exist, simple otherwise
simple, never
always make simple backups";
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum BackupMode { pub enum BackupMode {
@ -37,35 +33,177 @@ pub fn determine_backup_suffix(supplied_suffix: Option<&str>) -> String {
} }
} }
/// # TODO /// Determine the "mode" for the backup operation to perform, if any.
/// ///
/// This function currently deviates slightly from how the [manual][1] describes /// Parses the backup options according to the [GNU manual][1], and converts
/// that it should work. In particular, the current implementation: /// them to an instance of `BackupMode` for further processing.
/// ///
/// 1. Doesn't strictly respect the order in which to determine the backup type, /// For an explanation of what the arguments mean, refer to the examples below.
/// which is (in order of precedence)
/// 1. Take a valid value to the '--backup' option
/// 2. Take the value of the `VERSION_CONTROL` env var
/// 3. default to 'existing'
/// 2. Doesn't accept abbreviations to the 'backup_option' parameter
/// ///
/// [1]: https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html /// [1]: https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html
pub fn determine_backup_mode(backup_opt_exists: bool, backup_opt: Option<&str>) -> BackupMode { ///
if backup_opt_exists { ///
match backup_opt.map(String::from) { /// # Errors
// default is existing, see: ///
// https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html /// If an argument supplied directly to the long `backup` option, or read in
None => BackupMode::ExistingBackup, /// through the `VERSION CONTROL` env var is ambiguous (i.e. may resolve to
Some(mode) => match &mode[..] { /// multiple backup modes) or invalid, an error is returned. The error contains
"simple" | "never" => BackupMode::SimpleBackup, /// the formatted error string which may then be passed to the
"numbered" | "t" => BackupMode::NumberedBackup, /// [`show_usage_error`] macro.
"existing" | "nil" => BackupMode::ExistingBackup, ///
"none" | "off" => BackupMode::NoBackup, ///
_ => panic!(), // cannot happen as it is managed by clap /// # Examples
}, ///
/// Here's how one would integrate the backup mode determination into an
/// application.
///
/// ```
/// #[macro_use]
/// extern crate uucore;
/// use uucore::backup_control::{self, BackupMode};
/// use clap::{App, Arg};
///
/// fn main() {
/// let OPT_BACKUP: &str = "backup";
/// let OPT_BACKUP_NO_ARG: &str = "b";
/// let matches = App::new("app")
/// .arg(Arg::with_name(OPT_BACKUP_NO_ARG)
/// .short(OPT_BACKUP_NO_ARG))
/// .arg(Arg::with_name(OPT_BACKUP)
/// .long(OPT_BACKUP)
/// .takes_value(true)
/// .require_equals(true)
/// .min_values(0))
/// .get_matches_from(vec![
/// "app", "-b", "--backup=t"
/// ]);
///
/// let backup_mode = backup_control::determine_backup_mode(
/// matches.is_present(OPT_BACKUP_NO_ARG), matches.is_present(OPT_BACKUP),
/// matches.value_of(OPT_BACKUP)
/// );
/// let backup_mode = match backup_mode {
/// Err(err) => {
/// show_usage_error!("{}", err);
/// return;
/// },
/// Ok(mode) => mode,
/// };
/// }
/// ```
///
/// This example shows an ambiguous input, as 'n' may resolve to 4 different
/// backup modes.
///
///
/// ```
/// #[macro_use]
/// extern crate uucore;
/// use uucore::backup_control::{self, BackupMode};
/// use clap::{crate_version, App, Arg, ArgMatches};
///
/// fn main() {
/// let OPT_BACKUP: &str = "backup";
/// let OPT_BACKUP_NO_ARG: &str = "b";
/// let matches = App::new("app")
/// .arg(Arg::with_name(OPT_BACKUP_NO_ARG)
/// .short(OPT_BACKUP_NO_ARG))
/// .arg(Arg::with_name(OPT_BACKUP)
/// .long(OPT_BACKUP)
/// .takes_value(true)
/// .require_equals(true)
/// .min_values(0))
/// .get_matches_from(vec![
/// "app", "-b", "--backup=n"
/// ]);
///
/// let backup_mode = backup_control::determine_backup_mode(
/// matches.is_present(OPT_BACKUP_NO_ARG), matches.is_present(OPT_BACKUP),
/// matches.value_of(OPT_BACKUP)
/// );
/// let backup_mode = match backup_mode {
/// Err(err) => {
/// show_usage_error!("{}", err);
/// return;
/// },
/// Ok(mode) => mode,
/// };
/// }
/// ```
pub fn determine_backup_mode(
short_opt_present: bool,
long_opt_present: bool,
long_opt_value: Option<&str>,
) -> Result<BackupMode, String> {
if long_opt_present {
// Use method to determine the type of backups to make. When this option
// is used but method is not specified, then the value of the
// VERSION_CONTROL environment variable is used. And if VERSION_CONTROL
// is not set, the default backup type is existing.
if let Some(method) = long_opt_value {
// Second argument is for the error string that is returned.
match_method(method, "backup type")
} else if let Ok(method) = env::var("VERSION_CONTROL") {
// Second argument is for the error string that is returned.
match_method(&method, "$VERSION_CONTROL")
} else {
Ok(BackupMode::ExistingBackup)
}
} else if short_opt_present {
// the short form of this option, -b does not accept any argument.
// Using -b is equivalent to using --backup=existing.
Ok(BackupMode::ExistingBackup)
} else {
// No option was present at all
Ok(BackupMode::NoBackup)
}
}
/// Match a backup option string to a `BackupMode`.
///
/// The GNU manual specifies that abbreviations to options are valid as long as
/// they aren't ambiguous. This function matches the given `method` argument
/// against all valid backup options (via `starts_with`), and returns a valid
/// [`BackupMode`] if exactly one backup option matches the `method` given.
///
/// `origin` is required in order to format the generated error message
/// properly, when an error occurs.
///
///
/// # Errors
///
/// If `method` is ambiguous (i.e. may resolve to multiple backup modes) or
/// invalid, an error is returned. The error contains the formatted error string
/// which may then be passed to the [`show_usage_error`] macro.
fn match_method(method: &str, origin: &str) -> Result<BackupMode, String> {
let matches: Vec<&&str> = BACKUP_CONTROL_VALUES
.iter()
.filter(|val| val.starts_with(method))
.collect();
if matches.len() == 1 {
match *matches[0] {
"simple" | "never" => Ok(BackupMode::SimpleBackup),
"numbered" | "t" => Ok(BackupMode::NumberedBackup),
"existing" | "nil" => Ok(BackupMode::ExistingBackup),
"none" | "off" => Ok(BackupMode::NoBackup),
_ => unreachable!(), // cannot happen as we must have exactly one match
// from the list above.
} }
} else { } else {
BackupMode::NoBackup let error_type = if matches.is_empty() {
"invalid"
} else {
"ambiguous"
};
Err(format!(
"{0} argument {1} for {2}
Valid arguments are:
- none, off
- simple, never
- existing, nil
- numbered, t",
error_type, method, origin
))
} }
} }
@ -82,13 +220,13 @@ pub fn get_backup_path(
} }
} }
pub fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf { fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf {
let mut p = path.to_string_lossy().into_owned(); let mut p = path.to_string_lossy().into_owned();
p.push_str(suffix); p.push_str(suffix);
PathBuf::from(p) PathBuf::from(p)
} }
pub fn numbered_backup_path(path: &Path) -> PathBuf { fn numbered_backup_path(path: &Path) -> PathBuf {
for i in 1_u64.. { for i in 1_u64.. {
let path_str = &format!("{}.~{}~", path.to_string_lossy(), i); let path_str = &format!("{}.~{}~", path.to_string_lossy(), i);
let path = Path::new(path_str); let path = Path::new(path_str);
@ -99,7 +237,7 @@ pub fn numbered_backup_path(path: &Path) -> PathBuf {
panic!("cannot create backup") panic!("cannot create backup")
} }
pub fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf { fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf {
let test_path_str = &format!("{}.~1~", path.to_string_lossy()); let test_path_str = &format!("{}.~1~", path.to_string_lossy());
let test_path = Path::new(test_path_str); let test_path = Path::new(test_path_str);
if test_path.exists() { if test_path.exists() {
@ -108,3 +246,210 @@ pub fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf {
simple_backup_path(path, suffix) simple_backup_path(path, suffix)
} }
} }
//
// Tests for this module
//
#[cfg(test)]
mod tests {
use super::*;
use std::env;
// Required to instantiate mutex in shared context
use lazy_static::lazy_static;
use std::sync::Mutex;
// The mutex is required here as by default all tests are run as separate
// threads under the same parent process. As environment variables are
// specific to processes (and thus shared among threads), data races *will*
// occur if no precautions are taken. Thus we have all tests that rely on
// environment variables lock this empty mutex to ensure they don't access
// it concurrently.
lazy_static! {
static ref TEST_MUTEX: Mutex<()> = Mutex::new(());
}
// Environment variable for "VERSION_CONTROL"
static ENV_VERSION_CONTROL: &str = "VERSION_CONTROL";
// Defaults to --backup=existing
#[test]
fn test_backup_mode_short_only() {
let short_opt_present = true;
let long_opt_present = false;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::ExistingBackup);
}
// --backup takes precedence over -b
#[test]
fn test_backup_mode_long_preferred_over_short() {
let short_opt_present = true;
let long_opt_present = true;
let long_opt_value = Some("none");
let _dummy = TEST_MUTEX.lock().unwrap();
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::NoBackup);
}
// --backup can be passed without an argument
#[test]
fn test_backup_mode_long_without_args_no_env() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::ExistingBackup);
}
// --backup can be passed with an argument only
#[test]
fn test_backup_mode_long_with_args() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = Some("simple");
let _dummy = TEST_MUTEX.lock().unwrap();
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::SimpleBackup);
}
// --backup errors on invalid argument
#[test]
fn test_backup_mode_long_with_args_invalid() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = Some("foobar");
let _dummy = TEST_MUTEX.lock().unwrap();
let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value);
assert!(result.is_err());
let text = result.unwrap_err();
assert!(text.contains("invalid argument foobar for backup type"));
}
// --backup errors on ambiguous argument
#[test]
fn test_backup_mode_long_with_args_ambiguous() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = Some("n");
let _dummy = TEST_MUTEX.lock().unwrap();
let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value);
assert!(result.is_err());
let text = result.unwrap_err();
assert!(text.contains("ambiguous argument n for backup type"));
}
// --backup accepts shortened arguments (si for simple)
#[test]
fn test_backup_mode_long_with_arg_shortened() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = Some("si");
let _dummy = TEST_MUTEX.lock().unwrap();
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::SimpleBackup);
}
// -b ignores the "VERSION_CONTROL" environment variable
#[test]
fn test_backup_mode_short_only_ignore_env() {
let short_opt_present = true;
let long_opt_present = false;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
env::set_var(ENV_VERSION_CONTROL, "none");
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::ExistingBackup);
env::remove_var(ENV_VERSION_CONTROL);
}
// --backup can be passed without an argument, but reads env var if existent
#[test]
fn test_backup_mode_long_without_args_with_env() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
env::set_var(ENV_VERSION_CONTROL, "none");
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::NoBackup);
env::remove_var(ENV_VERSION_CONTROL);
}
// --backup errors on invalid VERSION_CONTROL env var
#[test]
fn test_backup_mode_long_with_env_var_invalid() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
env::set_var(ENV_VERSION_CONTROL, "foobar");
let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value);
assert!(result.is_err());
let text = result.unwrap_err();
assert!(text.contains("invalid argument foobar for $VERSION_CONTROL"));
env::remove_var(ENV_VERSION_CONTROL);
}
// --backup errors on ambiguous VERSION_CONTROL env var
#[test]
fn test_backup_mode_long_with_env_var_ambiguous() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
env::set_var(ENV_VERSION_CONTROL, "n");
let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value);
assert!(result.is_err());
let text = result.unwrap_err();
assert!(text.contains("ambiguous argument n for $VERSION_CONTROL"));
env::remove_var(ENV_VERSION_CONTROL);
}
// --backup accepts shortened env vars (si for simple)
#[test]
fn test_backup_mode_long_with_env_var_shortened() {
let short_opt_present = false;
let long_opt_present = true;
let long_opt_value = None;
let _dummy = TEST_MUTEX.lock().unwrap();
env::set_var(ENV_VERSION_CONTROL, "si");
let result =
determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap();
assert_eq!(result, BackupMode::SimpleBackup);
env::remove_var(ENV_VERSION_CONTROL);
}
}

View file

@ -138,6 +138,12 @@ pub enum UError {
} }
impl UError { impl UError {
/// The error code of [`UResult`]
///
/// This function defines the error code associated with an instance of
/// [`UResult`]. To associate error codes for self-defined instances of
/// `UResult::Custom` (i.e. [`UCustomError`]), implement the
/// [`code`-function there](UCustomError::code).
pub fn code(&self) -> i32 { pub fn code(&self) -> i32 {
match self { match self {
UError::Common(e) => e.code(), UError::Common(e) => e.code(),
@ -145,6 +151,13 @@ impl UError {
} }
} }
/// Whether to print usage help for a [`UResult`]
///
/// Defines if a variant of [`UResult`] should print a short usage message
/// below the error. The usage message is printed when this function returns
/// `true`. To do this for self-defined instances of `UResult::Custom` (i.e.
/// [`UCustomError`]), implement the [`usage`-function
/// there](UCustomError::usage).
pub fn usage(&self) -> bool { pub fn usage(&self) -> bool {
match self { match self {
UError::Common(e) => e.usage(), UError::Common(e) => e.usage(),
@ -183,12 +196,13 @@ impl Display for UError {
/// Custom errors defined by the utils. /// Custom errors defined by the utils.
/// ///
/// All errors should implement [`std::error::Error`], [`std::fmt::Display`] and /// All errors should implement [`std::error::Error`], [`std::fmt::Display`] and
/// [`std::fmt::Debug`] and have an additional `code` method that specifies the exit code of the /// [`std::fmt::Debug`] and have an additional `code` method that specifies the
/// program if the error is returned from `uumain`. /// exit code of the program if the error is returned from `uumain`.
/// ///
/// An example of a custom error from `ls`: /// An example of a custom error from `ls`:
///
/// ``` /// ```
/// use uucore::error::{UCustomError}; /// use uucore::error::{UCustomError, UResult};
/// use std::{ /// use std::{
/// error::Error, /// error::Error,
/// fmt::{Display, Debug}, /// fmt::{Display, Debug},
@ -221,13 +235,121 @@ impl Display for UError {
/// } /// }
/// } /// }
/// ``` /// ```
/// A crate like [`quick_error`](https://crates.io/crates/quick-error) might also be used, but will ///
/// still require an `impl` for the `code` method. /// The main routine would look like this:
///
/// ```ignore
/// #[uucore_procs::gen_uumain]
/// pub fn uumain(args: impl uucore::Args) -> UResult<()> {
/// // Perform computations here ...
/// return Err(LsError::InvalidLineWidth(String::from("test")).into())
/// }
/// ```
///
/// The call to `into()` is required to convert the [`UCustomError`] to an
/// instance of [`UError`].
///
/// A crate like [`quick_error`](https://crates.io/crates/quick-error) might
/// also be used, but will still require an `impl` for the `code` method.
pub trait UCustomError: Error { pub trait UCustomError: Error {
/// Error code of a custom error.
///
/// Set a return value for each variant of an enum-type to associate an
/// error code (which is returned to the system shell) with an error
/// variant.
///
/// # Example
///
/// ```
/// use uucore::error::{UCustomError};
/// use std::{
/// error::Error,
/// fmt::{Display, Debug},
/// path::PathBuf
/// };
///
/// #[derive(Debug)]
/// enum MyError {
/// Foo(String),
/// Bar(PathBuf),
/// Bing(),
/// }
///
/// impl UCustomError for MyError {
/// fn code(&self) -> i32 {
/// match self {
/// MyError::Foo(_) => 2,
/// // All other errors yield the same error code, there's no
/// // need to list them explicitly.
/// _ => 1,
/// }
/// }
/// }
///
/// impl Error for MyError {}
///
/// impl Display for MyError {
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
/// use MyError as ME;
/// match self {
/// ME::Foo(s) => write!(f, "Unknown Foo: '{}'", s),
/// ME::Bar(p) => write!(f, "Couldn't find Bar: '{}'", p.display()),
/// ME::Bing() => write!(f, "Exterminate!"),
/// }
/// }
/// }
/// ```
fn code(&self) -> i32 { fn code(&self) -> i32 {
1 1
} }
/// Print usage help to a custom error.
///
/// Return true or false to control whether a short usage help is printed
/// below the error message. The usage help is in the format: "Try '{name}
/// --help' for more information." and printed only if `true` is returned.
///
/// # Example
///
/// ```
/// use uucore::error::{UCustomError};
/// use std::{
/// error::Error,
/// fmt::{Display, Debug},
/// path::PathBuf
/// };
///
/// #[derive(Debug)]
/// enum MyError {
/// Foo(String),
/// Bar(PathBuf),
/// Bing(),
/// }
///
/// impl UCustomError for MyError {
/// fn usage(&self) -> bool {
/// match self {
/// // This will have a short usage help appended
/// MyError::Bar(_) => true,
/// // These matches won't have a short usage help appended
/// _ => false,
/// }
/// }
/// }
///
/// impl Error for MyError {}
///
/// impl Display for MyError {
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
/// use MyError as ME;
/// match self {
/// ME::Foo(s) => write!(f, "Unknown Foo: '{}'", s),
/// ME::Bar(p) => write!(f, "Couldn't find Bar: '{}'", p.display()),
/// ME::Bing() => write!(f, "Exterminate!"),
/// }
/// }
/// }
/// ```
fn usage(&self) -> bool { fn usage(&self) -> bool {
false false
} }

View file

@ -398,8 +398,7 @@ fn test_du_time() {
let result = ts.ucmd().arg("--time=ctime").arg("date_test").succeeds(); let result = ts.ucmd().arg("--time=ctime").arg("date_test").succeeds();
result.stdout_only("0\t2016-06-16 00:00\tdate_test\n"); result.stdout_only("0\t2016-06-16 00:00\tdate_test\n");
#[cfg(not(target_env = "musl"))] if birth_supported() {
{
use regex::Regex; use regex::Regex;
let re_birth = let re_birth =
@ -409,6 +408,16 @@ fn test_du_time() {
} }
} }
#[cfg(feature = "touch")]
fn birth_supported() -> bool {
let ts = TestScenario::new(util_name!());
let m = match std::fs::metadata(ts.fixtures.subdir) {
Ok(m) => m,
Err(e) => panic!("{}", e),
};
m.created().is_ok()
}
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
#[cfg(feature = "chmod")] #[cfg(feature = "chmod")]
#[test] #[test]

View file

@ -110,3 +110,8 @@ fn test_sleep_sum_duration_many() {
let duration = before_test.elapsed(); let duration = before_test.elapsed();
assert!(duration >= millis_900); assert!(duration >= millis_900);
} }
#[test]
fn test_sleep_wrong_time() {
new_ucmd!().args(&["0.1s", "abc"]).fails();
}