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:
Wind 2024-02-23 09:17:09 +08:00 committed by GitHub
parent a2a1c1656f
commit f7d647ac3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 534 additions and 109 deletions

View File

@ -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),

View File

@ -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())
}

View File

@ -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, .. } => {

View File

@ -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),

View 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 {})
}
}

View File

@ -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;

View File

@ -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)

View File

@ -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(),

View File

@ -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()

View File

@ -303,6 +303,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
IntoInt,
IntoRecord,
IntoString,
IntoGlob,
IntoValue,
};

View File

@ -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(),
},
&current_dir,

View File

@ -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,
};

View File

@ -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")?;

View File

@ -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,
});
}

View File

@ -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,
};

View File

@ -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(),

View File

@ -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(),

View File

@ -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)
}
}

View File

@ -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),

View File

@ -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()

View File

@ -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)),
}
}

View File

@ -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))

View File

@ -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 {

View File

@ -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, .. } => {

View File

@ -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) {

View File

@ -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());
});
}

View File

@ -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

View File

@ -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]

View File

@ -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"));
});
}

View File

@ -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| {

View File

@ -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))]

View File

@ -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();

View File

@ -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,
}
}

View File

@ -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(_)

View File

@ -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,

View File

@ -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"),
}
}
}

View File

@ -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(),

View 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())
}
}

View File

@ -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),

View File

@ -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())
}

View File

@ -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