mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +00:00
Merge pull request #2355 from tertsdiepraam/improve-error-handling
Improve error handling with `UResult`
This commit is contained in:
commit
2428a1ccfb
10 changed files with 797 additions and 297 deletions
|
@ -26,10 +26,11 @@ use quoting_style::{escape_name, QuotingStyle};
|
|||
use std::os::windows::fs::MetadataExt;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
fs::{self, DirEntry, FileType, Metadata},
|
||||
io::{stdout, BufWriter, Stdout, Write},
|
||||
path::{Path, PathBuf},
|
||||
process::exit,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
#[cfg(unix)]
|
||||
|
@ -38,8 +39,8 @@ use std::{
|
|||
os::unix::fs::{FileTypeExt, MetadataExt},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
||||
use uucore::error::{set_exit_code, FromIo, UCustomError, UResult};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
#[cfg(unix)]
|
||||
|
@ -125,6 +126,32 @@ pub mod options {
|
|||
pub static IGNORE: &str = "ignore";
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LsError {
|
||||
InvalidLineWidth(String),
|
||||
NoMetadata(PathBuf),
|
||||
}
|
||||
|
||||
impl UCustomError for LsError {
|
||||
fn code(&self) -> i32 {
|
||||
match self {
|
||||
LsError::InvalidLineWidth(_) => 2,
|
||||
LsError::NoMetadata(_) => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for LsError {}
|
||||
|
||||
impl Display for LsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LsError::InvalidLineWidth(s) => write!(f, "invalid line width: '{}'", s),
|
||||
LsError::NoMetadata(p) => write!(f, "could not open file: '{}'", p.display()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum Format {
|
||||
Columns,
|
||||
|
@ -218,7 +245,7 @@ struct LongFormat {
|
|||
|
||||
impl Config {
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn from(options: clap::ArgMatches) -> Config {
|
||||
fn from(options: clap::ArgMatches) -> UResult<Config> {
|
||||
let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) {
|
||||
(
|
||||
match format_ {
|
||||
|
@ -369,15 +396,13 @@ impl Config {
|
|||
}
|
||||
};
|
||||
|
||||
let width = options
|
||||
.value_of(options::WIDTH)
|
||||
.map(|x| {
|
||||
x.parse::<u16>().unwrap_or_else(|_e| {
|
||||
show_error!("invalid line width: '{}'", x);
|
||||
exit(2);
|
||||
})
|
||||
})
|
||||
.or_else(|| termsize::get().map(|s| s.cols));
|
||||
let width = match options.value_of(options::WIDTH) {
|
||||
Some(x) => match x.parse::<u16>() {
|
||||
Ok(u) => Some(u),
|
||||
Err(_) => return Err(LsError::InvalidLineWidth(x.into()).into()),
|
||||
},
|
||||
None => termsize::get().map(|s| s.cols),
|
||||
};
|
||||
|
||||
#[allow(clippy::needless_bool)]
|
||||
let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) {
|
||||
|
@ -528,7 +553,7 @@ impl Config {
|
|||
Dereference::DirArgs
|
||||
};
|
||||
|
||||
Config {
|
||||
Ok(Config {
|
||||
format,
|
||||
files,
|
||||
sort,
|
||||
|
@ -547,11 +572,12 @@ impl Config {
|
|||
quoting_style,
|
||||
indicator_style,
|
||||
time_style,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -567,7 +593,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_else(|| vec![String::from(".")]);
|
||||
|
||||
list(locs, Config::from(matches))
|
||||
list(locs, Config::from(matches)?)
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
@ -1190,10 +1216,9 @@ impl PathData {
|
|||
}
|
||||
}
|
||||
|
||||
fn list(locs: Vec<String>, config: Config) -> i32 {
|
||||
fn list(locs: Vec<String>, config: Config) -> UResult<()> {
|
||||
let mut files = Vec::<PathData>::new();
|
||||
let mut dirs = Vec::<PathData>::new();
|
||||
let mut has_failed = false;
|
||||
|
||||
let mut out = BufWriter::new(stdout());
|
||||
|
||||
|
@ -1202,19 +1227,16 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
|
|||
let path_data = PathData::new(p, None, None, &config, true);
|
||||
|
||||
if path_data.md().is_none() {
|
||||
show_error!("'{}': {}", &loc, "No such file or directory");
|
||||
/*
|
||||
We found an error, the return code of ls should not be 0
|
||||
And no need to continue the execution
|
||||
*/
|
||||
has_failed = true;
|
||||
show!(std::io::ErrorKind::NotFound
|
||||
.map_err_context(|| format!("cannot access '{}'", path_data.p_buf.display())));
|
||||
// We found an error, no need to continue the execution
|
||||
continue;
|
||||
}
|
||||
|
||||
let show_dir_contents = match path_data.file_type() {
|
||||
Some(ft) => !config.directory && ft.is_dir(),
|
||||
None => {
|
||||
has_failed = true;
|
||||
set_exit_code(1);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
@ -1235,11 +1257,8 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
|
|||
}
|
||||
enter_directory(&dir, &config, &mut out);
|
||||
}
|
||||
if has_failed {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
|
||||
|
@ -1478,7 +1497,7 @@ fn display_item_long(
|
|||
) {
|
||||
let md = match item.md() {
|
||||
None => {
|
||||
show_error!("could not show file: {}", &item.p_buf.display());
|
||||
show!(LsError::NoMetadata(item.p_buf.clone()));
|
||||
return;
|
||||
}
|
||||
Some(md) => md,
|
||||
|
|
|
@ -8,25 +8,26 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::OsValues;
|
||||
use clap::{crate_version, App, Arg};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
|
||||
static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
|
||||
static OPT_MODE: &str = "mode";
|
||||
static OPT_PARENTS: &str = "parents";
|
||||
static OPT_VERBOSE: &str = "verbose";
|
||||
|
||||
static ARG_DIRS: &str = "dirs";
|
||||
mod options {
|
||||
pub const MODE: &str = "mode";
|
||||
pub const PARENTS: &str = "parents";
|
||||
pub const VERBOSE: &str = "verbose";
|
||||
pub const DIRS: &str = "dirs";
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION]... [USER]", executable!())
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles option parsing
|
||||
*/
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let usage = get_usage();
|
||||
|
||||
// Linux-specific options, not implemented
|
||||
|
@ -34,26 +35,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
// " of each created directory to CTX"),
|
||||
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
||||
|
||||
let dirs: Vec<String> = matches
|
||||
.values_of(ARG_DIRS)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let verbose = matches.is_present(OPT_VERBOSE);
|
||||
let recursive = matches.is_present(OPT_PARENTS);
|
||||
let dirs = matches.values_of_os(options::DIRS).unwrap_or_default();
|
||||
let verbose = matches.is_present(options::VERBOSE);
|
||||
let recursive = matches.is_present(options::PARENTS);
|
||||
|
||||
// Translate a ~str in octal form to u16, default to 755
|
||||
// Not tested on Windows
|
||||
let mode_match = matches.value_of(OPT_MODE);
|
||||
let mode: u16 = match mode_match {
|
||||
Some(m) => {
|
||||
let res: Option<u16> = u16::from_str_radix(m, 8).ok();
|
||||
match res {
|
||||
Some(r) => r,
|
||||
_ => crash!(1, "no mode given"),
|
||||
}
|
||||
}
|
||||
_ => 0o755_u16,
|
||||
let mode: u16 = match matches.value_of(options::MODE) {
|
||||
Some(m) => u16::from_str_radix(m, 8)
|
||||
.map_err(|_| USimpleError::new(1, format!("invalid mode '{}'", m)))?,
|
||||
None => 0o755_u16,
|
||||
};
|
||||
|
||||
exec(dirs, recursive, mode, verbose)
|
||||
|
@ -64,27 +55,27 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.arg(
|
||||
Arg::with_name(OPT_MODE)
|
||||
Arg::with_name(options::MODE)
|
||||
.short("m")
|
||||
.long(OPT_MODE)
|
||||
.long(options::MODE)
|
||||
.help("set file mode (not implemented on windows)")
|
||||
.default_value("755"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_PARENTS)
|
||||
Arg::with_name(options::PARENTS)
|
||||
.short("p")
|
||||
.long(OPT_PARENTS)
|
||||
.long(options::PARENTS)
|
||||
.alias("parent")
|
||||
.help("make parent directories as needed"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_VERBOSE)
|
||||
Arg::with_name(options::VERBOSE)
|
||||
.short("v")
|
||||
.long(OPT_VERBOSE)
|
||||
.long(options::VERBOSE)
|
||||
.help("print a message for each printed directory"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(ARG_DIRS)
|
||||
Arg::with_name(options::DIRS)
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
|
@ -94,64 +85,43 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
/**
|
||||
* Create the list of new directories
|
||||
*/
|
||||
fn exec(dirs: Vec<String>, recursive: bool, mode: u16, verbose: bool) -> i32 {
|
||||
let mut status = 0;
|
||||
let empty = Path::new("");
|
||||
for dir in &dirs {
|
||||
fn exec(dirs: OsValues, recursive: bool, mode: u16, verbose: bool) -> UResult<()> {
|
||||
for dir in dirs {
|
||||
let path = Path::new(dir);
|
||||
if !recursive {
|
||||
if let Some(parent) = path.parent() {
|
||||
if parent != empty && !parent.exists() {
|
||||
show_error!(
|
||||
"cannot create directory '{}': No such file or directory",
|
||||
path.display()
|
||||
);
|
||||
status = 1;
|
||||
continue;
|
||||
show_if_err!(mkdir(path, recursive, mode, verbose));
|
||||
}
|
||||
}
|
||||
}
|
||||
status |= mkdir(path, recursive, mode, verbose);
|
||||
}
|
||||
status
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to catch errors, return 1 if failed
|
||||
*/
|
||||
fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> i32 {
|
||||
fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> UResult<()> {
|
||||
let create_dir = if recursive {
|
||||
fs::create_dir_all
|
||||
} else {
|
||||
fs::create_dir
|
||||
};
|
||||
if let Err(e) = create_dir(path) {
|
||||
show_error!("{}: {}", path.display(), e.to_string());
|
||||
return 1;
|
||||
}
|
||||
|
||||
create_dir(path).map_err_context(|| format!("cannot create directory '{}'", path.display()))?;
|
||||
|
||||
if verbose {
|
||||
println!("{}: created directory '{}'", executable!(), path.display());
|
||||
}
|
||||
|
||||
chmod(path, mode)
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
fn chmod(path: &Path, mode: u16) -> i32 {
|
||||
fn chmod(path: &Path, mode: u16) -> UResult<()> {
|
||||
use std::fs::{set_permissions, Permissions};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let mode = Permissions::from_mode(u32::from(mode));
|
||||
|
||||
if let Err(err) = set_permissions(path, mode) {
|
||||
show_error!("{}: {}", path.display(), err);
|
||||
return 1;
|
||||
}
|
||||
0
|
||||
set_permissions(path, mode)
|
||||
.map_err_context(|| format!("cannot set permissions '{}'", path.display()))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[allow(unused_variables)]
|
||||
fn chmod(path: &Path, mode: u16) -> i32 {
|
||||
fn chmod(_path: &Path, _mode: u16) -> UResult<()> {
|
||||
// chmod on Windows only sets the readonly flag, which isn't even honored on directories
|
||||
0
|
||||
}
|
||||
chmod(path, mode)
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -12,8 +12,11 @@
|
|||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use uucore::error::{FromIo, UCustomError, UResult};
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
use std::iter;
|
||||
use std::path::{is_separator, PathBuf};
|
||||
|
||||
|
@ -37,7 +40,40 @@ fn get_usage() -> String {
|
|||
format!("{0} [OPTION]... [TEMPLATE]", executable!())
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
#[derive(Debug)]
|
||||
enum MkTempError {
|
||||
PersistError(PathBuf),
|
||||
MustEndInX(String),
|
||||
TooFewXs(String),
|
||||
ContainsDirSeparator(String),
|
||||
InvalidTemplate(String),
|
||||
}
|
||||
|
||||
impl UCustomError for MkTempError {}
|
||||
|
||||
impl Error for MkTempError {}
|
||||
|
||||
impl Display for MkTempError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use MkTempError::*;
|
||||
match self {
|
||||
PersistError(p) => write!(f, "could not persist file '{}'", p.display()),
|
||||
MustEndInX(s) => write!(f, "with --suffix, template '{}' must end in X", s),
|
||||
TooFewXs(s) => write!(f, "too few X's in template '{}'", s),
|
||||
ContainsDirSeparator(s) => {
|
||||
write!(f, "invalid suffix '{}', contains directory separator", s)
|
||||
}
|
||||
InvalidTemplate(s) => write!(
|
||||
f,
|
||||
"invalid template, '{}'; with --tmpdir, it may not be absolute",
|
||||
s
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
|
@ -73,47 +109,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let dry_run = matches.is_present(OPT_DRY_RUN);
|
||||
let suppress_file_err = matches.is_present(OPT_QUIET);
|
||||
|
||||
let (prefix, rand, suffix) = match parse_template(template) {
|
||||
Some((p, r, s)) => match matches.value_of(OPT_SUFFIX) {
|
||||
Some(suf) => {
|
||||
if s.is_empty() {
|
||||
(p, r, suf)
|
||||
} else {
|
||||
crash!(
|
||||
1,
|
||||
"Template should end with 'X' when you specify suffix option."
|
||||
)
|
||||
}
|
||||
}
|
||||
None => (p, r, s),
|
||||
},
|
||||
None => ("", 0, ""),
|
||||
};
|
||||
|
||||
if rand < 3 {
|
||||
crash!(1, "Too few 'X's in template")
|
||||
}
|
||||
|
||||
if suffix.chars().any(is_separator) {
|
||||
crash!(1, "suffix cannot contain any path separators");
|
||||
}
|
||||
let (prefix, rand, suffix) = parse_template(template, matches.value_of(OPT_SUFFIX))?;
|
||||
|
||||
if matches.is_present(OPT_TMPDIR) && PathBuf::from(prefix).is_absolute() {
|
||||
show_error!(
|
||||
"invalid template, '{}'; with --tmpdir, it may not be absolute",
|
||||
template
|
||||
);
|
||||
return 1;
|
||||
};
|
||||
return Err(MkTempError::InvalidTemplate(template.into()).into());
|
||||
}
|
||||
|
||||
if matches.is_present(OPT_T) {
|
||||
tmpdir = env::temp_dir()
|
||||
};
|
||||
}
|
||||
|
||||
if dry_run {
|
||||
let res = if dry_run {
|
||||
dry_exec(tmpdir, prefix, rand, suffix)
|
||||
} else {
|
||||
exec(tmpdir, prefix, rand, suffix, make_dir, suppress_file_err)
|
||||
exec(tmpdir, prefix, rand, suffix, make_dir)
|
||||
};
|
||||
|
||||
if suppress_file_err {
|
||||
// Mapping all UErrors to ExitCodes prevents the errors from being printed
|
||||
res.map_err(|e| e.code().into())
|
||||
} else {
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,19 +189,40 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
)
|
||||
}
|
||||
|
||||
fn parse_template(temp: &str) -> Option<(&str, usize, &str)> {
|
||||
fn parse_template<'a>(
|
||||
temp: &'a str,
|
||||
suffix: Option<&'a str>,
|
||||
) -> UResult<(&'a str, usize, &'a str)> {
|
||||
let right = match temp.rfind('X') {
|
||||
Some(r) => r + 1,
|
||||
None => return None,
|
||||
None => return Err(MkTempError::TooFewXs(temp.into()).into()),
|
||||
};
|
||||
let left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1);
|
||||
let prefix = &temp[..left];
|
||||
let rand = right - left;
|
||||
let suffix = &temp[right..];
|
||||
Some((prefix, rand, suffix))
|
||||
|
||||
if rand < 3 {
|
||||
return Err(MkTempError::TooFewXs(temp.into()).into());
|
||||
}
|
||||
|
||||
pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> i32 {
|
||||
let mut suf = &temp[right..];
|
||||
|
||||
if let Some(s) = suffix {
|
||||
if suf.is_empty() {
|
||||
suf = s;
|
||||
} else {
|
||||
return Err(MkTempError::MustEndInX(temp.into()).into());
|
||||
}
|
||||
};
|
||||
|
||||
if suf.chars().any(is_separator) {
|
||||
return Err(MkTempError::ContainsDirSeparator(suf.into()).into());
|
||||
}
|
||||
|
||||
Ok((prefix, rand, suf))
|
||||
}
|
||||
|
||||
pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> UResult<()> {
|
||||
let len = prefix.len() + suffix.len() + rand;
|
||||
let mut buf = String::with_capacity(len);
|
||||
buf.push_str(prefix);
|
||||
|
@ -208,51 +245,35 @@ pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) ->
|
|||
}
|
||||
tmpdir.push(buf);
|
||||
println!("{}", tmpdir.display());
|
||||
0
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool, quiet: bool) -> i32 {
|
||||
let res = if make_dir {
|
||||
let tmpdir = Builder::new()
|
||||
.prefix(prefix)
|
||||
.rand_bytes(rand)
|
||||
.suffix(suffix)
|
||||
.tempdir_in(&dir);
|
||||
|
||||
// `into_path` consumes the TempDir without removing it
|
||||
tmpdir.map(|d| d.into_path().to_string_lossy().to_string())
|
||||
} else {
|
||||
let tmpfile = Builder::new()
|
||||
.prefix(prefix)
|
||||
.rand_bytes(rand)
|
||||
.suffix(suffix)
|
||||
.tempfile_in(&dir);
|
||||
|
||||
match tmpfile {
|
||||
Ok(f) => {
|
||||
// `keep` ensures that the file is not deleted
|
||||
match f.keep() {
|
||||
Ok((_, p)) => Ok(p.to_string_lossy().to_string()),
|
||||
Err(e) => {
|
||||
show_error!("'{}': {}", dir.display(), e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(x) => Err(x),
|
||||
}
|
||||
fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> {
|
||||
let context = || {
|
||||
format!(
|
||||
"failed to create file via template '{}{}{}'",
|
||||
prefix,
|
||||
"X".repeat(rand),
|
||||
suffix
|
||||
)
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(ref f) => {
|
||||
println!("{}", f);
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
if !quiet {
|
||||
show_error!("{}: {}", e, dir.display());
|
||||
}
|
||||
1
|
||||
}
|
||||
}
|
||||
let mut builder = Builder::new();
|
||||
builder.prefix(prefix).rand_bytes(rand).suffix(suffix);
|
||||
|
||||
let path = if make_dir {
|
||||
builder
|
||||
.tempdir_in(&dir)
|
||||
.map_err_context(context)?
|
||||
.into_path() // `into_path` consumes the TempDir without removing it
|
||||
} else {
|
||||
builder
|
||||
.tempfile_in(&dir)
|
||||
.map_err_context(context)?
|
||||
.keep() // `keep` ensures that the file is not deleted
|
||||
.map_err(|e| MkTempError::PersistError(e.file.path().to_path_buf()))?
|
||||
.1
|
||||
};
|
||||
println!("{}", path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -16,9 +16,8 @@ extern crate uucore;
|
|||
use clap::{crate_version, App, Arg, ArgGroup};
|
||||
use filetime::*;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Error;
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
|
||||
static ABOUT: &str = "Update the access and modification times of each FILE to the current time.";
|
||||
pub mod options {
|
||||
|
@ -52,57 +51,38 @@ fn get_usage() -> String {
|
|||
format!("{0} [OPTION]... [USER]", 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);
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.values_of(ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
let files = matches.values_of_os(ARG_FILES).unwrap();
|
||||
|
||||
let (mut atime, mut mtime) = if matches.is_present(options::sources::REFERENCE) {
|
||||
stat(
|
||||
matches.value_of(options::sources::REFERENCE).unwrap(),
|
||||
!matches.is_present(options::NO_DEREF),
|
||||
)
|
||||
} else if matches.is_present(options::sources::DATE)
|
||||
|| matches.is_present(options::sources::CURRENT)
|
||||
{
|
||||
let timestamp = if matches.is_present(options::sources::DATE) {
|
||||
parse_date(matches.value_of(options::sources::DATE).unwrap())
|
||||
let (mut atime, mut mtime) =
|
||||
if let Some(reference) = matches.value_of_os(options::sources::REFERENCE) {
|
||||
stat(Path::new(reference), !matches.is_present(options::NO_DEREF))?
|
||||
} else {
|
||||
parse_timestamp(matches.value_of(options::sources::CURRENT).unwrap())
|
||||
let timestamp = if let Some(date) = matches.value_of(options::sources::DATE) {
|
||||
parse_date(date)?
|
||||
} else if let Some(current) = matches.value_of(options::sources::CURRENT) {
|
||||
parse_timestamp(current)?
|
||||
} else {
|
||||
local_tm_to_filetime(time::now())
|
||||
};
|
||||
(timestamp, timestamp)
|
||||
} else {
|
||||
let now = local_tm_to_filetime(time::now());
|
||||
(now, now)
|
||||
};
|
||||
|
||||
let mut error_code = 0;
|
||||
|
||||
for filename in &files {
|
||||
let path = &filename[..];
|
||||
|
||||
if !Path::new(path).exists() {
|
||||
for filename in files {
|
||||
let path = Path::new(filename);
|
||||
if !path.exists() {
|
||||
// no-dereference included here for compatibility
|
||||
if matches.is_present(options::NO_CREATE) || matches.is_present(options::NO_DEREF) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = File::create(path) {
|
||||
match e.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
show_error!("cannot touch '{}': {}", path, "No such file or directory")
|
||||
}
|
||||
std::io::ErrorKind::PermissionDenied => {
|
||||
show_error!("cannot touch '{}': {}", path, "Permission denied")
|
||||
}
|
||||
_ => show_error!("cannot touch '{}': {}", path, e),
|
||||
}
|
||||
error_code = 1;
|
||||
show!(e.map_err_context(|| format!("cannot touch '{}'", path.display())));
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -118,7 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|| matches.is_present(options::MODIFICATION)
|
||||
|| matches.is_present(options::TIME)
|
||||
{
|
||||
let st = stat(path, !matches.is_present(options::NO_DEREF));
|
||||
let st = stat(path, !matches.is_present(options::NO_DEREF))?;
|
||||
let time = matches.value_of(options::TIME).unwrap_or("");
|
||||
|
||||
if !(matches.is_present(options::ACCESS)
|
||||
|
@ -138,29 +118,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
|
||||
if matches.is_present(options::NO_DEREF) {
|
||||
if let Err(e) = set_symlink_file_times(path, atime, mtime) {
|
||||
// we found an error, it should fail in any case
|
||||
error_code = 1;
|
||||
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
// GNU compatibility (not-owner.sh)
|
||||
show_error!("setting times of '{}': {}", path, "Permission denied");
|
||||
set_symlink_file_times(path, atime, mtime)
|
||||
} else {
|
||||
show_error!("setting times of '{}': {}", path, e);
|
||||
filetime::set_file_times(path, atime, mtime)
|
||||
}
|
||||
.map_err_context(|| format!("setting times of '{}'", path.display()))?;
|
||||
}
|
||||
} else if let Err(e) = filetime::set_file_times(path, atime, mtime) {
|
||||
// we found an error, it should fail in any case
|
||||
error_code = 1;
|
||||
|
||||
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
// GNU compatibility (not-owner.sh)
|
||||
show_error!("setting times of '{}': {}", path, "Permission denied");
|
||||
} else {
|
||||
show_error!("setting times of '{}': {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
error_code
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
@ -238,28 +203,21 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
]))
|
||||
}
|
||||
|
||||
fn stat(path: &str, follow: bool) -> (FileTime, FileTime) {
|
||||
fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> {
|
||||
let metadata = if follow {
|
||||
fs::symlink_metadata(path)
|
||||
} else {
|
||||
fs::metadata(path)
|
||||
};
|
||||
|
||||
match metadata {
|
||||
Ok(m) => (
|
||||
FileTime::from_last_access_time(&m),
|
||||
FileTime::from_last_modification_time(&m),
|
||||
),
|
||||
Err(_) => crash!(
|
||||
1,
|
||||
"failed to get attributes of '{}': {}",
|
||||
path,
|
||||
Error::last_os_error()
|
||||
),
|
||||
}
|
||||
.map_err_context(|| format!("failed to get attributes of '{}'", path.display()))?;
|
||||
|
||||
Ok((
|
||||
FileTime::from_last_access_time(&metadata),
|
||||
FileTime::from_last_modification_time(&metadata),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_date(str: &str) -> FileTime {
|
||||
fn parse_date(str: &str) -> UResult<FileTime> {
|
||||
// This isn't actually compatible with GNU touch, but there doesn't seem to
|
||||
// be any simple specification for what format this parameter allows and I'm
|
||||
// not about to implement GNU parse_datetime.
|
||||
|
@ -267,18 +225,22 @@ fn parse_date(str: &str) -> FileTime {
|
|||
let formats = vec!["%c", "%F"];
|
||||
for f in formats {
|
||||
if let Ok(tm) = time::strptime(str, f) {
|
||||
return local_tm_to_filetime(to_local(tm));
|
||||
return Ok(local_tm_to_filetime(to_local(tm)));
|
||||
}
|
||||
}
|
||||
if let Ok(tm) = time::strptime(str, "@%s") {
|
||||
// Don't convert to local time in this case - seconds since epoch are not time-zone dependent
|
||||
return local_tm_to_filetime(tm);
|
||||
}
|
||||
show_error!("Unable to parse date: {}\n", str);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
fn parse_timestamp(s: &str) -> FileTime {
|
||||
if let Ok(tm) = time::strptime(str, "@%s") {
|
||||
// Don't convert to local time in this case - seconds since epoch are not time-zone dependent
|
||||
return Ok(local_tm_to_filetime(tm));
|
||||
}
|
||||
|
||||
Err(USimpleError::new(
|
||||
1,
|
||||
format!("Unable to parse date: {}", str),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
||||
let now = time::now();
|
||||
let (format, ts) = match s.chars().count() {
|
||||
15 => ("%Y%m%d%H%M.%S", s.to_owned()),
|
||||
|
@ -287,11 +249,12 @@ fn parse_timestamp(s: &str) -> FileTime {
|
|||
10 => ("%y%m%d%H%M", s.to_owned()),
|
||||
11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)),
|
||||
8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)),
|
||||
_ => panic!("Unknown timestamp format"),
|
||||
_ => return Err(USimpleError::new(1, format!("invalid date format '{}'", s))),
|
||||
};
|
||||
|
||||
match time::strptime(&ts, format) {
|
||||
Ok(tm) => {
|
||||
let tm = time::strptime(&ts, format)
|
||||
.map_err(|_| USimpleError::new(1, format!("invalid date format '{}'", s)))?;
|
||||
|
||||
let mut local = to_local(tm);
|
||||
local.tm_isdst = -1;
|
||||
let ft = local_tm_to_filetime(local);
|
||||
|
@ -306,12 +269,8 @@ fn parse_timestamp(s: &str) -> FileTime {
|
|||
};
|
||||
let tm2 = time::at(ts);
|
||||
if tm.tm_hour != tm2.tm_hour {
|
||||
show_error!("invalid date format {}", s);
|
||||
process::exit(1);
|
||||
return Err(USimpleError::new(1, format!("invalid date format '{}'", s)));
|
||||
}
|
||||
|
||||
ft
|
||||
}
|
||||
Err(e) => panic!("Unable to parse timestamp\n{}", e),
|
||||
}
|
||||
Ok(ft)
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ mod parser; // string parsing modules
|
|||
// * cross-platform modules
|
||||
pub use crate::mods::backup_control;
|
||||
pub use crate::mods::coreopts;
|
||||
pub use crate::mods::error;
|
||||
pub use crate::mods::os;
|
||||
pub use crate::mods::panic;
|
||||
pub use crate::mods::ranges;
|
||||
|
|
|
@ -21,6 +21,24 @@ macro_rules! executable(
|
|||
})
|
||||
);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! show(
|
||||
($err:expr) => ({
|
||||
let e = $err;
|
||||
uucore::error::set_exit_code(e.code());
|
||||
eprintln!("{}: {}", executable!(), e);
|
||||
})
|
||||
);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! show_if_err(
|
||||
($res:expr) => ({
|
||||
if let Err(e) = $res {
|
||||
show!(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
/// Show an error to stderr in a similar style to GNU coreutils.
|
||||
#[macro_export]
|
||||
macro_rules! show_error(
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub mod backup_control;
|
||||
pub mod coreopts;
|
||||
pub mod error;
|
||||
pub mod os;
|
||||
pub mod panic;
|
||||
pub mod ranges;
|
||||
|
|
479
src/uucore/src/lib/mods/error.rs
Normal file
479
src/uucore/src/lib/mods/error.rs
Normal file
|
@ -0,0 +1,479 @@
|
|||
//! All utils return exit with an exit code. Usually, the following scheme is used:
|
||||
//! * `0`: succeeded
|
||||
//! * `1`: minor problems
|
||||
//! * `2`: major problems
|
||||
//!
|
||||
//! This module provides types to reconcile these exit codes with idiomatic Rust error
|
||||
//! handling. This has a couple advantages over manually using [`std::process::exit`]:
|
||||
//! 1. It enables the use of `?`, `map_err`, `unwrap_or`, etc. in `uumain`.
|
||||
//! 1. It encourages the use of `UResult`/`Result` in functions in the utils.
|
||||
//! 1. The error messages are largely standardized across utils.
|
||||
//! 1. Standardized error messages can be created from external result types
|
||||
//! (i.e. [`std::io::Result`] & `clap::ClapResult`).
|
||||
//! 1. `set_exit_code` takes away the burden of manually tracking exit codes for non-fatal errors.
|
||||
//!
|
||||
//! # Usage
|
||||
//! The signature of a typical util should be:
|
||||
//! ```ignore
|
||||
//! fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
//! ...
|
||||
//! }
|
||||
//! ```
|
||||
//! [`UResult`] is a simple wrapper around [`Result`] with a custom error type: [`UError`]. The
|
||||
//! most important difference with types implementing [`std::error::Error`] is that [`UError`]s
|
||||
//! can specify the exit code of the program when they are returned from `uumain`:
|
||||
//! * When `Ok` is returned, the code set with [`set_exit_code`] is used as exit code. If
|
||||
//! [`set_exit_code`] was not used, then `0` is used.
|
||||
//! * When `Err` is returned, the code corresponding with the error is used as exit code and the
|
||||
//! error message is displayed.
|
||||
//!
|
||||
//! Additionally, the errors can be displayed manually with the [`show`] and [`show_if_err`] macros:
|
||||
//! ```ignore
|
||||
//! let res = Err(USimpleError::new(1, "Error!!"));
|
||||
//! show_if_err!(res);
|
||||
//! // or
|
||||
//! if let Err(e) = res {
|
||||
//! show!(e);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! **Note**: The [`show`] and [`show_if_err`] macros set the exit code of the program using
|
||||
//! [`set_exit_code`]. See the documentation on that function for more information.
|
||||
//!
|
||||
//! # Guidelines
|
||||
//! * Use common errors where possible.
|
||||
//! * Add variants to [`UCommonError`] if an error appears in multiple utils.
|
||||
//! * Prefer proper custom error types over [`ExitCode`] and [`USimpleError`].
|
||||
//! * [`USimpleError`] may be used in small utils with simple error handling.
|
||||
//! * Using [`ExitCode`] is not recommended but can be useful for converting utils to use
|
||||
//! [`UResult`].
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{Display, Formatter},
|
||||
sync::atomic::{AtomicI32, Ordering},
|
||||
};
|
||||
|
||||
static EXIT_CODE: AtomicI32 = AtomicI32::new(0);
|
||||
|
||||
/// Get the last exit code set with [`set_exit_code`].
|
||||
/// The default value is `0`.
|
||||
pub fn get_exit_code() -> i32 {
|
||||
EXIT_CODE.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Set the exit code for the program if `uumain` returns `Ok(())`.
|
||||
///
|
||||
/// This function is most useful for non-fatal errors, for example when applying an operation to
|
||||
/// multiple files:
|
||||
/// ```ignore
|
||||
/// use uucore::error::{UResult, set_exit_code};
|
||||
///
|
||||
/// fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
/// ...
|
||||
/// for file in files {
|
||||
/// let res = some_operation_that_might_fail(file);
|
||||
/// match res {
|
||||
/// Ok() => {},
|
||||
/// Err(_) => set_exit_code(1),
|
||||
/// }
|
||||
/// }
|
||||
/// Ok(()) // If any of the operations failed, 1 is returned.
|
||||
/// }
|
||||
/// ```
|
||||
pub fn set_exit_code(code: i32) {
|
||||
EXIT_CODE.store(code, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Should be returned by all utils.
|
||||
///
|
||||
/// Two additional methods are implemented on [`UResult`] on top of the normal [`Result`] methods:
|
||||
/// `map_err_code` & `map_err_code_message`.
|
||||
///
|
||||
/// These methods are used to convert [`UCommonError`]s into errors with a custom error code and
|
||||
/// message.
|
||||
pub type UResult<T> = Result<T, UError>;
|
||||
|
||||
trait UResultTrait<T> {
|
||||
fn map_err_code(self, mapper: fn(&UCommonError) -> Option<i32>) -> Self;
|
||||
fn map_err_code_and_message(self, mapper: fn(&UCommonError) -> Option<(i32, String)>) -> Self;
|
||||
}
|
||||
|
||||
impl<T> UResultTrait<T> for UResult<T> {
|
||||
fn map_err_code(self, mapper: fn(&UCommonError) -> Option<i32>) -> Self {
|
||||
if let Err(UError::Common(error)) = self {
|
||||
if let Some(code) = mapper(&error) {
|
||||
Err(UCommonErrorWithCode { code, error }.into())
|
||||
} else {
|
||||
Err(error.into())
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn map_err_code_and_message(self, mapper: fn(&UCommonError) -> Option<(i32, String)>) -> Self {
|
||||
if let Err(UError::Common(ref error)) = self {
|
||||
if let Some((code, message)) = mapper(error) {
|
||||
return Err(USimpleError { code, message }.into());
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type of [`UResult`].
|
||||
///
|
||||
/// `UError::Common` errors are defined in [`uucore`](crate) while `UError::Custom` errors are
|
||||
/// defined by the utils.
|
||||
/// ```
|
||||
/// use uucore::error::USimpleError;
|
||||
/// let err = USimpleError::new(1, "Error!!".into());
|
||||
/// assert_eq!(1, err.code());
|
||||
/// assert_eq!(String::from("Error!!"), format!("{}", err));
|
||||
/// ```
|
||||
pub enum UError {
|
||||
Common(UCommonError),
|
||||
Custom(Box<dyn UCustomError>),
|
||||
}
|
||||
|
||||
impl UError {
|
||||
pub fn code(&self) -> i32 {
|
||||
match self {
|
||||
UError::Common(e) => e.code(),
|
||||
UError::Custom(e) => e.code(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UCommonError> for UError {
|
||||
fn from(v: UCommonError) -> Self {
|
||||
UError::Common(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for UError {
|
||||
fn from(v: i32) -> Self {
|
||||
UError::Custom(Box::new(ExitCode(v)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: UCustomError + 'static> From<E> for UError {
|
||||
fn from(v: E) -> Self {
|
||||
UError::Custom(Box::new(v) as Box<dyn UCustomError>)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
UError::Common(e) => e.fmt(f),
|
||||
UError::Custom(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
///
|
||||
/// An example of a custom error from `ls`:
|
||||
/// ```
|
||||
/// use uucore::error::{UCustomError};
|
||||
/// use std::{
|
||||
/// error::Error,
|
||||
/// fmt::{Display, Debug},
|
||||
/// path::PathBuf
|
||||
/// };
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// enum LsError {
|
||||
/// InvalidLineWidth(String),
|
||||
/// NoMetadata(PathBuf),
|
||||
/// }
|
||||
///
|
||||
/// impl UCustomError for LsError {
|
||||
/// fn code(&self) -> i32 {
|
||||
/// match self {
|
||||
/// LsError::InvalidLineWidth(_) => 2,
|
||||
/// LsError::NoMetadata(_) => 1,
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl Error for LsError {}
|
||||
///
|
||||
/// impl Display for LsError {
|
||||
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// match self {
|
||||
/// LsError::InvalidLineWidth(s) => write!(f, "invalid line width: '{}'", s),
|
||||
/// LsError::NoMetadata(p) => write!(f, "could not open file: '{}'", p.display()),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// 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 {
|
||||
fn code(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn UCustomError>> for i32 {
|
||||
fn from(e: Box<dyn UCustomError>) -> i32 {
|
||||
e.code()
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`UCommonError`] with an overridden exit code.
|
||||
///
|
||||
/// This exit code is returned instead of the default exit code for the [`UCommonError`]. This is
|
||||
/// typically created with the either the `UResult::map_err_code` or `UCommonError::with_code`
|
||||
/// method.
|
||||
#[derive(Debug)]
|
||||
pub struct UCommonErrorWithCode {
|
||||
code: i32,
|
||||
error: UCommonError,
|
||||
}
|
||||
|
||||
impl Error for UCommonErrorWithCode {}
|
||||
|
||||
impl Display for UCommonErrorWithCode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
self.error.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl UCustomError for UCommonErrorWithCode {
|
||||
fn code(&self) -> i32 {
|
||||
self.code
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple error type with an exit code and a message that implements [`UCustomError`].
|
||||
///
|
||||
/// It is typically created with the `UResult::map_err_code_and_message` method. Alternatively, it
|
||||
/// can be constructed by manually:
|
||||
/// ```
|
||||
/// use uucore::error::{UResult, USimpleError};
|
||||
/// let err = USimpleError { code: 1, message: "error!".into()};
|
||||
/// let res: UResult<()> = Err(err.into());
|
||||
/// // or using the `new` method:
|
||||
/// let res: UResult<()> = Err(USimpleError::new(1, "error!".into()));
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct USimpleError {
|
||||
pub code: i32,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl USimpleError {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(code: i32, message: String) -> UError {
|
||||
UError::Custom(Box::new(Self { code, message }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for USimpleError {}
|
||||
|
||||
impl Display for USimpleError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
self.message.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl UCustomError for USimpleError {
|
||||
fn code(&self) -> i32 {
|
||||
self.code
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper type around [`std::io::Error`].
|
||||
///
|
||||
/// The messages displayed by [`UIoError`] should match the error messages displayed by GNU
|
||||
/// coreutils.
|
||||
///
|
||||
/// There are two ways to construct this type: with [`UIoError::new`] or by calling the
|
||||
/// [`FromIo::map_err_context`] method on a [`std::io::Result`] or [`std::io::Error`].
|
||||
/// ```
|
||||
/// use uucore::error::{FromIo, UResult, UIoError, UCommonError};
|
||||
/// use std::fs::File;
|
||||
/// use std::path::Path;
|
||||
/// let path = Path::new("test.txt");
|
||||
///
|
||||
/// // Manual construction
|
||||
/// let e: UIoError = UIoError::new(
|
||||
/// std::io::ErrorKind::NotFound,
|
||||
/// format!("cannot access '{}'", path.display())
|
||||
/// );
|
||||
/// let res: UResult<()> = Err(e.into());
|
||||
///
|
||||
/// // Converting from an `std::io::Error`.
|
||||
/// let res: UResult<File> = File::open(path).map_err_context(|| format!("cannot access '{}'", path.display()));
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct UIoError {
|
||||
context: String,
|
||||
inner: std::io::Error,
|
||||
}
|
||||
|
||||
impl UIoError {
|
||||
pub fn new(kind: std::io::ErrorKind, context: String) -> Self {
|
||||
Self {
|
||||
context,
|
||||
inner: std::io::Error::new(kind, ""),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn code(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for UIoError {}
|
||||
|
||||
impl Display for UIoError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
use std::io::ErrorKind::*;
|
||||
write!(
|
||||
f,
|
||||
"{}: {}",
|
||||
self.context,
|
||||
match self.inner.kind() {
|
||||
NotFound => "No such file or directory",
|
||||
PermissionDenied => "Permission denied",
|
||||
ConnectionRefused => "Connection refused",
|
||||
ConnectionReset => "Connection reset",
|
||||
ConnectionAborted => "Connection aborted",
|
||||
NotConnected => "Not connected",
|
||||
AddrInUse => "Address in use",
|
||||
AddrNotAvailable => "Address not available",
|
||||
BrokenPipe => "Broken pipe",
|
||||
AlreadyExists => "Already exists",
|
||||
WouldBlock => "Would block",
|
||||
InvalidInput => "Invalid input",
|
||||
InvalidData => "Invalid data",
|
||||
TimedOut => "Timed out",
|
||||
WriteZero => "Write zero",
|
||||
Interrupted => "Interrupted",
|
||||
Other => "Other",
|
||||
UnexpectedEof => "Unexpected end of file",
|
||||
_ => panic!("Unexpected io error: {}", self.inner),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables the conversion from `std::io::Error` to `UError` and from `std::io::Result` to
|
||||
/// `UResult`.
|
||||
pub trait FromIo<T> {
|
||||
fn map_err_context(self, context: impl FnOnce() -> String) -> T;
|
||||
}
|
||||
|
||||
impl FromIo<UIoError> for std::io::Error {
|
||||
fn map_err_context(self, context: impl FnOnce() -> String) -> UIoError {
|
||||
UIoError {
|
||||
context: (context)(),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromIo<UResult<T>> for std::io::Result<T> {
|
||||
fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
|
||||
self.map_err(|e| UError::Common(UCommonError::Io(e.map_err_context(context))))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIo<UIoError> for std::io::ErrorKind {
|
||||
fn map_err_context(self, context: impl FnOnce() -> String) -> UIoError {
|
||||
UIoError {
|
||||
context: (context)(),
|
||||
inner: std::io::Error::new(self, ""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UIoError> for UCommonError {
|
||||
fn from(e: UIoError) -> UCommonError {
|
||||
UCommonError::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UIoError> for UError {
|
||||
fn from(e: UIoError) -> UError {
|
||||
let common: UCommonError = e.into();
|
||||
common.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Common errors for utilities.
|
||||
///
|
||||
/// If identical errors appear across multiple utilities, they should be added here.
|
||||
#[derive(Debug)]
|
||||
pub enum UCommonError {
|
||||
Io(UIoError),
|
||||
// Clap(UClapError),
|
||||
}
|
||||
|
||||
impl UCommonError {
|
||||
pub fn with_code(self, code: i32) -> UCommonErrorWithCode {
|
||||
UCommonErrorWithCode { code, error: self }
|
||||
}
|
||||
|
||||
pub fn code(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UCommonError> for i32 {
|
||||
fn from(common: UCommonError) -> i32 {
|
||||
match common {
|
||||
UCommonError::Io(e) => e.code(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for UCommonError {}
|
||||
|
||||
impl Display for UCommonError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
UCommonError::Io(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A special error type that does not print any message when returned from
|
||||
/// `uumain`. Especially useful for porting utilities to using [`UResult`].
|
||||
///
|
||||
/// There are two ways to construct an [`ExitCode`]:
|
||||
/// ```
|
||||
/// use uucore::error::{ExitCode, UResult};
|
||||
/// // Explicit
|
||||
/// let res: UResult<()> = Err(ExitCode(1).into());
|
||||
///
|
||||
/// // Using into on `i32`:
|
||||
/// let res: UResult<()> = Err(1.into());
|
||||
/// ```
|
||||
/// This type is especially useful for a trivial conversion from utils returning [`i32`] to
|
||||
/// returning [`UResult`].
|
||||
#[derive(Debug)]
|
||||
pub struct ExitCode(pub i32);
|
||||
|
||||
impl Error for ExitCode {}
|
||||
|
||||
impl Display for ExitCode {
|
||||
fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl UCustomError for ExitCode {
|
||||
fn code(&self) -> i32 {
|
||||
self.0
|
||||
}
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
// Copyright (C) ~ Roy Ivy III <rivy.dev@gmail.com>; MIT license
|
||||
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::quote;
|
||||
use syn::{self, parse_macro_input, ItemFn};
|
||||
|
||||
//## rust proc-macro background info
|
||||
//* ref: <https://dev.to/naufraghi/procedural-macro-in-rust-101-k3f> @@ <http://archive.is/Vbr5e>
|
||||
|
@ -41,7 +45,7 @@ impl syn::parse::Parse for Tokens {
|
|||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn main(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
pub fn main(stream: TokenStream) -> TokenStream {
|
||||
let Tokens { expr } = syn::parse_macro_input!(stream as Tokens);
|
||||
proc_dbg!(&expr);
|
||||
|
||||
|
@ -78,5 +82,32 @@ pub fn main(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|||
std::process::exit(code);
|
||||
}
|
||||
};
|
||||
proc_macro::TokenStream::from(result)
|
||||
TokenStream::from(result)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn gen_uumain(_args: TokenStream, stream: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(stream as ItemFn);
|
||||
|
||||
// Change the name of the function to "uumain_result" to prevent name-conflicts
|
||||
ast.sig.ident = Ident::new("uumain_result", Span::call_site());
|
||||
|
||||
let new = quote!(
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
#ast
|
||||
let result = uumain_result(args);
|
||||
match result {
|
||||
Ok(()) => uucore::error::get_exit_code(),
|
||||
Err(e) => {
|
||||
let s = format!("{}", e);
|
||||
if s != "" {
|
||||
show_error!("{}", s);
|
||||
}
|
||||
e.code()
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
TokenStream::from(new)
|
||||
}
|
||||
|
|
|
@ -125,7 +125,8 @@ fn test_mktemp_mktemp_t() {
|
|||
.arg(TEST_TEMPLATE8)
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("suffix cannot contain any path separators");
|
||||
.stderr_contains("invalid suffix")
|
||||
.stderr_contains("contains directory separator");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue