diff --git a/Cargo.lock b/Cargo.lock index 0634e1ad9..7b9b88451 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,6 +34,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + [[package]] name = "aliasable" version = "0.1.3" @@ -569,7 +578,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fdaa01904c12a8989dbfa110b41ef27efc432ac9934f691b9732f01cb64dc01" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.19", "byteorder", "cpp_common", "lazy_static", @@ -1145,6 +1154,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "humantime_to_duration" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d564881e1f668089d5653740167844f6ca8af54cd56bd6951f3dca85ba354fc" +dependencies = [ + "regex", + "time", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1880,11 +1899,11 @@ checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f" [[package]] name = "regex" -version = "1.7.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ - "aho-corasick", + "aho-corasick 1.0.1", "memchr", "regex-syntax", ] @@ -1897,9 +1916,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "rlimit" @@ -3213,6 +3232,7 @@ version = "0.0.18" dependencies = [ "clap", "filetime", + "humantime_to_duration", "time", "uucore", "windows-sys 0.45.0", diff --git a/Cargo.toml b/Cargo.toml index e3da0212a..f422555ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ # coreutils (uutils) # * see the repository LICENSE, README, and CONTRIBUTING files for more information -# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu mangen +# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu mangen humantime [package] name = "coreutils" @@ -286,6 +286,7 @@ fundu = "0.5.0" gcd = "2.3" glob = "0.3.1" half = "2.2" +humantime_to_duration = "0.1.2" indicatif = "0.17" is-terminal = "0.4.6" itertools = "0.10.5" diff --git a/deny.toml b/deny.toml index aa05cdf3e..31a170754 100644 --- a/deny.toml +++ b/deny.toml @@ -69,6 +69,8 @@ skip = [ { name = "linux-raw-sys", version = "0.1.4" }, # is-terminal { name = "windows-sys", version = "0.45.0" }, + # cpp_macros + { name = "aho-corasick", version = "0.7.19" }, ] # spell-checker: enable diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index 794273bba..0bb89ec84 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -1,3 +1,4 @@ +# spell-checker:ignore humantime [package] name = "uu_touch" version = "0.0.18" @@ -17,6 +18,7 @@ path = "src/touch.rs" [dependencies] filetime = { workspace=true } clap = { workspace=true } +humantime_to_duration = { workspace=true } time = { workspace=true, features = ["parsing", "formatting", "local-offset", "macros"] } uucore = { workspace=true, features=["libc"] } diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index eeb1dd13a..8bd483712 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -6,7 +6,7 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult mktime YYYYMMDDHHMM YYMMDDHHMM DATETIME YYYYMMDDHHMMS subsecond +// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult mktime YYYYMMDDHHMM YYMMDDHHMM DATETIME YYYYMMDDHHMMS subsecond humantime use clap::builder::ValueParser; use clap::{crate_version, Arg, ArgAction, ArgGroup, Command}; @@ -83,7 +83,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ) { (Some(reference), Some(date)) => { let (atime, mtime) = stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?; - if let Some(offset) = parse_relative_time(date) { + if let Ok(offset) = humantime_to_duration::from_str(date) { let mut seconds = offset.whole_seconds(); let mut nanos = offset.subsec_nanoseconds(); if nanos < 0 { @@ -428,7 +428,7 @@ fn parse_date(s: &str) -> UResult { } } - if let Some(duration) = parse_relative_time(s) { + if let Ok(duration) = humantime_to_duration::from_str(s) { let now_local = time::OffsetDateTime::now_local().unwrap(); let diff = now_local.checked_add(duration).unwrap(); return Ok(local_dt_to_filetime(diff)); @@ -437,39 +437,6 @@ fn parse_date(s: &str) -> UResult { Err(USimpleError::new(1, format!("Unable to parse date: {s}"))) } -fn parse_relative_time(s: &str) -> Option { - // Relative time, like "-1 hour" or "+3 days". - // - // TODO Add support for "year" and "month". - // TODO Add support for times without spaces like "-1hour". - let tokens: Vec<&str> = s.split_whitespace().collect(); - match &tokens[..] { - [num_str, "fortnight" | "fortnights"] => num_str - .parse::() - .ok() - .map(|n| time::Duration::weeks(2 * n)), - ["fortnight" | "fortnights"] => Some(time::Duration::weeks(2)), - [num_str, "week" | "weeks"] => num_str.parse::().ok().map(time::Duration::weeks), - ["week" | "weeks"] => Some(time::Duration::weeks(1)), - [num_str, "day" | "days"] => num_str.parse::().ok().map(time::Duration::days), - ["day" | "days"] => Some(time::Duration::days(1)), - [num_str, "hour" | "hours"] => num_str.parse::().ok().map(time::Duration::hours), - ["hour" | "hours"] => Some(time::Duration::hours(1)), - [num_str, "minute" | "minutes" | "min" | "mins"] => { - num_str.parse::().ok().map(time::Duration::minutes) - } - ["minute" | "minutes" | "min" | "mins"] => Some(time::Duration::minutes(1)), - [num_str, "second" | "seconds" | "sec" | "secs"] => { - num_str.parse::().ok().map(time::Duration::seconds) - } - ["second" | "seconds" | "sec" | "secs"] => Some(time::Duration::seconds(1)), - ["now" | "today"] => Some(time::Duration::ZERO), - ["yesterday"] => Some(time::Duration::days(-1)), - ["tomorrow"] => Some(time::Duration::days(1)), - _ => None, - } -} - fn parse_timestamp(s: &str) -> UResult { // TODO: handle error let now = time::OffsetDateTime::now_utc(); diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index ae3a28e49..d13da0bf6 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -608,7 +608,15 @@ fn test_touch_set_date_relative_smoke() { // > (equivalent to ‘day’), the string ‘yesterday’ is worth one day // > in the past (equivalent to ‘day ago’). // - let times = ["yesterday", "tomorrow", "now"]; + let times = [ + "yesterday", + "tomorrow", + "now", + "2 seconds", + "2 years 1 week", + "2 days ago", + "2 months and 1 second", + ]; for time in times { let (at, mut ucmd) = at_and_ucmd!(); at.touch("f"); @@ -617,6 +625,11 @@ fn test_touch_set_date_relative_smoke() { .no_stderr() .no_stdout(); } + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("f"); + ucmd.args(&["-d", "a", "f"]) + .fails() + .stderr_contains("touch: Unable to parse date"); } #[test]