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
26 changed files with 310 additions and 60 deletions

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