diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index fd2ea37555..b7e5157dcc 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -35,12 +35,14 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(LetEnv)); working_set.add_decl(Box::new(Lines)); working_set.add_decl(Box::new(Ls)); + working_set.add_decl(Box::new(Mkdir)); working_set.add_decl(Box::new(Module)); working_set.add_decl(Box::new(Mv)); working_set.add_decl(Box::new(Ps)); working_set.add_decl(Box::new(Select)); working_set.add_decl(Box::new(Sys)); working_set.add_decl(Box::new(Table)); + working_set.add_decl(Box::new(Touch)); working_set.add_decl(Box::new(Use)); working_set.add_decl(Box::new(Where)); working_set.add_decl(Box::new(Wrap)); diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 79751f2c93..b042d50f11 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -60,7 +60,7 @@ impl Command for Cp { } let any_source_is_dir = sources.iter().any(|f| matches!(f, Ok(f) if f.is_dir())); - let recursive = call.named.iter().any(|p| &p.0 == "recursive"); + let recursive: bool = call.has_flag("recursive"); if any_source_is_dir && !recursive { return Err(ShellError::MoveNotPossibleSingle( "Directories must be copied using \"--recursive\"".to_string(), diff --git a/crates/nu-command/src/filesystem/mkdir.rs b/crates/nu-command/src/filesystem/mkdir.rs new file mode 100644 index 0000000000..5635c85538 --- /dev/null +++ b/crates/nu-command/src/filesystem/mkdir.rs @@ -0,0 +1,74 @@ +use std::collections::VecDeque; +use std::env::current_dir; + +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{ShellError, Signature, SyntaxShape, Value, ValueStream}; + +pub struct Mkdir; + +impl Command for Mkdir { + fn name(&self) -> &str { + "mkdir" + } + + fn signature(&self) -> Signature { + Signature::build("mkdir") + .rest( + "rest", + SyntaxShape::Filepath, + "the name(s) of the path(s) to create", + ) + .switch("show-created-paths", "show the path(s) created.", Some('s')) + } + + fn usage(&self) -> &str { + "Make directories, creates intermediary directories as required." + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let path = current_dir()?; + let mut directories = call + .rest::(context, 0)? + .into_iter() + .map(|dir| path.join(dir)) + .peekable(); + + let show_created_paths = call.has_flag("show-created-paths"); + let mut stream: VecDeque = VecDeque::new(); + + if directories.peek().is_none() { + return Err(ShellError::MissingParameter( + "requires directory paths".to_string(), + call.head, + )); + } + + for (i, dir) in directories.enumerate() { + let span = call.positional[i].span; + let dir_res = std::fs::create_dir_all(&dir); + + if let Err(reason) = dir_res { + return Err(ShellError::CreateNotPossible( + format!("failed to create directory: {}", reason), + call.positional[i].span, + )); + } + + if show_created_paths { + let val = format!("{:}", dir.to_string_lossy()); + stream.push_back(Value::String { val, span }); + } + } + + let stream = ValueStream::from_stream(stream.into_iter()); + let span = call.head; + Ok(Value::Stream { stream, span }) + } +} diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index 2d2212766a..afbbac5250 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -1,10 +1,14 @@ mod cd; mod cp; mod ls; +mod mkdir; mod mv; +mod touch; mod util; pub use cd::Cd; pub use cp::Cp; pub use ls::Ls; +pub use mkdir::Mkdir; pub use mv::Mv; +pub use touch::Touch; diff --git a/crates/nu-command/src/filesystem/touch.rs b/crates/nu-command/src/filesystem/touch.rs new file mode 100644 index 0000000000..bb23d47189 --- /dev/null +++ b/crates/nu-command/src/filesystem/touch.rs @@ -0,0 +1,52 @@ +use std::fs::OpenOptions; + +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; + +pub struct Touch; + +impl Command for Touch { + fn name(&self) -> &str { + "touch" + } + + fn signature(&self) -> Signature { + Signature::build("touch") + .required( + "filename", + SyntaxShape::Filepath, + "the path of the file you want to create", + ) + .rest("rest", SyntaxShape::Filepath, "additional files to create") + } + + fn usage(&self) -> &str { + "Creates one or more files." + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let target: String = call.req(context, 0)?; + let rest: Vec = call.rest(context, 1)?; + + 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), + call.positional[index].span, + )); + } + } + } + + Ok(Value::Nothing { span: call.head }) + } +} diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 6a7464508e..4be91303d0 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -27,6 +27,10 @@ pub enum ShellError { #[diagnostic(code(nu::shell::unknown_operator), url(docsrs))] UnknownOperator(String, #[label = "unsupported operator"] Span), + #[error("Missing parameter: {0}.")] + #[diagnostic(code(nu::shell::missing_parameter), url(docsrs))] + MissingParameter(String, #[label = "missing parameter: {0}"] Span), + #[error("External commands not yet supported")] #[diagnostic(code(nu::shell::external_commands), url(docsrs))] ExternalNotSupported(#[label = "external not supported"] Span), @@ -109,6 +113,10 @@ pub enum ShellError { #[error("Move not possible")] #[diagnostic(code(nu::shell::move_not_possible_single), url(docsrs))] MoveNotPossibleSingle(String, #[label("{0}")] Span), + + #[error("Create not possible")] + #[diagnostic(code(nu::shell::create_not_possible), url(docsrs))] + CreateNotPossible(String, #[label("{0}")] Span), } impl From for ShellError {