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:
Yehuda Katz 2019-09-10 08:31:21 -07:00
parent 4d3e7efe25
commit b15bb2c667
15 changed files with 146 additions and 4 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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