mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 14:40:06 +02:00
add rm command + stubs for open and save
This commit is contained in:
@ -13,6 +13,7 @@ nu-protocol = { path = "../nu-protocol" }
|
||||
nu-table = { path = "../nu-table" }
|
||||
nu-term-grid = { path = "../nu-term-grid" }
|
||||
nu-parser = { path = "../nu-parser" }
|
||||
trash = { version = "1.3.0", optional = true }
|
||||
|
||||
# Potential dependencies for extras
|
||||
glob = "0.3.0"
|
||||
@ -21,3 +22,6 @@ sysinfo = "0.20.4"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
terminal_size = "0.1.17"
|
||||
lscolors = { version = "0.8.0", features = ["crossterm"] }
|
||||
|
||||
[features]
|
||||
trash-support = ["trash"]
|
||||
|
@ -13,51 +13,25 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
||||
let engine_state = engine_state.borrow();
|
||||
let mut working_set = StateWorkingSet::new(&*engine_state);
|
||||
|
||||
working_set.add_decl(Box::new(Alias));
|
||||
working_set.add_decl(Box::new(Benchmark));
|
||||
working_set.add_decl(Box::new(BuildString));
|
||||
working_set.add_decl(Box::new(Cd));
|
||||
working_set.add_decl(Box::new(Cp));
|
||||
working_set.add_decl(Box::new(Def));
|
||||
working_set.add_decl(Box::new(Do));
|
||||
working_set.add_decl(Box::new(Each));
|
||||
working_set.add_decl(Box::new(ExportDef));
|
||||
working_set.add_decl(Box::new(External));
|
||||
working_set.add_decl(Box::new(For));
|
||||
working_set.add_decl(Box::new(From));
|
||||
working_set.add_decl(Box::new(FromJson));
|
||||
working_set.add_decl(Box::new(Get));
|
||||
working_set.add_decl(Box::new(Griddle));
|
||||
working_set.add_decl(Box::new(Help));
|
||||
working_set.add_decl(Box::new(Hide));
|
||||
working_set.add_decl(Box::new(If));
|
||||
working_set.add_decl(Box::new(Length));
|
||||
working_set.add_decl(Box::new(Let));
|
||||
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(Split));
|
||||
working_set.add_decl(Box::new(SplitChars));
|
||||
working_set.add_decl(Box::new(SplitColumn));
|
||||
working_set.add_decl(Box::new(SplitRow));
|
||||
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));
|
||||
macro_rules! bind_command {
|
||||
( $command:expr ) => {
|
||||
working_set.add_decl(Box::new($command));
|
||||
};
|
||||
( $( $command:expr ),* ) => {
|
||||
$( working_set.add_decl(Box::new($command)); )*
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: sort items categorically
|
||||
#[rustfmt::skip] bind_command!( Alias, Benchmark, BuildString,
|
||||
Cd, Cp, Def, Do, Each, ExportDef, External, For, From,
|
||||
FromJson, Get, Griddle, Help, Hide, If, Length, Let, LetEnv,
|
||||
Lines, Ls, Mkdir, Module, Mv, Open, Ps, Rm, Save, Select,
|
||||
Split, SplitChars, SplitColumn, SplitRow, Sys, Table, Touch,
|
||||
Use, Where, Wrap );
|
||||
|
||||
// This is a WIP proof of concept
|
||||
working_set.add_decl(Box::new(ListGitBranches));
|
||||
working_set.add_decl(Box::new(Git));
|
||||
working_set.add_decl(Box::new(GitCheckout));
|
||||
|
||||
working_set.add_decl(Box::new(Source));
|
||||
bind_command!(ListGitBranches, Git, GitCheckout, Source);
|
||||
|
||||
let sig = Signature::build("exit");
|
||||
working_set.add_decl(sig.predeclare());
|
||||
|
@ -3,6 +3,9 @@ mod cp;
|
||||
mod ls;
|
||||
mod mkdir;
|
||||
mod mv;
|
||||
mod open;
|
||||
mod rm;
|
||||
mod save;
|
||||
mod touch;
|
||||
mod util;
|
||||
|
||||
@ -11,4 +14,7 @@ pub use cp::Cp;
|
||||
pub use ls::Ls;
|
||||
pub use mkdir::Mkdir;
|
||||
pub use mv::Mv;
|
||||
pub use open::Open;
|
||||
pub use rm::Rm;
|
||||
pub use save::Save;
|
||||
pub use touch::Touch;
|
||||
|
53
crates/nu-command/src/filesystem/open.rs
Normal file
53
crates/nu-command/src/filesystem/open.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{ShellError, Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct Open;
|
||||
|
||||
impl Command for Open {
|
||||
fn name(&self) -> &str {
|
||||
"open"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Load a file into a cell, convert to table if possible (avoid by appending '--raw')."
|
||||
}
|
||||
|
||||
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 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(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
_call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
252
crates/nu-command/src/filesystem/rm.rs
Normal file
252
crates/nu-command/src/filesystem/rm.rs
Normal file
@ -0,0 +1,252 @@
|
||||
use std::collections::HashMap;
|
||||
use std::env::current_dir;
|
||||
use std::os::unix::prelude::FileTypeExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use glob::Paths;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::{Call, Expression};
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{ShellError, Signature, SyntaxShape, Value, ValueStream};
|
||||
|
||||
pub struct Rm;
|
||||
|
||||
// Where self.0 is the unexpanded target's positional index (i.e. call.positional[self.0].span)
|
||||
struct Target(usize, PathBuf);
|
||||
|
||||
struct RmArgs {
|
||||
targets: Vec<Target>,
|
||||
recursive: bool,
|
||||
trash: bool,
|
||||
permanent: bool,
|
||||
force: bool,
|
||||
}
|
||||
|
||||
impl Command for Rm {
|
||||
fn name(&self) -> &str {
|
||||
"rm"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Remove file(s)."
|
||||
}
|
||||
|
||||
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(
|
||||
"rest",
|
||||
SyntaxShape::GlobPattern,
|
||||
"the file path(s) to remove",
|
||||
)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
context: &EvaluationContext,
|
||||
call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
rm(context, call)
|
||||
}
|
||||
}
|
||||
|
||||
fn rm(context: &EvaluationContext, call: &Call) -> Result<Value, ShellError> {
|
||||
let trash = call.has_flag("trash");
|
||||
let permanent = call.has_flag("permanent");
|
||||
|
||||
if trash && permanent {
|
||||
return Err(ShellError::IncompatibleParametersSingle(
|
||||
"Can't use \"--trash\" with \"--permanent\"".to_string(),
|
||||
call.head,
|
||||
));
|
||||
|
||||
// let trash_span = call.get_flag_expr("trash").unwrap().span;
|
||||
// let perm_span = call.get_flag_expr("permanent").unwrap().span;
|
||||
|
||||
// let left_message = "cannot use".to_string();
|
||||
// let right_message = "with".to_string();
|
||||
// let (left_span, right_span) = match trash_span.start < perm_span.start {
|
||||
// true => (trash_span, perm_span),
|
||||
// false => (perm_span, trash_span),
|
||||
// };
|
||||
|
||||
// return Err(ShellError::IncompatibleParameters {
|
||||
// left_message,
|
||||
// left_span,
|
||||
// right_message,
|
||||
// right_span,
|
||||
// });
|
||||
}
|
||||
|
||||
let current_path = current_dir()?;
|
||||
let mut paths = call
|
||||
.rest::<String>(context, 0)?
|
||||
.into_iter()
|
||||
.map(|path| current_path.join(path))
|
||||
.peekable();
|
||||
|
||||
if paths.peek().is_none() {
|
||||
return Err(ShellError::FileNotFound(call.positional[0].span));
|
||||
}
|
||||
|
||||
// Expand and flatten files
|
||||
let resolve_path = |i: usize, path: PathBuf| {
|
||||
glob::glob(&path.to_string_lossy()).map_or_else(
|
||||
|_| Vec::new(),
|
||||
|path_iter| path_iter.flatten().map(|f| Target(i, f)).collect(),
|
||||
)
|
||||
};
|
||||
|
||||
let mut targets: Vec<Target> = vec![];
|
||||
for (i, path) in paths.enumerate() {
|
||||
let mut paths: Vec<Target> = resolve_path(i, path);
|
||||
|
||||
if paths.is_empty() {
|
||||
return Err(ShellError::FileNotFound(call.positional[i].span));
|
||||
}
|
||||
|
||||
targets.append(paths.as_mut());
|
||||
}
|
||||
|
||||
let recursive = call.has_flag("recursive");
|
||||
let force = call.has_flag("force");
|
||||
|
||||
let args = RmArgs {
|
||||
targets,
|
||||
recursive,
|
||||
trash,
|
||||
permanent,
|
||||
force,
|
||||
};
|
||||
let response = rm_helper(call, args);
|
||||
|
||||
// let temp = rm_helper(call, args).flatten();
|
||||
// let temp = input.flatten(call.head, move |_| rm_helper(call, args));
|
||||
|
||||
Ok(Value::Stream {
|
||||
stream: ValueStream::from_stream(response.into_iter()),
|
||||
span: call.head,
|
||||
})
|
||||
|
||||
// Ok(Value::Nothing { span })
|
||||
}
|
||||
|
||||
fn rm_helper(call: &Call, args: RmArgs) -> Vec<Value> {
|
||||
let (targets, recursive, trash, permanent, force) = (
|
||||
args.targets,
|
||||
args.recursive,
|
||||
args.trash,
|
||||
args.permanent,
|
||||
args.force,
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "trash-support"))]
|
||||
{
|
||||
if trash {
|
||||
return vec![Value::Error {
|
||||
error: ShellError::FeatureNotEnabled(call.get_flag_expr("trash").unwrap().span),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
if targets.is_empty() && !force {
|
||||
return vec![Value::Error {
|
||||
error: ShellError::FileNotFound(call.head),
|
||||
}];
|
||||
}
|
||||
|
||||
targets
|
||||
.into_iter()
|
||||
.map(move |target| {
|
||||
let (i, f) = (target.0, target.1);
|
||||
|
||||
let is_empty = || match f.read_dir() {
|
||||
Ok(mut p) => p.next().is_none(),
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
if let Ok(metadata) = f.symlink_metadata() {
|
||||
#[cfg(unix)]
|
||||
let is_socket = metadata.file_type().is_socket();
|
||||
#[cfg(unix)]
|
||||
let is_fifo = metadata.file_type().is_fifo();
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let is_socket = false;
|
||||
#[cfg(not(unix))]
|
||||
let is_fifo = false;
|
||||
|
||||
if metadata.is_file()
|
||||
|| metadata.file_type().is_symlink()
|
||||
|| recursive
|
||||
|| is_socket
|
||||
|| is_fifo
|
||||
|| is_empty()
|
||||
{
|
||||
let result;
|
||||
#[cfg(feature = "trash-support")]
|
||||
{
|
||||
use std::io::Error;
|
||||
result = if trash {
|
||||
trash::delete(&f).map_err(|e: trash::Error| {
|
||||
Error::new(ErrorKind::Other, format!("{:?}", e))
|
||||
})
|
||||
} else if metadata.is_file() {
|
||||
std::fs::remove_file(&f)
|
||||
} else {
|
||||
std::fs::remove_dir_all(&f)
|
||||
};
|
||||
}
|
||||
#[cfg(not(feature = "trash-support"))]
|
||||
{
|
||||
result = if metadata.is_file() || is_socket || is_fifo {
|
||||
std::fs::remove_file(&f)
|
||||
} else {
|
||||
std::fs::remove_dir_all(&f)
|
||||
};
|
||||
}
|
||||
|
||||
if let Err(e) = result {
|
||||
return Value::Error {
|
||||
error: ShellError::RemoveNotPossible(
|
||||
format!("Could not delete because: {:}\nTry '--trash' flag", e),
|
||||
call.head,
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return Value::String {
|
||||
val: format!("deleted {:}", f.to_string_lossy()).into(),
|
||||
span: call.positional[i].span,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return Value::Error {
|
||||
error: ShellError::RemoveNotPossible(
|
||||
"Cannot remove. try --recursive".to_string(),
|
||||
call.positional[i].span,
|
||||
),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return Value::Error {
|
||||
error: ShellError::RemoveNotPossible(
|
||||
"no such file or directory".to_string(),
|
||||
call.positional[i].span,
|
||||
),
|
||||
};
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
39
crates/nu-command/src/filesystem/save.rs
Normal file
39
crates/nu-command/src/filesystem/save.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EvaluationContext};
|
||||
use nu_protocol::{ShellError, Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct Save;
|
||||
|
||||
impl Command 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'),
|
||||
)
|
||||
.switch("append", "append values rather than overriding", Some('a'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Save the contents of the pipeline to a file."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_context: &EvaluationContext,
|
||||
_call: &Call,
|
||||
_input: Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
@ -64,7 +64,8 @@ impl Command for SubCommand {
|
||||
fn split_chars(call: &Call, input: Value) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
let span = call.head;
|
||||
|
||||
Ok(input.flat_map(span, move |x| split_chars_helper(&x, span)))
|
||||
let temp = input.flat_map(span, move |x| split_chars_helper(&x, span));
|
||||
Ok(temp)
|
||||
}
|
||||
|
||||
fn split_chars_helper(v: &Value, name: Span) -> Vec<Value> {
|
||||
|
@ -41,6 +41,26 @@ pub enum ShellError {
|
||||
#[diagnostic(code(nu::shell::missing_parameter), url(docsrs))]
|
||||
MissingParameter(String, #[label = "missing parameter: {0}"] Span),
|
||||
|
||||
// Be cautious, as flags can share the same span, resulting in a panic (ex: `rm -pt`)
|
||||
#[error("Incompatible parameters.")]
|
||||
#[diagnostic(code(nu::shell::incompatible_parameters), url(docsrs))]
|
||||
IncompatibleParameters {
|
||||
left_message: String,
|
||||
#[label("{left_message}")]
|
||||
left_span: Span,
|
||||
right_message: String,
|
||||
#[label("{right_message}")]
|
||||
right_span: Span,
|
||||
},
|
||||
|
||||
#[error("Incompatible parameters.")]
|
||||
#[diagnostic(code(nu::shell::incompatible_parameters), url(docsrs))]
|
||||
IncompatibleParametersSingle(String, #[label = "{0}"] Span),
|
||||
|
||||
#[error("Feature not enabled.")]
|
||||
#[diagnostic(code(nu::shell::feature_not_enabled), url(docsrs))]
|
||||
FeatureNotEnabled(#[label = "feature not enabled"] Span),
|
||||
|
||||
#[error("External commands not yet supported")]
|
||||
#[diagnostic(code(nu::shell::external_commands), url(docsrs))]
|
||||
ExternalNotSupported(#[label = "external not supported"] Span),
|
||||
@ -131,6 +151,10 @@ pub enum ShellError {
|
||||
#[error("Create not possible")]
|
||||
#[diagnostic(code(nu::shell::create_not_possible), url(docsrs))]
|
||||
CreateNotPossible(String, #[label("{0}")] Span),
|
||||
|
||||
#[error("Remove not possible")]
|
||||
#[diagnostic(code(nu::shell::remove_not_possible), url(docsrs))]
|
||||
RemoveNotPossible(String, #[label("{0}")] Span),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ShellError {
|
||||
|
Reference in New Issue
Block a user