do not attempt to glob expand if the file path is wrapped in quotes (#11569)

# Description
Fixes: #11455

### For arguments which is annotated with `:path/:directory/:glob`
To fix the issue, we need to have a way to know if a path is originally
quoted during runtime. So the information needed to be added at several
levels:
* parse time (from user input to expression)
We need to add quoted information into `Expr::Filepath`,
`Expr::Directory`, `Expr::GlobPattern`
* eval time
When convert from `Expr::Filepath`, `Expr::Directory`,
`Expr::GlobPattern` to `Value::String` during runtime, we won't auto
expanded the path if it's quoted

### For `ls`
It's really special, because it accepts a `String` as a pattern, and it
generates `glob` expression inside the command itself.

So the idea behind the change is introducing a special SyntaxShape to
ls: `SyntaxShape::LsGlobPattern`. So we can track if the pattern is
originally quoted easier, and we don't auto expand the path either.

Then when constructing a glob pattern inside ls, we check if input
pattern is quoted, if so: we escape the input pattern, so we can run `ls
a[123]b`, because it's already escaped.
Finally, to accomplish the checking process, we also need to introduce a
new value type called `Value::QuotedString` to differ from
`Value::String`, it's used to generate an enum called `NuPath`, which is
finally used in `ls` function. `ls` learned from `NuPath` to know if
user input is quoted.

# User-Facing Changes
Actually it contains several changes
### For arguments which is annotated with `:path/:directory/:glob`
#### Before
```nushell
> def foo [p: path] { echo $p }; print (foo "~/a"); print (foo '~/a')
/home/windsoilder/a
/home/windsoilder/a
> def foo [p: directory] { echo $p }; print (foo "~/a"); print (foo '~/a')
/home/windsoilder/a
/home/windsoilder/a
> def foo [p: glob] { echo $p }; print (foo "~/a"); print (foo '~/a')
/home/windsoilder/a
/home/windsoilder/a
```
#### After
```nushell
> def foo [p: path] { echo $p }; print (foo "~/a"); print (foo '~/a')
~/a
~/a
> def foo [p: directory] { echo $p }; print (foo "~/a"); print (foo '~/a')
~/a
~/a
> def foo [p: glob] { echo $p }; print (foo "~/a"); print (foo '~/a')
~/a
~/a
```
### For ls command
`touch '[uwu]'`
#### Before
```
❯ ls -D "[uwu]"
Error:   × No matches found for [uwu]
   ╭─[entry #6:1:1]
 1 │ ls -D "[uwu]"
   ·       ───┬───
   ·          ╰── Pattern, file or folder not found
   ╰────
  help: no matches found
```

#### After
```
❯ ls -D "[uwu]"
╭───┬───────┬──────┬──────┬──────────╮
│ # │ name  │ type │ size │ modified │
├───┼───────┼──────┼──────┼──────────┤
│ 0 │ [uwu] │ file │  0 B │ now      │
╰───┴───────┴──────┴──────┴──────────╯
```

# Tests + Formatting
Done

# After Submitting
NaN
This commit is contained in:
WindSoilder 2024-01-21 23:22:25 +08:00 committed by GitHub
parent 64f34e0287
commit c59d6d31bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 310 additions and 60 deletions

View File

@ -321,9 +321,10 @@ fn find_matching_block_end_in_expr(
Expr::Keyword(..) => None,
Expr::ValueWithUnit(..) => None,
Expr::DateTime(_) => None,
Expr::Filepath(_) => None,
Expr::Directory(_) => None,
Expr::GlobPattern(_) => None,
Expr::Filepath(_, _) => None,
Expr::Directory(_, _) => None,
Expr::GlobPattern(_, _) => None,
Expr::LsGlobPattern(_, _) => None,
Expr::String(_) => None,
Expr::CellPath(_) => None,
Expr::ImportPattern(_) => None,

View File

@ -305,6 +305,7 @@ fn describe_value(
| Value::Date { .. }
| Value::Range { .. }
| Value::String { .. }
| Value::QuotedString { .. }
| Value::Nothing { .. } => Value::record(
record!(
"type" => Value::string(value.get_type().to_string(), head),

View File

@ -228,7 +228,7 @@ impl<'a> std::fmt::Debug for DebuggableValue<'a> {
val.from, val.to, val.incr
),
},
Value::String { val, .. } => {
Value::String { val, .. } | Value::QuotedString { val, .. } => {
write!(f, "{:?}", val)
}
Value::Record { val, .. } => {

View File

@ -122,6 +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::Nothing { .. } => TextStyle::with_style(Left, s),
Value::Binary { .. } => TextStyle::with_style(Left, s),
Value::CellPath { .. } => TextStyle::with_style(Left, s),

View File

@ -251,6 +251,7 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
)
}
Value::String { val, .. } => val.clone(),
Value::QuotedString { val, .. } => val.clone(),
Value::List { vals: val, .. } => format!(
"[{}]",
val.iter()

View File

@ -3,10 +3,11 @@ use crate::DirInfo;
use chrono::{DateTime, Local, LocalResult, TimeZone, Utc};
use nu_engine::env::current_dir;
use nu_engine::CallExt;
use nu_glob::MatchOptions;
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::{
Category, DataSource, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
PipelineMetadata, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
@ -38,8 +39,9 @@ impl Command for Ls {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("ls")
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
// Using a string instead of a glob pattern shape so it won't auto-expand
.optional("pattern", SyntaxShape::String, "The glob pattern to use.")
// LsGlobPattern is similar to string, it won't auto-expand
// and we use it to track if the user input is quoted.
.optional("pattern", SyntaxShape::LsGlobPattern, "The glob pattern to use.")
.switch("all", "Show hidden files", Some('a'))
.switch(
"long",
@ -84,23 +86,32 @@ impl Command for Ls {
let call_span = call.head;
let cwd = current_dir(engine_state, stack)?;
let pattern_arg: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
let pattern_arg: Option<Spanned<NuPath>> = call.opt(engine_state, stack, 0)?;
let pattern_arg = {
if let Some(path) = pattern_arg {
Some(Spanned {
item: nu_utils::strip_ansi_string_unlikely(path.item),
span: path.span,
})
match path.item {
NuPath::Quoted(p) => Some(Spanned {
item: NuPath::Quoted(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)),
span: path.span,
}),
}
} else {
pattern_arg
}
};
let (path, p_tag, absolute_path) = match pattern_arg {
Some(p) => {
let p_tag = p.span;
let mut p = expand_to_real_path(p.item);
// it indicates we need to append an extra '*' after pattern for listing given directory
// Example: 'ls directory' -> 'ls directory/*'
let mut extra_star_under_given_directory = false;
let (path, p_tag, absolute_path, quoted) = match pattern_arg {
Some(pat) => {
let p_tag = pat.span;
let p = expand_to_real_path(pat.item.as_ref());
let expanded = nu_path::expand_path_with(&p, &cwd);
// Avoid checking and pushing "*" to the path when directory (do not show contents) flag is true
@ -131,27 +142,50 @@ impl Command for Ls {
if is_empty_dir(&expanded) {
return Ok(Value::list(vec![], call_span).into_pipeline_data());
}
p.push("*");
extra_star_under_given_directory = true;
}
let absolute_path = p.is_absolute();
(p, p_tag, absolute_path)
(
p,
p_tag,
absolute_path,
matches!(pat.item, NuPath::Quoted(_)),
)
}
None => {
// Avoid pushing "*" to the default path when directory (do not show contents) flag is true
if directory {
(PathBuf::from("."), call_span, false)
(PathBuf::from("."), call_span, false, false)
} else if is_empty_dir(current_dir(engine_state, stack)?) {
return Ok(Value::list(vec![], call_span).into_pipeline_data());
} else {
(PathBuf::from("*"), call_span, false)
(PathBuf::from("*"), call_span, false, false)
}
}
};
let hidden_dir_specified = is_hidden_dir(&path);
// when it's quoted, we need to escape our glob pattern
// so we can do ls for a file or directory like `a[123]b`
let path = if quoted {
let p = path.display().to_string();
let mut glob_escaped = Pattern::escape(&p);
if extra_star_under_given_directory {
glob_escaped.push(std::path::MAIN_SEPARATOR);
glob_escaped.push('*');
}
glob_escaped
} else {
let mut p = path.display().to_string();
if extra_star_under_given_directory {
p.push(std::path::MAIN_SEPARATOR);
p.push('*');
}
p
};
let glob_path = Spanned {
item: path.display().to_string(),
item: path.clone(),
span: p_tag,
};
@ -169,7 +203,7 @@ impl Command for Ls {
let mut paths_peek = paths.peekable();
if paths_peek.peek().is_none() {
return Err(ShellError::GenericError {
error: format!("No matches found for {}", &path.display().to_string()),
error: format!("No matches found for {}", &path),
msg: "Pattern, file or folder not found".into(),
span: Some(p_tag),
help: Some("no matches found".into()),

View File

@ -495,6 +495,7 @@ fn value_should_be_printed(
| Value::Nothing { .. }
| Value::Error { .. } => term_equals_value(term, &lower_value, span),
Value::String { .. }
| Value::QuotedString { .. }
| Value::List { .. }
| Value::CellPath { .. }
| Value::CustomValue { .. } => term_contains_value(term, &lower_value, span),

View File

@ -220,8 +220,8 @@ fn convert_to_value(
msg: "calls not supported in nuon".into(),
span: expr.span,
}),
Expr::Filepath(val) => Ok(Value::string(val, span)),
Expr::Directory(val) => Ok(Value::string(val, span)),
Expr::Filepath(val, _) => Ok(Value::string(val, span)),
Expr::Directory(val, _) => Ok(Value::string(val, span)),
Expr::Float(val) => Ok(Value::float(val, span)),
Expr::FullCellPath(full_cell_path) => {
if !full_cell_path.tail.is_empty() {
@ -242,7 +242,8 @@ fn convert_to_value(
msg: "extra tokens in input file".into(),
span: expr.span,
}),
Expr::GlobPattern(val) => Ok(Value::string(val, span)),
Expr::GlobPattern(val, _) => Ok(Value::string(val, span)),
Expr::LsGlobPattern(val, _) => Ok(Value::string(val, span)),
Expr::ImportPattern(..) => Err(ShellError::OutsideSpannedLabeledError {
src: original_text.to_string(),
error: "Error when loading".into(),

View File

@ -115,6 +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::CellPath { val, .. } => nu_json::Value::Array(
val.members
.iter()

View File

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

View File

@ -127,6 +127,7 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
)
}
Value::String { val, .. } => val,
Value::QuotedString { val, .. } => val,
Value::List { vals: val, .. } => val
.into_iter()
.map(|x| local_into_string(x, ", ", config))

View File

@ -54,7 +54,9 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result<toml::Value, ShellErr
Value::Date { val, .. } => toml::Value::String(val.to_string()),
Value::Range { .. } => toml::Value::String("<Range>".to_string()),
Value::Float { val, .. } => toml::Value::Float(*val),
Value::String { val, .. } => toml::Value::String(val.clone()),
Value::String { val, .. } | Value::QuotedString { 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,9 @@ 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, .. } => serde_yaml::Value::String(val.clone()),
Value::String { val, .. } | Value::QuotedString { val, .. } => {
serde_yaml::Value::String(val.clone())
}
Value::Record { val, .. } => {
let mut m = serde_yaml::Mapping::new();
for (k, v) in val {

View File

@ -115,6 +115,8 @@ fn lists_regular_files_in_special_folder() {
#[case("[[]?bcd].txt", 2)]
#[case("[[]abcd].txt", 1)]
#[case("[[][abcd]bcd[]].txt", 2)]
#[case("'[abcd].txt'", 1)]
#[case("'[bbcd].txt'", 1)]
fn lists_regular_files_using_question_mark(#[case] command: &str, #[case] expected: usize) {
Playground::setup("ls_test_3", |dirs, sandbox| {
sandbox.mkdir("abcd").mkdir("bbcd").with_files(vec![

View File

@ -387,7 +387,7 @@ fn eval_element_with_input(
Expr::String(_)
| Expr::FullCellPath(_)
| Expr::StringInterpolation(_)
| Expr::Filepath(_) => {
| Expr::Filepath(_, _) => {
let exit_code = match &mut input {
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
_ => None,
@ -485,11 +485,11 @@ fn eval_element_with_input(
Expr::String(_)
| Expr::FullCellPath(_)
| Expr::StringInterpolation(_)
| Expr::Filepath(_),
| Expr::Filepath(_, _),
Expr::String(_)
| Expr::FullCellPath(_)
| Expr::StringInterpolation(_)
| Expr::Filepath(_),
| Expr::Filepath(_, _),
) => {
if let Some(save_command) = engine_state.find_decl(b"save", &[]) {
let exit_code = match &mut input {
@ -917,22 +917,30 @@ impl Eval for EvalRuntime {
engine_state: Self::State<'_>,
stack: &mut Self::MutState,
path: String,
quoted: bool,
span: Span,
) -> Result<Value, ShellError> {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(path, cwd);
if quoted {
Ok(Value::string(path, span))
} else {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(path, cwd);
Ok(Value::string(path.to_string_lossy(), span))
Ok(Value::string(path.to_string_lossy(), span))
}
}
fn eval_directory(
engine_state: Self::State<'_>,
stack: &mut Self::MutState,
path: String,
quoted: bool,
span: Span,
) -> Result<Value, ShellError> {
if path == "-" {
Ok(Value::string("-", span))
} else if quoted {
Ok(Value::string(path, span))
} else {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(path, cwd);
@ -1163,12 +1171,17 @@ impl Eval for EvalRuntime {
engine_state: Self::State<'_>,
stack: &mut Self::MutState,
pattern: String,
quoted: bool,
span: Span,
) -> Result<Value, ShellError> {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(pattern, cwd);
if quoted {
Ok(Value::string(pattern, span))
} else {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(pattern, cwd);
Ok(Value::string(path.to_string_lossy(), span))
Ok(Value::string(path.to_string_lossy(), span))
}
}
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {

View File

@ -361,13 +361,16 @@ pub fn flatten_expression(
Expr::Bool(_) => {
vec![(expr.span, FlatShape::Bool)]
}
Expr::Filepath(_) => {
Expr::Filepath(_, _) => {
vec![(expr.span, FlatShape::Filepath)]
}
Expr::Directory(_) => {
Expr::Directory(_, _) => {
vec![(expr.span, FlatShape::Directory)]
}
Expr::GlobPattern(_) => {
Expr::GlobPattern(_, _) => {
vec![(expr.span, FlatShape::GlobPattern)]
}
Expr::LsGlobPattern(_, _) => {
vec![(expr.span, FlatShape::GlobPattern)]
}
Expr::List(list) => {

View File

@ -2179,6 +2179,7 @@ pub fn parse_full_cell_path(
pub fn parse_directory(working_set: &mut StateWorkingSet, span: Span) -> Expression {
let bytes = working_set.get_span_contents(span);
let quoted = is_quoted(bytes);
let (token, err) = unescape_unquote_string(bytes, span);
trace!("parsing: directory");
@ -2186,7 +2187,7 @@ pub fn parse_directory(working_set: &mut StateWorkingSet, span: Span) -> Express
trace!("-- found {}", token);
Expression {
expr: Expr::Directory(token),
expr: Expr::Directory(token, quoted),
span,
ty: Type::String,
custom_completion: None,
@ -2200,6 +2201,7 @@ pub fn parse_directory(working_set: &mut StateWorkingSet, span: Span) -> Express
pub fn parse_filepath(working_set: &mut StateWorkingSet, span: Span) -> Expression {
let bytes = working_set.get_span_contents(span);
let quoted = is_quoted(bytes);
let (token, err) = unescape_unquote_string(bytes, span);
trace!("parsing: filepath");
@ -2207,7 +2209,7 @@ pub fn parse_filepath(working_set: &mut StateWorkingSet, span: Span) -> Expressi
trace!("-- found {}", token);
Expression {
expr: Expr::Filepath(token),
expr: Expr::Filepath(token, quoted),
span,
ty: Type::String,
custom_completion: None,
@ -2467,6 +2469,7 @@ fn modf(x: f64) -> (f64, f64) {
pub fn parse_glob_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expression {
let bytes = working_set.get_span_contents(span);
let quoted = is_quoted(bytes);
let (token, err) = unescape_unquote_string(bytes, span);
trace!("parsing: glob pattern");
@ -2474,7 +2477,29 @@ pub fn parse_glob_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expr
trace!("-- found {}", token);
Expression {
expr: Expr::GlobPattern(token),
expr: Expr::GlobPattern(token, quoted),
span,
ty: Type::String,
custom_completion: None,
}
} else {
working_set.error(ParseError::Expected("glob pattern string", span));
garbage(span)
}
}
pub fn parse_ls_glob_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expression {
let bytes = working_set.get_span_contents(span);
let quoted = is_quoted(bytes);
let (token, err) = unescape_unquote_string(bytes, span);
trace!("parsing: glob pattern");
if err.is_none() {
trace!("-- found {}", token);
Expression {
expr: Expr::LsGlobPattern(token, quoted),
span,
ty: Type::String,
custom_completion: None,
@ -2709,6 +2734,11 @@ pub fn parse_string(working_set: &mut StateWorkingSet, span: Span) -> Expression
}
}
fn is_quoted(bytes: &[u8]) -> bool {
(bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
|| (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
}
pub fn parse_string_strict(working_set: &mut StateWorkingSet, span: Span) -> Expression {
trace!("parsing: string, with required delimiters");
@ -4544,7 +4574,8 @@ pub fn parse_value(
| SyntaxShape::Table(_)
| SyntaxShape::Signature
| SyntaxShape::Filepath
| SyntaxShape::String => {}
| SyntaxShape::String
| SyntaxShape::LsGlobPattern => {}
_ => {
working_set.error(ParseError::Expected("non-[] value", span));
return Expression::garbage(span);
@ -4569,6 +4600,7 @@ pub fn parse_value(
SyntaxShape::Filepath => parse_filepath(working_set, span),
SyntaxShape::Directory => parse_directory(working_set, span),
SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span),
SyntaxShape::LsGlobPattern => parse_ls_glob_pattern(working_set, span),
SyntaxShape::String => parse_string(working_set, span),
SyntaxShape::Binary => parse_binary(working_set, span),
SyntaxShape::Signature => {
@ -5961,8 +5993,8 @@ pub fn discover_captures_in_expr(
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
}
}
Expr::Filepath(_) => {}
Expr::Directory(_) => {}
Expr::Filepath(_, _) => {}
Expr::Directory(_, _) => {}
Expr::Float(_) => {}
Expr::FullCellPath(cell_path) => {
discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks, output)?;
@ -5971,7 +6003,8 @@ pub fn discover_captures_in_expr(
Expr::Overlay(_) => {}
Expr::Garbage => {}
Expr::Nothing => {}
Expr::GlobPattern(_) => {}
Expr::GlobPattern(_, _) => {}
Expr::LsGlobPattern(_, _) => {}
Expr::Int(_) => {}
Expr::Keyword(_, _, expr) => {
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;

View File

@ -37,9 +37,10 @@ pub enum Expr {
Keyword(Vec<u8>, Span, Box<Expression>),
ValueWithUnit(Box<Expression>, Spanned<Unit>),
DateTime(chrono::DateTime<FixedOffset>),
Filepath(String),
Directory(String),
GlobPattern(String),
Filepath(String, bool),
Directory(String, bool),
GlobPattern(String, bool),
LsGlobPattern(String, bool),
String(String),
CellPath(CellPath),
FullCellPath(Box<FullCellPath>),

View File

@ -197,8 +197,8 @@ impl Expression {
}
Expr::ImportPattern(_) => false,
Expr::Overlay(_) => false,
Expr::Filepath(_) => false,
Expr::Directory(_) => false,
Expr::Filepath(_, _) => false,
Expr::Directory(_, _) => false,
Expr::Float(_) => false,
Expr::FullCellPath(full_cell_path) => {
if full_cell_path.head.has_in_variable(working_set) {
@ -208,7 +208,8 @@ impl Expression {
}
Expr::Garbage => false,
Expr::Nothing => false,
Expr::GlobPattern(_) => false,
Expr::GlobPattern(_, _) => false,
Expr::LsGlobPattern(_, _) => false,
Expr::Int(_) => false,
Expr::Keyword(_, _, expr) => expr.has_in_variable(working_set),
Expr::List(list) => {
@ -375,8 +376,8 @@ impl Expression {
expr.replace_span(working_set, replaced, new_span);
}
}
Expr::Filepath(_) => {}
Expr::Directory(_) => {}
Expr::Filepath(_, _) => {}
Expr::Directory(_, _) => {}
Expr::Float(_) => {}
Expr::FullCellPath(full_cell_path) => {
full_cell_path
@ -387,7 +388,8 @@ impl Expression {
Expr::Overlay(_) => {}
Expr::Garbage => {}
Expr::Nothing => {}
Expr::GlobPattern(_) => {}
Expr::GlobPattern(_, _) => {}
Expr::LsGlobPattern(_, _) => {}
Expr::MatchBlock(_) => {}
Expr::Int(_) => {}
Expr::Keyword(_, _, expr) => expr.replace_span(working_set, replaced, new_span),

View File

@ -27,9 +27,9 @@ pub trait Eval {
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
Expr::Float(f) => Ok(Value::float(*f, expr.span)),
Expr::Binary(b) => Ok(Value::binary(b.clone(), expr.span)),
Expr::Filepath(path) => Self::eval_filepath(state, mut_state, path.clone(), expr.span),
Expr::Directory(path) => {
Self::eval_directory(state, mut_state, path.clone(), expr.span)
Expr::Filepath(path, quoted) => Self::eval_filepath(state, mut_state, path.clone(), *quoted, expr.span),
Expr::Directory(path, quoted) => {
Self::eval_directory(state, mut_state, path.clone(), *quoted, expr.span)
}
Expr::Var(var_id) => Self::eval_var(state, mut_state, *var_id, expr.span),
Expr::CellPath(cell_path) => Ok(Value::cell_path(cell_path.clone(), expr.span)),
@ -274,8 +274,15 @@ pub trait Eval {
Self::eval_string_interpolation(state, mut_state, exprs, expr.span)
}
Expr::Overlay(_) => Self::eval_overlay(state, expr.span),
Expr::GlobPattern(pattern) => {
Self::eval_glob_pattern(state, mut_state, pattern.clone(), expr.span)
Expr::GlobPattern(pattern, quoted) => {
Self::eval_glob_pattern(state, mut_state, pattern.clone(), *quoted, expr.span)
}
Expr::LsGlobPattern(pattern, quoted) => {
if *quoted {
Ok(Value::quoted_string(pattern, expr.span))
} else {
Ok(Value::string(pattern, expr.span))
}
}
Expr::MatchBlock(_) // match blocks are handled by `match`
| Expr::VarDecl(_)
@ -291,6 +298,7 @@ pub trait Eval {
state: Self::State<'_>,
mut_state: &mut Self::MutState,
path: String,
quoted: bool,
span: Span,
) -> Result<Value, ShellError>;
@ -298,6 +306,7 @@ pub trait Eval {
state: Self::State<'_>,
mut_state: &mut Self::MutState,
path: String,
quoted: bool,
span: Span,
) -> Result<Value, ShellError>;
@ -370,6 +379,7 @@ pub trait Eval {
state: Self::State<'_>,
mut_state: &mut Self::MutState,
pattern: String,
quoted: bool,
span: Span,
) -> Result<Value, ShellError>;

View File

@ -282,6 +282,7 @@ impl Eval for EvalConst {
_: &StateWorkingSet,
_: &mut (),
path: String,
_: bool,
span: Span,
) -> Result<Value, ShellError> {
Ok(Value::string(path, span))
@ -291,6 +292,7 @@ impl Eval for EvalConst {
_: &StateWorkingSet,
_: &mut (),
_: String,
_: bool,
span: Span,
) -> Result<Value, ShellError> {
Err(ShellError::NotAConstant { span })
@ -392,6 +394,7 @@ impl Eval for EvalConst {
_: &StateWorkingSet,
_: &mut (),
_: String,
_: bool,
span: Span,
) -> Result<Value, ShellError> {
Err(ShellError::NotAConstant { span })

View File

@ -64,6 +64,9 @@ pub enum SyntaxShape {
/// A glob pattern is allowed, eg `foo*`
GlobPattern,
/// A special glob pattern for ls.
LsGlobPattern,
/// Only an integer value is allowed
Int,
@ -151,6 +154,7 @@ impl SyntaxShape {
SyntaxShape::Filesize => Type::Filesize,
SyntaxShape::FullCellPath => Type::Any,
SyntaxShape::GlobPattern => Type::String,
SyntaxShape::LsGlobPattern => Type::String,
SyntaxShape::Error => Type::Error,
SyntaxShape::ImportPattern => Type::Any,
SyntaxShape::Int => Type::Int,
@ -201,6 +205,7 @@ impl Display for SyntaxShape {
SyntaxShape::Filepath => write!(f, "path"),
SyntaxShape::Directory => write!(f, "directory"),
SyntaxShape::GlobPattern => write!(f, "glob"),
SyntaxShape::LsGlobPattern => write!(f, "glob"),
SyntaxShape::ImportPattern => write!(f, "import"),
SyntaxShape::Block => write!(f, "block"),
SyntaxShape::Closure(args) => {

View File

@ -1,5 +1,6 @@
use std::path::PathBuf;
use super::NuPath;
use crate::ast::{CellPath, PathMember};
use crate::engine::{Block, Closure};
use crate::{Range, Record, ShellError, Spanned, Value};
@ -203,6 +204,45 @@ impl FromValue for Spanned<String> {
}
}
impl FromValue for NuPath {
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)),
v => Err(ShellError::CantConvert {
to_type: "string".into(),
from_type: v.get_type().to_string(),
span: v.span(),
help: None,
}),
}
}
}
impl FromValue for Spanned<NuPath> {
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),
v => {
return Err(ShellError::CantConvert {
to_type: "string".into(),
from_type: v.get_type().to_string(),
span: v.span(),
help: None,
})
}
},
span,
})
}
}
impl FromValue for Vec<String> {
fn from_value(v: Value) -> Result<Self, ShellError> {
// FIXME: we may want to fail a little nicer here

View File

@ -2,6 +2,7 @@ mod custom_value;
mod from;
mod from_value;
mod lazy_record;
mod path;
mod range;
mod record;
mod stream;
@ -24,6 +25,7 @@ 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};
@ -90,6 +92,12 @@ pub enum Value {
// please use .span() instead of matching this span value
internal_span: Span,
},
QuotedString {
val: String,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
internal_span: Span,
},
Record {
val: Record,
// note: spans are being refactored out of Value
@ -179,6 +187,10 @@ impl Clone for Value {
val: val.clone(),
internal_span: *internal_span,
},
Value::QuotedString { val, internal_span } => Value::QuotedString {
val: val.clone(),
internal_span: *internal_span,
},
Value::Record { val, internal_span } => Value::Record {
val: val.clone(),
internal_span: *internal_span,
@ -509,6 +521,7 @@ impl Value {
| Value::Date { internal_span, .. }
| Value::Range { internal_span, .. }
| Value::String { internal_span, .. }
| Value::QuotedString { internal_span, .. }
| Value::Record { internal_span, .. }
| Value::List { internal_span, .. }
| Value::Block { internal_span, .. }
@ -533,6 +546,7 @@ impl Value {
| Value::Date { internal_span, .. }
| Value::Range { internal_span, .. }
| Value::String { internal_span, .. }
| Value::QuotedString { internal_span, .. }
| Value::Record { internal_span, .. }
| Value::LazyRecord { internal_span, .. }
| Value::List { internal_span, .. }
@ -559,6 +573,7 @@ impl Value {
Value::Date { .. } => Type::Date,
Value::Range { .. } => Type::Range,
Value::String { .. } => Type::String,
Value::QuotedString { .. } => Type::String,
Value::Record { val, .. } => {
Type::Record(val.iter().map(|(x, y)| (x.clone(), y.get_type())).collect())
}
@ -672,6 +687,7 @@ impl Value {
)
}
Value::String { val, .. } => val.clone(),
Value::QuotedString { val, .. } => val.clone(),
Value::List { vals: val, .. } => format!(
"[{}]",
val.iter()
@ -726,6 +742,7 @@ impl Value {
)
}
Value::String { val, .. } => val.to_string(),
Value::QuotedString { val, .. } => val.to_string(),
Value::List { ref vals, .. } => {
if !vals.is_empty() && vals.iter().all(|x| matches!(x, Value::Record { .. })) {
format!(
@ -852,6 +869,7 @@ impl Value {
)
}
Value::String { val, .. } => val.clone(),
Value::QuotedString { val, .. } => val.clone(),
Value::List { vals: val, .. } => format!(
"[{}]",
val.iter()
@ -1750,6 +1768,13 @@ impl Value {
}
}
pub fn quoted_string(val: impl Into<String>, span: Span) -> Value {
Value::QuotedString {
val: val.into(),
internal_span: span,
}
}
pub fn record(val: Record, span: Span) -> Value {
Value::Record {
val,
@ -1955,6 +1980,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::Record { .. } => Some(Ordering::Less),
Value::LazyRecord { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
@ -1975,6 +2001,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::Record { .. } => Some(Ordering::Less),
Value::LazyRecord { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
@ -1995,6 +2022,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::Record { .. } => Some(Ordering::Less),
Value::LazyRecord { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
@ -2015,6 +2043,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::Record { .. } => Some(Ordering::Less),
Value::LazyRecord { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
@ -2035,6 +2064,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::Record { .. } => Some(Ordering::Less),
Value::LazyRecord { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
@ -2055,6 +2085,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::Record { .. } => Some(Ordering::Less),
Value::LazyRecord { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
@ -2075,6 +2106,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::Record { .. } => Some(Ordering::Less),
Value::LazyRecord { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
@ -2095,6 +2127,28 @@ 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::Record { .. } => Some(Ordering::Less),
Value::LazyRecord { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
Value::Block { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::CustomValue { .. } => Some(Ordering::Less),
},
(Value::QuotedString { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { .. } => Some(Ordering::Greater),
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::Record { .. } => Some(Ordering::Less),
Value::LazyRecord { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
@ -2115,6 +2169,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::Record { val: rhs, .. } => {
// reorder cols and vals to make more logically compare.
// more general, if two record have same col and values,
@ -2154,6 +2209,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::Record { .. } => Some(Ordering::Greater),
Value::LazyRecord { .. } => Some(Ordering::Greater),
Value::List { vals: rhs, .. } => lhs.partial_cmp(rhs),
@ -2174,6 +2230,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::Record { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),
Value::LazyRecord { .. } => Some(Ordering::Greater),
@ -2194,6 +2251,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::Record { .. } => Some(Ordering::Greater),
Value::LazyRecord { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),
@ -2214,6 +2272,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::Record { .. } => Some(Ordering::Greater),
Value::LazyRecord { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),
@ -2234,6 +2293,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::Record { .. } => Some(Ordering::Greater),
Value::LazyRecord { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),
@ -2254,6 +2314,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::Record { .. } => Some(Ordering::Greater),
Value::LazyRecord { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),
@ -2274,6 +2335,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::Record { .. } => Some(Ordering::Greater),
Value::LazyRecord { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),

View File

@ -0,0 +1,19 @@
/// 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)]
pub enum NuPath {
/// A quoted path(except backtick), in this case, nushell shouldn't auto-expand path.
Quoted(String),
/// An unquoted path, in this case, nushell should auto-expand path.
UnQuoted(String),
}
impl AsRef<str> for NuPath {
fn as_ref(&self) -> &str {
match self {
NuPath::Quoted(s) | NuPath::UnQuoted(s) => s,
}
}
}

View File

@ -229,3 +229,13 @@ fn type_check_for_during_eval2() -> TestResult {
"can't convert nothing to string",
)
}
#[test]
fn path_argument_dont_auto_expand_if_single_quoted() -> TestResult {
run_test("def spam [foo: path] { echo $foo }; spam '~/aa'", "~/aa")
}
#[test]
fn path_argument_dont_auto_expand_if_double_quoted() -> TestResult {
run_test(r#"def spam [foo: path] { echo $foo }; spam "~/aa""#, "~/aa")
}