From 060e118ab4974ad114edb3e93f8e465b7ed40eb9 Mon Sep 17 00:00:00 2001 From: ysthakur <45539777+ysthakur@users.noreply.github.com> Date: Sun, 4 Feb 2024 23:25:37 -0500 Subject: [PATCH 1/8] Add utouch command --- Cargo.lock | 64 ++++++- crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/default_context.rs | 2 + crates/nu-command/src/filesystem/mod.rs | 2 + crates/nu-command/src/filesystem/utouch.rs | 188 +++++++++++++++++++++ crates/nu-command/tests/commands/mod.rs | 1 + crates/nu-command/tests/commands/utouch.rs | 163 ++++++++++++++++++ 7 files changed, 414 insertions(+), 7 deletions(-) create mode 100644 crates/nu-command/src/filesystem/utouch.rs create mode 100644 crates/nu-command/tests/commands/utouch.rs diff --git a/Cargo.lock b/Cargo.lock index 354e8319fb..660140c4a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3033,6 +3033,7 @@ dependencies = [ "uu_mkdir", "uu_mktemp", "uu_mv", + "uu_touch", "uu_whoami", "uuid", "v_htmlescape", @@ -3688,6 +3689,16 @@ dependencies = [ "regex", ] +[[package]] +name = "parse_datetime" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bbf4e25b13841080e018a1e666358adfe5e39b6d353f986ca5091c210b586a1" +dependencies = [ + "chrono", + "regex", +] + [[package]] name = "paste" version = "1.0.14" @@ -6128,7 +6139,7 @@ dependencies = [ "indicatif", "libc", "quick-error 2.0.1", - "uucore", + "uucore 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir", "xattr", ] @@ -6140,7 +6151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbf657c9e738d16ebc5c161a611ff25327c1fb599645afb2831062efb23c851" dependencies = [ "clap", - "uucore", + "uucore 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -6152,7 +6163,7 @@ dependencies = [ "clap", "rand", "tempfile", - "uucore", + "uucore 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -6164,7 +6175,19 @@ dependencies = [ "clap", "fs_extra", "indicatif", - "uucore", + "uucore 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "uu_touch" +version = "0.0.24" +dependencies = [ + "chrono", + "clap", + "filetime", + "parse_datetime", + "uucore 0.0.24", + "windows-sys 0.48.0", ] [[package]] @@ -6175,10 +6198,24 @@ checksum = "70589dc3b41f34cbfe1fb22b8f20fcac233fa4565409905f12dd06780b18374d" dependencies = [ "clap", "libc", - "uucore", + "uucore 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "windows-sys 0.48.0", ] +[[package]] +name = "uucore" +version = "0.0.24" +dependencies = [ + "clap", + "glob", + "libc", + "nix 0.27.1", + "once_cell", + "os_display", + "uucore_procs 0.0.24", + "wild", +] + [[package]] name = "uucore" version = "0.0.24" @@ -6192,13 +6229,22 @@ dependencies = [ "nix 0.27.1", "once_cell", "os_display", - "uucore_procs", + "uucore_procs 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir", "wild", "winapi-util", "windows-sys 0.48.0", ] +[[package]] +name = "uucore_procs" +version = "0.0.24" +dependencies = [ + "proc-macro2", + "quote", + "uuhelp_parser 0.0.24", +] + [[package]] name = "uucore_procs" version = "0.0.24" @@ -6207,9 +6253,13 @@ checksum = "3eb9aeeb06d1f15c5b3b51acddddf3436e3e1480902b2a200618ca5dbb24e392" dependencies = [ "proc-macro2", "quote", - "uuhelp_parser", + "uuhelp_parser 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "uuhelp_parser" +version = "0.0.24" + [[package]] name = "uuhelp_parser" version = "0.0.24" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index cca111bc51..ea4c786e45 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -92,6 +92,7 @@ uu_cp = "0.0.23" uu_whoami = "0.0.23" uu_mkdir = "0.0.23" uu_mktemp = "0.0.23" +uu_touch = { path = "../../../coreutils/src/uu/touch" } uuid = { version = "1.6", features = ["v4"] } v_htmlescape = "0.15.0" wax = { version = "0.6" } diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 3eb2d5659a..dc1dc4589b 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -213,6 +213,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { Rm, Save, Touch, + UTouch, Glob, Watch, }; @@ -277,6 +278,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { ToTsv, Touch, Upsert, + UTouch, Where, ToXml, ToYaml, diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index 385f4d07b1..eabd080fc3 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -13,6 +13,7 @@ mod ucp; mod umkdir; mod umv; mod util; +mod utouch; mod watch; pub use self::open::Open; @@ -29,4 +30,5 @@ pub use touch::Touch; pub use ucp::UCp; pub use umkdir::UMkdir; pub use umv::UMv; +pub use utouch::UTouch; pub use watch::Watch; diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs new file mode 100644 index 0000000000..7a2095b796 --- /dev/null +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -0,0 +1,188 @@ +use std::path::{Path, PathBuf}; + +use chrono::{DateTime, FixedOffset, Local}; + +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, +}; +use uu_touch::{datetime_to_filetime, stat, InputFile, Options}; + +#[derive(Clone)] +pub struct UTouch; + +impl Command for UTouch { + fn name(&self) -> &str { + "utouch" + } + + fn search_terms(&self) -> Vec<&str> { + vec!["create", "file"] + } + + fn signature(&self) -> Signature { + Signature::build("touch") + .input_output_types(vec![ (Type::Nothing, Type::Nothing) ]) + .required( + "filename", + SyntaxShape::Filepath, + "The path of the file you want to create.", + ) + .named( + "reference", + SyntaxShape::Filepath, + "change the file or directory time to the time of the reference file/directory", + Some('r'), + ) + .named( + "timestamp", + SyntaxShape::DateTime, + "use the given time instead of the current time", + Some('t') + ) + .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'), + ) + .switch( + "no-dereference", + "affect each symbolic link instead of any referenced file (only for systems that can change the timestamps of a symlink)", + None + ) + .rest("rest", SyntaxShape::Filepath, "Additional files to create.") + .category(Category::FileSystem) + } + + fn usage(&self) -> &str { + "Creates one or more files." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let change_mtime: bool = call.has_flag(engine_state, stack, "modified")?; + let change_atime: bool = call.has_flag(engine_state, stack, "access")?; + let no_create: bool = call.has_flag(engine_state, stack, "no-create")?; + let no_deref: bool = call.has_flag(engine_state, stack, "no-dereference")?; + let target: Spanned = call.req(engine_state, stack, 0)?; + let rest: Vec> = call.rest(engine_state, stack, 1)?; + + let reference: Option> = + call.get_flag(engine_state, stack, "reference")?; + let timestamp: Option>> = + call.get_flag(engine_state, stack, "timestamp")?; + + let (atime, mtime) = if let Some(timestamp) = timestamp { + if let Some(reference) = reference { + return Err(ShellError::IncompatibleParameters { + left_message: "timestamp given".to_string(), + left_span: timestamp.span, + right_message: "reference given".to_string(), + right_span: reference.span, + }); + } + let filetime = datetime_to_filetime(×tamp.item); + (filetime, filetime) + } else if let Some(reference) = reference { + let reference_path = Path::new(&reference.item); + if !reference_path.exists() { + return Err(ShellError::TypeMismatch { + err_message: format!("path provided is invalid: {}", reference_path.display()), + span: reference.span, + }); + } + stat(reference_path, !no_deref).map_err(|e| ShellError::GenericError { + error: "couldn't get metadata".to_string(), + msg: format!("{}", e), + span: Some(reference.span), + help: None, + inner: Vec::new(), + })? + } else { + let now = datetime_to_filetime(&Local::now()); + (now, now) + }; + + for file in vec![target].into_iter().chain(rest) { + if let Err(err) = uu_touch::touch( + &InputFile::Path(PathBuf::from(file.item)), + &Options { + no_create, + no_deref, + atime: if !change_mtime || change_atime { + Some(atime) + } else { + None + }, + mtime: if !change_atime || change_mtime { + Some(mtime) + } else { + None + }, + }, + ) { + return Err(ShellError::GenericError { + error: "utouch failed".to_string(), + msg: err.to_string(), + span: Some(file.span), + help: None, + inner: Vec::new(), + }); + } + } + + Ok(PipelineData::empty()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Creates \"fixture.json\"", + example: "utouch fixture.json", + result: None, + }, + Example { + description: "Creates files a, b and c", + example: "utouch a b c", + result: None, + }, + Example { + description: r#"Changes the last modified time of "fixture.json" to today's date"#, + example: "utouch -m fixture.json", + result: None, + }, + Example { + description: "Changes the last modified time of files a, b and c to a date", + example: r#"utouch -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#"utouch -m -r fixture.json d e"#, + result: None, + }, + Example { + description: r#"Changes the last accessed time of "fixture.json" to a date"#, + example: r#"utouch -a -d "August 24, 2019; 12:30:30" fixture.json"#, + result: None, + }, + ] + } +} diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index da56a2629b..0dc50690c1 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -117,6 +117,7 @@ mod update; mod upsert; mod url; mod use_; +mod utouch; mod where_; #[cfg(feature = "which-support")] mod which; diff --git a/crates/nu-command/tests/commands/utouch.rs b/crates/nu-command/tests/commands/utouch.rs new file mode 100644 index 0000000000..dcab8174c6 --- /dev/null +++ b/crates/nu-command/tests/commands/utouch.rs @@ -0,0 +1,163 @@ +use chrono::{DateTime, Local}; +use nu_test_support::fs::Stub; +use nu_test_support::nu; +use nu_test_support::playground::Playground; + +#[test] +fn creates_a_file_when_it_doesnt_exist() { + Playground::setup("create_test_1", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch i_will_be_created.txt" + ); + + let path = dirs.test().join("i_will_be_created.txt"); + assert!(path.exists()); + }) +} + +#[test] +fn creates_two_files() { + Playground::setup("create_test_2", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch a b" + ); + + let path = dirs.test().join("a"); + assert!(path.exists()); + + let path2 = dirs.test().join("b"); + assert!(path2.exists()); + }) +} + +#[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(), + "utouch -m file.txt" + ); + + let path = dirs.test().join("file.txt"); + + // Check only the date since the time may not match exactly + let date = Local::now().date_naive(); + let actual_date_time: DateTime = + DateTime::from(path.metadata().unwrap().modified().unwrap()); + let actual_date = actual_date_time.date_naive(); + + assert_eq!(date, actual_date); + }) +} + +#[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(), + "utouch -a file.txt" + ); + + let path = dirs.test().join("file.txt"); + + // Check only the date since the time may not match exactly + let date = Local::now().date_naive(); + let actual_date_time: DateTime = + DateTime::from(path.metadata().unwrap().accessed().unwrap()); + let actual_date = actual_date_time.date_naive(); + + assert_eq!(date, actual_date); + }) +} + +#[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(), + "utouch -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 = Local::now().date_naive(); + let adate_time: DateTime = DateTime::from(metadata.accessed().unwrap()); + let adate = adate_time.date_naive(); + let mdate_time: DateTime = DateTime::from(metadata.modified().unwrap()); + let mdate = mdate_time.date_naive(); + + assert_eq!(date, adate); + assert_eq!(date, mdate); + }) +} + +#[test] +fn not_create_file_if_it_not_exists() { + Playground::setup("change_time_test_28", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch -c file.txt" + ); + + let path = dirs.test().join("file.txt"); + + assert!(!path.exists()); + + nu!( + cwd: dirs.test(), + "utouch -c file.txt" + ); + + let path = dirs.test().join("file.txt"); + + assert!(!path.exists()); + }) +} + +#[test] +fn creates_file_three_dots() { + Playground::setup("create_test_1", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch file..." + ); + + let path = dirs.test().join("file..."); + assert!(path.exists()); + }) +} + +#[test] +fn creates_file_four_dots() { + Playground::setup("create_test_1", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch file...." + ); + + let path = dirs.test().join("file...."); + assert!(path.exists()); + }) +} + +#[test] +fn creates_file_four_dots_quotation_marks() { + Playground::setup("create_test_1", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch 'file....'" + ); + + let path = dirs.test().join("file...."); + assert!(path.exists()); + }) +} From 7c27b376513e584d36d022304babddae534c4572 Mon Sep 17 00:00:00 2001 From: ysthakur <45539777+ysthakur@users.noreply.github.com> Date: Tue, 5 Mar 2024 02:00:20 -0500 Subject: [PATCH 2/8] Give all arguments to uu_touch at once --- Cargo.lock | 12 +- crates/nu-command/src/filesystem/utouch.rs | 155 ++++++++++++++------- 2 files changed, 108 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 660140c4a4..816eb2a2f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -658,9 +658,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2238,9 +2238,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libflate" @@ -4473,9 +4473,9 @@ dependencies = [ [[package]] name = "pure-rust-locales" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed02a829e62dc2715ceb8afb4f80e298148e1345749ceb369540fe0eb3368432" +checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" [[package]] name = "pwd" diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs index 7a2095b796..d06abb11fa 100644 --- a/crates/nu-command/src/filesystem/utouch.rs +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -1,14 +1,16 @@ -use std::path::{Path, PathBuf}; +use std::io::ErrorKind; +use std::path::PathBuf; -use chrono::{DateTime, FixedOffset, Local}; +use chrono::{DateTime, FixedOffset}; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, }; -use uu_touch::{datetime_to_filetime, stat, InputFile, Options}; +use uu_touch::error::{TouchError, TouchFileError}; +use uu_touch::{ChangeTimes, InputFile, Options, Source}; #[derive(Clone)] pub struct UTouch; @@ -23,7 +25,7 @@ impl Command for UTouch { } fn signature(&self) -> Signature { - Signature::build("touch") + Signature::build("utouch") .input_output_types(vec![ (Type::Nothing, Type::Nothing) ]) .required( "filename", @@ -84,68 +86,101 @@ impl Command for UTouch { let target: Spanned = call.req(engine_state, stack, 0)?; let rest: Vec> = call.rest(engine_state, stack, 1)?; - let reference: Option> = - call.get_flag(engine_state, stack, "reference")?; + let (reference_file, reference_span) = if let Some(reference) = + call.get_flag::>(engine_state, stack, "reference")? + { + (Some(reference.item), Some(reference.span)) + } else { + (None, None) + }; let timestamp: Option>> = call.get_flag(engine_state, stack, "timestamp")?; - let (atime, mtime) = if let Some(timestamp) = timestamp { - if let Some(reference) = reference { + let source = if let Some(timestamp) = timestamp { + if let Some(reference_span) = reference_span { return Err(ShellError::IncompatibleParameters { left_message: "timestamp given".to_string(), left_span: timestamp.span, right_message: "reference given".to_string(), - right_span: reference.span, + right_span: reference_span, }); } - let filetime = datetime_to_filetime(×tamp.item); - (filetime, filetime) - } else if let Some(reference) = reference { - let reference_path = Path::new(&reference.item); - if !reference_path.exists() { - return Err(ShellError::TypeMismatch { - err_message: format!("path provided is invalid: {}", reference_path.display()), - span: reference.span, - }); - } - stat(reference_path, !no_deref).map_err(|e| ShellError::GenericError { - error: "couldn't get metadata".to_string(), - msg: format!("{}", e), - span: Some(reference.span), - help: None, - inner: Vec::new(), - })? + Source::Timestamp(timestamp.item.into()) + } else if let Some(reference_file) = reference_file { + Source::Reference(reference_file) } else { - let now = datetime_to_filetime(&Local::now()); - (now, now) + Source::Now }; - for file in vec![target].into_iter().chain(rest) { - if let Err(err) = uu_touch::touch( - &InputFile::Path(PathBuf::from(file.item)), - &Options { - no_create, - no_deref, - atime: if !change_mtime || change_atime { - Some(atime) + let change_times = if change_atime && !change_mtime { + ChangeTimes::AtimeOnly + } else if change_mtime && !change_atime { + ChangeTimes::MtimeOnly + } else { + ChangeTimes::Both + }; + + let mut files = vec![InputFile::Path(PathBuf::from(target.item))]; + let mut file_spans = vec![target.span]; + for file in rest { + files.push(InputFile::Path(PathBuf::from(file.item))); + file_spans.push(file.span); + } + + if let Err(err) = uu_touch::touch( + &files, + &Options { + no_create, + no_deref, + source, + date: None, + change_times, + strict: true, + }, + ) { + let nu_err = match err { + TouchError::ReferenceFileInaccessible(reference_path, io_err) => { + let span = reference_span.expect("utouch was given a reference file"); + if io_err.kind() == ErrorKind::NotFound { + // todo merge main into this to say which file not found + ShellError::FileNotFound { span } } else { - None - }, - mtime: if !change_atime || change_mtime { - Some(mtime) - } else { - None - }, - }, - ) { - return Err(ShellError::GenericError { - error: "utouch failed".to_string(), + io_to_nu_err( + io_err, + format!("Failed to read metadata of {}", reference_path.display()), + span, + ) + } + } + TouchError::TouchFileError { path, index, error } => { + let span = file_spans[index]; + match error { + TouchFileError::CannotCreate(_) => ShellError::CreateNotPossible { + msg: format!("Cannot create {}", path.display()), + span, + }, + TouchFileError::CannotReadTimes(io_err) => io_to_nu_err( + io_err, + format!("Cannot read times for {}", path.display()), + span, + ), + TouchFileError::CannotSetTimes(io_err) => io_to_nu_err( + io_err, + format!("Cannot set times for {}", path.display()), + span, + ), + TouchFileError::TargetFileNotFound => ShellError::FileNotFound { span }, + } + } + _ => ShellError::GenericError { + error: err.to_string(), msg: err.to_string(), - span: Some(file.span), + span: Some(call.head), help: None, inner: Vec::new(), - }); - } + }, + }; + return Err(nu_err); } Ok(PipelineData::empty()) @@ -179,10 +214,24 @@ impl Command for UTouch { result: None, }, Example { - description: r#"Changes the last accessed time of "fixture.json" to a date"#, - example: r#"utouch -a -d "August 24, 2019; 12:30:30" fixture.json"#, + description: r#"Changes the last accessed time of "fixture.json" to a datetime"#, + example: r#"utouch -a -t "August 24, 2019; 12:30:30" fixture.json"#, result: None, }, ] } } + +fn io_to_nu_err(err: std::io::Error, msg: String, span: Span) -> ShellError { + if err.kind() == ErrorKind::PermissionDenied { + ShellError::PermissionDeniedError { msg, span } + } else { + ShellError::GenericError { + error: err.to_string(), + msg, + span: Some(span), + help: None, + inner: Vec::new(), + } + } +} From e1506c57ffc39769e8ea02fb36865cf03fb0be2d Mon Sep 17 00:00:00 2001 From: ysthakur <45539777+ysthakur@users.noreply.github.com> Date: Tue, 5 Mar 2024 04:28:04 -0500 Subject: [PATCH 3/8] Add date flag --- crates/nu-command/src/filesystem/utouch.rs | 31 +++++++++++++++++++--- crates/nu-command/tests/commands/utouch.rs | 13 +++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs index d06abb11fa..1d77f48f1f 100644 --- a/crates/nu-command/src/filesystem/utouch.rs +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -44,6 +44,12 @@ impl Command for UTouch { "use the given time instead of the current time", Some('t') ) + .named( + "date", + SyntaxShape::String, + "use the given date instead of the current date", + Some('d') + ) .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", @@ -93,6 +99,12 @@ impl Command for UTouch { } else { (None, None) }; + let (date_str, date_span) = + if let Some(date) = call.get_flag::>(engine_state, stack, "date")? { + (Some(date.item), Some(date.span)) + } else { + (None, None) + }; let timestamp: Option>> = call.get_flag(engine_state, stack, "timestamp")?; @@ -105,6 +117,14 @@ impl Command for UTouch { right_span: reference_span, }); } + if let Some(date_span) = date_span { + return Err(ShellError::IncompatibleParameters { + left_message: "timestamp given".to_string(), + left_span: timestamp.span, + right_message: "date given".to_string(), + right_span: date_span, + }); + } Source::Timestamp(timestamp.item.into()) } else if let Some(reference_file) = reference_file { Source::Reference(reference_file) @@ -133,12 +153,17 @@ impl Command for UTouch { no_create, no_deref, source, - date: None, + date: date_str, change_times, strict: true, }, ) { let nu_err = match err { + TouchError::InvalidDateFormat(date) => ShellError::IncorrectValue { + msg: format!("Invalid date: {}", date), + val_span: date_span.expect("utouch was given a date"), + call_span: call.head, + }, TouchError::ReferenceFileInaccessible(reference_path, io_err) => { let span = reference_span.expect("utouch was given a reference file"); if io_err.kind() == ErrorKind::NotFound { @@ -204,7 +229,7 @@ impl Command for UTouch { result: None, }, Example { - description: "Changes the last modified time of files a, b and c to a date", + description: "Changes the last modified time of files a, b and c to the current time but yesterday", example: r#"utouch -m -d "yesterday" a b c"#, result: None, }, @@ -215,7 +240,7 @@ impl Command for UTouch { }, Example { description: r#"Changes the last accessed time of "fixture.json" to a datetime"#, - example: r#"utouch -a -t "August 24, 2019; 12:30:30" fixture.json"#, + example: r#"utouch -a -t 2019-08-24T12:30:30 fixture.json"#, result: None, }, ] diff --git a/crates/nu-command/tests/commands/utouch.rs b/crates/nu-command/tests/commands/utouch.rs index dcab8174c6..1180248153 100644 --- a/crates/nu-command/tests/commands/utouch.rs +++ b/crates/nu-command/tests/commands/utouch.rs @@ -161,3 +161,16 @@ fn creates_file_four_dots_quotation_marks() { assert!(path.exists()); }) } + +#[test] +fn creates_with_date() { + Playground::setup("create_test_1", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch 'file....'" + ); + + let path = dirs.test().join("file...."); + assert!(path.exists()); + }) +} From acacb3450a13cf7df8e176033aecf694bbd4e710 Mon Sep 17 00:00:00 2001 From: ysthakur <45539777+ysthakur@users.noreply.github.com> Date: Thu, 21 Mar 2024 13:28:00 -0400 Subject: [PATCH 4/8] Pass FileTimes to uu_touch --- Cargo.lock | 18 +++++++++++++++--- crates/nu-command/src/filesystem/utouch.rs | 6 +++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 816eb2a2f3..13e7631567 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -658,9 +658,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2697,6 +2697,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -6209,7 +6221,7 @@ dependencies = [ "clap", "glob", "libc", - "nix 0.27.1", + "nix 0.28.0", "once_cell", "os_display", "uucore_procs 0.0.24", diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs index 1d77f48f1f..e691cd49f5 100644 --- a/crates/nu-command/src/filesystem/utouch.rs +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -2,6 +2,7 @@ use std::io::ErrorKind; use std::path::PathBuf; use chrono::{DateTime, FixedOffset}; +use filetime::FileTime; use nu_engine::CallExt; use nu_protocol::ast::Call; @@ -125,7 +126,10 @@ impl Command for UTouch { right_span: date_span, }); } - Source::Timestamp(timestamp.item.into()) + Source::Timestamp(FileTime::from_unix_time( + timestamp.item.timestamp(), + timestamp.item.timestamp_subsec_nanos(), + )) } else if let Some(reference_file) = reference_file { Source::Reference(reference_file) } else { From 76b38e62f73ce044e606b55c0fc86e00fd21dd63 Mon Sep 17 00:00:00 2001 From: ysthakur <45539777+ysthakur@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:51:34 -0400 Subject: [PATCH 5/8] Update Cargo.lock --- Cargo.lock | 64 +++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13e7631567..abc789cdb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -658,9 +658,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -6151,7 +6151,7 @@ dependencies = [ "indicatif", "libc", "quick-error 2.0.1", - "uucore 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.24", "walkdir", "xattr", ] @@ -6163,7 +6163,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbf657c9e738d16ebc5c161a611ff25327c1fb599645afb2831062efb23c851" dependencies = [ "clap", - "uucore 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.24", ] [[package]] @@ -6175,7 +6175,7 @@ dependencies = [ "clap", "rand", "tempfile", - "uucore 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.24", ] [[package]] @@ -6187,18 +6187,18 @@ dependencies = [ "clap", "fs_extra", "indicatif", - "uucore 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.24", ] [[package]] name = "uu_touch" -version = "0.0.24" +version = "0.0.26" dependencies = [ "chrono", "clap", "filetime", "parse_datetime", - "uucore 0.0.24", + "uucore 0.0.26", "windows-sys 0.48.0", ] @@ -6210,24 +6210,10 @@ checksum = "70589dc3b41f34cbfe1fb22b8f20fcac233fa4565409905f12dd06780b18374d" dependencies = [ "clap", "libc", - "uucore 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.24", "windows-sys 0.48.0", ] -[[package]] -name = "uucore" -version = "0.0.24" -dependencies = [ - "clap", - "glob", - "libc", - "nix 0.28.0", - "once_cell", - "os_display", - "uucore_procs 0.0.24", - "wild", -] - [[package]] name = "uucore" version = "0.0.24" @@ -6241,7 +6227,7 @@ dependencies = [ "nix 0.27.1", "once_cell", "os_display", - "uucore_procs 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore_procs 0.0.24", "walkdir", "wild", "winapi-util", @@ -6249,12 +6235,17 @@ dependencies = [ ] [[package]] -name = "uucore_procs" -version = "0.0.24" +name = "uucore" +version = "0.0.26" dependencies = [ - "proc-macro2", - "quote", - "uuhelp_parser 0.0.24", + "clap", + "glob", + "libc", + "nix 0.28.0", + "once_cell", + "os_display", + "uucore_procs 0.0.26", + "wild", ] [[package]] @@ -6265,12 +6256,17 @@ checksum = "3eb9aeeb06d1f15c5b3b51acddddf3436e3e1480902b2a200618ca5dbb24e392" dependencies = [ "proc-macro2", "quote", - "uuhelp_parser 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "uuhelp_parser 0.0.24", ] [[package]] -name = "uuhelp_parser" -version = "0.0.24" +name = "uucore_procs" +version = "0.0.26" +dependencies = [ + "proc-macro2", + "quote", + "uuhelp_parser 0.0.26", +] [[package]] name = "uuhelp_parser" @@ -6278,6 +6274,10 @@ version = "0.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d841f8408028085ca65896cdd60b9925d4e407cb69989a64889f2bebbb51147b" +[[package]] +name = "uuhelp_parser" +version = "0.0.26" + [[package]] name = "uuid" version = "1.7.0" From b58967a539e3fc94cdc3eb0ad7a69fad6f742037 Mon Sep 17 00:00:00 2001 From: ysthakur <45539777+ysthakur@users.noreply.github.com> Date: Fri, 31 May 2024 19:42:19 -0400 Subject: [PATCH 6/8] Remove TouchFileError enum --- Cargo.lock | 22 ++++++++++++---------- crates/nu-command/src/filesystem/utouch.rs | 22 +--------------------- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abc789cdb8..5a99e18231 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -658,9 +658,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -3703,11 +3703,12 @@ dependencies = [ [[package]] name = "parse_datetime" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bbf4e25b13841080e018a1e666358adfe5e39b6d353f986ca5091c210b586a1" +checksum = "a8720474e3dd4af20cea8716703498b9f3b690f318fa9d9d9e2e38eaf44b96d0" dependencies = [ "chrono", + "nom", "regex", ] @@ -4421,9 +4422,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -4554,9 +4555,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -4714,9 +4715,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -6242,6 +6243,7 @@ dependencies = [ "glob", "libc", "nix 0.28.0", + "number_prefix", "once_cell", "os_display", "uucore_procs 0.0.26", diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs index e691cd49f5..b8e5f2f971 100644 --- a/crates/nu-command/src/filesystem/utouch.rs +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -10,7 +10,7 @@ use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, }; -use uu_touch::error::{TouchError, TouchFileError}; +use uu_touch::error::TouchError; use uu_touch::{ChangeTimes, InputFile, Options, Source}; #[derive(Clone)] @@ -181,26 +181,6 @@ impl Command for UTouch { ) } } - TouchError::TouchFileError { path, index, error } => { - let span = file_spans[index]; - match error { - TouchFileError::CannotCreate(_) => ShellError::CreateNotPossible { - msg: format!("Cannot create {}", path.display()), - span, - }, - TouchFileError::CannotReadTimes(io_err) => io_to_nu_err( - io_err, - format!("Cannot read times for {}", path.display()), - span, - ), - TouchFileError::CannotSetTimes(io_err) => io_to_nu_err( - io_err, - format!("Cannot set times for {}", path.display()), - span, - ), - TouchFileError::TargetFileNotFound => ShellError::FileNotFound { span }, - } - } _ => ShellError::GenericError { error: err.to_string(), msg: err.to_string(), From 617bfb8759450ce1b6f9eba34ba9a53b1db64236 Mon Sep 17 00:00:00 2001 From: ysthakur <45539777+ysthakur@users.noreply.github.com> Date: Fri, 31 May 2024 20:41:30 -0400 Subject: [PATCH 7/8] Update code after merge --- Cargo.lock | 93 +++++++++++----------- Cargo.toml | 1 + crates/nu-command/src/filesystem/utouch.rs | 21 +++-- 3 files changed, 57 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b49c796df..11500a4d24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2708,18 +2708,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags 2.4.2", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -6389,7 +6377,7 @@ dependencies = [ "indicatif", "libc", "quick-error 2.0.1", - "uucore 0.0.26", + "uucore 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir", "xattr", ] @@ -6401,7 +6389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "040aa4584036b2f65e05387b0ea9ac468afce1db325743ce5f350689fd9ce4ae" dependencies = [ "clap", - "uucore 0.0.26", + "uucore 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -6413,7 +6401,7 @@ dependencies = [ "clap", "rand", "tempfile", - "uucore 0.0.26", + "uucore 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -6425,7 +6413,7 @@ dependencies = [ "clap", "fs_extra", "indicatif", - "uucore 0.0.26", + "uucore 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -6440,6 +6428,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "uu_uname" +version = "0.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5951832d73199636bde6c0d61cf960932b3c4450142c290375bc10c7abed6db5" +dependencies = [ + "clap", + "platform-info", + "uucore 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "uu_whoami" version = "0.0.25" @@ -6448,7 +6447,7 @@ checksum = "e3b44166eb6335aeac42744ea368cc4c32d3f2287a4ff765a5ce44d927ab8bb4" dependencies = [ "clap", "libc", - "uucore 0.0.26", + "uucore 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "windows-sys 0.48.0", ] @@ -6464,7 +6463,22 @@ dependencies = [ "nix", "once_cell", "os_display", - "uucore_procs", + "uucore_procs 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "wild", +] + +[[package]] +name = "uucore" +version = "0.0.26" +dependencies = [ + "clap", + "glob", + "libc", + "nix", + "number_prefix", + "once_cell", + "os_display", + "uucore_procs 0.0.26", "wild", ] @@ -6482,7 +6496,7 @@ dependencies = [ "number_prefix", "once_cell", "os_display", - "uucore_procs 0.0.24", + "uucore_procs 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir", "wild", "winapi-util", @@ -6490,32 +6504,6 @@ dependencies = [ "xattr", ] -[[package]] -name = "uucore" -version = "0.0.26" -dependencies = [ - "clap", - "glob", - "libc", - "nix 0.28.0", - "number_prefix", - "once_cell", - "os_display", - "uucore_procs 0.0.26", - "wild", -] - -[[package]] -name = "uucore_procs" -version = "0.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a233a488da42f3ddb0aaa8a9f75a969e3f37e4de7e909d2d23f6aa3ee401d20" -dependencies = [ - "proc-macro2", - "quote", - "uuhelp_parser 0.0.24", -] - [[package]] name = "uucore_procs" version = "0.0.26" @@ -6526,15 +6514,26 @@ dependencies = [ ] [[package]] -name = "uuhelp_parser" +name = "uucore_procs" version = "0.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "425a23c7b7145bc7620c9c445817c37b1f78b6790aee9f208133f3c028975b60" +checksum = "1a233a488da42f3ddb0aaa8a9f75a969e3f37e4de7e909d2d23f6aa3ee401d20" +dependencies = [ + "proc-macro2", + "quote", + "uuhelp_parser 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "uuhelp_parser" version = "0.0.26" +[[package]] +name = "uuhelp_parser" +version = "0.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "425a23c7b7145bc7620c9c445817c37b1f78b6790aee9f208133f3c028975b60" + [[package]] name = "uuid" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index a8576baa95..286e23cb75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,6 +163,7 @@ uu_cp = "0.0.25" uu_mkdir = "0.0.25" uu_mktemp = "0.0.25" uu_mv = "0.0.25" +uu_touch = { path = "../coreutils/src/uu/touch" } uu_whoami = "0.0.25" uu_uname = "0.0.25" uucore = "0.0.25" diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs index b8e5f2f971..7bf712b1eb 100644 --- a/crates/nu-command/src/filesystem/utouch.rs +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -172,7 +172,10 @@ impl Command for UTouch { let span = reference_span.expect("utouch was given a reference file"); if io_err.kind() == ErrorKind::NotFound { // todo merge main into this to say which file not found - ShellError::FileNotFound { span } + ShellError::FileNotFound { + span, + file: reference_path.display().to_string(), + } } else { io_to_nu_err( io_err, @@ -232,15 +235,11 @@ impl Command for UTouch { } fn io_to_nu_err(err: std::io::Error, msg: String, span: Span) -> ShellError { - if err.kind() == ErrorKind::PermissionDenied { - ShellError::PermissionDeniedError { msg, span } - } else { - ShellError::GenericError { - error: err.to_string(), - msg, - span: Some(span), - help: None, - inner: Vec::new(), - } + ShellError::GenericError { + error: err.to_string(), + msg, + span: Some(span), + help: None, + inner: Vec::new(), } } From 21c331ad8302dbec3e21acdfba327cf02f76df05 Mon Sep 17 00:00:00 2001 From: ysthakur <45539777+ysthakur@users.noreply.github.com> Date: Fri, 31 May 2024 21:20:11 -0400 Subject: [PATCH 8/8] Fix utouch tests --- crates/nu-command/tests/commands/utouch.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/tests/commands/utouch.rs b/crates/nu-command/tests/commands/utouch.rs index 1180248153..8665837149 100644 --- a/crates/nu-command/tests/commands/utouch.rs +++ b/crates/nu-command/tests/commands/utouch.rs @@ -35,7 +35,7 @@ fn creates_two_files() { #[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")]); + sandbox.with_files(&[Stub::EmptyFile("file.txt")]); nu!( cwd: dirs.test(), @@ -57,7 +57,7 @@ fn change_modified_time_of_file_to_today() { #[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")]); + sandbox.with_files(&[Stub::EmptyFile("file.txt")]); nu!( cwd: dirs.test(), @@ -79,7 +79,7 @@ fn change_access_time_of_file_to_today() { #[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")]); + sandbox.with_files(&[Stub::EmptyFile("file.txt")]); nu!( cwd: dirs.test(),