2021-10-01 18:11:49 +13:00
|
|
|
use serde::{Deserialize, Serialize};
|
2023-06-25 20:28:37 +02:00
|
|
|
#[cfg(test)]
|
2022-11-09 16:55:05 -05:00
|
|
|
use strum_macros::EnumIter;
|
2021-10-01 18:11:49 +13:00
|
|
|
|
2021-09-02 13:29:43 +12:00
|
|
|
use std::fmt::Display;
|
|
|
|
|
2022-03-07 15:08:56 -05:00
|
|
|
use crate::SyntaxShape;
|
|
|
|
|
2023-06-25 20:28:37 +02:00
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
|
|
|
#[cfg_attr(test, derive(EnumIter))]
|
2021-09-02 13:29:43 +12:00
|
|
|
pub enum Type {
|
2022-12-13 10:46:22 -06:00
|
|
|
Any,
|
|
|
|
Binary,
|
2021-09-02 13:29:43 +12:00
|
|
|
Block,
|
2022-12-13 10:46:22 -06:00
|
|
|
Bool,
|
2021-09-07 10:02:24 +12:00
|
|
|
CellPath,
|
2022-12-13 10:46:22 -06:00
|
|
|
Closure,
|
2024-04-24 15:46:35 +00:00
|
|
|
Custom(Box<str>),
|
2021-10-05 15:27:39 +13:00
|
|
|
Date,
|
2022-12-13 10:46:22 -06:00
|
|
|
Duration,
|
|
|
|
Error,
|
2021-09-02 13:29:43 +12:00
|
|
|
Filesize,
|
2022-12-13 10:46:22 -06:00
|
|
|
Float,
|
|
|
|
Int,
|
2021-09-02 13:29:43 +12:00
|
|
|
List(Box<Type>),
|
2022-12-13 10:46:22 -06:00
|
|
|
ListStream,
|
2022-11-09 16:55:05 -05:00
|
|
|
#[default]
|
2021-09-02 13:29:43 +12:00
|
|
|
Nothing,
|
2022-12-13 10:46:22 -06:00
|
|
|
Number,
|
|
|
|
Range,
|
2024-04-24 15:46:35 +00:00
|
|
|
Record(Box<[(String, Type)]>),
|
2021-12-27 19:13:52 +00:00
|
|
|
Signature,
|
2022-12-13 10:46:22 -06:00
|
|
|
String,
|
`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:
https://github.com/nushell/nushell/blob/0487e9ffcbc57c2d5feca606e10c3f8221ff5e00/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.
2024-02-23 09:17:09 +08:00
|
|
|
Glob,
|
2024-04-24 15:46:35 +00:00
|
|
|
Table(Box<[(String, Type)]>),
|
2021-09-02 13:29:43 +12:00
|
|
|
}
|
|
|
|
|
2022-03-07 15:08:56 -05:00
|
|
|
impl Type {
|
2024-04-24 15:46:35 +00:00
|
|
|
pub fn record() -> Self {
|
|
|
|
Self::Record([].into())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn table() -> Self {
|
|
|
|
Self::Table([].into())
|
|
|
|
}
|
|
|
|
|
2022-11-09 16:55:05 -05:00
|
|
|
pub fn is_subtype(&self, other: &Type) -> bool {
|
allow records to have type annotations (#8914)
# Description
follow up to #8529
cleaned up version of #8892
- the original syntax is okay
```nu
def okay [rec: record] {}
```
- you can now add type annotations for fields if you know
them before hand
```nu
def okay [rec: record<name: string>] {}
```
- you can specify multiple fields
```nu
def okay [person: record<name: string age: int>] {}
# an optional comma is allowed
def okay [person: record<name: string, age: int>] {}
```
- if annotations are specified, any use of the command will be type
checked against the specified type
```nu
def unwrap [result: record<ok: bool, value: any>] {}
unwrap {ok: 2, value: "value"}
# errors with
Error: nu::parser::type_mismatch
× Type mismatch.
╭─[entry #4:1:1]
1 │ unwrap {ok: 2, value: "value"}
· ───────┬─────
· ╰── expected record<ok: bool, value: any>, found record<ok: int, value: string>
╰────
```
> here the error is in the `ok` field, since `any` is coerced into any
type
> as a result `unwrap {ok: true, value: "value"}` is okay
- the key must be a string, either quoted or unquoted
```nu
def err [rec: record<{}: list>] {}
# errors with
Error:
× `record` type annotations key not string
╭─[entry #7:1:1]
1 │ def unwrap [result: record<{}: bool, value: any>] {}
· ─┬
· ╰── must be a string
╰────
```
- a key doesn't have to have a type in which case it is assumed to be
`any`
```nu
def okay [person: record<name age>] {}
def okay [person: record<name: string age>] {}
```
- however, if you put a colon, you have to specify a type
```nu
def err [person: record<name: >] {}
# errors with
Error: nu::parser::parse_mismatch
× Parse mismatch during operation.
╭─[entry #12:1:1]
1 │ def unwrap [res: record<name: >] { $res }
· ┬
· ╰── expected type after colon
╰────
```
# User-Facing Changes
**[BREAKING CHANGES]**
- this change adds a field to `SyntaxShape::Record` so any plugins that
used it will have to update and include the field. though if you are
unsure of the type the record expects, `SyntaxShape::Record(vec![])`
will suffice
2023-04-26 16:16:55 +03:00
|
|
|
// Structural subtyping
|
|
|
|
let is_subtype_collection = |this: &[(String, Type)], that: &[(String, Type)]| {
|
|
|
|
if this.is_empty() || that.is_empty() {
|
|
|
|
true
|
2024-01-12 23:48:53 +08:00
|
|
|
} else if this.len() < that.len() {
|
allow records to have type annotations (#8914)
# Description
follow up to #8529
cleaned up version of #8892
- the original syntax is okay
```nu
def okay [rec: record] {}
```
- you can now add type annotations for fields if you know
them before hand
```nu
def okay [rec: record<name: string>] {}
```
- you can specify multiple fields
```nu
def okay [person: record<name: string age: int>] {}
# an optional comma is allowed
def okay [person: record<name: string, age: int>] {}
```
- if annotations are specified, any use of the command will be type
checked against the specified type
```nu
def unwrap [result: record<ok: bool, value: any>] {}
unwrap {ok: 2, value: "value"}
# errors with
Error: nu::parser::type_mismatch
× Type mismatch.
╭─[entry #4:1:1]
1 │ unwrap {ok: 2, value: "value"}
· ───────┬─────
· ╰── expected record<ok: bool, value: any>, found record<ok: int, value: string>
╰────
```
> here the error is in the `ok` field, since `any` is coerced into any
type
> as a result `unwrap {ok: true, value: "value"}` is okay
- the key must be a string, either quoted or unquoted
```nu
def err [rec: record<{}: list>] {}
# errors with
Error:
× `record` type annotations key not string
╭─[entry #7:1:1]
1 │ def unwrap [result: record<{}: bool, value: any>] {}
· ─┬
· ╰── must be a string
╰────
```
- a key doesn't have to have a type in which case it is assumed to be
`any`
```nu
def okay [person: record<name age>] {}
def okay [person: record<name: string age>] {}
```
- however, if you put a colon, you have to specify a type
```nu
def err [person: record<name: >] {}
# errors with
Error: nu::parser::parse_mismatch
× Parse mismatch during operation.
╭─[entry #12:1:1]
1 │ def unwrap [res: record<name: >] { $res }
· ┬
· ╰── expected type after colon
╰────
```
# User-Facing Changes
**[BREAKING CHANGES]**
- this change adds a field to `SyntaxShape::Record` so any plugins that
used it will have to update and include the field. though if you are
unsure of the type the record expects, `SyntaxShape::Record(vec![])`
will suffice
2023-04-26 16:16:55 +03:00
|
|
|
false
|
|
|
|
} else {
|
2024-01-12 23:48:53 +08:00
|
|
|
that.iter().all(|(col_y, ty_y)| {
|
|
|
|
if let Some((_, ty_x)) = this.iter().find(|(col_x, _)| col_x == col_y) {
|
2023-07-06 11:25:39 +03:00
|
|
|
ty_x.is_subtype(ty_y)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
})
|
allow records to have type annotations (#8914)
# Description
follow up to #8529
cleaned up version of #8892
- the original syntax is okay
```nu
def okay [rec: record] {}
```
- you can now add type annotations for fields if you know
them before hand
```nu
def okay [rec: record<name: string>] {}
```
- you can specify multiple fields
```nu
def okay [person: record<name: string age: int>] {}
# an optional comma is allowed
def okay [person: record<name: string, age: int>] {}
```
- if annotations are specified, any use of the command will be type
checked against the specified type
```nu
def unwrap [result: record<ok: bool, value: any>] {}
unwrap {ok: 2, value: "value"}
# errors with
Error: nu::parser::type_mismatch
× Type mismatch.
╭─[entry #4:1:1]
1 │ unwrap {ok: 2, value: "value"}
· ───────┬─────
· ╰── expected record<ok: bool, value: any>, found record<ok: int, value: string>
╰────
```
> here the error is in the `ok` field, since `any` is coerced into any
type
> as a result `unwrap {ok: true, value: "value"}` is okay
- the key must be a string, either quoted or unquoted
```nu
def err [rec: record<{}: list>] {}
# errors with
Error:
× `record` type annotations key not string
╭─[entry #7:1:1]
1 │ def unwrap [result: record<{}: bool, value: any>] {}
· ─┬
· ╰── must be a string
╰────
```
- a key doesn't have to have a type in which case it is assumed to be
`any`
```nu
def okay [person: record<name age>] {}
def okay [person: record<name: string age>] {}
```
- however, if you put a colon, you have to specify a type
```nu
def err [person: record<name: >] {}
# errors with
Error: nu::parser::parse_mismatch
× Parse mismatch during operation.
╭─[entry #12:1:1]
1 │ def unwrap [res: record<name: >] { $res }
· ┬
· ╰── expected type after colon
╰────
```
# User-Facing Changes
**[BREAKING CHANGES]**
- this change adds a field to `SyntaxShape::Record` so any plugins that
used it will have to update and include the field. though if you are
unsure of the type the record expects, `SyntaxShape::Record(vec![])`
will suffice
2023-04-26 16:16:55 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-09 16:55:05 -05:00
|
|
|
match (self, other) {
|
|
|
|
(t, u) if t == u => true,
|
|
|
|
(Type::Float, Type::Number) => true,
|
|
|
|
(Type::Int, Type::Number) => true,
|
|
|
|
(_, Type::Any) => true,
|
|
|
|
(Type::List(t), Type::List(u)) if t.is_subtype(u) => true, // List is covariant
|
allow records to have type annotations (#8914)
# Description
follow up to #8529
cleaned up version of #8892
- the original syntax is okay
```nu
def okay [rec: record] {}
```
- you can now add type annotations for fields if you know
them before hand
```nu
def okay [rec: record<name: string>] {}
```
- you can specify multiple fields
```nu
def okay [person: record<name: string age: int>] {}
# an optional comma is allowed
def okay [person: record<name: string, age: int>] {}
```
- if annotations are specified, any use of the command will be type
checked against the specified type
```nu
def unwrap [result: record<ok: bool, value: any>] {}
unwrap {ok: 2, value: "value"}
# errors with
Error: nu::parser::type_mismatch
× Type mismatch.
╭─[entry #4:1:1]
1 │ unwrap {ok: 2, value: "value"}
· ───────┬─────
· ╰── expected record<ok: bool, value: any>, found record<ok: int, value: string>
╰────
```
> here the error is in the `ok` field, since `any` is coerced into any
type
> as a result `unwrap {ok: true, value: "value"}` is okay
- the key must be a string, either quoted or unquoted
```nu
def err [rec: record<{}: list>] {}
# errors with
Error:
× `record` type annotations key not string
╭─[entry #7:1:1]
1 │ def unwrap [result: record<{}: bool, value: any>] {}
· ─┬
· ╰── must be a string
╰────
```
- a key doesn't have to have a type in which case it is assumed to be
`any`
```nu
def okay [person: record<name age>] {}
def okay [person: record<name: string age>] {}
```
- however, if you put a colon, you have to specify a type
```nu
def err [person: record<name: >] {}
# errors with
Error: nu::parser::parse_mismatch
× Parse mismatch during operation.
╭─[entry #12:1:1]
1 │ def unwrap [res: record<name: >] { $res }
· ┬
· ╰── expected type after colon
╰────
```
# User-Facing Changes
**[BREAKING CHANGES]**
- this change adds a field to `SyntaxShape::Record` so any plugins that
used it will have to update and include the field. though if you are
unsure of the type the record expects, `SyntaxShape::Record(vec![])`
will suffice
2023-04-26 16:16:55 +03:00
|
|
|
(Type::Record(this), Type::Record(that)) | (Type::Table(this), Type::Table(that)) => {
|
|
|
|
is_subtype_collection(this, that)
|
|
|
|
}
|
2024-01-15 16:58:26 +08:00
|
|
|
(Type::Table(_), Type::List(_)) => true,
|
2022-11-09 16:55:05 -05:00
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_numeric(&self) -> bool {
|
|
|
|
matches!(self, Type::Int | Type::Float | Type::Number)
|
|
|
|
}
|
|
|
|
|
2023-02-22 06:53:11 -06:00
|
|
|
pub fn is_list(&self) -> bool {
|
|
|
|
matches!(self, Type::List(_))
|
|
|
|
}
|
|
|
|
|
2022-11-09 16:55:05 -05:00
|
|
|
/// Does this type represent a data structure containing values that can be addressed using 'cell paths'?
|
|
|
|
pub fn accepts_cell_paths(&self) -> bool {
|
|
|
|
matches!(self, Type::List(_) | Type::Record(_) | Type::Table(_))
|
|
|
|
}
|
|
|
|
|
2022-03-07 15:08:56 -05:00
|
|
|
pub fn to_shape(&self) -> SyntaxShape {
|
2023-07-07 12:06:09 +03:00
|
|
|
let mk_shape = |tys: &[(String, Type)]| {
|
|
|
|
tys.iter()
|
|
|
|
.map(|(key, val)| (key.clone(), val.to_shape()))
|
|
|
|
.collect()
|
|
|
|
};
|
|
|
|
|
2022-03-07 15:08:56 -05:00
|
|
|
match self {
|
|
|
|
Type::Int => SyntaxShape::Int,
|
2023-10-11 12:27:09 -05:00
|
|
|
Type::Float => SyntaxShape::Float,
|
2022-03-07 15:08:56 -05:00
|
|
|
Type::Range => SyntaxShape::Range,
|
|
|
|
Type::Bool => SyntaxShape::Boolean,
|
|
|
|
Type::String => SyntaxShape::String,
|
2022-11-10 21:21:49 +13:00
|
|
|
Type::Block => SyntaxShape::Block, // FIXME needs more accuracy
|
|
|
|
Type::Closure => SyntaxShape::Closure(None), // FIXME needs more accuracy
|
2022-03-07 15:08:56 -05:00
|
|
|
Type::CellPath => SyntaxShape::CellPath,
|
|
|
|
Type::Duration => SyntaxShape::Duration,
|
|
|
|
Type::Date => SyntaxShape::DateTime,
|
|
|
|
Type::Filesize => SyntaxShape::Filesize,
|
|
|
|
Type::List(x) => SyntaxShape::List(Box::new(x.to_shape())),
|
|
|
|
Type::Number => SyntaxShape::Number,
|
2022-11-09 16:55:05 -05:00
|
|
|
Type::Nothing => SyntaxShape::Nothing,
|
2023-07-07 12:06:09 +03:00
|
|
|
Type::Record(entries) => SyntaxShape::Record(mk_shape(entries)),
|
|
|
|
Type::Table(columns) => SyntaxShape::Table(mk_shape(columns)),
|
2022-03-07 15:08:56 -05:00
|
|
|
Type::ListStream => SyntaxShape::List(Box::new(SyntaxShape::Any)),
|
2022-04-07 16:34:09 +12:00
|
|
|
Type::Any => SyntaxShape::Any,
|
2022-03-07 15:08:56 -05:00
|
|
|
Type::Error => SyntaxShape::Any,
|
|
|
|
Type::Binary => SyntaxShape::Binary,
|
2022-06-10 10:59:35 -05:00
|
|
|
Type::Custom(_) => SyntaxShape::Any,
|
2022-03-07 15:08:56 -05:00
|
|
|
Type::Signature => SyntaxShape::Signature,
|
`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:
https://github.com/nushell/nushell/blob/0487e9ffcbc57c2d5feca606e10c3f8221ff5e00/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.
2024-02-23 09:17:09 +08:00
|
|
|
Type::Glob => SyntaxShape::GlobPattern,
|
2022-03-07 15:08:56 -05:00
|
|
|
}
|
|
|
|
}
|
2023-02-22 19:18:33 +03:00
|
|
|
|
|
|
|
/// Get a string representation, without inner type specification of lists,
|
|
|
|
/// tables and records (get `list` instead of `list<any>`
|
|
|
|
pub fn get_non_specified_string(&self) -> String {
|
|
|
|
match self {
|
|
|
|
Type::Block => String::from("block"),
|
|
|
|
Type::Closure => String::from("closure"),
|
|
|
|
Type::Bool => String::from("bool"),
|
2023-09-06 20:22:12 +02:00
|
|
|
Type::CellPath => String::from("cell-path"),
|
2023-02-22 19:18:33 +03:00
|
|
|
Type::Date => String::from("date"),
|
|
|
|
Type::Duration => String::from("duration"),
|
|
|
|
Type::Filesize => String::from("filesize"),
|
|
|
|
Type::Float => String::from("float"),
|
|
|
|
Type::Int => String::from("int"),
|
|
|
|
Type::Range => String::from("range"),
|
|
|
|
Type::Record(_) => String::from("record"),
|
|
|
|
Type::Table(_) => String::from("table"),
|
|
|
|
Type::List(_) => String::from("list"),
|
|
|
|
Type::Nothing => String::from("nothing"),
|
|
|
|
Type::Number => String::from("number"),
|
|
|
|
Type::String => String::from("string"),
|
2023-09-06 20:22:12 +02:00
|
|
|
Type::ListStream => String::from("list-stream"),
|
2023-02-22 19:18:33 +03:00
|
|
|
Type::Any => String::from("any"),
|
|
|
|
Type::Error => String::from("error"),
|
|
|
|
Type::Binary => String::from("binary"),
|
|
|
|
Type::Custom(_) => String::from("custom"),
|
|
|
|
Type::Signature => String::from("signature"),
|
`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:
https://github.com/nushell/nushell/blob/0487e9ffcbc57c2d5feca606e10c3f8221ff5e00/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.
2024-02-23 09:17:09 +08:00
|
|
|
Type::Glob => String::from("glob"),
|
2023-02-22 19:18:33 +03:00
|
|
|
}
|
|
|
|
}
|
2022-03-07 15:08:56 -05:00
|
|
|
}
|
|
|
|
|
2021-09-02 13:29:43 +12:00
|
|
|
impl Display for Type {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
match self {
|
|
|
|
Type::Block => write!(f, "block"),
|
2022-11-10 21:21:49 +13:00
|
|
|
Type::Closure => write!(f, "closure"),
|
2021-09-02 13:29:43 +12:00
|
|
|
Type::Bool => write!(f, "bool"),
|
2023-09-06 20:22:12 +02:00
|
|
|
Type::CellPath => write!(f, "cell-path"),
|
2021-10-05 15:27:39 +13:00
|
|
|
Type::Date => write!(f, "date"),
|
2021-09-02 13:29:43 +12:00
|
|
|
Type::Duration => write!(f, "duration"),
|
|
|
|
Type::Filesize => write!(f, "filesize"),
|
|
|
|
Type::Float => write!(f, "float"),
|
|
|
|
Type::Int => write!(f, "int"),
|
2021-09-05 00:52:57 +03:00
|
|
|
Type::Range => write!(f, "range"),
|
2022-11-20 21:22:42 +08:00
|
|
|
Type::Record(fields) => {
|
|
|
|
if fields.is_empty() {
|
|
|
|
write!(f, "record")
|
|
|
|
} else {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"record<{}>",
|
|
|
|
fields
|
|
|
|
.iter()
|
2023-01-30 02:37:54 +01:00
|
|
|
.map(|(x, y)| format!("{x}: {y}"))
|
2022-11-20 21:22:42 +08:00
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(", "),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Type::Table(columns) => {
|
|
|
|
if columns.is_empty() {
|
|
|
|
write!(f, "table")
|
|
|
|
} else {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"table<{}>",
|
|
|
|
columns
|
|
|
|
.iter()
|
2023-01-30 02:37:54 +01:00
|
|
|
.map(|(x, y)| format!("{x}: {y}"))
|
2022-11-20 21:22:42 +08:00
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(", ")
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2023-01-30 02:37:54 +01:00
|
|
|
Type::List(l) => write!(f, "list<{l}>"),
|
2021-09-02 13:29:43 +12:00
|
|
|
Type::Nothing => write!(f, "nothing"),
|
|
|
|
Type::Number => write!(f, "number"),
|
|
|
|
Type::String => write!(f, "string"),
|
2023-09-06 20:22:12 +02:00
|
|
|
Type::ListStream => write!(f, "list-stream"),
|
2022-04-07 16:34:09 +12:00
|
|
|
Type::Any => write!(f, "any"),
|
2021-09-06 11:16:27 +12:00
|
|
|
Type::Error => write!(f, "error"),
|
2021-09-23 17:42:03 +01:00
|
|
|
Type::Binary => write!(f, "binary"),
|
2023-01-30 02:37:54 +01:00
|
|
|
Type::Custom(custom) => write!(f, "{custom}"),
|
2021-12-27 19:13:52 +00:00
|
|
|
Type::Signature => write!(f, "signature"),
|
`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:
https://github.com/nushell/nushell/blob/0487e9ffcbc57c2d5feca606e10c3f8221ff5e00/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.
2024-02-23 09:17:09 +08:00
|
|
|
Type::Glob => write!(f, "glob"),
|
2021-09-02 13:29:43 +12:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 16:55:05 -05:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::Type;
|
|
|
|
use strum::IntoEnumIterator;
|
|
|
|
|
|
|
|
mod subtype_relation {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_reflexivity() {
|
|
|
|
for ty in Type::iter() {
|
|
|
|
assert!(ty.is_subtype(&ty));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_any_is_top_type() {
|
|
|
|
for ty in Type::iter() {
|
|
|
|
assert!(ty.is_subtype(&Type::Any));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_number_supertype() {
|
|
|
|
assert!(Type::Int.is_subtype(&Type::Number));
|
|
|
|
assert!(Type::Float.is_subtype(&Type::Number));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_list_covariance() {
|
|
|
|
for ty1 in Type::iter() {
|
|
|
|
for ty2 in Type::iter() {
|
|
|
|
let list_ty1 = Type::List(Box::new(ty1.clone()));
|
|
|
|
let list_ty2 = Type::List(Box::new(ty2.clone()));
|
|
|
|
assert_eq!(list_ty1.is_subtype(&list_ty2), ty1.is_subtype(&ty2));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|