mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge branch 'master' of github.com:backwaterred/coreutils
This commit is contained in:
commit
95d9f03cff
18 changed files with 724 additions and 184 deletions
|
@ -17,7 +17,7 @@ search the issues to make sure no one else is working on it.
|
|||
|
||||
## 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. Use clap for argument management.
|
||||
1. Make sure that the code coverage is covering all of the cases, including errors.
|
||||
|
|
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -1,5 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
|
@ -2811,6 +2813,7 @@ dependencies = [
|
|||
name = "uucore"
|
||||
version = "0.0.9"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"data-encoding",
|
||||
"dns-lookup",
|
||||
"dunce",
|
||||
|
|
|
@ -14,6 +14,8 @@ use uucore::fs::resolve_relative_path;
|
|||
use uucore::libc::{gid_t, uid_t};
|
||||
use uucore::perms::{wrap_chown, Verbosity};
|
||||
|
||||
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
|
||||
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
|
||||
.collect_str(InvalidEncodingHandling::Ignore)
|
||||
.accept_any();
|
||||
|
@ -104,8 +107,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
if recursive {
|
||||
if bit_flag == FTS_PHYSICAL {
|
||||
if derefer == 1 {
|
||||
show_error!("-R --dereference requires -H or -L");
|
||||
return 1;
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
"-R --dereference requires -H or -L".to_string(),
|
||||
));
|
||||
}
|
||||
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) {
|
||||
match parse_spec(spec) {
|
||||
Ok((Some(uid), None)) => IfFrom::User(uid),
|
||||
Ok((None, Some(gid))) => IfFrom::Group(gid),
|
||||
Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid),
|
||||
Ok((None, None)) => IfFrom::All,
|
||||
Err(e) => {
|
||||
show_error!("{}", e);
|
||||
return 1;
|
||||
}
|
||||
match parse_spec(spec)? {
|
||||
(Some(uid), None) => IfFrom::User(uid),
|
||||
(None, Some(gid)) => IfFrom::Group(gid),
|
||||
(Some(uid), Some(gid)) => IfFrom::UserGroup(uid, gid),
|
||||
(None, None) => IfFrom::All,
|
||||
}
|
||||
} else {
|
||||
IfFrom::All
|
||||
|
@ -143,27 +144,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let dest_uid: Option<u32>;
|
||||
let dest_gid: Option<u32>;
|
||||
if let Some(file) = matches.value_of(options::REFERENCE) {
|
||||
match fs::metadata(&file) {
|
||||
Ok(meta) => {
|
||||
dest_gid = Some(meta.gid());
|
||||
dest_uid = Some(meta.uid());
|
||||
}
|
||||
Err(e) => {
|
||||
show_error!("failed to get attributes of '{}': {}", file, e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
let meta = fs::metadata(&file)
|
||||
.map_err_context(|| format!("failed to get attributes of '{}'", file))?;
|
||||
dest_gid = Some(meta.gid());
|
||||
dest_uid = Some(meta.uid());
|
||||
} else {
|
||||
match parse_spec(owner) {
|
||||
Ok((u, g)) => {
|
||||
dest_uid = u;
|
||||
dest_gid = g;
|
||||
}
|
||||
Err(e) => {
|
||||
show_error!("{}", e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
let (u, g) = parse_spec(owner)?;
|
||||
dest_uid = u;
|
||||
dest_gid = g;
|
||||
}
|
||||
let executor = Chowner {
|
||||
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 usr_only = args.len() == 1 && !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 {
|
||||
Some(
|
||||
Passwd::locate(args[0])
|
||||
.map_err(|_| format!("invalid user: '{}'", spec))?
|
||||
.map_err(|_| USimpleError::new(1, format!("invalid user: '{}'", spec)))?
|
||||
.uid(),
|
||||
)
|
||||
} else {
|
||||
|
@ -292,7 +280,7 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
|
|||
let gid = if grp_only || usr_grp {
|
||||
Some(
|
||||
Group::locate(args[1])
|
||||
.map_err(|_| format!("invalid group: '{}'", spec))?
|
||||
.map_err(|_| USimpleError::new(1, format!("invalid group: '{}'", spec)))?
|
||||
.gid(),
|
||||
)
|
||||
} else {
|
||||
|
@ -330,12 +318,15 @@ macro_rules! unwrap {
|
|||
}
|
||||
|
||||
impl Chowner {
|
||||
fn exec(&self) -> i32 {
|
||||
fn exec(&self) -> UResult<()> {
|
||||
let mut ret = 0;
|
||||
for f in &self.files {
|
||||
ret |= self.traverse(f);
|
||||
}
|
||||
ret
|
||||
if ret != 0 {
|
||||
return Err(UError::from(ret));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 {
|
||||
|
|
|
@ -98,6 +98,9 @@ quick_error! {
|
|||
/// path, but those that are not implemented yet should return
|
||||
/// a NotImplemented error.
|
||||
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)
|
||||
.require_equals(true)
|
||||
.min_values(0)
|
||||
.possible_values(backup_control::BACKUP_CONTROL_VALUES)
|
||||
.value_name("CONTROL")
|
||||
)
|
||||
.arg(Arg::with_name(options::BACKUP_NO_ARG)
|
||||
|
@ -604,9 +606,17 @@ impl Options {
|
|||
|| matches.is_present(options::ARCHIVE);
|
||||
|
||||
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),
|
||||
);
|
||||
let backup_mode = match backup_mode {
|
||||
Err(err) => {
|
||||
return Err(Error::Backup(err));
|
||||
}
|
||||
Ok(mode) => mode,
|
||||
};
|
||||
|
||||
let backup_suffix =
|
||||
backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX));
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// 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;
|
||||
|
||||
|
@ -13,6 +16,7 @@ use clap::{crate_version, App, Arg};
|
|||
use std::io::{self, Write};
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
const NAME: &str = "echo";
|
||||
|
@ -113,7 +117,8 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result<bool> {
|
|||
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
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
|
@ -126,13 +131,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
None => vec!["".to_string()],
|
||||
};
|
||||
|
||||
match execute(no_newline, escaped, values) {
|
||||
Ok(_) => 0,
|
||||
Err(f) => {
|
||||
show_error!("{}", f);
|
||||
1
|
||||
}
|
||||
}
|
||||
execute(no_newline, escaped, values).map_err_context(|| "could not write to stdout".to_string())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
|
|
@ -5,12 +5,20 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * 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 uucore::error::{UError, UResult};
|
||||
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);
|
||||
1
|
||||
Err(UError::from(1))
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
|
|
@ -308,15 +308,25 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
|||
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());
|
||||
|
||||
Ok(Behavior {
|
||||
main_function,
|
||||
specified_mode,
|
||||
backup_mode: backup_control::determine_backup_mode(
|
||||
matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP),
|
||||
matches.value_of(OPT_BACKUP),
|
||||
),
|
||||
backup_mode,
|
||||
suffix: backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)),
|
||||
owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(),
|
||||
group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(),
|
||||
|
|
|
@ -7,20 +7,21 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) signalname pids
|
||||
|
||||
// clippy bug https://github.com/rust-lang/rust-clippy/issues/7422
|
||||
#![allow(clippy::nonstandard_macro_braces)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use libc::{c_int, pid_t};
|
||||
use std::io::Error;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::signals::ALL_SIGNALS;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
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 static PIDS_OR_SIGNALS: &str = "pids_of_signals";
|
||||
pub static LIST: &str = "list";
|
||||
|
@ -36,7 +37,8 @@ pub enum Mode {
|
|||
List,
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -66,13 +68,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
(None, Some(s)) => s.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()),
|
||||
}
|
||||
|
||||
EXIT_OK
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
@ -139,20 +142,23 @@ fn table() {
|
|||
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() {
|
||||
if signal == signal_name_or_value || (format!("SIG{}", signal)) == signal_name_or_value {
|
||||
println!("{}", value);
|
||||
exit!(EXIT_OK as i32)
|
||||
return Ok(());
|
||||
} else if signal_name_or_value == value.to_string() {
|
||||
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() {
|
||||
|
@ -170,30 +176,41 @@ fn print_signals() {
|
|||
}
|
||||
}
|
||||
|
||||
fn list(arg: Option<String>) {
|
||||
fn list(arg: Option<String>) -> UResult<()> {
|
||||
match arg {
|
||||
Some(ref x) => print_signal(x),
|
||||
None => print_signals(),
|
||||
};
|
||||
None => {
|
||||
print_signals();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn kill(signalname: &str, pids: &[String]) -> i32 {
|
||||
let mut status = 0;
|
||||
fn kill(signalname: &str, pids: &[String]) -> UResult<()> {
|
||||
let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname);
|
||||
let signal_value = match optional_signal_value {
|
||||
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 {
|
||||
match pid.parse::<usize>() {
|
||||
Ok(x) => {
|
||||
if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 {
|
||||
show_error!("{}", Error::last_os_error());
|
||||
status = 1;
|
||||
show!(USimpleError::new(1, format!("{}", Error::last_os_error())));
|
||||
}
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ use std::os::unix::fs::symlink;
|
|||
#[cfg(windows)]
|
||||
use std::os::windows::fs::{symlink_dir, symlink_file};
|
||||
use std::path::{Path, PathBuf};
|
||||
use uucore::backup_control::{self, BackupMode};
|
||||
use uucore::fs::{canonicalize, CanonicalizeMode};
|
||||
|
||||
pub struct Settings {
|
||||
|
@ -43,14 +44,6 @@ pub enum OverwriteMode {
|
|||
Force,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum BackupMode {
|
||||
NoBackup,
|
||||
SimpleBackup,
|
||||
NumberedBackup,
|
||||
ExistingBackup,
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!(
|
||||
"{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";
|
||||
|
||||
mod options {
|
||||
pub const B: &str = "b";
|
||||
pub const BACKUP_NO_ARG: &str = "b";
|
||||
pub const BACKUP: &str = "backup";
|
||||
pub const FORCE: &str = "force";
|
||||
pub const INTERACTIVE: &str = "interactive";
|
||||
|
@ -99,7 +92,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
let matches = uu_app()
|
||||
.usage(&usage[..])
|
||||
.after_help(&long_usage[..])
|
||||
.after_help(&*format!(
|
||||
"{}\n{}",
|
||||
long_usage,
|
||||
backup_control::BACKUP_CONTROL_LONG_HELP
|
||||
))
|
||||
.get_matches_from(args);
|
||||
|
||||
/* the list of files */
|
||||
|
@ -118,33 +115,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
OverwriteMode::NoClobber
|
||||
};
|
||||
|
||||
let backup_mode = if matches.is_present(options::B) {
|
||||
BackupMode::ExistingBackup
|
||||
} else if matches.is_present(options::BACKUP) {
|
||||
match matches.value_of(options::BACKUP) {
|
||||
None => BackupMode::ExistingBackup,
|
||||
Some(mode) => match mode {
|
||||
"simple" | "never" => BackupMode::SimpleBackup,
|
||||
"numbered" | "t" => BackupMode::NumberedBackup,
|
||||
"existing" | "nil" => BackupMode::ExistingBackup,
|
||||
"none" | "off" => BackupMode::NoBackup,
|
||||
_ => panic!(), // cannot happen as it is managed by clap
|
||||
},
|
||||
let backup_mode = backup_control::determine_backup_mode(
|
||||
matches.is_present(options::BACKUP_NO_ARG),
|
||||
matches.is_present(options::BACKUP),
|
||||
matches.value_of(options::BACKUP),
|
||||
);
|
||||
let backup_mode = match backup_mode {
|
||||
Err(err) => {
|
||||
show_usage_error!("{}", err);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
BackupMode::NoBackup
|
||||
Ok(mode) => mode,
|
||||
};
|
||||
|
||||
let backup_suffix = if matches.is_present(options::SUFFIX) {
|
||||
matches.value_of(options::SUFFIX).unwrap()
|
||||
} else {
|
||||
"~"
|
||||
};
|
||||
let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX));
|
||||
|
||||
let settings = Settings {
|
||||
overwrite: overwrite_mode,
|
||||
backup: backup_mode,
|
||||
suffix: backup_suffix.to_string(),
|
||||
suffix: backup_suffix,
|
||||
symbolic: matches.is_present(options::SYMBOLIC),
|
||||
relative: matches.is_present(options::RELATIVE),
|
||||
target_dir: matches
|
||||
|
@ -162,22 +151,19 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
App::new(executable!())
|
||||
.version(crate_version!())
|
||||
.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::with_name(options::BACKUP)
|
||||
.long(options::BACKUP)
|
||||
.help(
|
||||
"make a backup of each file that would otherwise be overwritten \
|
||||
or removed",
|
||||
)
|
||||
.help("make a backup of each existing destination file")
|
||||
.takes_value(true)
|
||||
.possible_values(&[
|
||||
"simple", "never", "numbered", "t", "existing", "nil", "none", "off",
|
||||
])
|
||||
.value_name("METHOD"),
|
||||
.require_equals(true)
|
||||
.min_values(0)
|
||||
.value_name("CONTROL"),
|
||||
)
|
||||
.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(
|
||||
// Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \
|
||||
|
|
|
@ -86,9 +86,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
let overwrite_mode = determine_overwrite_mode(&matches);
|
||||
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),
|
||||
);
|
||||
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 {
|
||||
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)
|
||||
.require_equals(true)
|
||||
.min_values(0)
|
||||
.possible_values(backup_control::BACKUP_CONTROL_VALUES)
|
||||
.value_name("CONTROL")
|
||||
)
|
||||
.arg(
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * 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;
|
||||
|
||||
|
@ -13,6 +16,8 @@ use std::env;
|
|||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
|
||||
static ABOUT: &str = "Display the full filename of the current working directory.";
|
||||
static OPT_LOGICAL: &str = "logical";
|
||||
static OPT_PHYSICAL: &str = "physical";
|
||||
|
@ -36,7 +41,8 @@ fn get_usage() -> String {
|
|||
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 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) {
|
||||
println!("{}", logical_path.display());
|
||||
} else {
|
||||
match absolute_path(&logical_path) {
|
||||
Ok(physical_path) => println!("{}", physical_path.display()),
|
||||
Err(e) => crash!(1, "failed to get absolute path {}", e),
|
||||
};
|
||||
let physical_path = absolute_path(&logical_path)
|
||||
.map_err_context(|| "failed to get absolute path".to_string())?;
|
||||
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> {
|
||||
|
|
|
@ -5,12 +5,17 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * 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 std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
|
||||
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 matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
||||
|
||||
if let Some(values) = matches.values_of(options::NUMBER) {
|
||||
let numbers = values.collect();
|
||||
sleep(numbers);
|
||||
return sleep(numbers);
|
||||
}
|
||||
|
||||
0
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 =
|
||||
args.iter().fold(
|
||||
args.iter().try_fold(
|
||||
Duration::new(0, 0),
|
||||
|result, arg| match uucore::parse_time::from_str(&arg[..]) {
|
||||
Ok(m) => m + result,
|
||||
Err(f) => crash!(1, "{}", f),
|
||||
Ok(m) => Ok(m + result),
|
||||
Err(f) => Err(USimpleError::new(1, f)),
|
||||
},
|
||||
);
|
||||
|
||||
)?;
|
||||
thread::sleep(sleep_dur);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -5,12 +5,20 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * 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 uucore::error::UResult;
|
||||
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);
|
||||
0
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
|
|
@ -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
|
||||
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]
|
||||
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }
|
||||
|
||||
|
|
|
@ -7,19 +7,15 @@ pub static BACKUP_CONTROL_VALUES: &[&str] = &[
|
|||
"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
|
||||
never make backups (even if --backup is given)
|
||||
|
||||
numbered, t
|
||||
make numbered backups
|
||||
|
||||
existing, nil
|
||||
numbered if numbered backups exist, simple otherwise
|
||||
|
||||
simple, never
|
||||
always make simple backups";
|
||||
none, off never make backups (even if --backup is given)
|
||||
numbered, t make numbered backups
|
||||
existing, nil numbered if numbered backups exist, simple otherwise
|
||||
simple, never always make simple backups";
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
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
|
||||
/// that it should work. In particular, the current implementation:
|
||||
/// Parses the backup options according to the [GNU manual][1], and converts
|
||||
/// them to an instance of `BackupMode` for further processing.
|
||||
///
|
||||
/// 1. Doesn't strictly respect the order in which to determine the backup type,
|
||||
/// 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
|
||||
/// For an explanation of what the arguments mean, refer to the examples below.
|
||||
///
|
||||
/// [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) {
|
||||
// default is existing, see:
|
||||
// https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html
|
||||
None => BackupMode::ExistingBackup,
|
||||
Some(mode) => match &mode[..] {
|
||||
"simple" | "never" => BackupMode::SimpleBackup,
|
||||
"numbered" | "t" => BackupMode::NumberedBackup,
|
||||
"existing" | "nil" => BackupMode::ExistingBackup,
|
||||
"none" | "off" => BackupMode::NoBackup,
|
||||
_ => panic!(), // cannot happen as it is managed by clap
|
||||
},
|
||||
///
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If an argument supplied directly to the long `backup` option, or read in
|
||||
/// through the `VERSION CONTROL` env var 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.
|
||||
///
|
||||
///
|
||||
/// # 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 {
|
||||
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();
|
||||
p.push_str(suffix);
|
||||
PathBuf::from(p)
|
||||
}
|
||||
|
||||
pub fn numbered_backup_path(path: &Path) -> PathBuf {
|
||||
fn numbered_backup_path(path: &Path) -> PathBuf {
|
||||
for i in 1_u64.. {
|
||||
let path_str = &format!("{}.~{}~", path.to_string_lossy(), i);
|
||||
let path = Path::new(path_str);
|
||||
|
@ -99,7 +237,7 @@ pub fn numbered_backup_path(path: &Path) -> PathBuf {
|
|||
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 = Path::new(test_path_str);
|
||||
if test_path.exists() {
|
||||
|
@ -108,3 +246,210 @@ pub fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,6 +138,12 @@ pub enum 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 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
UError::Common(e) => e.usage(),
|
||||
|
@ -183,12 +196,13 @@ impl Display for UError {
|
|||
/// Custom errors defined by the utils.
|
||||
///
|
||||
/// 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
|
||||
/// program if the error is returned from `uumain`.
|
||||
/// [`std::fmt::Debug`] and have an additional `code` method that specifies the
|
||||
/// exit code of the program if the error is returned from `uumain`.
|
||||
///
|
||||
/// An example of a custom error from `ls`:
|
||||
///
|
||||
/// ```
|
||||
/// use uucore::error::{UCustomError};
|
||||
/// use uucore::error::{UCustomError, UResult};
|
||||
/// use std::{
|
||||
/// error::Error,
|
||||
/// 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 {
|
||||
/// 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 {
|
||||
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 {
|
||||
false
|
||||
}
|
||||
|
|
|
@ -398,8 +398,7 @@ fn test_du_time() {
|
|||
let result = ts.ucmd().arg("--time=ctime").arg("date_test").succeeds();
|
||||
result.stdout_only("0\t2016-06-16 00:00\tdate_test\n");
|
||||
|
||||
#[cfg(not(target_env = "musl"))]
|
||||
{
|
||||
if birth_supported() {
|
||||
use regex::Regex;
|
||||
|
||||
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(feature = "chmod")]
|
||||
#[test]
|
||||
|
|
|
@ -110,3 +110,8 @@ fn test_sleep_sum_duration_many() {
|
|||
let duration = before_test.elapsed();
|
||||
assert!(duration >= millis_900);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sleep_wrong_time() {
|
||||
new_ucmd!().args(&["0.1s", "abc"]).fails();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue