mirror of
https://github.com/uutils/coreutils
synced 2024-12-14 07:12:44 +00:00
Merge pull request #4717 from Xalfer/touch
touch: add support for --ref and --date together
This commit is contained in:
commit
213bbb8e54
2 changed files with 101 additions and 47 deletions
|
@ -77,19 +77,57 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let (mut atime, mut mtime) =
|
let (mut atime, mut mtime) = match (
|
||||||
if let Some(reference) = matches.get_one::<OsString>(options::sources::REFERENCE) {
|
matches.get_one::<OsString>(options::sources::REFERENCE),
|
||||||
stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?
|
matches.get_one::<String>(options::sources::DATE),
|
||||||
} else {
|
) {
|
||||||
let timestamp = if let Some(date) = matches.get_one::<String>(options::sources::DATE) {
|
(Some(reference), Some(date)) => {
|
||||||
parse_date(date)?
|
let (atime, mtime) = stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?;
|
||||||
} else if let Some(current) = matches.get_one::<String>(options::sources::CURRENT) {
|
if let Some(offset) = parse_relative_time(date) {
|
||||||
parse_timestamp(current)?
|
let mut seconds = offset.whole_seconds();
|
||||||
|
let mut nanos = offset.subsec_nanoseconds();
|
||||||
|
if nanos < 0 {
|
||||||
|
nanos += 1_000_000_000;
|
||||||
|
seconds -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ref_atime_secs = atime.unix_seconds();
|
||||||
|
let ref_atime_nanos = atime.nanoseconds();
|
||||||
|
let atime = FileTime::from_unix_time(
|
||||||
|
ref_atime_secs + seconds,
|
||||||
|
ref_atime_nanos + nanos as u32,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ref_mtime_secs = mtime.unix_seconds();
|
||||||
|
let ref_mtime_nanos = mtime.nanoseconds();
|
||||||
|
let mtime = FileTime::from_unix_time(
|
||||||
|
ref_mtime_secs + seconds,
|
||||||
|
ref_mtime_nanos + nanos as u32,
|
||||||
|
);
|
||||||
|
|
||||||
|
(atime, mtime)
|
||||||
} else {
|
} else {
|
||||||
local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap())
|
let timestamp = parse_date(date)?;
|
||||||
};
|
(timestamp, timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(reference), None) => {
|
||||||
|
stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?
|
||||||
|
}
|
||||||
|
(None, Some(date)) => {
|
||||||
|
let timestamp = parse_date(date)?;
|
||||||
(timestamp, timestamp)
|
(timestamp, timestamp)
|
||||||
};
|
}
|
||||||
|
(None, None) => {
|
||||||
|
let timestamp =
|
||||||
|
if let Some(current) = matches.get_one::<String>(options::sources::CURRENT) {
|
||||||
|
parse_timestamp(current)?
|
||||||
|
} else {
|
||||||
|
local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap())
|
||||||
|
};
|
||||||
|
(timestamp, timestamp)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for filename in files {
|
for filename in files {
|
||||||
// FIXME: find a way to avoid having to clone the path
|
// FIXME: find a way to avoid having to clone the path
|
||||||
|
@ -202,7 +240,8 @@ pub fn uu_app() -> Command {
|
||||||
.long(options::sources::DATE)
|
.long(options::sources::DATE)
|
||||||
.allow_hyphen_values(true)
|
.allow_hyphen_values(true)
|
||||||
.help("parse argument and use it instead of current time")
|
.help("parse argument and use it instead of current time")
|
||||||
.value_name("STRING"),
|
.value_name("STRING")
|
||||||
|
.conflicts_with(options::sources::CURRENT),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(options::MODIFICATION)
|
Arg::new(options::MODIFICATION)
|
||||||
|
@ -234,7 +273,8 @@ pub fn uu_app() -> Command {
|
||||||
.help("use this file's times instead of the current time")
|
.help("use this file's times instead of the current time")
|
||||||
.value_name("FILE")
|
.value_name("FILE")
|
||||||
.value_parser(ValueParser::os_string())
|
.value_parser(ValueParser::os_string())
|
||||||
.value_hint(clap::ValueHint::AnyPath),
|
.value_hint(clap::ValueHint::AnyPath)
|
||||||
|
.conflicts_with(options::sources::CURRENT),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(options::TIME)
|
Arg::new(options::TIME)
|
||||||
|
@ -254,11 +294,15 @@ pub fn uu_app() -> Command {
|
||||||
.value_parser(ValueParser::os_string())
|
.value_parser(ValueParser::os_string())
|
||||||
.value_hint(clap::ValueHint::AnyPath),
|
.value_hint(clap::ValueHint::AnyPath),
|
||||||
)
|
)
|
||||||
.group(ArgGroup::new(options::SOURCES).args([
|
.group(
|
||||||
options::sources::CURRENT,
|
ArgGroup::new(options::SOURCES)
|
||||||
options::sources::DATE,
|
.args([
|
||||||
options::sources::REFERENCE,
|
options::sources::CURRENT,
|
||||||
]))
|
options::sources::DATE,
|
||||||
|
options::sources::REFERENCE,
|
||||||
|
])
|
||||||
|
.multiple(true),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> {
|
fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> {
|
||||||
|
@ -384,33 +428,22 @@ fn parse_date(s: &str) -> UResult<FileTime> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relative day, like "today", "tomorrow", or "yesterday".
|
if let Some(duration) = parse_relative_time(s) {
|
||||||
match s {
|
let now_local = time::OffsetDateTime::now_local().unwrap();
|
||||||
"now" | "today" => {
|
let diff = now_local.checked_add(duration).unwrap();
|
||||||
let now_local = time::OffsetDateTime::now_local().unwrap();
|
return Ok(local_dt_to_filetime(diff));
|
||||||
return Ok(local_dt_to_filetime(now_local));
|
|
||||||
}
|
|
||||||
"tomorrow" => {
|
|
||||||
let duration = time::Duration::days(1);
|
|
||||||
let now_local = time::OffsetDateTime::now_local().unwrap();
|
|
||||||
let diff = now_local.checked_add(duration).unwrap();
|
|
||||||
return Ok(local_dt_to_filetime(diff));
|
|
||||||
}
|
|
||||||
"yesterday" => {
|
|
||||||
let duration = time::Duration::days(1);
|
|
||||||
let now_local = time::OffsetDateTime::now_local().unwrap();
|
|
||||||
let diff = now_local.checked_sub(duration).unwrap();
|
|
||||||
return Ok(local_dt_to_filetime(diff));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Err(USimpleError::new(1, format!("Unable to parse date: {s}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_relative_time(s: &str) -> Option<Duration> {
|
||||||
// Relative time, like "-1 hour" or "+3 days".
|
// Relative time, like "-1 hour" or "+3 days".
|
||||||
//
|
//
|
||||||
// TODO Add support for "year" and "month".
|
// TODO Add support for "year" and "month".
|
||||||
// TODO Add support for times without spaces like "-1hour".
|
// TODO Add support for times without spaces like "-1hour".
|
||||||
let tokens: Vec<&str> = s.split_whitespace().collect();
|
let tokens: Vec<&str> = s.split_whitespace().collect();
|
||||||
let maybe_duration = match &tokens[..] {
|
match &tokens[..] {
|
||||||
[num_str, "fortnight" | "fortnights"] => num_str
|
[num_str, "fortnight" | "fortnights"] => num_str
|
||||||
.parse::<i64>()
|
.parse::<i64>()
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -430,15 +463,11 @@ fn parse_date(s: &str) -> UResult<FileTime> {
|
||||||
num_str.parse::<i64>().ok().map(time::Duration::seconds)
|
num_str.parse::<i64>().ok().map(time::Duration::seconds)
|
||||||
}
|
}
|
||||||
["second" | "seconds" | "sec" | "secs"] => Some(time::Duration::seconds(1)),
|
["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,
|
_ => None,
|
||||||
};
|
|
||||||
if let Some(duration) = maybe_duration {
|
|
||||||
let now_local = time::OffsetDateTime::now_local().unwrap();
|
|
||||||
let diff = now_local.checked_add(duration).unwrap();
|
|
||||||
return Ok(local_dt_to_filetime(diff));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(USimpleError::new(1, format!("Unable to parse date: {s}")))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
||||||
|
|
|
@ -251,14 +251,39 @@ fn test_touch_set_both_date_and_reference() {
|
||||||
let ref_file = "test_touch_reference";
|
let ref_file = "test_touch_reference";
|
||||||
let file = "test_touch_set_both_date_and_reference";
|
let file = "test_touch_set_both_date_and_reference";
|
||||||
|
|
||||||
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000");
|
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501011234");
|
||||||
|
|
||||||
at.touch(ref_file);
|
at.touch(ref_file);
|
||||||
set_file_times(&at, ref_file, start_of_year, start_of_year);
|
set_file_times(&at, ref_file, start_of_year, start_of_year);
|
||||||
assert!(at.file_exists(ref_file));
|
assert!(at.file_exists(ref_file));
|
||||||
|
|
||||||
ucmd.args(&["-d", "Thu Jan 01 12:34:00 2015", "-r", ref_file, file])
|
ucmd.args(&["-d", "Thu Jan 01 12:34:00 2015", "-r", ref_file, file])
|
||||||
.fails();
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
|
assert_eq!(atime, start_of_year);
|
||||||
|
assert_eq!(mtime, start_of_year);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_touch_set_both_offset_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", "201501011234");
|
||||||
|
let five_days_later = str_to_filetime("%Y%m%d%H%M", "201501061234");
|
||||||
|
|
||||||
|
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", "+5 days", "-r", ref_file, file])
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
|
assert_eq!(atime, five_days_later);
|
||||||
|
assert_eq!(mtime, five_days_later);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue