diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs index b3d827f7b2..4e3d32d906 100644 --- a/crates/nu-command/src/filesystem/utouch.rs +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -1,6 +1,7 @@ use chrono::{DateTime, FixedOffset}; use filetime::FileTime; use nu_engine::command_prelude::*; +use nu_glob::{glob, is_glob}; use nu_path::expand_path_with; use nu_protocol::NuGlob; use std::{io::ErrorKind, path::PathBuf}; @@ -149,9 +150,52 @@ impl Command for UTouch { if file_glob.item.as_ref() == "-" { input_files.push(InputFile::Stdout); } else { - let path = + let file_path = expand_path_with(file_glob.item.as_ref(), &cwd, file_glob.item.is_expand()); - input_files.push(InputFile::Path(path)); + + if !file_glob.item.is_expand() { + input_files.push(InputFile::Path(file_path)); + continue; + } + + let mut expanded_globs = glob(&file_path.to_string_lossy()) + .unwrap_or_else(|_| { + panic!( + "Failed to process file path: {}", + &file_path.to_string_lossy() + ) + }) + .peekable(); + + if expanded_globs.peek().is_none() { + let file_name = file_path.file_name().unwrap_or_else(|| { + panic!( + "Failed to process file path: {}", + &file_path.to_string_lossy() + ) + }); + + if is_glob(&file_name.to_string_lossy()) { + return Err(ShellError::GenericError { + error: format!( + "No matches found for glob {}", + file_name.to_string_lossy() + ), + msg: "No matches found for glob".into(), + span: Some(file_glob.span), + help: Some(format!( + "Use quotes if you want to create a file named {}", + file_name.to_string_lossy() + )), + inner: vec![], + }); + } + + input_files.push(InputFile::Path(file_path)); + continue; + } + + input_files.extend(expanded_globs.filter_map(Result::ok).map(InputFile::Path)); } } @@ -228,6 +272,11 @@ impl Command for UTouch { example: "utouch -m fixture.json", result: None, }, + Example { + description: r#"Changes the last modified and accessed time of all files with the .json extension to today's date"#, + example: "utouch *.json", + result: None, + }, Example { description: "Changes the last accessed and modified times of files a, b and c to the current time but yesterday", example: r#"utouch -d "yesterday" a b c"#, diff --git a/crates/nu-command/tests/commands/utouch.rs b/crates/nu-command/tests/commands/utouch.rs index 062ec7ddfc..f0c7ffbff5 100644 --- a/crates/nu-command/tests/commands/utouch.rs +++ b/crates/nu-command/tests/commands/utouch.rs @@ -83,6 +83,34 @@ fn creates_two_files() { }) } +// Windows forbids file names with reserved characters +// https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file +#[test] +#[cfg(not(windows))] +fn creates_a_file_when_glob_is_quoted() { + Playground::setup("create_test_glob", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "utouch '*.txt'" + ); + + let path = dirs.test().join("*.txt"); + assert!(path.exists()); + }) +} + +#[test] +fn fails_when_glob_has_no_matches() { + Playground::setup("create_test_glob_no_matches", |dirs, _sandbox| { + let actual = nu!( + cwd: dirs.test(), + "utouch *.txt" + ); + + assert!(actual.err.contains("No matches found for glob *.txt")); + }) +} + #[test] fn change_modified_time_of_file_to_today() { Playground::setup("change_time_test_9", |dirs, sandbox| { @@ -168,6 +196,31 @@ fn change_modified_and_access_time_of_file_to_today() { }) } +#[test] +fn change_modified_and_access_time_of_files_matching_glob_to_today() { + Playground::setup("change_mtime_atime_test_glob", |dirs, sandbox| { + sandbox.with_files(&[Stub::EmptyFile("file.txt")]); + + let path = dirs.test().join("file.txt"); + filetime::set_file_times(&path, TIME_ONE, TIME_ONE).unwrap(); + + nu!( + cwd: dirs.test(), + "utouch *.txt" + ); + + let metadata = path.metadata().unwrap(); + + // Check only the date since the time may not match exactly + let today = Local::now().date_naive(); + let mtime_day = DateTime::::from(metadata.modified().unwrap()).date_naive(); + let atime_day = DateTime::::from(metadata.accessed().unwrap()).date_naive(); + + assert_eq!(today, mtime_day); + assert_eq!(today, atime_day); + }) +} + #[test] fn not_create_file_if_it_not_exists() { Playground::setup("change_time_test_28", |dirs, _sandbox| {