use filetime::FileTime; use nu_engine::command_prelude::*; use nu_path::expand_path_with; use nu_protocol::NuGlob; use std::{fs::OpenOptions, time::SystemTime}; use super::util::get_rest_for_glob_pattern; #[derive(Clone)] pub struct Touch; impl Command for Touch { fn name(&self) -> &str { "touch" } fn search_terms(&self) -> Vec<&str> { vec!["create", "file"] } fn signature(&self) -> Signature { Signature::build("touch") .input_output_types(vec![(Type::Nothing, Type::Nothing)]) .rest( "files", SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Filepath]), "The file(s) to create." ) .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 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 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'), ) .category(Category::FileSystem) } fn description(&self) -> &str { "Creates one or more files." } fn run( &self, engine_state: &EngineState, stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { let mut change_mtime: bool = call.has_flag(engine_state, stack, "modified")?; let mut change_atime: bool = call.has_flag(engine_state, stack, "access")?; let reference: Option> = call.get_flag(engine_state, stack, "reference")?; let no_create: bool = call.has_flag(engine_state, stack, "no-create")?; let files: Vec> = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; let cwd = engine_state.cwd(Some(stack))?; if files.is_empty() { return Err(ShellError::MissingParameter { param_name: "requires file paths".to_string(), span: call.head, }); } let mut mtime = SystemTime::now(); let mut atime = mtime; // Change both times if neither is specified if !change_mtime && !change_atime { change_mtime = true; change_atime = true; } if let Some(reference) = reference { let reference_path = nu_path::expand_path_with(reference.item, &cwd, true); if !reference_path.exists() { return Err(ShellError::FileNotFoundCustom { msg: "Reference path not found".into(), span: reference.span, }); } let metadata = reference_path .metadata() .map_err(|err| ShellError::IOErrorSpanned { msg: format!("Failed to read metadata: {err}"), span: reference.span, })?; mtime = metadata .modified() .map_err(|err| ShellError::IOErrorSpanned { msg: format!("Failed to read modified time: {err}"), span: reference.span, })?; atime = metadata .accessed() .map_err(|err| ShellError::IOErrorSpanned { msg: format!("Failed to read access time: {err}"), span: reference.span, })?; } for glob in files { let path = expand_path_with(glob.item.as_ref(), &cwd, glob.item.is_expand()); // If --no-create is passed and the file/dir does not exist there's nothing to do if no_create && !path.exists() { continue; } // Create a file at the given path unless the path is a directory if !path.is_dir() { if let Err(err) = OpenOptions::new() .write(true) .create(true) .truncate(false) .open(&path) { return Err(ShellError::CreateNotPossible { msg: format!("Failed to create file: {err}"), span: glob.span, }); }; } if change_mtime { if let Err(err) = filetime::set_file_mtime(&path, FileTime::from_system_time(mtime)) { return Err(ShellError::ChangeModifiedTimeNotPossible { msg: format!("Failed to change the modified time: {err}"), span: glob.span, }); }; } if change_atime { if let Err(err) = filetime::set_file_atime(&path, FileTime::from_system_time(atime)) { return Err(ShellError::ChangeAccessTimeNotPossible { msg: format!("Failed to change the access time: {err}"), span: glob.span, }); }; } } Ok(PipelineData::empty()) } fn examples(&self) -> Vec { vec![ Example { description: "Creates \"fixture.json\"", example: "touch fixture.json", result: None, }, Example { description: "Creates files a, b and c", 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: 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, }, ] } }