mirror of
https://github.com/nushell/nushell.git
synced 2025-02-16 10:32:29 +01:00
open
, rm
, umv
, cp
, rm
and du
: Don't globs if inputs are variables or string interpolation (#11886)
# Description
This is a follow up to
https://github.com/nushell/nushell/pull/11621#issuecomment-1937484322
Also Fixes: #11838
## About the code change
It applys the same logic when we pass variables to external commands:
0487e9ffcb/crates/nu-command/src/system/run_external.rs (L162-L170)
That is: if user input dynamic things(like variables, sub-expression, or
string interpolation), it returns a quoted `NuPath`, then user input
won't be globbed
# User-Facing Changes
Given two input files: `a*c.txt`, `abc.txt`
* `let f = "a*c.txt"; rm $f` will remove one file: `a*c.txt`.
~* `let f = "a*c.txt"; rm --glob $f` will remove `a*c.txt` and
`abc.txt`~
* `let f: glob = "a*c.txt"; rm $f` will remove `a*c.txt` and `abc.txt`
## Rules about globbing with *variable*
Given two files: `a*c.txt`, `abc.txt`
| Cmd Type | example | Result |
| ----- | ------------------ | ------ |
| builtin | let f = "a*c.txt"; rm $f | remove `a*c.txt` |
| builtin | let f: glob = "a*c.txt"; rm $f | remove `a*c.txt` and
`abc.txt`
| builtin | let f = "a*c.txt"; rm ($f \| into glob) | remove `a*c.txt`
and `abc.txt`
| custom | def crm [f: glob] { rm $f }; let f = "a*c.txt"; crm $f |
remove `a*c.txt` and `abc.txt`
| custom | def crm [f: glob] { rm ($f \| into string) }; let f =
"a*c.txt"; crm $f | remove `a*c.txt`
| custom | def crm [f: string] { rm $f }; let f = "a*c.txt"; crm $f |
remove `a*c.txt`
| custom | def crm [f: string] { rm $f }; let f = "a*c.txt"; crm ($f \|
into glob) | remove `a*c.txt` and `abc.txt`
In general, if a variable is annotated with `glob` type, nushell will
expand glob pattern. Or else, we need to use `into | glob` to expand
glob pattern
# Tests + Formatting
Done
# After Submitting
I think `str glob-escape` command will be no-longer required. We can
remove it.
This commit is contained in:
parent
a2a1c1656f
commit
f7d647ac3c
@ -305,7 +305,7 @@ fn describe_value(
|
||||
| Value::Date { .. }
|
||||
| Value::Range { .. }
|
||||
| Value::String { .. }
|
||||
| Value::QuotedString { .. }
|
||||
| Value::Glob { .. }
|
||||
| Value::Nothing { .. } => Value::record(
|
||||
record!(
|
||||
"type" => Value::string(value.get_type().to_string(), head),
|
||||
|
@ -1,7 +1,9 @@
|
||||
use nu_engine::eval_block;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Let;
|
||||
@ -61,8 +63,24 @@ impl Command for Let {
|
||||
.expect("internal error: missing right hand side");
|
||||
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let pipeline_data = eval_block(engine_state, stack, block, input, true, false)?;
|
||||
stack.add_var(var_id, pipeline_data.into_value(call.head));
|
||||
let mut value = pipeline_data.into_value(call.head);
|
||||
|
||||
// if given variable type is Glob, and our result is string
|
||||
// then nushell need to convert from Value::String to Value::Glob
|
||||
// it's assigned by demand, then it's not quoted, and it's required to expand
|
||||
// if we pass it to other commands.
|
||||
let var_type = &engine_state.get_var(var_id).ty;
|
||||
let val_span = value.span();
|
||||
match value {
|
||||
Value::String { val, .. } if var_type == &Type::Glob => {
|
||||
value = Value::glob(val, false, val_span);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
stack.add_var(var_id, value);
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
|
@ -228,7 +228,7 @@ impl<'a> std::fmt::Debug for DebuggableValue<'a> {
|
||||
val.from, val.to, val.incr
|
||||
),
|
||||
},
|
||||
Value::String { val, .. } | Value::QuotedString { val, .. } => {
|
||||
Value::String { val, .. } | Value::Glob { val, .. } => {
|
||||
write!(f, "{:?}", val)
|
||||
}
|
||||
Value::Record { val, .. } => {
|
||||
|
@ -122,7 +122,7 @@ impl<'a> StyleComputer<'a> {
|
||||
Value::Range { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Float { .. } => TextStyle::with_style(Right, s),
|
||||
Value::String { .. } => TextStyle::with_style(Left, s),
|
||||
Value::QuotedString { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Glob { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Nothing { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Binary { .. } => TextStyle::with_style(Left, s),
|
||||
Value::CellPath { .. } => TextStyle::with_style(Left, s),
|
||||
|
133
crates/nu-command/src/conversions/into/glob.rs
Normal file
133
crates/nu-command/src/conversions/into/glob.rs
Normal file
@ -0,0 +1,133 @@
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
cell_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl CmdArgument for Arguments {
|
||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.cell_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"into glob"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("into glob")
|
||||
.input_output_types(vec![
|
||||
(Type::String, Type::Glob),
|
||||
(
|
||||
Type::List(Box::new(Type::String)),
|
||||
Type::List(Box::new(Type::Glob)),
|
||||
),
|
||||
(Type::Table(vec![]), Type::Table(vec![])),
|
||||
(Type::Record(vec![]), Type::Record(vec![])),
|
||||
])
|
||||
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"For a data structure input, convert data at the given cell paths.",
|
||||
)
|
||||
.category(Category::Conversions)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert value to glob."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "text"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
glob_helper(engine_state, stack, call, input)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "convert string to glob",
|
||||
example: "'1234' | into glob",
|
||||
result: Some(Value::test_string("1234")),
|
||||
},
|
||||
Example {
|
||||
description: "convert filepath to string",
|
||||
example: "ls Cargo.toml | get name | into glob",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn glob_helper(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||
let args = Arguments { cell_paths };
|
||||
match input {
|
||||
PipelineData::ExternalStream { stdout: None, .. } => {
|
||||
Ok(Value::glob(String::new(), false, head).into_pipeline_data())
|
||||
}
|
||||
PipelineData::ExternalStream {
|
||||
stdout: Some(stream),
|
||||
..
|
||||
} => {
|
||||
// TODO: in the future, we may want this to stream out, converting each to bytes
|
||||
let output = stream.into_string()?;
|
||||
Ok(Value::glob(output.item, false, head).into_pipeline_data())
|
||||
}
|
||||
_ => operate(action, args, input, head, engine_state.ctrlc.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||
match input {
|
||||
Value::String { val, .. } => Value::glob(val.to_string(), false, span),
|
||||
x => Value::error(
|
||||
ShellError::CantConvert {
|
||||
to_type: String::from("glob"),
|
||||
from_type: x.get_type().to_string(),
|
||||
span,
|
||||
help: None,
|
||||
},
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ mod datetime;
|
||||
mod duration;
|
||||
mod filesize;
|
||||
mod float;
|
||||
mod glob;
|
||||
mod int;
|
||||
mod record;
|
||||
mod string;
|
||||
@ -19,6 +20,7 @@ pub use command::Into;
|
||||
pub use datetime::SubCommand as IntoDatetime;
|
||||
pub use duration::SubCommand as IntoDuration;
|
||||
pub use float::SubCommand as IntoFloat;
|
||||
pub use glob::SubCommand as IntoGlob;
|
||||
pub use int::SubCommand as IntoInt;
|
||||
pub use record::SubCommand as IntoRecord;
|
||||
pub use string::SubCommand as IntoString;
|
||||
|
@ -36,6 +36,7 @@ impl Command for SubCommand {
|
||||
(Type::Int, Type::String),
|
||||
(Type::Number, Type::String),
|
||||
(Type::String, Type::String),
|
||||
(Type::Glob, Type::String),
|
||||
(Type::Bool, Type::String),
|
||||
(Type::Filesize, Type::String),
|
||||
(Type::Date, Type::String),
|
||||
@ -202,6 +203,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
Value::Bool { val, .. } => Value::string(val.to_string(), span),
|
||||
Value::Date { val, .. } => Value::string(val.format("%c").to_string(), span),
|
||||
Value::String { val, .. } => Value::string(val.to_string(), span),
|
||||
Value::Glob { val, .. } => Value::string(val.to_string(), span),
|
||||
|
||||
Value::Filesize { val: _, .. } => {
|
||||
Value::string(input.to_expanded_string(", ", config), span)
|
||||
|
@ -354,6 +354,7 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> {
|
||||
| Type::Range
|
||||
| Type::Record(_)
|
||||
| Type::Signature
|
||||
| Type::Glob
|
||||
| Type::Table(_) => Err(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "sql".into(),
|
||||
wrong_type: val.get_type().to_string(),
|
||||
|
@ -251,7 +251,7 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
|
||||
)
|
||||
}
|
||||
Value::String { val, .. } => val.clone(),
|
||||
Value::QuotedString { val, .. } => val.clone(),
|
||||
Value::Glob { val, .. } => val.clone(),
|
||||
Value::List { vals: val, .. } => format!(
|
||||
"[{}]",
|
||||
val.iter()
|
||||
|
@ -303,6 +303,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
IntoInt,
|
||||
IntoRecord,
|
||||
IntoString,
|
||||
IntoGlob,
|
||||
IntoValue,
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
use super::util::opt_for_glob_pattern;
|
||||
use crate::{DirBuilder, DirInfo, FileInfo};
|
||||
use nu_engine::{current_dir, CallExt};
|
||||
use nu_glob::Pattern;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoInterruptiblePipelineData, NuPath, PipelineData, ShellError, Signature,
|
||||
Category, Example, IntoInterruptiblePipelineData, NuGlob, PipelineData, ShellError, Signature,
|
||||
Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
@ -14,7 +15,7 @@ pub struct Du;
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct DuArgs {
|
||||
path: Option<Spanned<NuPath>>,
|
||||
path: Option<Spanned<NuGlob>>,
|
||||
all: bool,
|
||||
deref: bool,
|
||||
exclude: Option<Spanned<String>>,
|
||||
@ -66,7 +67,7 @@ impl Command for Du {
|
||||
"Exclude files below this size",
|
||||
Some('m'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
.category(Category::FileSystem)
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -96,7 +97,7 @@ impl Command for Du {
|
||||
let current_dir = current_dir(engine_state, stack)?;
|
||||
|
||||
let args = DuArgs {
|
||||
path: call.opt(engine_state, stack, 0)?,
|
||||
path: opt_for_glob_pattern(engine_state, stack, call, 0)?,
|
||||
all: call.has_flag(engine_state, stack, "all")?,
|
||||
deref: call.has_flag(engine_state, stack, "deref")?,
|
||||
exclude: call.get_flag(engine_state, stack, "exclude")?,
|
||||
@ -119,7 +120,7 @@ impl Command for Du {
|
||||
// The * pattern should never fail.
|
||||
None => nu_engine::glob_from(
|
||||
&Spanned {
|
||||
item: NuPath::UnQuoted("*".into()),
|
||||
item: NuGlob::Expand("*".into()),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
¤t_dir,
|
||||
|
@ -1,3 +1,4 @@
|
||||
use super::util::opt_for_glob_pattern;
|
||||
use crate::DirBuilder;
|
||||
use crate::DirInfo;
|
||||
use chrono::{DateTime, Local, LocalResult, TimeZone, Utc};
|
||||
@ -7,7 +8,7 @@ use nu_glob::{MatchOptions, Pattern};
|
||||
use nu_path::expand_to_real_path;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::NuPath;
|
||||
use nu_protocol::NuGlob;
|
||||
use nu_protocol::{
|
||||
Category, DataSource, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||
PipelineMetadata, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
@ -86,17 +87,16 @@ impl Command for Ls {
|
||||
let call_span = call.head;
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
let pattern_arg: Option<Spanned<NuPath>> = call.opt(engine_state, stack, 0)?;
|
||||
|
||||
let pattern_arg = opt_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
let pattern_arg = {
|
||||
if let Some(path) = pattern_arg {
|
||||
match path.item {
|
||||
NuPath::Quoted(p) => Some(Spanned {
|
||||
item: NuPath::Quoted(nu_utils::strip_ansi_string_unlikely(p)),
|
||||
NuGlob::DoNotExpand(p) => Some(Spanned {
|
||||
item: NuGlob::DoNotExpand(nu_utils::strip_ansi_string_unlikely(p)),
|
||||
span: path.span,
|
||||
}),
|
||||
NuPath::UnQuoted(p) => Some(Spanned {
|
||||
item: NuPath::UnQuoted(nu_utils::strip_ansi_string_unlikely(p)),
|
||||
NuGlob::Expand(p) => Some(Spanned {
|
||||
item: NuGlob::Expand(nu_utils::strip_ansi_string_unlikely(p)),
|
||||
span: path.span,
|
||||
}),
|
||||
}
|
||||
@ -149,7 +149,7 @@ impl Command for Ls {
|
||||
p,
|
||||
p_tag,
|
||||
absolute_path,
|
||||
matches!(pat.item, NuPath::Quoted(_)),
|
||||
matches!(pat.item, NuGlob::DoNotExpand(_)),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
@ -186,8 +186,8 @@ impl Command for Ls {
|
||||
};
|
||||
|
||||
let glob_path = Spanned {
|
||||
// It needs to be un-quoted, the relative logic is handled previously
|
||||
item: NuPath::UnQuoted(path.clone()),
|
||||
// use NeedExpand, the relative escaping logic is handled previously
|
||||
item: NuGlob::Expand(path.clone()),
|
||||
span: p_tag,
|
||||
};
|
||||
|
||||
|
@ -6,7 +6,7 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoInterruptiblePipelineData, NuPath, PipelineData, ShellError, Signature,
|
||||
Category, Example, IntoInterruptiblePipelineData, NuGlob, PipelineData, ShellError, Signature,
|
||||
Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
@ -62,7 +62,7 @@ impl Command for Mv {
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
// TODO: handle invalid directory or insufficient permissions when moving
|
||||
let mut spanned_source: Spanned<NuPath> = call.req(engine_state, stack, 0)?;
|
||||
let mut spanned_source: Spanned<NuGlob> = call.req(engine_state, stack, 0)?;
|
||||
spanned_source.item = spanned_source.item.strip_ansi_string_unlikely();
|
||||
let spanned_destination: Spanned<String> = call.req(engine_state, stack, 1)?;
|
||||
let verbose = call.has_flag(engine_state, stack, "verbose")?;
|
||||
|
@ -1,10 +1,11 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
use nu_engine::{current_dir, eval_block, CallExt};
|
||||
use nu_path::expand_to_real_path;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::util::BufferedReader;
|
||||
use nu_protocol::{
|
||||
Category, DataSource, Example, IntoInterruptiblePipelineData, NuPath, PipelineData,
|
||||
Category, DataSource, Example, IntoInterruptiblePipelineData, NuGlob, PipelineData,
|
||||
PipelineMetadata, RawStream, ShellError, Signature, Spanned, SyntaxShape, Type,
|
||||
};
|
||||
use std::io::BufReader;
|
||||
@ -58,7 +59,7 @@ impl Command for Open {
|
||||
let call_span = call.head;
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut paths = call.rest::<Spanned<NuPath>>(engine_state, stack, 0)?;
|
||||
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
|
||||
if paths.is_empty() && call.rest_iter(0).next().is_none() {
|
||||
// try to use path from pipeline input if there were no positional or spread args
|
||||
@ -76,7 +77,7 @@ impl Command for Open {
|
||||
};
|
||||
|
||||
paths.push(Spanned {
|
||||
item: NuPath::UnQuoted(filename),
|
||||
item: NuGlob::Expand(filename),
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use std::io::ErrorKind;
|
||||
use std::os::unix::prelude::FileTypeExt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
use super::util::try_interaction;
|
||||
|
||||
use nu_engine::env::current_dir;
|
||||
@ -14,7 +15,7 @@ use nu_path::expand_path_with;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoInterruptiblePipelineData, NuPath, PipelineData, ShellError, Signature,
|
||||
Category, Example, IntoInterruptiblePipelineData, NuGlob, PipelineData, ShellError, Signature,
|
||||
Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
@ -126,7 +127,7 @@ fn rm(
|
||||
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
let mut paths: Vec<Spanned<NuPath>> = call.rest(engine_state, stack, 0)?;
|
||||
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
|
||||
if paths.is_empty() {
|
||||
return Err(ShellError::MissingParameter {
|
||||
@ -166,8 +167,10 @@ fn rm(
|
||||
}
|
||||
let corrected_path = Spanned {
|
||||
item: match path.item {
|
||||
NuPath::Quoted(s) => NuPath::Quoted(nu_utils::strip_ansi_string_unlikely(s)),
|
||||
NuPath::UnQuoted(s) => NuPath::UnQuoted(nu_utils::strip_ansi_string_unlikely(s)),
|
||||
NuGlob::DoNotExpand(s) => {
|
||||
NuGlob::DoNotExpand(nu_utils::strip_ansi_string_unlikely(s))
|
||||
}
|
||||
NuGlob::Expand(s) => NuGlob::Expand(nu_utils::strip_ansi_string_unlikely(s)),
|
||||
},
|
||||
span: path.span,
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
use nu_engine::{current_dir, CallExt};
|
||||
use nu_protocol::NuPath;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use uu_cp::{BackupMode, CopyMode, UpdateMode};
|
||||
@ -155,7 +155,7 @@ impl Command for UCp {
|
||||
target_os = "macos"
|
||||
)))]
|
||||
let reflink_mode = uu_cp::ReflinkMode::Never;
|
||||
let mut paths: Vec<Spanned<NuPath>> = call.rest(engine_state, stack, 0)?;
|
||||
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
if paths.is_empty() {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Missing file operand".into(),
|
||||
|
@ -1,11 +1,10 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
use nu_engine::current_dir;
|
||||
use nu_engine::CallExt;
|
||||
use nu_path::{expand_path_with, expand_to_real_path};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, NuPath, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
|
||||
};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use uu_mv::{BackupMode, UpdateMode};
|
||||
@ -83,7 +82,7 @@ impl Command for UMv {
|
||||
};
|
||||
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut paths: Vec<Spanned<NuPath>> = call.rest(engine_state, stack, 0)?;
|
||||
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
if paths.is_empty() {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Missing file operand".into(),
|
||||
|
@ -1,4 +1,12 @@
|
||||
use dialoguer::Input;
|
||||
use nu_engine::eval_expression;
|
||||
use nu_protocol::ast::Expr;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack},
|
||||
ShellError, Spanned, Value,
|
||||
};
|
||||
use nu_protocol::{FromValue, NuGlob, Type};
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@ -200,3 +208,74 @@ pub mod users {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get rest arguments from given `call`, starts with `starting_pos`.
|
||||
///
|
||||
/// It's similar to `call.rest`, except that it always returns NuGlob. And if input argument has
|
||||
/// Type::Glob, the NuGlob is unquoted, which means it's required to expand.
|
||||
pub fn get_rest_for_glob_pattern(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
starting_pos: usize,
|
||||
) -> Result<Vec<Spanned<NuGlob>>, ShellError> {
|
||||
let mut output = vec![];
|
||||
|
||||
for result in call.rest_iter_flattened(starting_pos, |expr| {
|
||||
let result = eval_expression(engine_state, stack, expr);
|
||||
match result {
|
||||
Err(e) => Err(e),
|
||||
Ok(result) => {
|
||||
let span = result.span();
|
||||
// convert from string to quoted string if expr is a variable
|
||||
// or string interpolation
|
||||
match result {
|
||||
Value::String { val, .. }
|
||||
if matches!(
|
||||
&expr.expr,
|
||||
Expr::FullCellPath(_) | Expr::StringInterpolation(_)
|
||||
) =>
|
||||
{
|
||||
// should not expand if given input type is not glob.
|
||||
Ok(Value::glob(val, expr.ty != Type::Glob, span))
|
||||
}
|
||||
other => Ok(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
})? {
|
||||
output.push(FromValue::from_value(result)?);
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Get optional arguments from given `call` with position `pos`.
|
||||
///
|
||||
/// It's similar to `call.opt`, except that it always returns NuGlob.
|
||||
pub fn opt_for_glob_pattern(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
pos: usize,
|
||||
) -> Result<Option<Spanned<NuGlob>>, ShellError> {
|
||||
if let Some(expr) = call.positional_nth(pos) {
|
||||
let result = eval_expression(engine_state, stack, expr)?;
|
||||
let result_span = result.span();
|
||||
let result = match result {
|
||||
Value::String { val, .. }
|
||||
if matches!(
|
||||
&expr.expr,
|
||||
Expr::FullCellPath(_) | Expr::StringInterpolation(_)
|
||||
) =>
|
||||
{
|
||||
// should quote if given input type is not glob.
|
||||
Value::glob(val, expr.ty != Type::Glob, result_span)
|
||||
}
|
||||
other => other,
|
||||
};
|
||||
FromValue::from_value(result).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
@ -534,7 +534,7 @@ fn value_should_be_printed(
|
||||
| Value::Nothing { .. }
|
||||
| Value::Error { .. } => term_equals_value(term, &lower_value, span),
|
||||
Value::String { .. }
|
||||
| Value::QuotedString { .. }
|
||||
| Value::Glob { .. }
|
||||
| Value::List { .. }
|
||||
| Value::CellPath { .. }
|
||||
| Value::CustomValue { .. } => term_contains_value(term, &lower_value, span),
|
||||
|
@ -115,7 +115,7 @@ pub fn value_to_json_value(v: &Value) -> Result<nu_json::Value, ShellError> {
|
||||
Value::Int { val, .. } => nu_json::Value::I64(*val),
|
||||
Value::Nothing { .. } => nu_json::Value::Null,
|
||||
Value::String { val, .. } => nu_json::Value::String(val.to_string()),
|
||||
Value::QuotedString { val, .. } => nu_json::Value::String(val.to_string()),
|
||||
Value::Glob { val, .. } => nu_json::Value::String(val.to_string()),
|
||||
Value::CellPath { val, .. } => nu_json::Value::Array(
|
||||
val.members
|
||||
.iter()
|
||||
|
@ -279,7 +279,7 @@ pub fn value_to_string(
|
||||
// All strings outside data structures are quoted because they are in 'command position'
|
||||
// (could be mistaken for commands by the Nu parser)
|
||||
Value::String { val, .. } => Ok(escape_quote_string(val)),
|
||||
Value::QuotedString { val, .. } => Ok(escape_quote_string(val)),
|
||||
Value::Glob { val, .. } => Ok(escape_quote_string(val)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
|
||||
)
|
||||
}
|
||||
Value::String { val, .. } => val,
|
||||
Value::QuotedString { val, .. } => val,
|
||||
Value::Glob { val, .. } => val,
|
||||
Value::List { vals: val, .. } => val
|
||||
.into_iter()
|
||||
.map(|x| local_into_string(x, ", ", config))
|
||||
|
@ -57,9 +57,7 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result<toml::Value, ShellErr
|
||||
}
|
||||
Value::Range { .. } => toml::Value::String("<Range>".to_string()),
|
||||
Value::Float { val, .. } => toml::Value::Float(*val),
|
||||
Value::String { val, .. } | Value::QuotedString { val, .. } => {
|
||||
toml::Value::String(val.clone())
|
||||
}
|
||||
Value::String { val, .. } | Value::Glob { val, .. } => toml::Value::String(val.clone()),
|
||||
Value::Record { val, .. } => {
|
||||
let mut m = toml::map::Map::new();
|
||||
for (k, v) in val {
|
||||
|
@ -52,7 +52,7 @@ pub fn value_to_yaml_value(v: &Value) -> Result<serde_yaml::Value, ShellError> {
|
||||
Value::Date { val, .. } => serde_yaml::Value::String(val.to_string()),
|
||||
Value::Range { .. } => serde_yaml::Value::Null,
|
||||
Value::Float { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)),
|
||||
Value::String { val, .. } | Value::QuotedString { val, .. } => {
|
||||
Value::String { val, .. } | Value::Glob { val, .. } => {
|
||||
serde_yaml::Value::String(val.clone())
|
||||
}
|
||||
Value::Record { val, .. } => {
|
||||
|
@ -2,7 +2,7 @@ use nu_cmd_base::hook::eval_hook;
|
||||
use nu_engine::env_to_strings;
|
||||
use nu_engine::eval_expression;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::NuPath;
|
||||
use nu_protocol::NuGlob;
|
||||
use nu_protocol::{
|
||||
ast::{Call, Expr},
|
||||
did_you_mean,
|
||||
@ -730,9 +730,9 @@ fn trim_expand_and_apply_arg(
|
||||
}
|
||||
let cwd = PathBuf::from(cwd);
|
||||
if arg.item.contains('*') && run_glob_expansion {
|
||||
// we need to run glob expansion, so it's unquoted.
|
||||
// we need to run glob expansion, so it's NeedExpand.
|
||||
let path = Spanned {
|
||||
item: NuPath::UnQuoted(arg.item.clone()),
|
||||
item: NuGlob::Expand(arg.item.clone()),
|
||||
span: arg.span,
|
||||
};
|
||||
if let Ok((prefix, matches)) = nu_engine::glob_from(&path, &cwd, arg.span, None) {
|
||||
|
@ -62,6 +62,15 @@ fn du_files_with_glob_metachars(#[case] src_name: &str) {
|
||||
);
|
||||
|
||||
assert!(actual.err.is_empty());
|
||||
|
||||
// also test for variables.
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"let f = '{}'; du -d 1 $f",
|
||||
src.display(),
|
||||
);
|
||||
|
||||
assert!(actual.err.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -493,7 +493,7 @@ fn lists_with_directory_flag() {
|
||||
r#"
|
||||
cd dir_empty;
|
||||
['.' '././.' '..' '../dir_files' '../dir_files/*']
|
||||
| each { |it| ls --directory $it }
|
||||
| each { |it| ls --directory ($it | into glob) }
|
||||
| flatten
|
||||
| get name
|
||||
| to text
|
||||
|
@ -584,6 +584,32 @@ fn mv_files_with_glob_metachars(#[case] src_name: &str) {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("a]c")]
|
||||
#[case("a[c")]
|
||||
#[case("a[bc]d")]
|
||||
#[case("a][c")]
|
||||
fn mv_files_with_glob_metachars_when_input_are_variables(#[case] src_name: &str) {
|
||||
Playground::setup("umv_test_18", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
src_name,
|
||||
"What is the sound of one hand clapping?",
|
||||
)]);
|
||||
|
||||
let src = dirs.test().join(src_name);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"let f = '{}'; umv $f {}",
|
||||
src.display(),
|
||||
"hello_world_dest"
|
||||
);
|
||||
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(dirs.test().join("hello_world_dest").exists());
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[rstest]
|
||||
#[case("a]?c")]
|
||||
@ -591,6 +617,7 @@ fn mv_files_with_glob_metachars(#[case] src_name: &str) {
|
||||
// windows doesn't allow filename with `*`.
|
||||
fn mv_files_with_glob_metachars_nw(#[case] src_name: &str) {
|
||||
mv_files_with_glob_metachars(src_name);
|
||||
mv_files_with_glob_metachars_when_input_are_variables(src_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -357,6 +357,15 @@ fn open_files_with_glob_metachars(#[case] src_name: &str) {
|
||||
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(actual.out.contains("hello"));
|
||||
|
||||
// also test for variables.
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"let f = '{}'; open $f",
|
||||
src.display(),
|
||||
);
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(actual.out.contains("hello"));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
use nu_test_support::fs::{files_exist_at, Stub::EmptyFile};
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
use rstest::rstest;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
@ -481,6 +482,48 @@ fn rm_files_inside_glob_metachars_dir() {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("a]c")]
|
||||
#[case("a[c")]
|
||||
#[case("a[bc]d")]
|
||||
#[case("a][c")]
|
||||
fn rm_files_with_glob_metachars(#[case] src_name: &str) {
|
||||
Playground::setup("rm_files_with_glob_metachars", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![EmptyFile(src_name)]);
|
||||
|
||||
let src = dirs.test().join(src_name);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"rm '{}'",
|
||||
src.display(),
|
||||
);
|
||||
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(!src.exists());
|
||||
|
||||
// test with variables
|
||||
sandbox.with_files(vec![EmptyFile(src_name)]);
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"let f = '{}'; rm $f",
|
||||
src.display(),
|
||||
);
|
||||
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(!src.exists());
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[rstest]
|
||||
#[case("a]?c")]
|
||||
#[case("a*.?c")]
|
||||
// windows doesn't allow filename with `*`.
|
||||
fn rm_files_with_glob_metachars_nw(#[case] src_name: &str) {
|
||||
rm_files_with_glob_metachars(src_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_rm_suppress_error() {
|
||||
Playground::setup("force_rm_suppress_error", |dirs, sandbox| {
|
||||
|
@ -1019,6 +1019,40 @@ fn copies_files_with_glob_metachars(#[case] src_name: &str) {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("a]c")]
|
||||
#[case("a[c")]
|
||||
#[case("a[bc]d")]
|
||||
#[case("a][c")]
|
||||
fn copies_files_with_glob_metachars_when_input_are_variables(#[case] src_name: &str) {
|
||||
Playground::setup("ucp_test_35", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
src_name,
|
||||
"What is the sound of one hand clapping?",
|
||||
)]);
|
||||
|
||||
let src = dirs.test().join(src_name);
|
||||
|
||||
// -- open command doesn't like file name
|
||||
//// Get the hash of the file content to check integrity after copy.
|
||||
//let src_hash = get_file_hash(src.display());
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"let f = '{}'; cp $f {}",
|
||||
src.display(),
|
||||
TEST_HELLO_WORLD_DEST
|
||||
);
|
||||
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(dirs.test().join(TEST_HELLO_WORLD_DEST).exists());
|
||||
|
||||
//// Get the hash of the copied file content to check against first_hash.
|
||||
//let after_cp_hash = get_file_hash(dirs.test().join(TEST_HELLO_WORLD_DEST).display());
|
||||
//assert_eq!(src_hash, after_cp_hash);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[rstest]
|
||||
#[case(r#"'a]?c'"#)]
|
||||
@ -1026,6 +1060,7 @@ fn copies_files_with_glob_metachars(#[case] src_name: &str) {
|
||||
// windows doesn't allow filename with `*`.
|
||||
fn copies_files_with_glob_metachars_nw(#[case] src_name: &str) {
|
||||
copies_files_with_glob_metachars(src_name);
|
||||
copies_files_with_glob_metachars_when_input_are_variables(src_name);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
|
@ -5,7 +5,7 @@ use std::{
|
||||
|
||||
use nu_glob::MatchOptions;
|
||||
use nu_path::{canonicalize_with, expand_path_with};
|
||||
use nu_protocol::{NuPath, ShellError, Span, Spanned};
|
||||
use nu_protocol::{NuGlob, ShellError, Span, Spanned};
|
||||
|
||||
const GLOB_CHARS: &[char] = &['*', '?', '['];
|
||||
|
||||
@ -18,7 +18,7 @@ const GLOB_CHARS: &[char] = &['*', '?', '['];
|
||||
/// The second of the two values is an iterator over the matching filepaths.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn glob_from(
|
||||
pattern: &Spanned<NuPath>,
|
||||
pattern: &Spanned<NuGlob>,
|
||||
cwd: &Path,
|
||||
span: Span,
|
||||
options: Option<MatchOptions>,
|
||||
@ -29,7 +29,7 @@ pub fn glob_from(
|
||||
),
|
||||
ShellError,
|
||||
> {
|
||||
let no_glob_for_pattern = matches!(pattern.item, NuPath::Quoted(_));
|
||||
let no_glob_for_pattern = matches!(pattern.item, NuGlob::DoNotExpand(_));
|
||||
let (prefix, pattern) = if pattern.item.as_ref().contains(GLOB_CHARS) {
|
||||
// Pattern contains glob, split it
|
||||
let mut p = PathBuf::new();
|
||||
|
@ -63,6 +63,8 @@ pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool {
|
||||
(Type::Record(lhs), Type::Record(rhs)) | (Type::Table(lhs), Type::Table(rhs)) => {
|
||||
is_compatible(lhs, rhs)
|
||||
}
|
||||
(Type::Glob, Type::String) => true,
|
||||
(Type::String, Type::Glob) => true,
|
||||
(lhs, rhs) => lhs == rhs,
|
||||
}
|
||||
}
|
||||
|
@ -287,11 +287,7 @@ pub trait Eval {
|
||||
Expr::GlobPattern(pattern, quoted) => {
|
||||
// GlobPattern is similar to Filepath
|
||||
// But we don't want to expand path during eval time, it's required for `nu_engine::glob_from` to run correctly
|
||||
if *quoted {
|
||||
Ok(Value::quoted_string(pattern, expr.span))
|
||||
} else {
|
||||
Ok(Value::string(pattern, expr.span))
|
||||
}
|
||||
Ok(Value::glob(pattern, *quoted, expr.span))
|
||||
}
|
||||
Expr::MatchBlock(_) // match blocks are handled by `match`
|
||||
| Expr::VarDecl(_)
|
||||
|
@ -150,7 +150,7 @@ impl SyntaxShape {
|
||||
SyntaxShape::Float => Type::Float,
|
||||
SyntaxShape::Filesize => Type::Filesize,
|
||||
SyntaxShape::FullCellPath => Type::Any,
|
||||
SyntaxShape::GlobPattern => Type::String,
|
||||
SyntaxShape::GlobPattern => Type::Glob,
|
||||
SyntaxShape::Error => Type::Error,
|
||||
SyntaxShape::ImportPattern => Type::Any,
|
||||
SyntaxShape::Int => Type::Int,
|
||||
|
@ -31,6 +31,7 @@ pub enum Type {
|
||||
Record(Vec<(String, Type)>),
|
||||
Signature,
|
||||
String,
|
||||
Glob,
|
||||
Table(Vec<(String, Type)>),
|
||||
}
|
||||
|
||||
@ -63,6 +64,8 @@ impl Type {
|
||||
is_subtype_collection(this, that)
|
||||
}
|
||||
(Type::Table(_), Type::List(_)) => true,
|
||||
(Type::Glob, Type::String) => true,
|
||||
(Type::String, Type::Glob) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -110,6 +113,7 @@ impl Type {
|
||||
Type::Binary => SyntaxShape::Binary,
|
||||
Type::Custom(_) => SyntaxShape::Any,
|
||||
Type::Signature => SyntaxShape::Signature,
|
||||
Type::Glob => SyntaxShape::GlobPattern,
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,6 +143,7 @@ impl Type {
|
||||
Type::Binary => String::from("binary"),
|
||||
Type::Custom(_) => String::from("custom"),
|
||||
Type::Signature => String::from("signature"),
|
||||
Type::Glob => String::from("glob"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -196,6 +201,7 @@ impl Display for Type {
|
||||
Type::Binary => write!(f, "binary"),
|
||||
Type::Custom(custom) => write!(f, "{custom}"),
|
||||
Type::Signature => write!(f, "signature"),
|
||||
Type::Glob => write!(f, "glob"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::NuPath;
|
||||
use super::NuGlob;
|
||||
use crate::ast::{CellPath, PathMember};
|
||||
use crate::engine::{Block, Closure};
|
||||
use crate::{Range, Record, ShellError, Spanned, Value};
|
||||
@ -204,13 +204,23 @@ impl FromValue for Spanned<String> {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for NuPath {
|
||||
impl FromValue for NuGlob {
|
||||
fn from_value(v: Value) -> Result<Self, ShellError> {
|
||||
// FIXME: we may want to fail a little nicer here
|
||||
match v {
|
||||
Value::CellPath { val, .. } => Ok(NuPath::UnQuoted(val.to_string())),
|
||||
Value::String { val, .. } => Ok(NuPath::UnQuoted(val)),
|
||||
Value::QuotedString { val, .. } => Ok(NuPath::Quoted(val)),
|
||||
Value::CellPath { val, .. } => Ok(NuGlob::Expand(val.to_string())),
|
||||
Value::String { val, .. } => Ok(NuGlob::DoNotExpand(val)),
|
||||
Value::Glob {
|
||||
val,
|
||||
no_expand: quoted,
|
||||
..
|
||||
} => {
|
||||
if quoted {
|
||||
Ok(NuGlob::DoNotExpand(val))
|
||||
} else {
|
||||
Ok(NuGlob::Expand(val))
|
||||
}
|
||||
}
|
||||
v => Err(ShellError::CantConvert {
|
||||
to_type: "string".into(),
|
||||
from_type: v.get_type().to_string(),
|
||||
@ -221,14 +231,24 @@ impl FromValue for NuPath {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromValue for Spanned<NuPath> {
|
||||
impl FromValue for Spanned<NuGlob> {
|
||||
fn from_value(v: Value) -> Result<Self, ShellError> {
|
||||
let span = v.span();
|
||||
Ok(Spanned {
|
||||
item: match v {
|
||||
Value::CellPath { val, .. } => NuPath::UnQuoted(val.to_string()),
|
||||
Value::String { val, .. } => NuPath::UnQuoted(val),
|
||||
Value::QuotedString { val, .. } => NuPath::Quoted(val),
|
||||
Value::CellPath { val, .. } => NuGlob::Expand(val.to_string()),
|
||||
Value::String { val, .. } => NuGlob::DoNotExpand(val),
|
||||
Value::Glob {
|
||||
val,
|
||||
no_expand: quoted,
|
||||
..
|
||||
} => {
|
||||
if quoted {
|
||||
NuGlob::DoNotExpand(val)
|
||||
} else {
|
||||
NuGlob::Expand(val)
|
||||
}
|
||||
}
|
||||
v => {
|
||||
return Err(ShellError::CantConvert {
|
||||
to_type: "string".into(),
|
||||
|
37
crates/nu-protocol/src/value/glob.rs
Normal file
37
crates/nu-protocol/src/value/glob.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use serde::Deserialize;
|
||||
use std::fmt::Display;
|
||||
|
||||
// Introduce this `NuGlob` enum rather than using `Value::Glob` directlry
|
||||
// So we can handle glob easily without considering too much variant of `Value` enum.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub enum NuGlob {
|
||||
/// Don't expand the glob pattern, normally it includes a quoted string(except backtick)
|
||||
/// And a variable that doesn't annotated with `glob` type
|
||||
DoNotExpand(String),
|
||||
/// A glob pattern that is required to expand, it includes bare word
|
||||
/// And a variable which is annotated with `glob` type
|
||||
Expand(String),
|
||||
}
|
||||
|
||||
impl NuGlob {
|
||||
pub fn strip_ansi_string_unlikely(self) -> Self {
|
||||
match self {
|
||||
NuGlob::DoNotExpand(s) => NuGlob::DoNotExpand(nu_utils::strip_ansi_string_unlikely(s)),
|
||||
NuGlob::Expand(s) => NuGlob::Expand(nu_utils::strip_ansi_string_unlikely(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for NuGlob {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
NuGlob::DoNotExpand(s) | NuGlob::Expand(s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NuGlob {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_ref())
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
mod custom_value;
|
||||
mod from;
|
||||
mod from_value;
|
||||
mod glob;
|
||||
mod lazy_record;
|
||||
mod path;
|
||||
mod range;
|
||||
mod stream;
|
||||
mod unit;
|
||||
@ -19,13 +19,13 @@ use chrono_humanize::HumanTime;
|
||||
pub use custom_value::CustomValue;
|
||||
use fancy_regex::Regex;
|
||||
pub use from_value::FromValue;
|
||||
pub use glob::*;
|
||||
pub use lazy_record::LazyRecord;
|
||||
use nu_utils::locale::LOCALE_OVERRIDE_ENV_VAR;
|
||||
use nu_utils::{
|
||||
contains_emoji, get_system_locale, locale::get_system_locale_string, IgnoreCaseExt,
|
||||
};
|
||||
use num_format::ToFormattedString;
|
||||
pub use path::*;
|
||||
pub use range::*;
|
||||
pub use record::Record;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -93,8 +93,9 @@ pub enum Value {
|
||||
// please use .span() instead of matching this span value
|
||||
internal_span: Span,
|
||||
},
|
||||
QuotedString {
|
||||
Glob {
|
||||
val: String,
|
||||
no_expand: bool,
|
||||
// note: spans are being refactored out of Value
|
||||
// please use .span() instead of matching this span value
|
||||
internal_span: Span,
|
||||
@ -188,8 +189,13 @@ impl Clone for Value {
|
||||
val: val.clone(),
|
||||
internal_span: *internal_span,
|
||||
},
|
||||
Value::QuotedString { val, internal_span } => Value::QuotedString {
|
||||
Value::Glob {
|
||||
val,
|
||||
no_expand: quoted,
|
||||
internal_span,
|
||||
} => Value::Glob {
|
||||
val: val.clone(),
|
||||
no_expand: *quoted,
|
||||
internal_span: *internal_span,
|
||||
},
|
||||
Value::Record { val, internal_span } => Value::Record {
|
||||
@ -721,7 +727,7 @@ impl Value {
|
||||
| Value::Date { internal_span, .. }
|
||||
| Value::Range { internal_span, .. }
|
||||
| Value::String { internal_span, .. }
|
||||
| Value::QuotedString { internal_span, .. }
|
||||
| Value::Glob { internal_span, .. }
|
||||
| Value::Record { internal_span, .. }
|
||||
| Value::List { internal_span, .. }
|
||||
| Value::Block { internal_span, .. }
|
||||
@ -746,7 +752,7 @@ impl Value {
|
||||
| Value::Date { internal_span, .. }
|
||||
| Value::Range { internal_span, .. }
|
||||
| Value::String { internal_span, .. }
|
||||
| Value::QuotedString { internal_span, .. }
|
||||
| Value::Glob { internal_span, .. }
|
||||
| Value::Record { internal_span, .. }
|
||||
| Value::LazyRecord { internal_span, .. }
|
||||
| Value::List { internal_span, .. }
|
||||
@ -773,7 +779,7 @@ impl Value {
|
||||
Value::Date { .. } => Type::Date,
|
||||
Value::Range { .. } => Type::Range,
|
||||
Value::String { .. } => Type::String,
|
||||
Value::QuotedString { .. } => Type::String,
|
||||
Value::Glob { .. } => Type::Glob,
|
||||
Value::Record { val, .. } => {
|
||||
Type::Record(val.iter().map(|(x, y)| (x.clone(), y.get_type())).collect())
|
||||
}
|
||||
@ -903,7 +909,7 @@ impl Value {
|
||||
)
|
||||
}
|
||||
Value::String { val, .. } => val.clone(),
|
||||
Value::QuotedString { val, .. } => val.clone(),
|
||||
Value::Glob { val, .. } => val.clone(),
|
||||
Value::List { vals: val, .. } => format!(
|
||||
"[{}]",
|
||||
val.iter()
|
||||
@ -1891,9 +1897,10 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn quoted_string(val: impl Into<String>, span: Span) -> Value {
|
||||
Value::QuotedString {
|
||||
pub fn glob(val: impl Into<String>, no_expand: bool, span: Span) -> Value {
|
||||
Value::Glob {
|
||||
val: val.into(),
|
||||
no_expand,
|
||||
internal_span: span,
|
||||
}
|
||||
}
|
||||
@ -2144,7 +2151,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Less),
|
||||
Value::Range { .. } => Some(Ordering::Less),
|
||||
Value::String { .. } => Some(Ordering::Less),
|
||||
Value::QuotedString { .. } => Some(Ordering::Less),
|
||||
Value::Glob { .. } => Some(Ordering::Less),
|
||||
Value::Record { .. } => Some(Ordering::Less),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Less),
|
||||
Value::List { .. } => Some(Ordering::Less),
|
||||
@ -2165,7 +2172,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Less),
|
||||
Value::Range { .. } => Some(Ordering::Less),
|
||||
Value::String { .. } => Some(Ordering::Less),
|
||||
Value::QuotedString { .. } => Some(Ordering::Less),
|
||||
Value::Glob { .. } => Some(Ordering::Less),
|
||||
Value::Record { .. } => Some(Ordering::Less),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Less),
|
||||
Value::List { .. } => Some(Ordering::Less),
|
||||
@ -2186,7 +2193,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Less),
|
||||
Value::Range { .. } => Some(Ordering::Less),
|
||||
Value::String { .. } => Some(Ordering::Less),
|
||||
Value::QuotedString { .. } => Some(Ordering::Less),
|
||||
Value::Glob { .. } => Some(Ordering::Less),
|
||||
Value::Record { .. } => Some(Ordering::Less),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Less),
|
||||
Value::List { .. } => Some(Ordering::Less),
|
||||
@ -2207,7 +2214,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Less),
|
||||
Value::Range { .. } => Some(Ordering::Less),
|
||||
Value::String { .. } => Some(Ordering::Less),
|
||||
Value::QuotedString { .. } => Some(Ordering::Less),
|
||||
Value::Glob { .. } => Some(Ordering::Less),
|
||||
Value::Record { .. } => Some(Ordering::Less),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Less),
|
||||
Value::List { .. } => Some(Ordering::Less),
|
||||
@ -2228,7 +2235,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Less),
|
||||
Value::Range { .. } => Some(Ordering::Less),
|
||||
Value::String { .. } => Some(Ordering::Less),
|
||||
Value::QuotedString { .. } => Some(Ordering::Less),
|
||||
Value::Glob { .. } => Some(Ordering::Less),
|
||||
Value::Record { .. } => Some(Ordering::Less),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Less),
|
||||
Value::List { .. } => Some(Ordering::Less),
|
||||
@ -2249,7 +2256,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { val: rhs, .. } => lhs.partial_cmp(rhs),
|
||||
Value::Range { .. } => Some(Ordering::Less),
|
||||
Value::String { .. } => Some(Ordering::Less),
|
||||
Value::QuotedString { .. } => Some(Ordering::Less),
|
||||
Value::Glob { .. } => Some(Ordering::Less),
|
||||
Value::Record { .. } => Some(Ordering::Less),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Less),
|
||||
Value::List { .. } => Some(Ordering::Less),
|
||||
@ -2270,7 +2277,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { val: rhs, .. } => lhs.partial_cmp(rhs),
|
||||
Value::String { .. } => Some(Ordering::Less),
|
||||
Value::QuotedString { .. } => Some(Ordering::Less),
|
||||
Value::Glob { .. } => Some(Ordering::Less),
|
||||
Value::Record { .. } => Some(Ordering::Less),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Less),
|
||||
Value::List { .. } => Some(Ordering::Less),
|
||||
@ -2291,7 +2298,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { val: rhs, .. } => lhs.partial_cmp(rhs),
|
||||
Value::QuotedString { val: rhs, .. } => lhs.partial_cmp(rhs),
|
||||
Value::Glob { val: rhs, .. } => lhs.partial_cmp(rhs),
|
||||
Value::Record { .. } => Some(Ordering::Less),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Less),
|
||||
Value::List { .. } => Some(Ordering::Less),
|
||||
@ -2303,7 +2310,7 @@ impl PartialOrd for Value {
|
||||
Value::CellPath { .. } => Some(Ordering::Less),
|
||||
Value::CustomValue { .. } => Some(Ordering::Less),
|
||||
},
|
||||
(Value::QuotedString { val: lhs, .. }, rhs) => match rhs {
|
||||
(Value::Glob { val: lhs, .. }, rhs) => match rhs {
|
||||
Value::Bool { .. } => Some(Ordering::Greater),
|
||||
Value::Int { .. } => Some(Ordering::Greater),
|
||||
Value::Float { .. } => Some(Ordering::Greater),
|
||||
@ -2312,7 +2319,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { val: rhs, .. } => lhs.partial_cmp(rhs),
|
||||
Value::QuotedString { val: rhs, .. } => lhs.partial_cmp(rhs),
|
||||
Value::Glob { val: rhs, .. } => lhs.partial_cmp(rhs),
|
||||
Value::Record { .. } => Some(Ordering::Less),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Less),
|
||||
Value::List { .. } => Some(Ordering::Less),
|
||||
@ -2333,7 +2340,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { .. } => Some(Ordering::Greater),
|
||||
Value::QuotedString { .. } => Some(Ordering::Greater),
|
||||
Value::Glob { .. } => Some(Ordering::Greater),
|
||||
Value::Record { val: rhs, .. } => {
|
||||
// reorder cols and vals to make more logically compare.
|
||||
// more general, if two record have same col and values,
|
||||
@ -2373,7 +2380,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { .. } => Some(Ordering::Greater),
|
||||
Value::QuotedString { .. } => Some(Ordering::Greater),
|
||||
Value::Glob { .. } => Some(Ordering::Greater),
|
||||
Value::Record { .. } => Some(Ordering::Greater),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Greater),
|
||||
Value::List { vals: rhs, .. } => lhs.partial_cmp(rhs),
|
||||
@ -2394,7 +2401,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { .. } => Some(Ordering::Greater),
|
||||
Value::QuotedString { .. } => Some(Ordering::Greater),
|
||||
Value::Glob { .. } => Some(Ordering::Greater),
|
||||
Value::Record { .. } => Some(Ordering::Greater),
|
||||
Value::List { .. } => Some(Ordering::Greater),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Greater),
|
||||
@ -2415,7 +2422,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { .. } => Some(Ordering::Greater),
|
||||
Value::QuotedString { .. } => Some(Ordering::Greater),
|
||||
Value::Glob { .. } => Some(Ordering::Greater),
|
||||
Value::Record { .. } => Some(Ordering::Greater),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Greater),
|
||||
Value::List { .. } => Some(Ordering::Greater),
|
||||
@ -2436,7 +2443,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { .. } => Some(Ordering::Greater),
|
||||
Value::QuotedString { .. } => Some(Ordering::Greater),
|
||||
Value::Glob { .. } => Some(Ordering::Greater),
|
||||
Value::Record { .. } => Some(Ordering::Greater),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Greater),
|
||||
Value::List { .. } => Some(Ordering::Greater),
|
||||
@ -2457,7 +2464,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { .. } => Some(Ordering::Greater),
|
||||
Value::QuotedString { .. } => Some(Ordering::Greater),
|
||||
Value::Glob { .. } => Some(Ordering::Greater),
|
||||
Value::Record { .. } => Some(Ordering::Greater),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Greater),
|
||||
Value::List { .. } => Some(Ordering::Greater),
|
||||
@ -2478,7 +2485,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { .. } => Some(Ordering::Greater),
|
||||
Value::QuotedString { .. } => Some(Ordering::Greater),
|
||||
Value::Glob { .. } => Some(Ordering::Greater),
|
||||
Value::Record { .. } => Some(Ordering::Greater),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Greater),
|
||||
Value::List { .. } => Some(Ordering::Greater),
|
||||
@ -2499,7 +2506,7 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { .. } => Some(Ordering::Greater),
|
||||
Value::QuotedString { .. } => Some(Ordering::Greater),
|
||||
Value::Glob { .. } => Some(Ordering::Greater),
|
||||
Value::Record { .. } => Some(Ordering::Greater),
|
||||
Value::LazyRecord { .. } => Some(Ordering::Greater),
|
||||
Value::List { .. } => Some(Ordering::Greater),
|
||||
|
@ -1,36 +1,32 @@
|
||||
use serde::Deserialize;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// A simple wrapper to String.
|
||||
///
|
||||
/// But it tracks if the string is originally quoted.
|
||||
/// So commands can make decision on path auto-expanding behavior.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub enum NuPath {
|
||||
pub enum NuGlob {
|
||||
/// A quoted path(except backtick), in this case, nushell shouldn't auto-expand path.
|
||||
Quoted(String),
|
||||
NoExpand(String),
|
||||
/// An unquoted path, in this case, nushell should auto-expand path.
|
||||
UnQuoted(String),
|
||||
NeedExpand(String),
|
||||
}
|
||||
|
||||
impl NuPath {
|
||||
impl NuGlob {
|
||||
pub fn strip_ansi_string_unlikely(self) -> Self {
|
||||
match self {
|
||||
NuPath::Quoted(s) => NuPath::Quoted(nu_utils::strip_ansi_string_unlikely(s)),
|
||||
NuPath::UnQuoted(s) => NuPath::UnQuoted(nu_utils::strip_ansi_string_unlikely(s)),
|
||||
NuGlob::NoExpand(s) => NuGlob::NoExpand(nu_utils::strip_ansi_string_unlikely(s)),
|
||||
NuGlob::NeedExpand(s) => NuGlob::NeedExpand(nu_utils::strip_ansi_string_unlikely(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for NuPath {
|
||||
impl AsRef<str> for NuGlob {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
NuPath::Quoted(s) | NuPath::UnQuoted(s) => s,
|
||||
NuGlob::NoExpand(s) | NuGlob::NeedExpand(s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NuPath {
|
||||
impl Display for NuGlob {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_ref())
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ export def run-tests [
|
||||
}
|
||||
|
||||
let modules = (
|
||||
ls ($path | path join $module_search_pattern)
|
||||
ls ($path | path join $module_search_pattern | into glob)
|
||||
| par-each --threads $threads {|row|
|
||||
{
|
||||
file: $row.name
|
||||
|
Loading…
Reference in New Issue
Block a user