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::os::windows::fs::MetadataExt;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Reverse,
|
cmp::Reverse,
|
||||||
|
error::Error,
|
||||||
|
fmt::Display,
|
||||||
fs::{self, DirEntry, FileType, Metadata},
|
fs::{self, DirEntry, FileType, Metadata},
|
||||||
io::{stdout, BufWriter, Stdout, Write},
|
io::{stdout, BufWriter, Stdout, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::exit,
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -38,8 +39,8 @@ use std::{
|
||||||
os::unix::fs::{FileTypeExt, MetadataExt},
|
os::unix::fs::{FileTypeExt, MetadataExt},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
||||||
|
use uucore::error::{set_exit_code, FromIo, UCustomError, UResult};
|
||||||
|
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -125,6 +126,32 @@ pub mod options {
|
||||||
pub static IGNORE: &str = "ignore";
|
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)]
|
#[derive(PartialEq, Eq)]
|
||||||
enum Format {
|
enum Format {
|
||||||
Columns,
|
Columns,
|
||||||
|
@ -218,7 +245,7 @@ struct LongFormat {
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[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) {
|
let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) {
|
||||||
(
|
(
|
||||||
match format_ {
|
match format_ {
|
||||||
|
@ -369,15 +396,13 @@ impl Config {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let width = options
|
let width = match options.value_of(options::WIDTH) {
|
||||||
.value_of(options::WIDTH)
|
Some(x) => match x.parse::<u16>() {
|
||||||
.map(|x| {
|
Ok(u) => Some(u),
|
||||||
x.parse::<u16>().unwrap_or_else(|_e| {
|
Err(_) => return Err(LsError::InvalidLineWidth(x.into()).into()),
|
||||||
show_error!("invalid line width: '{}'", x);
|
},
|
||||||
exit(2);
|
None => termsize::get().map(|s| s.cols),
|
||||||
})
|
};
|
||||||
})
|
|
||||||
.or_else(|| termsize::get().map(|s| s.cols));
|
|
||||||
|
|
||||||
#[allow(clippy::needless_bool)]
|
#[allow(clippy::needless_bool)]
|
||||||
let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) {
|
let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) {
|
||||||
|
@ -528,7 +553,7 @@ impl Config {
|
||||||
Dereference::DirArgs
|
Dereference::DirArgs
|
||||||
};
|
};
|
||||||
|
|
||||||
Config {
|
Ok(Config {
|
||||||
format,
|
format,
|
||||||
files,
|
files,
|
||||||
sort,
|
sort,
|
||||||
|
@ -547,11 +572,12 @@ impl Config {
|
||||||
quoting_style,
|
quoting_style,
|
||||||
indicator_style,
|
indicator_style,
|
||||||
time_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
|
let args = args
|
||||||
.collect_str(InvalidEncodingHandling::Ignore)
|
.collect_str(InvalidEncodingHandling::Ignore)
|
||||||
.accept_any();
|
.accept_any();
|
||||||
|
@ -567,7 +593,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
.unwrap_or_else(|| vec![String::from(".")]);
|
.unwrap_or_else(|| vec![String::from(".")]);
|
||||||
|
|
||||||
list(locs, Config::from(matches))
|
list(locs, Config::from(matches)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> App<'static, 'static> {
|
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 files = Vec::<PathData>::new();
|
||||||
let mut dirs = Vec::<PathData>::new();
|
let mut dirs = Vec::<PathData>::new();
|
||||||
let mut has_failed = false;
|
|
||||||
|
|
||||||
let mut out = BufWriter::new(stdout());
|
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);
|
let path_data = PathData::new(p, None, None, &config, true);
|
||||||
|
|
||||||
if path_data.md().is_none() {
|
if path_data.md().is_none() {
|
||||||
show_error!("'{}': {}", &loc, "No such file or directory");
|
show!(std::io::ErrorKind::NotFound
|
||||||
/*
|
.map_err_context(|| format!("cannot access '{}'", path_data.p_buf.display())));
|
||||||
We found an error, the return code of ls should not be 0
|
// We found an error, no need to continue the execution
|
||||||
And no need to continue the execution
|
|
||||||
*/
|
|
||||||
has_failed = true;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let show_dir_contents = match path_data.file_type() {
|
let show_dir_contents = match path_data.file_type() {
|
||||||
Some(ft) => !config.directory && ft.is_dir(),
|
Some(ft) => !config.directory && ft.is_dir(),
|
||||||
None => {
|
None => {
|
||||||
has_failed = true;
|
set_exit_code(1);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1235,11 +1257,8 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
|
||||||
}
|
}
|
||||||
enter_directory(&dir, &config, &mut out);
|
enter_directory(&dir, &config, &mut out);
|
||||||
}
|
}
|
||||||
if has_failed {
|
|
||||||
1
|
Ok(())
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
|
fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
|
||||||
|
@ -1478,7 +1497,7 @@ fn display_item_long(
|
||||||
) {
|
) {
|
||||||
let md = match item.md() {
|
let md = match item.md() {
|
||||||
None => {
|
None => {
|
||||||
show_error!("could not show file: {}", &item.p_buf.display());
|
show!(LsError::NoMetadata(item.p_buf.clone()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Some(md) => md,
|
Some(md) => md,
|
||||||
|
|
|
@ -8,25 +8,26 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
|
use clap::OsValues;
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use uucore::error::{FromIo, UResult, USimpleError};
|
||||||
|
|
||||||
static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
|
static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
|
||||||
static OPT_MODE: &str = "mode";
|
mod options {
|
||||||
static OPT_PARENTS: &str = "parents";
|
pub const MODE: &str = "mode";
|
||||||
static OPT_VERBOSE: &str = "verbose";
|
pub const PARENTS: &str = "parents";
|
||||||
|
pub const VERBOSE: &str = "verbose";
|
||||||
static ARG_DIRS: &str = "dirs";
|
pub const DIRS: &str = "dirs";
|
||||||
|
}
|
||||||
|
|
||||||
fn get_usage() -> String {
|
fn get_usage() -> String {
|
||||||
format!("{0} [OPTION]... [USER]", executable!())
|
format!("{0} [OPTION]... [USER]", executable!())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[uucore_procs::gen_uumain]
|
||||||
* Handles option parsing
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
*/
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
|
||||||
let usage = get_usage();
|
let usage = get_usage();
|
||||||
|
|
||||||
// Linux-specific options, not implemented
|
// Linux-specific options, not implemented
|
||||||
|
@ -34,26 +35,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
// " of each created directory to CTX"),
|
// " of each created directory to CTX"),
|
||||||
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
||||||
|
|
||||||
let dirs: Vec<String> = matches
|
let dirs = matches.values_of_os(options::DIRS).unwrap_or_default();
|
||||||
.values_of(ARG_DIRS)
|
let verbose = matches.is_present(options::VERBOSE);
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
let recursive = matches.is_present(options::PARENTS);
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let verbose = matches.is_present(OPT_VERBOSE);
|
|
||||||
let recursive = matches.is_present(OPT_PARENTS);
|
|
||||||
|
|
||||||
// Translate a ~str in octal form to u16, default to 755
|
// Translate a ~str in octal form to u16, default to 755
|
||||||
// Not tested on Windows
|
// Not tested on Windows
|
||||||
let mode_match = matches.value_of(OPT_MODE);
|
let mode: u16 = match matches.value_of(options::MODE) {
|
||||||
let mode: u16 = match mode_match {
|
Some(m) => u16::from_str_radix(m, 8)
|
||||||
Some(m) => {
|
.map_err(|_| USimpleError::new(1, format!("invalid mode '{}'", m)))?,
|
||||||
let res: Option<u16> = u16::from_str_radix(m, 8).ok();
|
None => 0o755_u16,
|
||||||
match res {
|
|
||||||
Some(r) => r,
|
|
||||||
_ => crash!(1, "no mode given"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => 0o755_u16,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exec(dirs, recursive, mode, verbose)
|
exec(dirs, recursive, mode, verbose)
|
||||||
|
@ -64,27 +55,27 @@ pub fn uu_app() -> App<'static, 'static> {
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_MODE)
|
Arg::with_name(options::MODE)
|
||||||
.short("m")
|
.short("m")
|
||||||
.long(OPT_MODE)
|
.long(options::MODE)
|
||||||
.help("set file mode (not implemented on windows)")
|
.help("set file mode (not implemented on windows)")
|
||||||
.default_value("755"),
|
.default_value("755"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_PARENTS)
|
Arg::with_name(options::PARENTS)
|
||||||
.short("p")
|
.short("p")
|
||||||
.long(OPT_PARENTS)
|
.long(options::PARENTS)
|
||||||
.alias("parent")
|
.alias("parent")
|
||||||
.help("make parent directories as needed"),
|
.help("make parent directories as needed"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_VERBOSE)
|
Arg::with_name(options::VERBOSE)
|
||||||
.short("v")
|
.short("v")
|
||||||
.long(OPT_VERBOSE)
|
.long(options::VERBOSE)
|
||||||
.help("print a message for each printed directory"),
|
.help("print a message for each printed directory"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(ARG_DIRS)
|
Arg::with_name(options::DIRS)
|
||||||
.multiple(true)
|
.multiple(true)
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.min_values(1),
|
.min_values(1),
|
||||||
|
@ -94,64 +85,43 @@ pub fn uu_app() -> App<'static, 'static> {
|
||||||
/**
|
/**
|
||||||
* Create the list of new directories
|
* Create the list of new directories
|
||||||
*/
|
*/
|
||||||
fn exec(dirs: Vec<String>, recursive: bool, mode: u16, verbose: bool) -> i32 {
|
fn exec(dirs: OsValues, recursive: bool, mode: u16, verbose: bool) -> UResult<()> {
|
||||||
let mut status = 0;
|
for dir in dirs {
|
||||||
let empty = Path::new("");
|
|
||||||
for dir in &dirs {
|
|
||||||
let path = Path::new(dir);
|
let path = Path::new(dir);
|
||||||
if !recursive {
|
show_if_err!(mkdir(path, recursive, mode, verbose));
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
status |= mkdir(path, recursive, mode, verbose);
|
|
||||||
}
|
}
|
||||||
status
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> UResult<()> {
|
||||||
* Wrapper to catch errors, return 1 if failed
|
|
||||||
*/
|
|
||||||
fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> i32 {
|
|
||||||
let create_dir = if recursive {
|
let create_dir = if recursive {
|
||||||
fs::create_dir_all
|
fs::create_dir_all
|
||||||
} else {
|
} else {
|
||||||
fs::create_dir
|
fs::create_dir
|
||||||
};
|
};
|
||||||
if let Err(e) = create_dir(path) {
|
|
||||||
show_error!("{}: {}", path.display(), e.to_string());
|
create_dir(path).map_err_context(|| format!("cannot create directory '{}'", path.display()))?;
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
println!("{}: created directory '{}'", executable!(), path.display());
|
println!("{}: created directory '{}'", executable!(), path.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(unix, target_os = "redox"))]
|
|
||||||
fn chmod(path: &Path, mode: u16) -> i32 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn chmod(path: &Path, mode: u16) -> i32 {
|
|
||||||
// chmod on Windows only sets the readonly flag, which isn't even honored on directories
|
|
||||||
0
|
|
||||||
}
|
|
||||||
chmod(path, mode)
|
chmod(path, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(unix, target_os = "redox"))]
|
||||||
|
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));
|
||||||
|
|
||||||
|
set_permissions(path, mode)
|
||||||
|
.map_err_context(|| format!("cannot set permissions '{}'", path.display()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn chmod(_path: &Path, _mode: u16) -> UResult<()> {
|
||||||
|
// chmod on Windows only sets the readonly flag, which isn't even honored on directories
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -12,8 +12,11 @@
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
|
use uucore::error::{FromIo, UCustomError, UResult};
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::path::{is_separator, PathBuf};
|
use std::path::{is_separator, PathBuf};
|
||||||
|
|
||||||
|
@ -37,7 +40,40 @@ fn get_usage() -> String {
|
||||||
format!("{0} [OPTION]... [TEMPLATE]", executable!())
|
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 usage = get_usage();
|
||||||
|
|
||||||
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
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 dry_run = matches.is_present(OPT_DRY_RUN);
|
||||||
let suppress_file_err = matches.is_present(OPT_QUIET);
|
let suppress_file_err = matches.is_present(OPT_QUIET);
|
||||||
|
|
||||||
let (prefix, rand, suffix) = match parse_template(template) {
|
let (prefix, rand, suffix) = parse_template(template, matches.value_of(OPT_SUFFIX))?;
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches.is_present(OPT_TMPDIR) && PathBuf::from(prefix).is_absolute() {
|
if matches.is_present(OPT_TMPDIR) && PathBuf::from(prefix).is_absolute() {
|
||||||
show_error!(
|
return Err(MkTempError::InvalidTemplate(template.into()).into());
|
||||||
"invalid template, '{}'; with --tmpdir, it may not be absolute",
|
}
|
||||||
template
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
if matches.is_present(OPT_T) {
|
if matches.is_present(OPT_T) {
|
||||||
tmpdir = env::temp_dir()
|
tmpdir = env::temp_dir()
|
||||||
};
|
}
|
||||||
|
|
||||||
if dry_run {
|
let res = if dry_run {
|
||||||
dry_exec(tmpdir, prefix, rand, suffix)
|
dry_exec(tmpdir, prefix, rand, suffix)
|
||||||
} else {
|
} 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') {
|
let right = match temp.rfind('X') {
|
||||||
Some(r) => r + 1,
|
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 left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1);
|
||||||
let prefix = &temp[..left];
|
let prefix = &temp[..left];
|
||||||
let rand = right - left;
|
let rand = right - left;
|
||||||
let suffix = &temp[right..];
|
|
||||||
Some((prefix, rand, suffix))
|
if rand < 3 {
|
||||||
|
return Err(MkTempError::TooFewXs(temp.into()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
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) -> i32 {
|
pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> UResult<()> {
|
||||||
let len = prefix.len() + suffix.len() + rand;
|
let len = prefix.len() + suffix.len() + rand;
|
||||||
let mut buf = String::with_capacity(len);
|
let mut buf = String::with_capacity(len);
|
||||||
buf.push_str(prefix);
|
buf.push_str(prefix);
|
||||||
|
@ -208,51 +245,35 @@ pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) ->
|
||||||
}
|
}
|
||||||
tmpdir.push(buf);
|
tmpdir.push(buf);
|
||||||
println!("{}", tmpdir.display());
|
println!("{}", tmpdir.display());
|
||||||
0
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool, quiet: bool) -> i32 {
|
fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> {
|
||||||
let res = if make_dir {
|
let context = || {
|
||||||
let tmpdir = Builder::new()
|
format!(
|
||||||
.prefix(prefix)
|
"failed to create file via template '{}{}{}'",
|
||||||
.rand_bytes(rand)
|
prefix,
|
||||||
.suffix(suffix)
|
"X".repeat(rand),
|
||||||
.tempdir_in(&dir);
|
suffix
|
||||||
|
)
|
||||||
// `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),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match res {
|
let mut builder = Builder::new();
|
||||||
Ok(ref f) => {
|
builder.prefix(prefix).rand_bytes(rand).suffix(suffix);
|
||||||
println!("{}", f);
|
|
||||||
0
|
let path = if make_dir {
|
||||||
}
|
builder
|
||||||
Err(e) => {
|
.tempdir_in(&dir)
|
||||||
if !quiet {
|
.map_err_context(context)?
|
||||||
show_error!("{}: {}", e, dir.display());
|
.into_path() // `into_path` consumes the TempDir without removing it
|
||||||
}
|
} else {
|
||||||
1
|
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 clap::{crate_version, App, Arg, ArgGroup};
|
||||||
use filetime::*;
|
use filetime::*;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::Error;
|
|
||||||
use std::path::Path;
|
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.";
|
static ABOUT: &str = "Update the access and modification times of each FILE to the current time.";
|
||||||
pub mod options {
|
pub mod options {
|
||||||
|
@ -52,57 +51,38 @@ fn get_usage() -> String {
|
||||||
format!("{0} [OPTION]... [USER]", executable!())
|
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 usage = get_usage();
|
||||||
|
|
||||||
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
||||||
|
|
||||||
let files: Vec<String> = matches
|
let files = matches.values_of_os(ARG_FILES).unwrap();
|
||||||
.values_of(ARG_FILES)
|
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let (mut atime, mut mtime) = if matches.is_present(options::sources::REFERENCE) {
|
let (mut atime, mut mtime) =
|
||||||
stat(
|
if let Some(reference) = matches.value_of_os(options::sources::REFERENCE) {
|
||||||
matches.value_of(options::sources::REFERENCE).unwrap(),
|
stat(Path::new(reference), !matches.is_present(options::NO_DEREF))?
|
||||||
!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())
|
|
||||||
} else {
|
} 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)
|
||||||
};
|
};
|
||||||
(timestamp, timestamp)
|
|
||||||
} else {
|
|
||||||
let now = local_tm_to_filetime(time::now());
|
|
||||||
(now, now)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut error_code = 0;
|
for filename in files {
|
||||||
|
let path = Path::new(filename);
|
||||||
for filename in &files {
|
if !path.exists() {
|
||||||
let path = &filename[..];
|
|
||||||
|
|
||||||
if !Path::new(path).exists() {
|
|
||||||
// no-dereference included here for compatibility
|
// no-dereference included here for compatibility
|
||||||
if matches.is_present(options::NO_CREATE) || matches.is_present(options::NO_DEREF) {
|
if matches.is_present(options::NO_CREATE) || matches.is_present(options::NO_DEREF) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = File::create(path) {
|
if let Err(e) = File::create(path) {
|
||||||
match e.kind() {
|
show!(e.map_err_context(|| format!("cannot touch '{}'", path.display())));
|
||||||
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;
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -118,7 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
|| matches.is_present(options::MODIFICATION)
|
|| matches.is_present(options::MODIFICATION)
|
||||||
|| matches.is_present(options::TIME)
|
|| 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("");
|
let time = matches.value_of(options::TIME).unwrap_or("");
|
||||||
|
|
||||||
if !(matches.is_present(options::ACCESS)
|
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 matches.is_present(options::NO_DEREF) {
|
||||||
if let Err(e) = set_symlink_file_times(path, atime, mtime) {
|
set_symlink_file_times(path, atime, mtime)
|
||||||
// we found an error, it should fail in any case
|
} else {
|
||||||
error_code = 1;
|
filetime::set_file_times(path, atime, mtime)
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.map_err_context(|| format!("setting times of '{}'", path.display()))?;
|
||||||
}
|
}
|
||||||
error_code
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> App<'static, 'static> {
|
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 {
|
let metadata = if follow {
|
||||||
fs::symlink_metadata(path)
|
fs::symlink_metadata(path)
|
||||||
} else {
|
} else {
|
||||||
fs::metadata(path)
|
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
|
// 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
|
// be any simple specification for what format this parameter allows and I'm
|
||||||
// not about to implement GNU parse_datetime.
|
// not about to implement GNU parse_datetime.
|
||||||
|
@ -267,18 +225,22 @@ fn parse_date(str: &str) -> FileTime {
|
||||||
let formats = vec!["%c", "%F"];
|
let formats = vec!["%c", "%F"];
|
||||||
for f in formats {
|
for f in formats {
|
||||||
if let Ok(tm) = time::strptime(str, f) {
|
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") {
|
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
|
// Don't convert to local time in this case - seconds since epoch are not time-zone dependent
|
||||||
return local_tm_to_filetime(tm);
|
return Ok(local_tm_to_filetime(tm));
|
||||||
}
|
}
|
||||||
show_error!("Unable to parse date: {}\n", str);
|
|
||||||
process::exit(1);
|
Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("Unable to parse date: {}", str),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_timestamp(s: &str) -> FileTime {
|
fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
||||||
let now = time::now();
|
let now = time::now();
|
||||||
let (format, ts) = match s.chars().count() {
|
let (format, ts) = match s.chars().count() {
|
||||||
15 => ("%Y%m%d%H%M.%S", s.to_owned()),
|
15 => ("%Y%m%d%H%M.%S", s.to_owned()),
|
||||||
|
@ -287,31 +249,28 @@ fn parse_timestamp(s: &str) -> FileTime {
|
||||||
10 => ("%y%m%d%H%M", s.to_owned()),
|
10 => ("%y%m%d%H%M", s.to_owned()),
|
||||||
11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)),
|
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)),
|
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) {
|
let tm = time::strptime(&ts, format)
|
||||||
Ok(tm) => {
|
.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);
|
|
||||||
|
|
||||||
// We have to check that ft is valid time. Due to daylight saving
|
let mut local = to_local(tm);
|
||||||
// time switch, local time can jump from 1:59 AM to 3:00 AM,
|
local.tm_isdst = -1;
|
||||||
// in which case any time between 2:00 AM and 2:59 AM is not valid.
|
let ft = local_tm_to_filetime(local);
|
||||||
// Convert back to local time and see if we got the same value back.
|
|
||||||
let ts = time::Timespec {
|
|
||||||
sec: ft.unix_seconds(),
|
|
||||||
nsec: 0,
|
|
||||||
};
|
|
||||||
let tm2 = time::at(ts);
|
|
||||||
if tm.tm_hour != tm2.tm_hour {
|
|
||||||
show_error!("invalid date format {}", s);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ft
|
// We have to check that ft is valid time. Due to daylight saving
|
||||||
}
|
// time switch, local time can jump from 1:59 AM to 3:00 AM,
|
||||||
Err(e) => panic!("Unable to parse timestamp\n{}", e),
|
// in which case any time between 2:00 AM and 2:59 AM is not valid.
|
||||||
|
// Convert back to local time and see if we got the same value back.
|
||||||
|
let ts = time::Timespec {
|
||||||
|
sec: ft.unix_seconds(),
|
||||||
|
nsec: 0,
|
||||||
|
};
|
||||||
|
let tm2 = time::at(ts);
|
||||||
|
if tm.tm_hour != tm2.tm_hour {
|
||||||
|
return Err(USimpleError::new(1, format!("invalid date format '{}'", s)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(ft)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ mod parser; // string parsing modules
|
||||||
// * cross-platform modules
|
// * cross-platform modules
|
||||||
pub use crate::mods::backup_control;
|
pub use crate::mods::backup_control;
|
||||||
pub use crate::mods::coreopts;
|
pub use crate::mods::coreopts;
|
||||||
|
pub use crate::mods::error;
|
||||||
pub use crate::mods::os;
|
pub use crate::mods::os;
|
||||||
pub use crate::mods::panic;
|
pub use crate::mods::panic;
|
||||||
pub use crate::mods::ranges;
|
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.
|
/// Show an error to stderr in a similar style to GNU coreutils.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! show_error(
|
macro_rules! show_error(
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
pub mod backup_control;
|
pub mod backup_control;
|
||||||
pub mod coreopts;
|
pub mod coreopts;
|
||||||
|
pub mod error;
|
||||||
pub mod os;
|
pub mod os;
|
||||||
pub mod panic;
|
pub mod panic;
|
||||||
pub mod ranges;
|
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
|
// Copyright (C) ~ Roy Ivy III <rivy.dev@gmail.com>; MIT license
|
||||||
|
|
||||||
extern crate proc_macro;
|
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
|
//## rust proc-macro background info
|
||||||
//* ref: <https://dev.to/naufraghi/procedural-macro-in-rust-101-k3f> @@ <http://archive.is/Vbr5e>
|
//* 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]
|
#[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);
|
let Tokens { expr } = syn::parse_macro_input!(stream as Tokens);
|
||||||
proc_dbg!(&expr);
|
proc_dbg!(&expr);
|
||||||
|
|
||||||
|
@ -78,5 +82,32 @@ pub fn main(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
std::process::exit(code);
|
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)
|
.arg(TEST_TEMPLATE8)
|
||||||
.fails()
|
.fails()
|
||||||
.no_stdout()
|
.no_stdout()
|
||||||
.stderr_contains("suffix cannot contain any path separators");
|
.stderr_contains("invalid suffix")
|
||||||
|
.stderr_contains("contains directory separator");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue