Implement rest of touch flags (#5119)

* Add timestamp flag to `touch` command

* Add modify flag to `touch` command

* Add date flag to `touch` command

* Remove unnecessary `touch` test and fix tests setups

* Change `touch` flags descriptions

* Update `touch` example

* Add reference flag to `touch` command

* Add access flag to `touch` command

* Add no-create flag to `touch` command

* Replace `unwrap` with `expect`
This commit is contained in:
Robert Broketa 2022-04-07 11:44:05 +00:00 committed by GitHub
parent b2c52b51b7
commit 690ec9abfa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 1031 additions and 6 deletions

13
Cargo.lock generated
View file

@ -1065,6 +1065,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "filetime"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"winapi",
]
[[package]]
name = "flate2"
version = "1.0.22"
@ -2329,6 +2341,7 @@ dependencies = [
"eml-parser",
"encoding_rs",
"filesize",
"filetime",
"fs_extra",
"hamcrest2",
"htmlescape",

View file

@ -40,6 +40,7 @@ dtparse = "1.2.0"
eml-parser = "0.1.0"
encoding_rs = "0.8.30"
filesize = "0.2.0"
filetime = "0.2.15"
fs_extra = "1.2.0"
htmlescape = "0.3.1"
ical = "0.7.0"

View file

@ -1,9 +1,20 @@
use std::fs::OpenOptions;
use std::path::Path;
use chrono::{DateTime, Datelike, Local};
use filetime::FileTime;
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
use crate::parse_date_from_string;
enum AddYear {
Full,
FirstDigits,
}
#[derive(Clone)]
pub struct Touch;
@ -20,6 +31,39 @@ impl Command for Touch {
SyntaxShape::Filepath,
"the path of the file you want to create",
)
.named(
"timestamp",
SyntaxShape::String,
"change the file or directory time to a timestamp. Format: [[CC]YY]MMDDhhmm[.ss]\n\n If neither YY or CC is given, the current year will be assumed. If YY is specified, but CC is not, CC will be derived as follows:\n \tIf YY is between [69, 99], CC is 19\n \tIf YY is between [00, 68], CC is 20\n Note: It is expected that in a future version of this standard the default century inferred from a 2-digit year will change",
Some('t'),
)
.named(
"date",
SyntaxShape::String,
"change the file or directory time to a date",
Some('d'),
)
.named(
"reference",
SyntaxShape::String,
"change the file or directory time to the time of the reference file/directory",
Some('r'),
)
.switch(
"modified",
"change the modification time of the file or directory. If no timestamp, date or reference file/directory is given, the current time is used",
Some('m'),
)
.switch(
"access",
"change the access time of the file or directory. If no timestamp, date or reference file/directory is given, the current time is used",
Some('a'),
)
.switch(
"no-create",
"do not create the file if it does not exist",
Some('c'),
)
.rest("rest", SyntaxShape::Filepath, "additional files to create")
.category(Category::FileSystem)
}
@ -35,17 +79,209 @@ impl Command for Touch {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut change_mtime: bool = call.has_flag("modified");
let mut change_atime: bool = call.has_flag("access");
let use_stamp: bool = call.has_flag("timestamp");
let use_date: bool = call.has_flag("date");
let use_reference: bool = call.has_flag("reference");
let no_create: bool = call.has_flag("no-create");
let target: String = call.req(engine_state, stack, 0)?;
let rest: Vec<String> = call.rest(engine_state, stack, 1)?;
let mut date: Option<DateTime<Local>> = None;
let mut ref_date_atime: Option<DateTime<Local>> = None;
// Change both times if none is specified
if !change_mtime && !change_atime {
change_mtime = true;
change_atime = true;
}
if change_mtime || change_atime {
date = Some(Local::now());
}
if use_stamp || use_date {
let (val, span) = if use_stamp {
let stamp: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "timestamp")?;
let (stamp, span) = match stamp {
Some(stamp) => (stamp.item, stamp.span),
None => {
return Err(ShellError::MissingParameter(
"timestamp".to_string(),
call.head,
));
}
};
// Checks for the seconds stamp and removes the '.' delimiter if any
let (val, has_sec): (String, bool) = match stamp.split_once('.') {
Some((dtime, sec)) => (format!("{}{}", dtime, sec), true),
None => (stamp.to_string(), false),
};
let size = val.len();
// Each stamp is a 2 digit number and the whole stamp must not be less than 4 or greater than 7 pairs
if (size % 2 != 0 || !(8..=14).contains(&size)) || val.parse::<usize>().is_err() {
return Err(ShellError::UnsupportedInput(
"input has an invalid timestamp".to_string(),
span,
));
}
let add_year: Option<AddYear> = if has_sec {
match size {
10 => Some(AddYear::Full),
12 => Some(AddYear::FirstDigits),
14 => None,
_ => unreachable!(), // This should never happen as the check above should catch it
}
} else {
match size {
8 => Some(AddYear::Full),
10 => Some(AddYear::FirstDigits),
12 => None,
_ => unreachable!(), // This should never happen as the check above should catch it
}
};
if let Some(add_year) = add_year {
let year = Local::now().year();
match add_year {
AddYear::Full => (format!("{}{}", year, val), span),
AddYear::FirstDigits => {
// Compliance with the Unix version of touch
let yy = val[0..2]
.parse::<u8>()
.expect("should be a valid 2 digit number");
let mut year = 20;
if (69..=99).contains(&yy) {
year = 19;
}
(format!("{}{}", year, val), span)
}
}
} else {
(val, span)
}
} else {
let date_string: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "date")?;
match date_string {
Some(date_string) => (date_string.item, date_string.span),
None => {
return Err(ShellError::MissingParameter("date".to_string(), call.head));
}
}
};
date = if let Ok(parsed_date) = parse_date_from_string(&val, span) {
Some(parsed_date.into())
} else {
let flag = if use_stamp { "timestamp" } else { "date" };
return Err(ShellError::UnsupportedInput(
format!("input has an invalid {}", flag),
span,
));
};
}
if use_reference {
let reference: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "reference")?;
match reference {
Some(reference) => {
let reference_path = Path::new(&reference.item);
if !reference_path.exists() {
return Err(ShellError::UnsupportedInput(
"path provided is invalid".to_string(),
reference.span,
));
}
date = Some(
reference_path
.metadata()
.expect("should be a valid path") // Should never fail as the path exists
.modified()
.expect("should have metadata") // This should always be valid as it is available on all nushell's supported platforms (Linux, Windows, MacOS)
.into(),
);
ref_date_atime = Some(
reference_path
.metadata()
.expect("should be a valid path") // Should never fail as the path exists
.accessed()
.expect("should have metadata") // This should always be valid as it is available on all nushell's supported platforms (Linux, Windows, MacOS)
.into(),
);
}
None => {
return Err(ShellError::MissingParameter(
"reference".to_string(),
call.head,
));
}
}
}
for (index, item) in vec![target].into_iter().chain(rest).enumerate() {
match OpenOptions::new().write(true).create(true).open(&item) {
Ok(_) => continue,
Err(err) => {
return Err(ShellError::CreateNotPossible(
format!("Failed to create file: {}", err),
if no_create {
let path = Path::new(&item);
if !path.exists() {
continue;
}
}
if let Err(err) = OpenOptions::new().write(true).create(true).open(&item) {
return Err(ShellError::CreateNotPossible(
format!("Failed to create file: {}", err),
call.positional[index].span,
));
};
if change_mtime {
// Should not panic as we return an error above if we can't parse the date
if let Err(err) = filetime::set_file_mtime(
&item,
FileTime::from_system_time(date.expect("should be a valid date").into()),
) {
return Err(ShellError::ChangeModifiedTimeNotPossible(
format!("Failed to change the modified time: {}", err),
call.positional[index].span,
));
};
}
if change_atime {
// Reference file/directory may have different access and modified times
if use_reference {
// Should not panic as we return an error above if we can't parse the date
if let Err(err) = filetime::set_file_atime(
&item,
FileTime::from_system_time(
ref_date_atime.expect("should be a valid date").into(),
),
) {
return Err(ShellError::ChangeAccessTimeNotPossible(
format!("Failed to change the access time: {}", err),
call.positional[index].span,
));
};
} else {
// Should not panic as we return an error above if we can't parse the date
if let Err(err) = filetime::set_file_atime(
&item,
FileTime::from_system_time(date.expect("should be a valid date").into()),
) {
return Err(ShellError::ChangeAccessTimeNotPossible(
format!("Failed to change the access time: {}", err),
call.positional[index].span,
));
};
}
}
}
@ -65,6 +301,36 @@ impl Command for Touch {
example: "touch a b c",
result: None,
},
Example {
description: r#"Changes the last modified time of "fixture.json" to today's date"#,
example: "touch -m fixture.json",
result: None,
},
Example {
description: "Creates files d and e and set its last modified time to a timestamp",
example: "touch -m -t 201908241230.30 d e",
result: None,
},
Example {
description: "Changes the last modified time of files a, b and c to a date",
example: r#"touch -m -d "yesterday" a b c"#,
result: None,
},
Example {
description: r#"Changes the last modified time of file d and e to "fixture.json"'s last modified time"#,
example: r#"touch -m -r fixture.json d e"#,
result: None,
},
Example {
description: r#"Changes the last accessed time of "fixture.json" to a date"#,
example: r#"touch -a -d "August 24, 2019; 12:30:30" fixture.json"#,
result: None,
},
Example {
description: "Changes both last modified and accessed time of a, b and c to a timestamp only if they exist",
example: r#"touch -c -t 201908241230.30 a b c"#,
result: None,
},
]
}
}

View file

@ -1,3 +1,5 @@
use chrono::{Date, DateTime, Local, TimeZone};
use nu_test_support::fs::Stub;
use nu_test_support::nu;
use nu_test_support::playground::Playground;
@ -29,3 +31,738 @@ fn creates_two_files() {
assert!(path2.exists());
})
}
#[test]
fn change_modified_time_of_file() {
Playground::setup("change_time_test_3", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -m -t 201908241230.30 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn create_and_change_modified_time_of_file() {
Playground::setup("change_time_test_4", |dirs, _sandbox| {
nu!(
cwd: dirs.test(),
"touch -m -t 201908241230 i_will_be_created.txt"
);
let path = dirs.test().join("i_will_be_created.txt");
assert!(path.exists());
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 0);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_modified_time_of_file_no_year() {
Playground::setup("change_time_test_5", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -m -t 08241230.12 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 12);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_modified_time_of_file_no_year_no_second() {
Playground::setup("change_time_test_6", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -m -t 08241230 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 0);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_modified_time_of_files() {
Playground::setup("change_time_test_7", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("file2.txt"),
]);
nu!(
cwd: dirs.test(),
"touch -m -t 1908241230.30 file.txt file2.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
let path = dirs.test().join("file2.txt");
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn errors_if_change_modified_time_of_file_with_invalid_timestamp() {
Playground::setup("change_time_test_8", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
let mut outcome = nu!(
cwd: dirs.test(),
"touch -m -t 1908241230.3030 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 1908241230.3O file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 08241230.3 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 8241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -t 01908241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
})
}
#[test]
fn change_modified_time_of_file_to_today() {
Playground::setup("change_time_test_9", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -m file.txt"
);
let path = dirs.test().join("file.txt");
// Check only the date since the time may not match exactly
let date: Date<Local> = Local::now().date();
let actual_date: Date<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap()).date();
assert_eq!(date, actual_date);
})
}
#[test]
fn change_modified_time_to_date() {
Playground::setup("change_time_test_10", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
r#"touch -m -d "August 24, 2019; 12:30:30" file.txt"#
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().modified().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_modified_time_to_time_of_reference() {
Playground::setup("change_time_test_11", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("reference.txt"),
]);
nu!(
cwd: dirs.test(),
r#"touch -m -t 201908241230.30 reference.txt"#
);
nu!(
cwd: dirs.test(),
r#"touch -m -r reference.txt file.txt"#
);
let path = dirs.test().join("file.txt");
let ref_path = dirs.test().join("reference.txt");
let time: DateTime<Local> = DateTime::from(path.metadata().unwrap().modified().unwrap());
let ref_time: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().modified().unwrap());
assert_eq!(time, ref_time);
})
}
#[test]
fn change_access_time_of_file() {
Playground::setup("change_time_test_12", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -a -t 201908241230.30 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn create_and_change_access_time_of_file() {
Playground::setup("change_time_test_13", |dirs, _sandbox| {
nu!(
cwd: dirs.test(),
"touch -a -t 201908241230 i_will_be_created.txt"
);
let path = dirs.test().join("i_will_be_created.txt");
assert!(path.exists());
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 0);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_access_time_of_file_no_year() {
Playground::setup("change_time_test_14", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -a -t 08241230.12 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 12);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_access_time_of_file_no_year_no_second() {
Playground::setup("change_time_test_15", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -a -t 08241230 file.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 0);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_access_time_of_files() {
Playground::setup("change_time_test_16", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("file2.txt"),
]);
nu!(
cwd: dirs.test(),
"touch -a -t 1908241230.30 file.txt file2.txt"
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
let path = dirs.test().join("file2.txt");
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn errors_if_change_access_time_of_file_with_invalid_timestamp() {
Playground::setup("change_time_test_17", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
let mut outcome = nu!(
cwd: dirs.test(),
"touch -a -t 1908241230.3030 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 1908241230.3O file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 08241230.3 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 8241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -t 01908241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
})
}
#[test]
fn change_access_time_of_file_to_today() {
Playground::setup("change_time_test_18", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -a file.txt"
);
let path = dirs.test().join("file.txt");
// Check only the date since the time may not match exactly
let date: Date<Local> = Local::now().date();
let actual_date: Date<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap()).date();
assert_eq!(date, actual_date);
})
}
#[test]
fn change_access_time_to_date() {
Playground::setup("change_time_test_19", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
r#"touch -a -d "August 24, 2019; 12:30:30" file.txt"#
);
let path = dirs.test().join("file.txt");
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let actual_time: DateTime<Local> =
DateTime::from(path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, actual_time);
})
}
#[test]
fn change_access_time_to_time_of_reference() {
Playground::setup("change_time_test_20", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("reference.txt"),
]);
nu!(
cwd: dirs.test(),
r#"touch -a -t 201908241230.30 reference.txt"#
);
nu!(
cwd: dirs.test(),
r#"touch -a -r reference.txt file.txt"#
);
let path = dirs.test().join("file.txt");
let ref_path = dirs.test().join("reference.txt");
let time: DateTime<Local> = DateTime::from(path.metadata().unwrap().accessed().unwrap());
let ref_time: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().accessed().unwrap());
assert_eq!(time, ref_time);
})
}
#[test]
fn change_modified_and_access_time_of_file() {
Playground::setup("change_time_test_21", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -m -a -t 201908241230.30 file.txt"
);
let path = dirs.test().join("file.txt");
let metadata = path.metadata().unwrap();
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn create_and_change_modified_and_access_time_of_file() {
Playground::setup("change_time_test_22", |dirs, _sandbox| {
nu!(
cwd: dirs.test(),
"touch -t 201908241230 i_will_be_created.txt"
);
let path = dirs.test().join("i_will_be_created.txt");
assert!(path.exists());
let metadata = path.metadata().unwrap();
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 0);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn change_modified_and_access_time_of_file_no_year() {
Playground::setup("change_time_test_23", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -a -m -t 08241230.12 file.txt"
);
let metadata = dirs.test().join("file.txt").metadata().unwrap();
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 12);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn change_modified_and_access_time_of_file_no_year_no_second() {
Playground::setup("change_time_test_24", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -t 08241230 file.txt"
);
let metadata = dirs.test().join("file.txt").metadata().unwrap();
let time = Local.ymd(2022, 8, 24).and_hms(12, 30, 0);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn change_modified_and_access_time_of_files() {
Playground::setup("change_time_test_25", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("file2.txt"),
]);
nu!(
cwd: dirs.test(),
"touch -a -m -t 1908241230.30 file.txt file2.txt"
);
let metadata = dirs.test().join("file.txt").metadata().unwrap();
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
let metadata = dirs.test().join("file2.txt").metadata().unwrap();
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn errors_if_change_modified_and_access_time_of_file_with_invalid_timestamp() {
Playground::setup("change_time_test_26", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
let mut outcome = nu!(
cwd: dirs.test(),
"touch -t 1908241230.3030 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -a -m -t 1908241230.3O file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -t 08241230.3 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -m -a -t 8241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
outcome = nu!(
cwd: dirs.test(),
"touch -t 01908241230 file.txt"
);
assert!(outcome.err.contains("input has an invalid timestamp"));
})
}
#[test]
fn change_modified_and_access_time_of_file_to_today() {
Playground::setup("change_time_test_27", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
"touch -a -m file.txt"
);
let metadata = dirs.test().join("file.txt").metadata().unwrap();
// Check only the date since the time may not match exactly
let date: Date<Local> = Local::now().date();
let adate: Date<Local> = DateTime::from(metadata.accessed().unwrap()).date();
let mdate: Date<Local> = DateTime::from(metadata.modified().unwrap()).date();
assert_eq!(date, adate);
assert_eq!(date, mdate);
})
}
#[test]
fn change_modified_and_access_time_to_date() {
Playground::setup("change_time_test_28", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("file.txt")]);
nu!(
cwd: dirs.test(),
r#"touch -d "August 24, 2019; 12:30:30" file.txt"#
);
let metadata = dirs.test().join("file.txt").metadata().unwrap();
let time = Local.ymd(2019, 8, 24).and_hms(12, 30, 30);
let atime: DateTime<Local> = DateTime::from(metadata.accessed().unwrap());
let mtime: DateTime<Local> = DateTime::from(metadata.modified().unwrap());
assert_eq!(time, atime);
assert_eq!(time, mtime);
})
}
#[test]
fn change_modified_and_access_time_to_time_of_reference() {
Playground::setup("change_time_test_29", |dirs, sandbox| {
sandbox.with_files(vec![
Stub::EmptyFile("file.txt"),
Stub::EmptyFile("reference.txt"),
]);
let path = dirs.test().join("file.txt");
let ref_path = dirs.test().join("reference.txt");
// Set the same time for the modified and access time of the reference file
nu!(
cwd: dirs.test(),
r#"touch -a -m -t 201908241230.30 reference.txt"#
);
nu!(
cwd: dirs.test(),
r#"touch -r reference.txt file.txt"#
);
let atime: DateTime<Local> = DateTime::from(path.metadata().unwrap().accessed().unwrap());
let ref_atime: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().accessed().unwrap());
assert_eq!(atime, ref_atime);
let mtime: DateTime<Local> = DateTime::from(path.metadata().unwrap().modified().unwrap());
let ref_mtime: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().modified().unwrap());
assert_eq!(mtime, ref_mtime);
// Set different time for the modified and access time of the reference file
nu!(
cwd: dirs.test(),
r#"touch -a -t 201908241230.30 reference.txt"#
);
nu!(
cwd: dirs.test(),
r#"touch -m -t 202009251340.40 reference.txt"#
);
nu!(
cwd: dirs.test(),
r#"touch -a -m -r reference.txt file.txt"#
);
let atime: DateTime<Local> = DateTime::from(path.metadata().unwrap().accessed().unwrap());
let ref_atime: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().accessed().unwrap());
assert_eq!(atime, ref_atime);
let mtime: DateTime<Local> = DateTime::from(path.metadata().unwrap().modified().unwrap());
let ref_mtime: DateTime<Local> =
DateTime::from(ref_path.metadata().unwrap().modified().unwrap());
assert_eq!(mtime, ref_mtime);
})
}
#[test]
fn not_create_file_if_it_not_exists() {
Playground::setup("change_time_test_28", |dirs, _sandbox| {
nu!(
cwd: dirs.test(),
r#"touch -c -d "August 24, 2019; 12:30:30" file.txt"#
);
let path = dirs.test().join("file.txt");
assert!(!path.exists());
nu!(
cwd: dirs.test(),
r#"touch -c file.txt"#
);
let path = dirs.test().join("file.txt");
assert!(!path.exists());
})
}

View file

@ -269,6 +269,14 @@ Either make sure {0} is a string, or add a 'to_string' entry for it in ENV_CONVE
#[diagnostic(code(nu::shell::create_not_possible), url(docsrs))]
CreateNotPossible(String, #[label("{0}")] Span),
#[error("Not possible to change the access time")]
#[diagnostic(code(nu::shell::change_access_time_not_possible), url(docsrs))]
ChangeAccessTimeNotPossible(String, #[label("{0}")] Span),
#[error("Not possible to change the modified time")]
#[diagnostic(code(nu::shell::change_modified_time_not_possible), url(docsrs))]
ChangeModifiedTimeNotPossible(String, #[label("{0}")] Span),
#[error("Remove not possible")]
#[diagnostic(code(nu::shell::remove_not_possible), url(docsrs))]
RemoveNotPossible(String, #[label("{0}")] Span),