mirror of
https://github.com/nushell/nushell.git
synced 2025-08-10 02:17:57 +02:00
Begin directory contrib docs and split commands (#3650)
* Begin directory contrib docs and split commands * Fix unused import warning
This commit is contained in:
72
crates/nu-command/src/commands/filesystem/cd.rs
Normal file
72
crates/nu-command/src/commands/filesystem/cd.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
|
||||
use nu_engine::shell::CdArgs;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
pub struct Cd;
|
||||
|
||||
impl WholeStreamCommand for Cd {
|
||||
fn name(&self) -> &str {
|
||||
"cd"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("cd").optional(
|
||||
"directory",
|
||||
SyntaxShape::FilePath,
|
||||
"the directory to change to",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Change to a new path."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let shell_manager = args.shell_manager();
|
||||
let args = CdArgs { path: args.opt(0)? };
|
||||
|
||||
shell_manager.cd(args, name)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Change to a new directory called 'dirname'",
|
||||
example: "cd dirname",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Change to your home directory",
|
||||
example: "cd",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Change to your home directory (alternate version)",
|
||||
example: "cd ~",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Change to the previous directory",
|
||||
example: "cd -",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cd;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cd {})
|
||||
}
|
||||
}
|
67
crates/nu-command/src/commands/filesystem/cp.rs
Normal file
67
crates/nu-command/src/commands/filesystem/cp.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::{shell::CopyArgs, WholeStreamCommand};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
pub struct Cpy;
|
||||
|
||||
impl WholeStreamCommand for Cpy {
|
||||
fn name(&self) -> &str {
|
||||
"cp"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("cp")
|
||||
.required("src", SyntaxShape::GlobPattern, "the place to copy from")
|
||||
.required("dst", SyntaxShape::FilePath, "the place to copy to")
|
||||
.switch(
|
||||
"recursive",
|
||||
"copy recursively through subdirectories",
|
||||
Some('r'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Copy files."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let shell_manager = args.shell_manager();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
||||
let args = CopyArgs {
|
||||
src: args.req(0)?,
|
||||
dst: args.req(1)?,
|
||||
recursive: args.has_flag("recursive"),
|
||||
};
|
||||
shell_manager.cp(args, name)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Copy myfile to dir_b",
|
||||
example: "cp myfile dir_b",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Recursively copy dir_a to dir_b",
|
||||
example: "cp -r dir_a dir_b",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Cpy;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Cpy {})
|
||||
}
|
||||
}
|
90
crates/nu-command/src/commands/filesystem/ls.rs
Normal file
90
crates/nu-command/src/commands/filesystem/ls.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::{shell::LsArgs, WholeStreamCommand};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
pub struct Ls;
|
||||
|
||||
impl WholeStreamCommand for Ls {
|
||||
fn name(&self) -> &str {
|
||||
"ls"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ls")
|
||||
.optional(
|
||||
"path",
|
||||
SyntaxShape::GlobPattern,
|
||||
"a path to get the directory contents from",
|
||||
)
|
||||
.switch("all", "Show hidden files", Some('a'))
|
||||
.switch(
|
||||
"long",
|
||||
"List all available columns for each entry",
|
||||
Some('l'),
|
||||
)
|
||||
.switch(
|
||||
"short-names",
|
||||
"Only print the file names and not the path",
|
||||
Some('s'),
|
||||
)
|
||||
.switch(
|
||||
"du",
|
||||
"Display the apparent directory size in place of the directory metadata size",
|
||||
Some('d'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View the contents of the current or given path."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctrl_c = args.ctrl_c();
|
||||
let shell_manager = args.shell_manager();
|
||||
|
||||
let args = LsArgs {
|
||||
path: args.opt(0)?,
|
||||
all: args.has_flag("all"),
|
||||
long: args.has_flag("long"),
|
||||
short_names: args.has_flag("short-names"),
|
||||
du: args.has_flag("du"),
|
||||
};
|
||||
|
||||
shell_manager.ls(args, name, ctrl_c)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "List all files in the current directory",
|
||||
example: "ls",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List all files in a subdirectory",
|
||||
example: "ls subdir",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List all rust files",
|
||||
example: "ls *.rs",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ls;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ls {})
|
||||
}
|
||||
}
|
61
crates/nu-command/src/commands/filesystem/mkdir.rs
Normal file
61
crates/nu-command/src/commands/filesystem/mkdir.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::{shell::MkdirArgs, WholeStreamCommand};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
pub struct Mkdir;
|
||||
|
||||
impl WholeStreamCommand for Mkdir {
|
||||
fn name(&self) -> &str {
|
||||
"mkdir"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("mkdir")
|
||||
.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, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
mkdir(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Make a directory named foo",
|
||||
example: "mkdir foo",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn mkdir(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let shell_manager = args.shell_manager();
|
||||
|
||||
let args = MkdirArgs {
|
||||
rest: args.rest(0)?,
|
||||
show_created_paths: args.has_flag("show-created-paths"),
|
||||
};
|
||||
|
||||
shell_manager.mkdir(args, name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Mkdir;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Mkdir {})
|
||||
}
|
||||
}
|
19
crates/nu-command/src/commands/filesystem/mod.rs
Normal file
19
crates/nu-command/src/commands/filesystem/mod.rs
Normal file
@ -0,0 +1,19 @@
|
||||
mod cd;
|
||||
mod cp;
|
||||
mod ls;
|
||||
mod mkdir;
|
||||
mod mv;
|
||||
pub(crate) mod open;
|
||||
mod rm;
|
||||
mod save;
|
||||
mod touch;
|
||||
|
||||
pub use cd::Cd;
|
||||
pub use cp::Cpy;
|
||||
pub use ls::Ls;
|
||||
pub use mkdir::Mkdir;
|
||||
pub use mv::Mv;
|
||||
pub use open::Open;
|
||||
pub use rm::Remove;
|
||||
pub use save::Save;
|
||||
pub use touch::Touch;
|
79
crates/nu-command/src/commands/filesystem/mv.rs
Normal file
79
crates/nu-command/src/commands/filesystem/mv.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::{shell::MvArgs, WholeStreamCommand};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
pub struct Mv;
|
||||
|
||||
impl WholeStreamCommand for Mv {
|
||||
fn name(&self) -> &str {
|
||||
"mv"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("mv")
|
||||
.required(
|
||||
"source",
|
||||
SyntaxShape::GlobPattern,
|
||||
"the location to move files/directories from",
|
||||
)
|
||||
.required(
|
||||
"destination",
|
||||
SyntaxShape::FilePath,
|
||||
"the location to move files/directories to",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Move files or directories."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
mv(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Rename a file",
|
||||
example: "mv before.txt after.txt",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Move a file into a directory",
|
||||
example: "mv test.txt my/subdirectory",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Move many files into a directory",
|
||||
example: "mv *.txt my/subdirectory",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn mv(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let shell_manager = args.shell_manager();
|
||||
|
||||
let args = MvArgs {
|
||||
src: args.req(0)?,
|
||||
dst: args.req(1)?,
|
||||
};
|
||||
|
||||
shell_manager.mv(args, name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Mv;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Mv {})
|
||||
}
|
||||
}
|
264
crates/nu-command/src/commands/filesystem/open.rs
Normal file
264
crates/nu-command/src/commands/filesystem/open.rs
Normal file
@ -0,0 +1,264 @@
|
||||
use crate::commands::viewers::BAT_LANGUAGES;
|
||||
use crate::prelude::*;
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
|
||||
use log::debug;
|
||||
use nu_engine::StringOrBinary;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::{AnchorLocation, Span, Tagged};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct Open;
|
||||
|
||||
impl WholeStreamCommand for Open {
|
||||
fn name(&self) -> &str {
|
||||
"open"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required(
|
||||
"path",
|
||||
SyntaxShape::FilePath,
|
||||
"the file path to load values from",
|
||||
)
|
||||
.switch(
|
||||
"raw",
|
||||
"load content as a string instead of a table",
|
||||
Some('r'),
|
||||
)
|
||||
.named(
|
||||
"encoding",
|
||||
SyntaxShape::String,
|
||||
"encoding to use to open file",
|
||||
Some('e'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Load a file into a cell, convert to table if possible (avoid by appending '--raw')."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"Multiple encodings are supported for reading text files by using
|
||||
the '--encoding <encoding>' parameter. Here is an example of a few:
|
||||
big5, euc-jp, euc-kr, gbk, iso-8859-1, utf-16, cp1252, latin5
|
||||
|
||||
For a more complete list of encodings please refer to the encoding_rs
|
||||
documentation link at https://docs.rs/encoding_rs/0.8.28/encoding_rs/#statics"#
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
open(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Opens \"users.csv\" and creates a table from the data",
|
||||
example: "open users.csv",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Opens file with iso-8859-1 encoding",
|
||||
example: "open file.csv --encoding iso-8859-1 | from csv",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Lists the contents of a directory (identical to `ls ../projectB`)",
|
||||
example: "open ../projectB",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_encoding(opt: Option<Tagged<String>>) -> Result<&'static Encoding, ShellError> {
|
||||
match opt {
|
||||
None => Ok(UTF_8),
|
||||
Some(label) => match Encoding::for_label((&label.item).as_bytes()) {
|
||||
None => Err(ShellError::labeled_error(
|
||||
format!(
|
||||
r#"{} is not a valid encoding, refer to https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics for a valid list of encodings"#,
|
||||
label.item
|
||||
),
|
||||
"invalid encoding",
|
||||
label.span(),
|
||||
)),
|
||||
Some(encoding) => Ok(encoding),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn open(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let scope = args.scope().clone();
|
||||
let shell_manager = args.shell_manager();
|
||||
let cwd = PathBuf::from(shell_manager.path());
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let ctrl_c = args.ctrl_c();
|
||||
|
||||
let path: Tagged<PathBuf> = args.req(0)?;
|
||||
let raw = args.has_flag("raw");
|
||||
let encoding: Option<Tagged<String>> = args.get_flag("encoding")?;
|
||||
|
||||
if path.is_dir() {
|
||||
let args = nu_engine::shell::LsArgs {
|
||||
path: Some(path),
|
||||
all: false,
|
||||
long: false,
|
||||
short_names: false,
|
||||
du: false,
|
||||
};
|
||||
return shell_manager.ls(args, name, ctrl_c);
|
||||
}
|
||||
|
||||
// TODO: Remove once Streams are supported everywhere!
|
||||
// As a short term workaround for getting AutoConvert and Bat functionality (Those don't currently support Streams)
|
||||
|
||||
// Check if the extension has a "from *" command OR "bat" supports syntax highlighting
|
||||
// AND the user doesn't want the raw output
|
||||
// In these cases, we will collect the Stream
|
||||
let ext = if raw {
|
||||
None
|
||||
} else {
|
||||
path.extension()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
};
|
||||
|
||||
if let Some(ext) = ext {
|
||||
// Check if we have a conversion command
|
||||
if let Some(_command) = scope.get_command(&format!("from {}", ext)) {
|
||||
let (_, tagged_contents) = crate::commands::open::fetch(
|
||||
&cwd,
|
||||
&PathBuf::from(&path.item),
|
||||
path.tag.span,
|
||||
encoding,
|
||||
)?;
|
||||
return Ok(ActionStream::one(ReturnSuccess::action(
|
||||
CommandAction::AutoConvert(tagged_contents, ext),
|
||||
)));
|
||||
}
|
||||
// Check if bat does syntax highlighting
|
||||
if BAT_LANGUAGES.contains(&ext.as_ref()) {
|
||||
let (_, tagged_contents) = crate::commands::open::fetch(
|
||||
&cwd,
|
||||
&PathBuf::from(&path.item),
|
||||
path.tag.span,
|
||||
encoding,
|
||||
)?;
|
||||
return Ok(ActionStream::one(ReturnSuccess::value(tagged_contents)));
|
||||
}
|
||||
}
|
||||
|
||||
// Normal Streaming operation
|
||||
let with_encoding = if encoding.is_none() {
|
||||
None
|
||||
} else {
|
||||
Some(get_encoding(encoding)?)
|
||||
};
|
||||
|
||||
let sob_stream = shell_manager.open(&path.item, path.tag.span, with_encoding)?;
|
||||
|
||||
let final_stream = sob_stream.map(move |x| {
|
||||
// The tag that will used when returning a Value
|
||||
let file_tag = Tag {
|
||||
span: path.tag.span,
|
||||
anchor: Some(AnchorLocation::File(path.to_string_lossy().to_string())),
|
||||
};
|
||||
|
||||
match x {
|
||||
Ok(StringOrBinary::String(s)) => {
|
||||
ReturnSuccess::value(UntaggedValue::string(s).into_value(file_tag))
|
||||
}
|
||||
Ok(StringOrBinary::Binary(b)) => ReturnSuccess::value(
|
||||
UntaggedValue::binary(b.into_iter().collect()).into_value(file_tag),
|
||||
),
|
||||
Err(se) => Err(se),
|
||||
}
|
||||
});
|
||||
|
||||
Ok(ActionStream::new(final_stream))
|
||||
}
|
||||
|
||||
// Note that we do not output a Stream in "fetch" since it is only used by "enter" command
|
||||
// Which we expect to use a concrete Value a not a Stream
|
||||
pub fn fetch(
|
||||
cwd: &Path,
|
||||
location: &Path,
|
||||
span: Span,
|
||||
encoding_choice: Option<Tagged<String>>,
|
||||
) -> Result<(Option<String>, Value), ShellError> {
|
||||
// TODO: I don't understand the point of this? Maybe for better error reporting
|
||||
let mut cwd = PathBuf::from(cwd);
|
||||
cwd.push(location);
|
||||
let nice_location = dunce::canonicalize(&cwd).map_err(|e| match e.kind() {
|
||||
std::io::ErrorKind::NotFound => ShellError::labeled_error(
|
||||
format!("Cannot find file {:?}", cwd),
|
||||
"cannot find file",
|
||||
span,
|
||||
),
|
||||
std::io::ErrorKind::PermissionDenied => {
|
||||
ShellError::labeled_error("Permission denied", "permission denied", span)
|
||||
}
|
||||
_ => ShellError::labeled_error(
|
||||
format!("Cannot open file {:?} because {:?}", &cwd, e),
|
||||
"Cannot open",
|
||||
span,
|
||||
),
|
||||
})?;
|
||||
|
||||
// The extension may be used in AutoConvert later on
|
||||
let ext = location
|
||||
.extension()
|
||||
.map(|name| name.to_string_lossy().to_string());
|
||||
|
||||
// The tag that will used when returning a Value
|
||||
let file_tag = Tag {
|
||||
span,
|
||||
anchor: Some(AnchorLocation::File(
|
||||
nice_location.to_string_lossy().to_string(),
|
||||
)),
|
||||
};
|
||||
|
||||
let res = std::fs::read(location)
|
||||
.map_err(|_| ShellError::labeled_error("Can't open filename given", "can't open", span))?;
|
||||
|
||||
// If no encoding is provided we try to guess the encoding to read the file with
|
||||
let encoding = if encoding_choice.is_none() {
|
||||
UTF_8
|
||||
} else {
|
||||
get_encoding(encoding_choice.clone())?
|
||||
};
|
||||
|
||||
// If the user specified an encoding, then do not do BOM sniffing
|
||||
let decoded_res = if encoding_choice.is_some() {
|
||||
let (cow_res, _replacements) = encoding.decode_with_bom_removal(&res);
|
||||
cow_res
|
||||
} else {
|
||||
// Otherwise, use the default UTF-8 encoder with BOM sniffing
|
||||
let (cow_res, actual_encoding, replacements) = encoding.decode(&res);
|
||||
// If we had to use replacement characters then fallback to binary
|
||||
if replacements {
|
||||
return Ok((ext, UntaggedValue::binary(res).into_value(file_tag)));
|
||||
}
|
||||
debug!("Decoded using {:?}", actual_encoding);
|
||||
cow_res
|
||||
};
|
||||
let v = UntaggedValue::string(decoded_res.to_string()).into_value(file_tag);
|
||||
Ok((ext, v))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Open;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Open {})
|
||||
}
|
||||
}
|
99
crates/nu-command/src/commands/filesystem/rm.rs
Normal file
99
crates/nu-command/src/commands/filesystem/rm.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::shell::RemoveArgs;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
|
||||
pub struct Remove;
|
||||
|
||||
impl WholeStreamCommand for Remove {
|
||||
fn name(&self) -> &str {
|
||||
"rm"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("rm")
|
||||
.switch(
|
||||
"trash",
|
||||
"use the platform's recycle bin instead of permanently deleting",
|
||||
Some('t'),
|
||||
)
|
||||
.switch(
|
||||
"permanent",
|
||||
"don't use recycle bin, delete permanently",
|
||||
Some('p'),
|
||||
)
|
||||
.switch("recursive", "delete subdirectories recursively", Some('r'))
|
||||
.switch("force", "suppress error when no file", Some('f'))
|
||||
.rest(SyntaxShape::GlobPattern, "the file path(s) to remove")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Remove file(s)."
|
||||
}
|
||||
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
rm(args)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Delete or move a file to the system trash (depending on 'rm_always_trash' config option)",
|
||||
example: "rm file.txt",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Move a file to the system trash",
|
||||
example: "rm --trash file.txt",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Delete a file permanently",
|
||||
example: "rm --permanent file.txt",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Delete a file, and suppress errors if no file is found",
|
||||
example: "rm --force file.txt",
|
||||
result: None,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn rm(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let shell_manager = args.shell_manager();
|
||||
|
||||
let args = RemoveArgs {
|
||||
rest: args.rest(0)?,
|
||||
recursive: args.has_flag("recursive"),
|
||||
trash: args.has_flag("trash"),
|
||||
permanent: args.has_flag("permanent"),
|
||||
force: args.has_flag("force"),
|
||||
};
|
||||
|
||||
if args.trash && args.permanent {
|
||||
return Ok(ActionStream::one(Err(ShellError::labeled_error(
|
||||
"only one of --permanent and --trash can be used",
|
||||
"conflicting flags",
|
||||
name,
|
||||
))));
|
||||
}
|
||||
|
||||
shell_manager.rm(args, name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Remove;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Remove {})
|
||||
}
|
||||
}
|
268
crates/nu-command/src/commands/filesystem/save.rs
Normal file
268
crates/nu-command/src/commands/filesystem/save.rs
Normal file
@ -0,0 +1,268 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::{UnevaluatedCallInfo, WholeStreamCommand};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::ExternalRedirection, Primitive, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct Save;
|
||||
|
||||
macro_rules! process_unknown {
|
||||
($scope:tt, $input:ident, $name_tag:ident) => {{
|
||||
if $input.len() > 0 {
|
||||
match $input[0] {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(_)),
|
||||
..
|
||||
} => process_binary!($scope, $input, $name_tag),
|
||||
_ => process_string!($scope, $input, $name_tag),
|
||||
}
|
||||
} else {
|
||||
process_string!($scope, $input, $name_tag)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! process_string {
|
||||
($scope:tt, $input:ident, $name_tag:ident) => {{
|
||||
let mut result_string = String::new();
|
||||
for res in $input {
|
||||
match res {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
..
|
||||
} => {
|
||||
result_string.push_str(&s);
|
||||
}
|
||||
_ => {
|
||||
break $scope Err(ShellError::labeled_error(
|
||||
"Save requires string data",
|
||||
"consider converting data to string (see `help commands`)",
|
||||
$name_tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result_string.into_bytes())
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! process_binary {
|
||||
($scope:tt, $input:ident, $name_tag:ident) => {{
|
||||
let mut result_binary: Vec<u8> = Vec::new();
|
||||
for res in $input {
|
||||
match res {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(b)),
|
||||
..
|
||||
} => {
|
||||
for u in b.into_iter() {
|
||||
result_binary.push(u);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
break $scope Err(ShellError::labeled_error(
|
||||
"Save could not successfully save",
|
||||
"unexpected data during binary save",
|
||||
$name_tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result_binary)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! process_string_return_success {
|
||||
($scope:tt, $result_vec:ident, $name_tag:ident) => {{
|
||||
let mut result_string = String::new();
|
||||
for res in $result_vec {
|
||||
match res {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
..
|
||||
} => {
|
||||
result_string.push_str(&s);
|
||||
}
|
||||
_ => {
|
||||
break $scope Err(ShellError::labeled_error(
|
||||
"Save could not successfully save",
|
||||
"unexpected data during text save",
|
||||
$name_tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result_string.into_bytes())
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! process_binary_return_success {
|
||||
($scope:tt, $result_vec:ident, $name_tag:ident) => {{
|
||||
let mut result_binary: Vec<u8> = Vec::new();
|
||||
for res in $result_vec {
|
||||
match res {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(b)),
|
||||
..
|
||||
} => {
|
||||
for u in b.into_iter() {
|
||||
result_binary.push(u);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
break $scope Err(ShellError::labeled_error(
|
||||
"Save could not successfully save",
|
||||
"unexpected data during binary save",
|
||||
$name_tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result_binary)
|
||||
}};
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for Save {
|
||||
fn name(&self) -> &str {
|
||||
"save"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("save")
|
||||
.optional(
|
||||
"path",
|
||||
SyntaxShape::FilePath,
|
||||
"the path to save contents to",
|
||||
)
|
||||
.switch(
|
||||
"raw",
|
||||
"treat values as-is rather than auto-converting based on file extension",
|
||||
Some('r'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Save the contents of the pipeline to a file."
|
||||
}
|
||||
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
save(args)
|
||||
}
|
||||
}
|
||||
|
||||
fn save(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let shell_manager = args.shell_manager();
|
||||
let mut full_path = PathBuf::from(shell_manager.path());
|
||||
let name_tag = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
let context = args.context.clone();
|
||||
let scope = args.scope().clone();
|
||||
|
||||
let head = args.call_info.args.head.clone();
|
||||
|
||||
let path: Option<Tagged<PathBuf>> = args.opt(0)?;
|
||||
let save_raw = args.has_flag("raw");
|
||||
|
||||
let input: Vec<Value> = args.input.collect();
|
||||
if path.is_none() {
|
||||
let mut should_return_file_path_error = true;
|
||||
|
||||
// If there is no filename, check the metadata for the anchor filename
|
||||
if !input.is_empty() {
|
||||
let anchor = input[0].tag.anchor();
|
||||
|
||||
if let Some(AnchorLocation::File(file)) = anchor {
|
||||
should_return_file_path_error = false;
|
||||
full_path.push(Path::new(&file));
|
||||
}
|
||||
}
|
||||
|
||||
if should_return_file_path_error {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Save requires a filepath",
|
||||
"needs path",
|
||||
name_tag,
|
||||
));
|
||||
}
|
||||
} else if let Some(file) = path {
|
||||
full_path.push(file.item());
|
||||
}
|
||||
|
||||
// TODO use label_break_value once it is stable:
|
||||
// https://github.com/rust-lang/rust/issues/48594
|
||||
#[allow(clippy::never_loop)]
|
||||
let content: Result<Vec<u8>, ShellError> = 'scope: loop {
|
||||
break if !save_raw {
|
||||
if let Some(extension) = full_path.extension() {
|
||||
let command_name = format!("to {}", extension.to_string_lossy());
|
||||
if let Some(converter) = scope.get_command(&command_name) {
|
||||
let new_args = CommandArgs {
|
||||
context,
|
||||
call_info: UnevaluatedCallInfo {
|
||||
args: nu_protocol::hir::Call {
|
||||
head,
|
||||
positional: None,
|
||||
named: None,
|
||||
span: Span::unknown(),
|
||||
external_redirection: ExternalRedirection::Stdout,
|
||||
},
|
||||
name_tag: name_tag.clone(),
|
||||
},
|
||||
input: InputStream::from_stream(input.into_iter()),
|
||||
};
|
||||
let mut result = converter.run(new_args)?;
|
||||
let result_vec: Vec<Value> = result.drain_vec();
|
||||
if converter.is_binary() {
|
||||
process_binary_return_success!('scope, result_vec, name_tag)
|
||||
} else {
|
||||
process_string_return_success!('scope, result_vec, name_tag)
|
||||
}
|
||||
} else {
|
||||
process_unknown!('scope, input, name_tag)
|
||||
}
|
||||
} else {
|
||||
process_unknown!('scope, input, name_tag)
|
||||
}
|
||||
} else {
|
||||
Ok(string_from(&input).into_bytes())
|
||||
};
|
||||
};
|
||||
|
||||
shell_manager.save(&full_path, &content?, name.span)
|
||||
}
|
||||
|
||||
fn string_from(input: &[Value]) -> String {
|
||||
let mut save_data = String::new();
|
||||
|
||||
if !input.is_empty() {
|
||||
let mut first = true;
|
||||
for i in input.iter() {
|
||||
if !first {
|
||||
save_data.push('\n');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
if let Ok(data) = &i.as_string() {
|
||||
save_data.push_str(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save_data
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Save;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Save {})
|
||||
}
|
||||
}
|
78
crates/nu-command/src/commands/filesystem/touch.rs
Normal file
78
crates/nu-command/src/commands/filesystem/touch.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use crate::prelude::*;
|
||||
use nu_engine::WholeStreamCommand;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Touch;
|
||||
|
||||
impl WholeStreamCommand 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(SyntaxShape::FilePath, "additional files to create")
|
||||
}
|
||||
fn usage(&self) -> &str {
|
||||
"Creates one or more files."
|
||||
}
|
||||
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
touch(args)
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn touch(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
||||
let target: Tagged<PathBuf> = args.req(0)?;
|
||||
let rest: Vec<Tagged<PathBuf>> = args.rest(1)?;
|
||||
|
||||
for item in vec![target].into_iter().chain(rest.into_iter()) {
|
||||
match OpenOptions::new().write(true).create(true).open(&item) {
|
||||
Ok(_) => continue,
|
||||
Err(err) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"File Error",
|
||||
err.to_string(),
|
||||
&item.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ActionStream::empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ShellError;
|
||||
use super::Touch;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Touch {})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user