diff --git a/Cargo.lock b/Cargo.lock index a5cc30f3e..5019f227b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2160,8 +2160,8 @@ dependencies = [ name = "uu_touch" version = "0.0.1" dependencies = [ + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "filetime 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.4", "uucore_procs 0.0.4", diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index 5881875c1..7fc56890a 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -16,7 +16,7 @@ path = "src/touch.rs" [dependencies] filetime = "0.2.1" -getopts = "0.2.18" +clap = "2.33" time = "0.1.40" uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["libc"] } uucore_procs = { version=">=0.0.4", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 48abd63a2..ffde06ca6 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -8,20 +8,31 @@ // spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm +extern crate clap; pub extern crate filetime; -extern crate getopts; extern crate time; #[macro_use] extern crate uucore; +use clap::{App, Arg}; use filetime::*; use std::fs::{self, File}; use std::io::Error; use std::path::Path; -static NAME: &str = "touch"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Update the access and modification times of each FILE to the current time."; +static OPT_ACCESS: &str = "access"; +static OPT_CURRENT: &str = "current"; +static OPT_DATE: &str = "date"; +static OPT_MODIFICATION: &str = "modification"; +static OPT_NO_CREATE: &str = "no-create"; +static OPT_NO_DEREF: &str = "no-dereference"; +static OPT_REFERENCE: &str = "reference"; +static OPT_TIME: &str = "time"; + +static ARG_FILES: &str = "files"; // Since touch's date/timestamp parsing doesn't account for timezone, the // returned value from time::strptime() is UTC. We get system's timezone to @@ -41,97 +52,108 @@ macro_rules! local_tm_to_filetime( }) ); +fn get_usage() -> String { + format!("{0} [OPTION]... [USER]", executable!()) +} + pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let usage = get_usage(); - let mut opts = getopts::Options::new(); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .arg( + Arg::with_name(OPT_ACCESS) + .short("a") + .help("change only the access time"), + ) + .arg( + Arg::with_name(OPT_CURRENT) + .short("t") + .help("use [[CC]YY]MMDDhhmm[.ss] instead of the current time") + .value_name("STAMP") + .takes_value(true), + ) + .arg( + Arg::with_name(OPT_DATE) + .short("d") + .long(OPT_DATE) + .help("parse argument and use it instead of current time") + .value_name("STRING"), + ) + .arg( + Arg::with_name(OPT_MODIFICATION) + .short("m") + .help("change only the modification time"), + ) + .arg( + Arg::with_name(OPT_NO_CREATE) + .short("c") + .long(OPT_NO_CREATE) + .help("do not create any files"), + ) + .arg( + Arg::with_name(OPT_NO_DEREF) + .short("h") + .long(OPT_NO_DEREF) + .help( + "affect each symbolic link instead of any referenced file \ + (only for systems that can change the timestamps of a symlink)", + ), + ) + .arg( + Arg::with_name(OPT_REFERENCE) + .short("r") + .long(OPT_REFERENCE) + .help("use this file's times instead of the current time") + .value_name("FILE"), + ) + .arg( + Arg::with_name(OPT_TIME) + .long(OPT_TIME) + .help( + "change only the specified time: \"access\", \"atime\", or \ + \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \ + equivalent to -m", + ) + .value_name("WORD") + .possible_values(&["access", "atime", "use"]) + .takes_value(true), + ) + .arg( + Arg::with_name(ARG_FILES) + .multiple(true) + .takes_value(true) + .min_values(1), + ) + .get_matches_from(args); - opts.optflag("a", "", "change only the access time"); - opts.optflag("c", "no-create", "do not create any files"); - opts.optopt( - "d", - "date", - "parse argument and use it instead of current time", - "STRING", - ); - opts.optflag( - "h", - "no-dereference", - "affect each symbolic link instead of any referenced file \ - (only for systems that can change the timestamps of a symlink)", - ); - opts.optflag("m", "", "change only the modification time"); - opts.optopt( - "r", - "reference", - "use this file's times instead of the current time", - "FILE", - ); - opts.optopt( - "t", - "", - "use [[CC]YY]MMDDhhmm[.ss] instead of the current time", - "STAMP", - ); - opts.optopt( - "", - "time", - "change only the specified time: \"access\", \"atime\", or \ - \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \ - equivalent to -m", - "WORD", - ); - opts.optflag("h", "help", "display this help and exit"); - opts.optflag("V", "version", "output version information and exit"); + let files: Vec = matches + .values_of(ARG_FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(e) => panic!("Invalid options\n{}", e), - }; - - if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - return 0; - } - - if matches.opt_present("help") || matches.free.is_empty() { - println!("{} {}", NAME, VERSION); - println!(); - println!("Usage: {} [OPTION]... FILE...", NAME); - println!(); - println!( - "{}", - opts.usage( - "Update the access and modification times of \ - each FILE to the current time." - ) - ); - if matches.free.is_empty() { - return 1; - } - return 0; - } - - if matches.opt_present("date") - && matches.opts_present(&["reference".to_owned(), "t".to_owned()]) - || matches.opt_present("reference") - && matches.opts_present(&["date".to_owned(), "t".to_owned()]) - || matches.opt_present("t") - && matches.opts_present(&["date".to_owned(), "reference".to_owned()]) + if matches.is_present(OPT_DATE) + && (matches.is_present(OPT_REFERENCE) || matches.is_present(OPT_CURRENT)) + || matches.is_present(OPT_REFERENCE) + && (matches.is_present(OPT_DATE) || matches.is_present(OPT_CURRENT)) + || matches.is_present(OPT_CURRENT) + && (matches.is_present(OPT_DATE) || matches.is_present(OPT_REFERENCE)) { panic!("Invalid options: cannot specify reference time from more than one source"); } - let (mut atime, mut mtime) = if matches.opt_present("reference") { + let (mut atime, mut mtime) = if matches.is_present(OPT_REFERENCE) { stat( - &matches.opt_str("reference").unwrap()[..], - !matches.opt_present("no-dereference"), + &matches.value_of(OPT_REFERENCE).unwrap()[..], + !matches.is_present(OPT_NO_DEREF), ) - } else if matches.opts_present(&["date".to_owned(), "t".to_owned()]) { - let timestamp = if matches.opt_present("date") { - parse_date(matches.opt_str("date").unwrap().as_ref()) + } else if matches.is_present(OPT_DATE) || matches.is_present(OPT_CURRENT) { + let timestamp = if matches.is_present(OPT_DATE) { + parse_date(matches.value_of(OPT_DATE).unwrap().as_ref()) } else { - parse_timestamp(matches.opt_str("t").unwrap().as_ref()) + parse_timestamp(matches.value_of(OPT_CURRENT).unwrap().as_ref()) }; (timestamp, timestamp) } else { @@ -139,12 +161,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { (now, now) }; - for filename in &matches.free { + for filename in &files { let path = &filename[..]; if !Path::new(path).exists() { // no-dereference included here for compatibility - if matches.opts_present(&["no-create".to_owned(), "no-dereference".to_owned()]) { + if matches.is_present(OPT_NO_CREATE) || matches.is_present(OPT_NO_DEREF) { continue; } @@ -154,18 +176,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; // Minor optimization: if no reference time was specified, we're done. - if !matches.opts_present(&["date".to_owned(), "reference".to_owned(), "t".to_owned()]) { + if !(matches.is_present(OPT_DATE) + || matches.is_present(OPT_REFERENCE) + || matches.is_present(OPT_CURRENT)) + { continue; } } // If changing "only" atime or mtime, grab the existing value of the other. // Note that "-a" and "-m" may be passed together; this is not an xor. - if matches.opts_present(&["a".to_owned(), "m".to_owned(), "time".to_owned()]) { - let st = stat(path, !matches.opt_present("no-dereference")); - let time = matches.opt_strs("time"); + if matches.is_present(OPT_ACCESS) + || matches.is_present(OPT_MODIFICATION) + || matches.is_present(OPT_TIME) + { + let st = stat(path, !matches.is_present(OPT_NO_DEREF)); + let time = matches.value_of(OPT_TIME).unwrap_or(""); - if !(matches.opt_present("a") + if !(matches.is_present(OPT_ACCESS) || time.contains(&"access".to_owned()) || time.contains(&"atime".to_owned()) || time.contains(&"use".to_owned())) @@ -173,7 +201,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { atime = st.0; } - if !(matches.opt_present("m") + if !(matches.is_present(OPT_MODIFICATION) || time.contains(&"modify".to_owned()) || time.contains(&"mtime".to_owned())) { @@ -181,7 +209,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } - if matches.opt_present("h") { + if matches.is_present(OPT_NO_DEREF) { if let Err(e) = set_symlink_file_times(path, atime, mtime) { show_warning!("cannot touch '{}': {}", path, e); } diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 84263376a..7e04beced 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -195,6 +195,14 @@ fn test_touch_set_only_atime() { assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45240); } +#[test] +fn test_touch_set_only_mtime_failed() { + let (_at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_only_mtime"; + + ucmd.args(&["-t", "2015010112342", "-m", file]).fails(); +} + #[test] fn test_touch_set_only_mtime() { let (at, mut ucmd) = at_and_ucmd!();