mirror of
https://github.com/nushell/nushell.git
synced 2024-11-26 02:13:47 +01:00
Added glob patterns to the syntax shapes
Bare words now represent literal file names, and globs are a different syntax shape called "Pattern". This allows commands like `cp` to ask for a pattern as a source and a literal file as a target. This also means that attempting to pass a glob to a command that expects a literal path will produce an error.
This commit is contained in:
parent
4d3e7efe25
commit
b15bb2c667
@ -21,7 +21,7 @@ impl PerItemCommand for Cpy {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("cp")
|
||||
.required("src", SyntaxType::Path)
|
||||
.required("src", SyntaxType::Pattern)
|
||||
.required("dst", SyntaxType::Path)
|
||||
.named("file", SyntaxType::Any)
|
||||
.switch("recursive")
|
||||
|
@ -50,6 +50,7 @@ pub fn value_to_bson_value(v: &Tagged<Value>) -> Result<Bson, ShellError> {
|
||||
}
|
||||
Value::Primitive(Primitive::Nothing) => Bson::Null,
|
||||
Value::Primitive(Primitive::String(s)) => Bson::String(s.clone()),
|
||||
Value::Primitive(Primitive::Pattern(p)) => Bson::String(p.clone()),
|
||||
Value::Primitive(Primitive::Path(s)) => Bson::String(s.display().to_string()),
|
||||
Value::Table(l) => Bson::Array(
|
||||
l.iter()
|
||||
|
@ -45,6 +45,7 @@ pub fn value_to_json_value(v: &Tagged<Value>) -> Result<serde_json::Value, Shell
|
||||
CoerceInto::<i64>::coerce_into(i.tagged(v.tag), "converting to JSON number")?,
|
||||
)),
|
||||
Value::Primitive(Primitive::Nothing) => serde_json::Value::Null,
|
||||
Value::Primitive(Primitive::Pattern(s)) => serde_json::Value::String(s.clone()),
|
||||
Value::Primitive(Primitive::String(s)) => serde_json::Value::String(s.clone()),
|
||||
Value::Primitive(Primitive::Path(s)) => serde_json::Value::String(s.display().to_string()),
|
||||
|
||||
|
@ -91,6 +91,7 @@ fn nu_value_to_sqlite_string(v: Value) -> String {
|
||||
Primitive::Int(i) => format!("{}", i),
|
||||
Primitive::Decimal(f) => format!("{}", f),
|
||||
Primitive::Bytes(u) => format!("{}", u),
|
||||
Primitive::Pattern(s) => format!("'{}'", s.replace("'", "''")),
|
||||
Primitive::String(s) => format!("'{}'", s.replace("'", "''")),
|
||||
Primitive::Boolean(true) => "1".into(),
|
||||
Primitive::Boolean(_) => "0".into(),
|
||||
|
@ -44,6 +44,7 @@ pub fn value_to_toml_value(v: &Tagged<Value>) -> Result<toml::Value, ShellError>
|
||||
toml::Value::Integer(i.tagged(v.tag).coerce_into("converting to TOML integer")?)
|
||||
}
|
||||
Value::Primitive(Primitive::Nothing) => toml::Value::String("<Nothing>".to_string()),
|
||||
Value::Primitive(Primitive::Pattern(s)) => toml::Value::String(s.clone()),
|
||||
Value::Primitive(Primitive::String(s)) => toml::Value::String(s.clone()),
|
||||
Value::Primitive(Primitive::Path(s)) => toml::Value::String(s.display().to_string()),
|
||||
|
||||
|
@ -42,6 +42,7 @@ pub fn value_to_yaml_value(v: &Tagged<Value>) -> Result<serde_yaml::Value, Shell
|
||||
CoerceInto::<i64>::coerce_into(i.tagged(v.tag), "converting to YAML number")?,
|
||||
)),
|
||||
Value::Primitive(Primitive::Nothing) => serde_yaml::Value::Null,
|
||||
Value::Primitive(Primitive::Pattern(s)) => serde_yaml::Value::String(s.clone()),
|
||||
Value::Primitive(Primitive::String(s)) => serde_yaml::Value::String(s.clone()),
|
||||
Value::Primitive(Primitive::Path(s)) => serde_yaml::Value::String(s.display().to_string()),
|
||||
|
||||
|
@ -20,6 +20,7 @@ pub enum Primitive {
|
||||
Decimal(BigDecimal),
|
||||
Bytes(u64),
|
||||
String(String),
|
||||
Pattern(String),
|
||||
Boolean(bool),
|
||||
Date(DateTime<Utc>),
|
||||
Path(PathBuf),
|
||||
@ -53,6 +54,7 @@ impl Primitive {
|
||||
Int(_) => "int",
|
||||
Decimal(_) => "decimal",
|
||||
Bytes(_) => "bytes",
|
||||
Pattern(_) => "pattern",
|
||||
String(_) => "string",
|
||||
Boolean(_) => "boolean",
|
||||
Date(_) => "date",
|
||||
@ -71,6 +73,7 @@ impl Primitive {
|
||||
Path(path) => write!(f, "{}", path.display()),
|
||||
Decimal(decimal) => write!(f, "{}", decimal),
|
||||
Bytes(bytes) => write!(f, "{}", bytes),
|
||||
Pattern(string) => write!(f, "{:?}", string),
|
||||
String(string) => write!(f, "{:?}", string),
|
||||
Boolean(boolean) => write!(f, "{}", boolean),
|
||||
Date(date) => write!(f, "{}", date),
|
||||
@ -108,6 +111,7 @@ impl Primitive {
|
||||
}
|
||||
Primitive::Int(i) => format!("{}", i),
|
||||
Primitive::Decimal(decimal) => format!("{}", decimal),
|
||||
Primitive::Pattern(s) => format!("{}", s),
|
||||
Primitive::String(s) => format!("{}", s),
|
||||
Primitive::Boolean(b) => match (b, field_name) {
|
||||
(true, None) => format!("Yes"),
|
||||
@ -577,6 +581,10 @@ impl Value {
|
||||
Value::Primitive(Primitive::String(s.into()))
|
||||
}
|
||||
|
||||
pub fn pattern(s: impl Into<String>) -> Value {
|
||||
Value::Primitive(Primitive::String(s.into()))
|
||||
}
|
||||
|
||||
pub fn path(s: impl Into<PathBuf>) -> Value {
|
||||
Value::Primitive(Primitive::Path(s.into()))
|
||||
}
|
||||
|
@ -114,6 +114,7 @@ fn evaluate_literal(literal: Tagged<&hir::Literal>, source: &Text) -> Tagged<Val
|
||||
hir::Literal::Number(int) => int.into(),
|
||||
hir::Literal::Size(int, unit) => unit.compute(int),
|
||||
hir::Literal::String(span) => Value::string(span.slice(source)),
|
||||
hir::Literal::GlobPattern => Value::pattern(literal.span().slice(source)),
|
||||
hir::Literal::Bare => Value::string(literal.span().slice(source)),
|
||||
};
|
||||
|
||||
|
@ -17,7 +17,7 @@ use crate::evaluate::Scope;
|
||||
|
||||
pub(crate) use self::baseline_parse::{
|
||||
baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path,
|
||||
baseline_parse_token_as_string,
|
||||
baseline_parse_token_as_pattern, baseline_parse_token_as_string,
|
||||
};
|
||||
pub(crate) use self::baseline_parse_tokens::{baseline_parse_next_expr, TokensIterator};
|
||||
pub(crate) use self::binary::Binary;
|
||||
@ -90,6 +90,7 @@ pub enum RawExpression {
|
||||
Block(Vec<Expression>),
|
||||
List(Vec<Expression>),
|
||||
Path(Box<Path>),
|
||||
|
||||
FilePath(PathBuf),
|
||||
ExternalCommand(ExternalCommand),
|
||||
|
||||
@ -164,6 +165,10 @@ impl Expression {
|
||||
Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Bare), span.into())
|
||||
}
|
||||
|
||||
pub(crate) fn pattern(tag: impl Into<Tag>) -> Expression {
|
||||
RawExpression::Literal(Literal::GlobPattern).tagged(tag.into())
|
||||
}
|
||||
|
||||
pub(crate) fn variable(inner: impl Into<Span>, outer: impl Into<Span>) -> Expression {
|
||||
Tagged::from_simple_spanned_item(
|
||||
RawExpression::Variable(Variable::Other(inner.into())),
|
||||
@ -238,6 +243,7 @@ pub enum Literal {
|
||||
Number(Number),
|
||||
Size(Number, Unit),
|
||||
String(Span),
|
||||
GlobPattern,
|
||||
Bare,
|
||||
}
|
||||
|
||||
@ -247,6 +253,7 @@ impl ToDebug for Tagged<&Literal> {
|
||||
Literal::Number(number) => write!(f, "{:?}", *number),
|
||||
Literal::Size(number, unit) => write!(f, "{:?}{:?}", *number, unit),
|
||||
Literal::String(span) => write!(f, "{}", span.slice(source)),
|
||||
Literal::GlobPattern => write!(f, "{}", self.span().slice(source)),
|
||||
Literal::Bare => write!(f, "{}", self.span().slice(source)),
|
||||
}
|
||||
}
|
||||
@ -259,6 +266,7 @@ impl Literal {
|
||||
Literal::Size(..) => "size",
|
||||
Literal::String(..) => "string",
|
||||
Literal::Bare => "string",
|
||||
Literal::GlobPattern => "pattern",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::context::Context;
|
||||
use crate::errors::ShellError;
|
||||
use crate::parser::{hir, RawToken, Token};
|
||||
use crate::TaggedItem;
|
||||
use crate::Text;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -20,6 +21,7 @@ pub fn baseline_parse_single_token(
|
||||
RawToken::Variable(span) => hir::Expression::variable(span, token.span()),
|
||||
RawToken::ExternalCommand(span) => hir::Expression::external_command(span, token.span()),
|
||||
RawToken::ExternalWord => return Err(ShellError::invalid_external_word(token.span())),
|
||||
RawToken::GlobPattern => hir::Expression::pattern(token.span()),
|
||||
RawToken::Bare => hir::Expression::bare(token.span()),
|
||||
})
|
||||
}
|
||||
@ -40,6 +42,12 @@ pub fn baseline_parse_token_as_number(
|
||||
hir::Expression::size(number.to_number(source), unit, token.span())
|
||||
}
|
||||
RawToken::Bare => hir::Expression::bare(token.span()),
|
||||
RawToken::GlobPattern => {
|
||||
return Err(ShellError::type_error(
|
||||
"Number",
|
||||
"glob pattern".to_string().tagged(token.tag()),
|
||||
))
|
||||
}
|
||||
RawToken::String(span) => hir::Expression::string(span, token.span()),
|
||||
})
|
||||
}
|
||||
@ -58,6 +66,12 @@ pub fn baseline_parse_token_as_string(
|
||||
RawToken::Number(_) => hir::Expression::bare(token.span()),
|
||||
RawToken::Size(_, _) => hir::Expression::bare(token.span()),
|
||||
RawToken::Bare => hir::Expression::bare(token.span()),
|
||||
RawToken::GlobPattern => {
|
||||
return Err(ShellError::type_error(
|
||||
"String",
|
||||
"glob pattern".tagged(token.tag()),
|
||||
))
|
||||
}
|
||||
RawToken::String(span) => hir::Expression::string(span, token.span()),
|
||||
})
|
||||
}
|
||||
@ -80,6 +94,41 @@ pub fn baseline_parse_token_as_path(
|
||||
expand_path(token.span().slice(source), context),
|
||||
token.span(),
|
||||
),
|
||||
RawToken::GlobPattern => {
|
||||
return Err(ShellError::type_error(
|
||||
"Path",
|
||||
"glob pattern".tagged(token.tag()),
|
||||
))
|
||||
}
|
||||
RawToken::String(span) => {
|
||||
hir::Expression::file_path(expand_path(span.slice(source), context), token.span())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn baseline_parse_token_as_pattern(
|
||||
token: &Token,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
) -> Result<hir::Expression, ShellError> {
|
||||
Ok(match *token.item() {
|
||||
RawToken::Variable(span) if span.slice(source) == "it" => {
|
||||
hir::Expression::it_variable(span, token.span())
|
||||
}
|
||||
RawToken::ExternalCommand(_) => {
|
||||
return Err(ShellError::syntax_error(
|
||||
"Invalid external command".to_string().tagged(token.tag()),
|
||||
))
|
||||
}
|
||||
RawToken::ExternalWord => return Err(ShellError::invalid_external_word(token.span())),
|
||||
RawToken::Variable(span) => hir::Expression::variable(span, token.span()),
|
||||
RawToken::Number(_) => hir::Expression::bare(token.span()),
|
||||
RawToken::Size(_, _) => hir::Expression::bare(token.span()),
|
||||
RawToken::GlobPattern => hir::Expression::pattern(token.span()),
|
||||
RawToken::Bare => hir::Expression::file_path(
|
||||
expand_path(token.span().slice(source), context),
|
||||
token.span(),
|
||||
),
|
||||
RawToken::String(span) => {
|
||||
hir::Expression::file_path(expand_path(span.slice(source), context), token.span())
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use crate::parser::{
|
||||
hir,
|
||||
hir::{
|
||||
baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path,
|
||||
baseline_parse_token_as_string,
|
||||
baseline_parse_token_as_pattern, baseline_parse_token_as_string,
|
||||
},
|
||||
DelimitedNode, Delimiter, PathNode, RawToken, TokenNode,
|
||||
};
|
||||
@ -43,6 +43,7 @@ pub enum SyntaxType {
|
||||
Variable,
|
||||
Number,
|
||||
Path,
|
||||
Pattern,
|
||||
Binary,
|
||||
Block,
|
||||
Boolean,
|
||||
@ -59,6 +60,7 @@ impl std::fmt::Display for SyntaxType {
|
||||
SyntaxType::Variable => write!(f, "Variable"),
|
||||
SyntaxType::Number => write!(f, "Number"),
|
||||
SyntaxType::Path => write!(f, "Path"),
|
||||
SyntaxType::Pattern => write!(f, "Pattern"),
|
||||
SyntaxType::Binary => write!(f, "Binary"),
|
||||
SyntaxType::Block => write!(f, "Block"),
|
||||
SyntaxType::Boolean => write!(f, "Boolean"),
|
||||
@ -90,6 +92,17 @@ pub fn baseline_parse_next_expr(
|
||||
))
|
||||
}
|
||||
|
||||
(SyntaxType::Pattern, TokenNode::Token(token)) => {
|
||||
return baseline_parse_token_as_pattern(token, context, source)
|
||||
}
|
||||
|
||||
(SyntaxType::Pattern, token) => {
|
||||
return Err(ShellError::type_error(
|
||||
"Path",
|
||||
token.type_name().simple_spanned(token.span()),
|
||||
))
|
||||
}
|
||||
|
||||
(SyntaxType::String, TokenNode::Token(token)) => {
|
||||
return baseline_parse_token_as_string(token, source);
|
||||
}
|
||||
@ -315,6 +328,7 @@ pub fn baseline_parse_path(
|
||||
| RawToken::Size(..)
|
||||
| RawToken::Variable(_)
|
||||
| RawToken::ExternalCommand(_)
|
||||
| RawToken::GlobPattern
|
||||
| RawToken::ExternalWord => {
|
||||
return Err(ShellError::type_error(
|
||||
"String",
|
||||
|
@ -231,6 +231,29 @@ pub fn external(input: NomSpan) -> IResult<NomSpan, TokenNode> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pattern(input: NomSpan) -> IResult<NomSpan, TokenNode> {
|
||||
trace_step(input, "bare", move |input| {
|
||||
let start = input.offset;
|
||||
let (input, _) = take_while1(is_start_glob_char)(input)?;
|
||||
let (input, _) = take_while(is_glob_char)(input)?;
|
||||
|
||||
let next_char = &input.fragment.chars().nth(0);
|
||||
|
||||
if let Some(next_char) = next_char {
|
||||
if is_external_word_char(*next_char) {
|
||||
return Err(nom::Err::Error(nom::error::make_error(
|
||||
input,
|
||||
nom::error::ErrorKind::TakeWhile1,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let end = input.offset;
|
||||
|
||||
Ok((input, TokenTreeBuilder::spanned_pattern((start, end))))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bare(input: NomSpan) -> IResult<NomSpan, TokenNode> {
|
||||
trace_step(input, "bare", move |input| {
|
||||
let start = input.offset;
|
||||
@ -240,7 +263,7 @@ pub fn bare(input: NomSpan) -> IResult<NomSpan, TokenNode> {
|
||||
let next_char = &input.fragment.chars().nth(0);
|
||||
|
||||
if let Some(next_char) = next_char {
|
||||
if is_external_word_char(*next_char) {
|
||||
if is_external_word_char(*next_char) || *next_char == '*' {
|
||||
return Err(nom::Err::Error(nom::error::make_error(
|
||||
input,
|
||||
nom::error::ErrorKind::TakeWhile1,
|
||||
@ -395,6 +418,7 @@ pub fn leaf(input: NomSpan) -> IResult<NomSpan, TokenNode> {
|
||||
var,
|
||||
external,
|
||||
bare,
|
||||
pattern,
|
||||
external_word,
|
||||
))(input)?;
|
||||
|
||||
@ -655,6 +679,14 @@ fn is_external_word_char(c: char) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_start_glob_char(c: char) -> bool {
|
||||
is_start_bare_char(c) || c == '*'
|
||||
}
|
||||
|
||||
fn is_glob_char(c: char) -> bool {
|
||||
is_bare_char(c) || c == '*'
|
||||
}
|
||||
|
||||
fn is_start_bare_char(c: char) -> bool {
|
||||
match c {
|
||||
'+' => false,
|
||||
@ -680,6 +712,7 @@ fn is_bare_char(c: char) -> bool {
|
||||
'-' => true,
|
||||
'=' => true,
|
||||
'~' => true,
|
||||
':' => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -152,6 +152,24 @@ impl TokenTreeBuilder {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn pattern(input: impl Into<String>) -> CurriedToken {
|
||||
let input = input.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, end) = b.consume(&input);
|
||||
b.pos = end;
|
||||
|
||||
TokenTreeBuilder::spanned_pattern((start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_pattern(input: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Token(Tagged::from_simple_spanned_item(
|
||||
RawToken::Bare,
|
||||
input.into(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn external_word(input: impl Into<String>) -> CurriedToken {
|
||||
let input = input.into();
|
||||
|
||||
|
@ -12,6 +12,7 @@ pub enum RawToken {
|
||||
Variable(Span),
|
||||
ExternalCommand(Span),
|
||||
ExternalWord,
|
||||
GlobPattern,
|
||||
Bare,
|
||||
}
|
||||
|
||||
@ -53,6 +54,7 @@ impl RawToken {
|
||||
RawToken::Variable(_) => "Variable",
|
||||
RawToken::ExternalCommand(_) => "ExternalCommand",
|
||||
RawToken::ExternalWord => "ExternalWord",
|
||||
RawToken::GlobPattern => "GlobPattern",
|
||||
RawToken::Bare => "String",
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +123,10 @@ fn paint_token_node(token_node: &TokenNode, line: &str) -> String {
|
||||
item: RawToken::Size(..),
|
||||
..
|
||||
}) => Color::Purple.bold().paint(token_node.span().slice(line)),
|
||||
TokenNode::Token(Tagged {
|
||||
item: RawToken::GlobPattern,
|
||||
..
|
||||
}) => Color::Cyan.normal().paint(token_node.span().slice(line)),
|
||||
TokenNode::Token(Tagged {
|
||||
item: RawToken::String(..),
|
||||
..
|
||||
|
Loading…
Reference in New Issue
Block a user