diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 1cd3b2a70..39405900e 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -13,7 +13,7 @@ pub extern crate filetime; #[macro_use] extern crate uucore; -use clap::{App, Arg}; +use clap::{App, Arg, ArgGroup}; use filetime::*; use std::fs::{self, File}; use std::io::Error; @@ -22,6 +22,8 @@ use std::path::Path; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Update the access and modification times of each FILE to the current time."; pub mod options { + // Both SOURCES and sources are needed as we need to be able to refer to the ArgGroup. + pub static SOURCES: &str = "sources"; pub mod sources { pub static DATE: &str = "date"; pub static REFERENCE: &str = "reference"; @@ -36,23 +38,15 @@ pub mod options { 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 -// localize the time. -macro_rules! to_local( - ($exp:expr) => ({ - let mut tm = $exp; - tm.tm_utcoff = time::now().tm_utcoff; - tm - }) -); +fn to_local(mut tm: time::Tm) -> time::Tm { + tm.tm_utcoff = time::now().tm_utcoff; + tm +} -macro_rules! local_tm_to_filetime( - ($exp:expr) => ({ - let ts = $exp.to_timespec(); - FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) - }) -); +fn local_tm_to_filetime(tm: time::Tm) -> FileTime { + let ts = tm.to_timespec(); + FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) +} fn get_usage() -> String { format!("{0} [OPTION]... [USER]", executable!()) @@ -129,6 +123,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .min_values(1), ) + .group(ArgGroup::with_name(options::SOURCES).args(&[ + options::sources::CURRENT, + options::sources::DATE, + options::sources::REFERENCE, + ])) .get_matches_from(args); let files: Vec = matches @@ -136,19 +135,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - if matches.is_present(options::sources::DATE) - && (matches.is_present(options::sources::REFERENCE) - || matches.is_present(options::sources::CURRENT)) - || matches.is_present(options::sources::REFERENCE) - && (matches.is_present(options::sources::DATE) - || matches.is_present(options::sources::CURRENT)) - || matches.is_present(options::sources::CURRENT) - && (matches.is_present(options::sources::DATE) - || matches.is_present(options::sources::REFERENCE)) - { - panic!("Invalid options: cannot specify reference time from more than one source"); - } - let (mut atime, mut mtime) = if matches.is_present(options::sources::REFERENCE) { stat( &matches.value_of(options::sources::REFERENCE).unwrap()[..], @@ -169,7 +155,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; (timestamp, timestamp) } else { - let now = local_tm_to_filetime!(time::now()); + let now = local_tm_to_filetime(time::now()); (now, now) }; @@ -188,10 +174,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; // Minor optimization: if no reference time was specified, we're done. - if !(matches.is_present(options::sources::DATE) - || matches.is_present(options::sources::REFERENCE) - || matches.is_present(options::sources::CURRENT)) - { + if !matches.is_present(options::SOURCES) { continue; } } @@ -260,7 +243,7 @@ fn parse_date(str: &str) -> FileTime { // not about to implement GNU parse_datetime. // http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y match time::strptime(str, "%c") { - Ok(tm) => local_tm_to_filetime!(to_local!(tm)), + Ok(tm) => local_tm_to_filetime(to_local(tm)), Err(e) => panic!("Unable to parse date\n{}", e), } } @@ -278,7 +261,7 @@ fn parse_timestamp(s: &str) -> FileTime { }; match time::strptime(&ts, format) { - Ok(tm) => local_tm_to_filetime!(to_local!(tm)), + Ok(tm) => local_tm_to_filetime(to_local(tm)), Err(e) => panic!("Unable to parse timestamp\n{}", e), } } diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 7e04beced..9921c16b5 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -203,6 +203,53 @@ fn test_touch_set_only_mtime_failed() { ucmd.args(&["-t", "2015010112342", "-m", file]).fails(); } +#[test] +fn test_touch_set_both_time_and_reference() { + let (at, mut ucmd) = at_and_ucmd!(); + let ref_file = "test_touch_reference"; + let file = "test_touch_set_both_time_and_reference"; + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + + at.touch(ref_file); + set_file_times(&at, ref_file, start_of_year, start_of_year); + assert!(at.file_exists(ref_file)); + + ucmd.args(&["-t", "2015010112342", "-r", ref_file, file]) + .fails(); +} + +#[test] +fn test_touch_set_both_date_and_reference() { + let (at, mut ucmd) = at_and_ucmd!(); + let ref_file = "test_touch_reference"; + let file = "test_touch_set_both_date_and_reference"; + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + + at.touch(ref_file); + set_file_times(&at, ref_file, start_of_year, start_of_year); + assert!(at.file_exists(ref_file)); + + ucmd.args(&["-d", "Thu Jan 01 12:34:00 2015", "-r", ref_file, file]) + .fails(); +} + +#[test] +fn test_touch_set_both_time_and_date() { + let (_at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_both_time_and_date"; + + ucmd.args(&[ + "-t", + "2015010112342", + "-d", + "Thu Jan 01 12:34:00 2015", + file, + ]) + .fails(); +} + #[test] fn test_touch_set_only_mtime() { let (at, mut ucmd) = at_and_ucmd!();