nushell/crates/nu-command/src/filesystem/touch.rs
YizhePKU f74dd33ba9
Fix touch --reference using PWD from the environment (#12976)
This PR fixes `touch --reference path` so that it resolves `path` using
PWD from the engine state.
2024-05-26 20:24:00 +03:00

200 lines
6.9 KiB
Rust

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 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'),
)
.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<PipelineData, ShellError> {
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<Spanned<String>> = call.get_flag(engine_state, stack, "reference")?;
let no_create: bool = call.has_flag(engine_state, stack, "no-create")?;
let files: Vec<Spanned<NuGlob>> = 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<Example> {
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: "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,
},
]
}
}