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

chmod: move from getopts to clap

This commit is contained in:
Jan Scheer 2021-03-28 13:11:39 +02:00
parent 035f811dd0
commit 43c6a52b63
2 changed files with 148 additions and 88 deletions

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/chmod.rs" path = "src/chmod.rs"
[dependencies] [dependencies]
clap = "2.33.3"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs", "mode"] } uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs", "mode"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -10,6 +10,7 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{App, Arg};
use std::fs; use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path; use std::path::Path;
@ -18,81 +19,137 @@ use uucore::fs::display_permissions_unix;
use uucore::mode; use uucore::mode;
use walkdir::WalkDir; use walkdir::WalkDir;
const NAME: &str = "chmod"; static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "Change the mode of each FILE to MODE. static ABOUT: &str = "Change the mode of each FILE to MODE.
With --reference, change the mode of each FILE to that of RFILE."; With --reference, change the mode of each FILE to that of RFILE.";
static LONG_HELP: &str = "
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'. mod options {
"; pub const CHANGES: &str = "changes";
pub const QUIET: &str = "quiet"; // visible_alias("silent")
pub const VERBOSE: &str = "verbose";
pub const NO_PRESERVE_ROOT: &str = "no-preserve-root";
pub const PRESERVE_ROOT: &str = "preserve-root";
pub const REFERENCE: &str = "RFILE";
pub const RECURSIVE: &str = "recursive";
pub const MODE: &str = "MODE";
pub const FILE: &str = "FILE";
}
fn get_usage() -> String {
format!(
"{0} [OPTION]... MODE[,MODE]... FILE...
or: {0} [OPTION]... OCTAL-MODE FILE...
or: {0} [OPTION]... --reference=RFILE FILE...",
executable!()
)
}
fn get_long_usage() -> String {
String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.")
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let mut args = args.collect_str(); let mut args = args.collect_str();
let syntax = format!( // Before we can parse 'args' with clap (and previously getopts),
"[OPTION]... MODE[,MODE]... FILE... // a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
{0} [OPTION]... OCTAL-MODE FILE... let mode_had_minus_prefix = strip_minus_from_mode(&mut args);
{0} [OPTION]... --reference=RFILE FILE...",
NAME
);
let mut opts = app!(&syntax, SUMMARY, LONG_HELP);
opts.optflag(
"c",
"changes",
"like verbose but report only when a change is made",
)
// TODO: support --silent (can be done using clap)
.optflag("f", "quiet", "suppress most error messages")
.optflag(
"v",
"verbose",
"output a diagnostic for every file processed",
)
.optflag(
"",
"no-preserve-root",
"do not treat '/' specially (the default)",
)
.optflag("", "preserve-root", "fail to operate recursively on '/'")
.optopt(
"",
"reference",
"use RFILE's mode instead of MODE values",
"RFILE",
)
.optflag("R", "recursive", "change files and directories recursively");
// sanitize input for - at beginning (e.g. chmod -x test_file). Remove let usage = get_usage();
// the option and save it for later, after parsing is finished. let after_help = get_long_usage();
let negative_option = sanitize_input(&mut args);
let mut matches = opts.parse(args); let matches = App::new(executable!())
if matches.free.is_empty() { .version(VERSION)
show_error!("missing an argument"); .about(ABOUT)
show_error!("for help, try '{} --help'", NAME); .usage(&usage[..])
return 1; .after_help(&after_help[..])
} else { .arg(
let changes = matches.opt_present("changes"); Arg::with_name(options::CHANGES)
let quiet = matches.opt_present("quiet"); .long(options::CHANGES)
let verbose = matches.opt_present("verbose"); .short("c")
let preserve_root = matches.opt_present("preserve-root"); .help("like verbose but report only when a change is made"),
let recursive = matches.opt_present("recursive"); )
let fmode = matches .arg(
.opt_str("reference") Arg::with_name(options::QUIET)
.long(options::QUIET)
.visible_alias("silent")
.short("f")
.help("suppress most error messages"),
)
.arg(
Arg::with_name(options::VERBOSE)
.long(options::VERBOSE)
.short("v")
.help("output a diagnostic for every file processed"),
)
.arg(
Arg::with_name(options::NO_PRESERVE_ROOT)
.long(options::NO_PRESERVE_ROOT)
.help("do not treat '/' specially (the default)"),
)
.arg(
Arg::with_name(options::PRESERVE_ROOT)
.long(options::PRESERVE_ROOT)
.help("fail to operate recursively on '/'"),
)
.arg(
Arg::with_name(options::RECURSIVE)
.long(options::RECURSIVE)
.short("R")
.help("change files and directories recursively"),
)
.arg(
Arg::with_name(options::REFERENCE)
.long("reference")
.takes_value(true)
.help("use RFILE's mode instead of MODE values"),
)
.arg(
Arg::with_name(options::MODE)
.required_unless(options::REFERENCE)
.takes_value(true),
// It would be nice if clap could parse with delimeter, e.g. "g-x,u+x",
// however .multiple(true) cannot be used here because FILE already needs that.
// Only one positional argument with .multiple(true) set is allowed per command
)
.arg(
Arg::with_name(options::FILE)
.required_unless(options::MODE)
.multiple(true),
)
.get_matches_from(args);
let changes = matches.is_present(options::CHANGES);
let quiet = matches.is_present(options::QUIET);
let verbose = matches.is_present(options::VERBOSE);
let preserve_root = matches.is_present(options::PRESERVE_ROOT);
let recursive = matches.is_present(options::RECURSIVE);
let fmode =
matches
.value_of(options::REFERENCE)
.and_then(|ref fref| match fs::metadata(fref) { .and_then(|ref fref| match fs::metadata(fref) {
Ok(meta) => Some(meta.mode()), Ok(meta) => Some(meta.mode()),
Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err), Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err),
}); });
let cmode = if fmode.is_none() { let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
// If there was a negative option, now it's a good time to let mut cmode = if mode_had_minus_prefix {
// use it. // clap parsing is finished, now put prefix back
if negative_option.is_some() { Some(format!("-{}", modes))
negative_option
} else { } else {
Some(matches.free.remove(0)) Some(modes.to_string())
}
} else {
None
}; };
let mut files: Vec<String> = matches
.values_of(options::FILE)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
if fmode.is_some() {
// "--reference" and MODE are mutually exclusive
// if "--reference" was used MODE needs to be interpreted as another FILE
// it wasn't possible to implement this behavior directly with clap
files.push(cmode.unwrap());
cmode = None;
}
let chmoder = Chmoder { let chmoder = Chmoder {
changes, changes,
quiet, quiet,
@ -102,31 +159,33 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
fmode, fmode,
cmode, cmode,
}; };
match chmoder.chmod(matches.free) { match chmoder.chmod(files) {
Ok(()) => {} Ok(()) => {}
Err(e) => return e, Err(e) => return e,
} }
}
0 0
} }
fn sanitize_input(args: &mut Vec<String>) -> Option<String> { // Iterate 'args' and delete the first occurrence
// of a prefix '-' if it's associated with MODE
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
for i in 0..args.len() { for i in 0..args.len() {
let first = args[i].chars().next().unwrap(); if args[i].starts_with("-") {
if first != '-' {
continue;
}
if let Some(second) = args[i].chars().nth(1) { if let Some(second) = args[i].chars().nth(1) {
match second { match second {
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => { 'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
return Some(args.remove(i)); // TODO: use strip_prefix() once minimum rust version reaches 1.45.0
args[i] = args[i][1..args[i].len()].to_string();
return true;
} }
_ => {} _ => {}
} }
} }
} }
None }
false
} }
struct Chmoder { struct Chmoder {