mirror of
https://github.com/liljencrantz/crush.git
synced 2024-10-03 22:07:10 +03:00
Fix glob, simplify AST code a bit
This commit is contained in:
parent
8c2bf5ab79
commit
0861da103a
@ -203,12 +203,12 @@ impl<'input> Lexer<'input> {
|
||||
return Some(Token::Flag(&self.full_str[i..end_idx + 1], Location::new(i, end_idx + 1)).into());
|
||||
}
|
||||
|
||||
Some((i, ch)) if string_or_glob_first_char(ch) => {
|
||||
Some((i, ch)) if string_or_file_or_glob_first_char(ch) => {
|
||||
let mut end_idx = i;
|
||||
loop {
|
||||
let cc2 = self.chars.peek();
|
||||
match cc2 {
|
||||
Some((_, ch2)) if string_or_glob_char(*ch2) => {
|
||||
Some((_, ch2)) if string_or_file_or_glob_char(*ch2) => {
|
||||
end_idx = self.chars.next().unwrap().0;
|
||||
}
|
||||
_ => {
|
||||
@ -222,26 +222,17 @@ impl<'input> Lexer<'input> {
|
||||
return match s {
|
||||
"and" => Some(Token::LogicalOperator(s, Location::new(i, end_idx + 1)).into()),
|
||||
"or" => Some(Token::LogicalOperator(s, Location::new(i, end_idx + 1)).into()),
|
||||
_ => Some(Token::StringOrGlob(s, Location::new(i, end_idx + 1)).into()),
|
||||
};
|
||||
}
|
||||
|
||||
Some((i, ch)) if file_or_glob_first_char(ch) => {
|
||||
let mut end_idx = i;
|
||||
loop {
|
||||
let cc2 = self.chars.peek();
|
||||
match cc2 {
|
||||
Some((_, ch2)) if file_or_glob_char(*ch2) => {
|
||||
end_idx = self.chars.next().unwrap().0;
|
||||
_ => {
|
||||
if s.contains('*') || s.contains('?') {
|
||||
Some(Token::Glob(s, Location::new(i, end_idx + 1)).into())
|
||||
}
|
||||
_ => {
|
||||
break
|
||||
else if s.contains('/') || s.contains('.') || s.starts_with('~') {
|
||||
Some(Token::File(s, Location::new(i, end_idx + 1)).into())
|
||||
} else {
|
||||
Some(Token::String(s, Location::new(i, end_idx + 1)).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let s = &self.full_str[i..end_idx + 1];
|
||||
return Some(Token::FileOrGlob(s, Location::new(i, end_idx + 1)).into());
|
||||
};
|
||||
}
|
||||
|
||||
Some((i, '"')) => {
|
||||
@ -529,12 +520,12 @@ impl<'input> Lexer<'input> {
|
||||
}
|
||||
}
|
||||
|
||||
fn string_or_glob_first_char(ch: char) -> bool {
|
||||
(ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '*' || ch == '?' || ch == '_' || ch == '-'
|
||||
fn string_or_file_or_glob_first_char(ch: char) -> bool {
|
||||
(ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '*' || ch == '?' || ch == '_' || ch == '-' || ch == '.' || ch == '~' || ch == '/'
|
||||
}
|
||||
|
||||
fn string_or_glob_char(ch: char) -> bool {
|
||||
(ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '*' || ch == '?' || ch == '.' || ch == '_' || ch == '-'
|
||||
fn string_or_file_or_glob_char(ch: char) -> bool {
|
||||
string_or_file_or_glob_first_char(ch) || (ch >= '0' && ch <= '9')
|
||||
}
|
||||
|
||||
fn identifier_first_char(ch: char) -> bool {
|
||||
@ -553,14 +544,6 @@ fn number_or_underscore_char(ch: char) -> bool {
|
||||
(ch >= '0' && ch <= '9') || ch == '_'
|
||||
}
|
||||
|
||||
fn file_or_glob_first_char(ch: char) -> bool {
|
||||
ch == '.' || ch == '~' || ch == '/'
|
||||
}
|
||||
|
||||
fn file_or_glob_char(ch: char) -> bool {
|
||||
string_or_glob_char(ch) || ch == '/'
|
||||
}
|
||||
|
||||
fn whitespace_char(ch: char) -> bool {
|
||||
(ch == ' ') || (ch == '\r')
|
||||
}
|
||||
|
@ -178,322 +178,6 @@ fn propose_name(name: &TrackedString, v: ValueDefinition) -> ValueDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn prefix(&self, pos: usize) -> CrushResult<Node> {
|
||||
match self {
|
||||
Node::Identifier(s) => Ok(Node::Identifier(s.prefix(pos))),
|
||||
_ => Ok(self.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn location(&self) -> Location {
|
||||
use Node::*;
|
||||
|
||||
match self {
|
||||
Glob(s) | Identifier(s) | Symbol(s) |
|
||||
String(s) | Integer(s) | Float(s) |
|
||||
Regex(s) | File(s, _) =>
|
||||
s.location,
|
||||
|
||||
Assignment(a, _, _, b) =>
|
||||
a.location().union(b.location()),
|
||||
|
||||
Unary(s, a) =>
|
||||
s.location.union(a.location()),
|
||||
|
||||
GetItem(a, b) => a.location().union(b.location()),
|
||||
GetAttr(p, n) => p.location().union(n.location),
|
||||
Substitution(j) => j.location,
|
||||
Closure(_, j) => {
|
||||
// Fixme: Can't tab complete or error report on parameters because they're not currently tracked
|
||||
j.location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_command(&self, env: &Scope) -> CrushResult<ArgumentDefinition> {
|
||||
self.compile(env, true)
|
||||
}
|
||||
|
||||
pub fn compile_argument(&self, env: &Scope) -> CrushResult<ArgumentDefinition> {
|
||||
self.compile(env, false)
|
||||
}
|
||||
|
||||
pub fn type_name(&self) -> &str {
|
||||
match self {
|
||||
Node::Assignment(_, _, _, _) => "assignment",
|
||||
Node::Unary(_, _) => "unary operator",
|
||||
Node::Glob(_) => "glob",
|
||||
Node::Identifier(_) => "identifier",
|
||||
Node::Regex(_) => "regular expression literal",
|
||||
Node::Symbol(_) => "symbol",
|
||||
Node::String(_) => "quoted string literal",
|
||||
Node::File(_, _) => "file literal",
|
||||
Node::Integer(_) => "integer literal",
|
||||
Node::Float(_) => "floating point number literal",
|
||||
Node::GetItem(_, _) => "subscript",
|
||||
Node::GetAttr(_, _) => "member access",
|
||||
Node::Substitution(_) => "command substitution",
|
||||
Node::Closure(_, _) => "closure",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(&self, env: &Scope, is_command: bool) -> CrushResult<ArgumentDefinition> {
|
||||
Ok(ArgumentDefinition::unnamed(match self {
|
||||
Node::Assignment(target, style, op, value) => match op.deref() {
|
||||
"=" => {
|
||||
return match target.as_ref() {
|
||||
Node::Symbol(t) | Node::Identifier(t) => Ok(ArgumentDefinition::named_with_style(
|
||||
t,
|
||||
*style,
|
||||
propose_name(&t, value.compile_argument(env)?.unnamed_value()?),
|
||||
)),
|
||||
_ => error(format!("Invalid left side in named argument. Expected a string or identifier, got a {}", target.type_name())),
|
||||
};
|
||||
}
|
||||
_ => return error("Invalid assignment operator"),
|
||||
},
|
||||
|
||||
Node::GetItem(a, o) => ValueDefinition::JobDefinition(
|
||||
Job::new(vec![self
|
||||
.compile_as_special_command(env)?
|
||||
.unwrap()],
|
||||
a.location().union(o.location()),
|
||||
)),
|
||||
|
||||
Node::Unary(op, r) => match op.string.as_str() {
|
||||
"@" => {
|
||||
return Ok(ArgumentDefinition::list(
|
||||
r.compile_argument(env)?.unnamed_value()?,
|
||||
));
|
||||
}
|
||||
"@@" => {
|
||||
return Ok(ArgumentDefinition::dict(
|
||||
r.compile_argument(env)?.unnamed_value()?,
|
||||
));
|
||||
}
|
||||
_ => return error("Unknown operator"),
|
||||
},
|
||||
Node::Identifier(l) => ValueDefinition::Identifier(l.clone()),
|
||||
Node::Regex(l) => ValueDefinition::Value(
|
||||
Value::Regex(
|
||||
l.string.clone(),
|
||||
to_crush_error(Regex::new(&l.string.clone()))?, ),
|
||||
l.location,
|
||||
),
|
||||
Node::String(t) => ValueDefinition::Value(Value::from(unescape(&t.string)?), t.location),
|
||||
Node::Integer(s) =>
|
||||
ValueDefinition::Value(
|
||||
Value::Integer(to_crush_error(
|
||||
s.string.replace("_", "").parse::<i128>()
|
||||
)?),
|
||||
s.location),
|
||||
Node::Float(s) =>
|
||||
ValueDefinition::Value(
|
||||
Value::Float(to_crush_error(
|
||||
s.string.replace("_", "").parse::<f64>()
|
||||
)?),
|
||||
s.location),
|
||||
Node::GetAttr(node, identifier) =>
|
||||
ValueDefinition::GetAttr(Box::new(node.compile(env, is_command)?.unnamed_value()?), identifier.clone()),
|
||||
|
||||
Node::Symbol(f) =>
|
||||
if is_command {
|
||||
ValueDefinition::Identifier(f.clone())
|
||||
} else {
|
||||
ValueDefinition::Value(Value::from(f), f.location)
|
||||
},
|
||||
Node::Substitution(s) => ValueDefinition::JobDefinition(s.compile(env)?),
|
||||
Node::Closure(s, c) => {
|
||||
let param = s.as_ref().map(|v| {
|
||||
v.iter()
|
||||
.map(|p| p.generate(env))
|
||||
.collect::<CrushResult<Vec<Parameter>>>()
|
||||
});
|
||||
let p = match param {
|
||||
None => None,
|
||||
Some(Ok(p)) => Some(p),
|
||||
Some(Err(e)) => return Err(e),
|
||||
};
|
||||
ValueDefinition::ClosureDefinition(None, p, c.compile(env)?, c.location)
|
||||
}
|
||||
Node::Glob(g) => ValueDefinition::Value(Value::Glob(Glob::new(&g.string)), g.location),
|
||||
Node::File(s, quoted) => ValueDefinition::Value(
|
||||
Value::from(
|
||||
if *quoted { PathBuf::from(&unescape(&s.string)?) } else { PathBuf::from(&s.string) }
|
||||
),
|
||||
s.location,
|
||||
),
|
||||
}))
|
||||
}
|
||||
|
||||
fn compile_standalone_assignment(
|
||||
target: &Box<Node>,
|
||||
op: &String,
|
||||
value: &Node,
|
||||
env: &Scope,
|
||||
) -> CrushResult<Option<CommandInvocation>> {
|
||||
match op.deref() {
|
||||
"=" => match target.as_ref() {
|
||||
Node::Identifier(t) => Node::function_invocation(
|
||||
env.global_static_cmd(vec!["global", "var", "set"])?,
|
||||
t.location,
|
||||
vec![ArgumentDefinition::named(
|
||||
t,
|
||||
propose_name(&t, value.compile_argument(env)?.unnamed_value()?),
|
||||
)],
|
||||
),
|
||||
|
||||
Node::GetItem(container, key) => container.method_invocation(
|
||||
&TrackedString::new("__setitem__", key.location()),
|
||||
vec![
|
||||
ArgumentDefinition::unnamed(key.compile_argument(env)?.unnamed_value()?),
|
||||
ArgumentDefinition::unnamed(value.compile_argument(env)?.unnamed_value()?),
|
||||
],
|
||||
env,
|
||||
true,
|
||||
),
|
||||
|
||||
Node::GetAttr(container, attr) => container.method_invocation(
|
||||
&TrackedString::new("__setattr__", attr.location),
|
||||
vec![
|
||||
ArgumentDefinition::unnamed(ValueDefinition::Value(Value::from(attr),
|
||||
attr.location)),
|
||||
ArgumentDefinition::unnamed(value.compile_argument(env)?.unnamed_value()?),
|
||||
],
|
||||
env,
|
||||
true,
|
||||
),
|
||||
|
||||
_ => error("Invalid left side in assignment"),
|
||||
},
|
||||
":=" => match target.as_ref() {
|
||||
Node::Identifier(t) => Node::function_invocation(
|
||||
env.global_static_cmd(vec!["global", "var", "let"])?,
|
||||
t.location,
|
||||
vec![ArgumentDefinition::named(
|
||||
t,
|
||||
propose_name(&t, value.compile_argument(env)?.unnamed_value()?),
|
||||
)],
|
||||
),
|
||||
_ => error("Invalid left side in declaration"),
|
||||
},
|
||||
_ => error("Unknown assignment operator"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_as_special_command(&self, env: &Scope) -> CrushResult<Option<CommandInvocation>> {
|
||||
match self {
|
||||
Node::Assignment(target, _style, op, value) => {
|
||||
Node::compile_standalone_assignment(target, op, value, env)
|
||||
}
|
||||
|
||||
Node::GetItem(val, key) => {
|
||||
val.method_invocation(&TrackedString::new("__getitem__", key.location()), vec![key.compile_argument(env)?], env, true)
|
||||
}
|
||||
|
||||
Node::Unary(op, _) => match op.string.as_ref() {
|
||||
"@" | "@@" => Ok(None),
|
||||
_ => error("Unknown operator"),
|
||||
},
|
||||
|
||||
Node::Glob(_)
|
||||
| Node::Identifier(_)
|
||||
| Node::Regex(_)
|
||||
| Node::Symbol(_)
|
||||
| Node::String(_)
|
||||
| Node::Integer(_)
|
||||
| Node::Float(_)
|
||||
| Node::GetAttr(_, _)
|
||||
| Node::Substitution(_)
|
||||
| Node::Closure(_, _)
|
||||
| Node::File(_, _) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn function_invocation(
|
||||
function: Command,
|
||||
location: Location,
|
||||
arguments: Vec<ArgumentDefinition>,
|
||||
) -> CrushResult<Option<CommandInvocation>> {
|
||||
Ok(Some(CommandInvocation::new(
|
||||
ValueDefinition::Value(Value::from(function), location),
|
||||
arguments,
|
||||
)))
|
||||
}
|
||||
|
||||
fn method_invocation(
|
||||
&self,
|
||||
name: &TrackedString,
|
||||
arguments: Vec<ArgumentDefinition>,
|
||||
env: &Scope,
|
||||
as_command: bool,
|
||||
) -> CrushResult<Option<CommandInvocation>> {
|
||||
Ok(Some(CommandInvocation::new(
|
||||
ValueDefinition::GetAttr(
|
||||
Box::from(self.compile(env, as_command)?.unnamed_value()?),
|
||||
name.clone(),
|
||||
),
|
||||
arguments,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn parse_symbol_or_glob(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
let s = is.into();
|
||||
let path = expand_user(s.string.clone()).unwrap_or_else(|_| { s.string.clone() });
|
||||
let ts = TrackedString::new(&path, s.location);
|
||||
if path.contains('*') || path.contains('?') {
|
||||
Box::from(Node::Glob(ts))
|
||||
} else if s.string.contains('/') || s.string.contains('.') {
|
||||
Box::from(Node::File(ts, false))
|
||||
} else {
|
||||
Box::from(Node::Symbol(ts))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identifier(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
let s = is.into();
|
||||
if s.string.starts_with("$") {
|
||||
Box::from(Node::Identifier(s.slice_to_end(1)))
|
||||
} else {
|
||||
Box::from(Node::Identifier(s))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_file_or_glob(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
let s = is.into();
|
||||
let path = expand_user(s.string.clone()).unwrap_or_else(|_| { s.string.clone() });
|
||||
let ts = TrackedString::new(&path, s.location);
|
||||
if ts.string.contains('*') || ts.string.contains('?') {
|
||||
Box::from(Node::Glob(ts.clone()))
|
||||
} else {
|
||||
Box::from(Node::File(ts.clone(), false))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file(is: impl Into<TrackedString>, quoted: bool) -> Box<Node> {
|
||||
Box::from(Node::File(is.into(), quoted))
|
||||
}
|
||||
|
||||
pub fn string(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
Box::from(Node::String(is.into()))
|
||||
}
|
||||
|
||||
pub fn integer(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
Box::from(Node::Integer(is.into()))
|
||||
}
|
||||
|
||||
pub fn float(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
Box::from(Node::Float(is.into()))
|
||||
}
|
||||
|
||||
pub fn regex(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
let ts = is.into();
|
||||
let s = ts.string;
|
||||
Box::from(Node::Regex(TrackedString::new(&s[3..s.len() - 1], ts.location)))
|
||||
}
|
||||
}
|
||||
|
||||
fn attr(parts: &[&str], location: Location) -> Node {
|
||||
let mut res = Node::Identifier(TrackedString::new(parts[0], location));
|
||||
for part in &parts[1..] {
|
||||
|
@ -1,8 +1,19 @@
|
||||
use crate::lang::argument::SwitchStyle;
|
||||
use crate::lang::ast::{CommandNode, JobListNode, JobNode};
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use regex::Regex;
|
||||
use crate::lang::argument::{ArgumentDefinition, SwitchStyle};
|
||||
use crate::lang::ast::{CommandNode, expand_user, JobListNode, JobNode, propose_name};
|
||||
use crate::lang::ast::location::Location;
|
||||
use crate::lang::ast::parameter_node::ParameterNode;
|
||||
use crate::lang::ast::tracked_string::TrackedString;
|
||||
use crate::lang::command::{Command, Parameter};
|
||||
use crate::lang::command_invocation::CommandInvocation;
|
||||
use crate::lang::errors::{CrushResult, error, to_crush_error};
|
||||
use crate::lang::job::Job;
|
||||
use crate::lang::state::scope::Scope;
|
||||
use crate::lang::value::{Value, ValueDefinition};
|
||||
use crate::util::escape::unescape;
|
||||
use crate::util::glob::Glob;
|
||||
|
||||
/**
|
||||
A type representing a node in the abstract syntax tree that is the output of parsing a Crush script.
|
||||
@ -14,8 +25,8 @@ pub enum Node {
|
||||
Glob(TrackedString),
|
||||
Identifier(TrackedString),
|
||||
Regex(TrackedString),
|
||||
Symbol(TrackedString),
|
||||
String(TrackedString),
|
||||
// true if filename is quoted
|
||||
String(TrackedString, bool),
|
||||
// true if filename is quoted
|
||||
File(TrackedString, bool),
|
||||
Integer(TrackedString),
|
||||
@ -63,3 +74,301 @@ impl Node {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn prefix(&self, pos: usize) -> CrushResult<Node> {
|
||||
match self {
|
||||
Node::Identifier(s) => Ok(Node::Identifier(s.prefix(pos))),
|
||||
_ => Ok(self.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn location(&self) -> Location {
|
||||
use Node::*;
|
||||
|
||||
match self {
|
||||
Glob(s) | Identifier(s) |
|
||||
String(s, _) | Integer(s) | Float(s) |
|
||||
Regex(s) | File(s, _) =>
|
||||
s.location,
|
||||
|
||||
Assignment(a, _, _, b) =>
|
||||
a.location().union(b.location()),
|
||||
|
||||
Unary(s, a) =>
|
||||
s.location.union(a.location()),
|
||||
|
||||
GetItem(a, b) => a.location().union(b.location()),
|
||||
GetAttr(p, n) => p.location().union(n.location),
|
||||
Substitution(j) => j.location,
|
||||
Closure(_, j) => {
|
||||
// Fixme: Can't tab complete or error report on parameters because they're not currently tracked
|
||||
j.location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_command(&self, env: &Scope) -> CrushResult<ArgumentDefinition> {
|
||||
self.compile(env, true)
|
||||
}
|
||||
|
||||
pub fn compile_argument(&self, env: &Scope) -> CrushResult<ArgumentDefinition> {
|
||||
self.compile(env, false)
|
||||
}
|
||||
|
||||
pub fn type_name(&self) -> &str {
|
||||
match self {
|
||||
Node::Assignment(_, _, _, _) => "assignment",
|
||||
Node::Unary(_, _) => "unary operator",
|
||||
Node::Glob(_) => "glob",
|
||||
Node::Identifier(_) => "identifier",
|
||||
Node::Regex(_) => "regular expression literal",
|
||||
Node::String(_, _) => "string literal",
|
||||
Node::File(_, _) => "file literal",
|
||||
Node::Integer(_) => "integer literal",
|
||||
Node::Float(_) => "floating point number literal",
|
||||
Node::GetItem(_, _) => "subscript",
|
||||
Node::GetAttr(_, _) => "member access",
|
||||
Node::Substitution(_) => "command substitution",
|
||||
Node::Closure(_, _) => "closure",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(&self, env: &Scope, is_command: bool) -> CrushResult<ArgumentDefinition> {
|
||||
Ok(ArgumentDefinition::unnamed(match self {
|
||||
Node::Assignment(target, style, op, value) => match op.deref() {
|
||||
"=" => {
|
||||
return match target.as_ref() {
|
||||
Node::String(t, false) | Node::Identifier(t) => Ok(ArgumentDefinition::named_with_style(
|
||||
t,
|
||||
*style,
|
||||
propose_name(&t, value.compile_argument(env)?.unnamed_value()?),
|
||||
)),
|
||||
_ => error(format!("Invalid left side in named argument. Expected a string or identifier, got a {}", target.type_name())),
|
||||
};
|
||||
}
|
||||
_ => return error("Invalid assignment operator"),
|
||||
},
|
||||
|
||||
Node::GetItem(a, o) => ValueDefinition::JobDefinition(
|
||||
Job::new(vec![self
|
||||
.compile_as_special_command(env)?
|
||||
.unwrap()],
|
||||
a.location().union(o.location()),
|
||||
)),
|
||||
|
||||
Node::Unary(op, r) => match op.string.as_str() {
|
||||
"@" => {
|
||||
return Ok(ArgumentDefinition::list(
|
||||
r.compile_argument(env)?.unnamed_value()?,
|
||||
));
|
||||
}
|
||||
"@@" => {
|
||||
return Ok(ArgumentDefinition::dict(
|
||||
r.compile_argument(env)?.unnamed_value()?,
|
||||
));
|
||||
}
|
||||
_ => return error("Unknown operator"),
|
||||
},
|
||||
Node::Identifier(l) => ValueDefinition::Identifier(l.clone()),
|
||||
Node::Regex(l) => ValueDefinition::Value(
|
||||
Value::Regex(
|
||||
l.string.clone(),
|
||||
to_crush_error(Regex::new(&l.string.clone()))?, ),
|
||||
l.location,
|
||||
),
|
||||
Node::String(t, true) => ValueDefinition::Value(Value::from(unescape(&t.string)?), t.location),
|
||||
Node::String(f, false) =>
|
||||
if is_command {
|
||||
ValueDefinition::Identifier(f.clone())
|
||||
} else {
|
||||
ValueDefinition::Value(Value::from(f), f.location)
|
||||
},
|
||||
Node::Integer(s) =>
|
||||
ValueDefinition::Value(
|
||||
Value::Integer(to_crush_error(
|
||||
s.string.replace("_", "").parse::<i128>()
|
||||
)?),
|
||||
s.location),
|
||||
Node::Float(s) =>
|
||||
ValueDefinition::Value(
|
||||
Value::Float(to_crush_error(
|
||||
s.string.replace("_", "").parse::<f64>()
|
||||
)?),
|
||||
s.location),
|
||||
Node::GetAttr(node, identifier) =>
|
||||
ValueDefinition::GetAttr(Box::new(node.compile(env, is_command)?.unnamed_value()?), identifier.clone()),
|
||||
|
||||
Node::Substitution(s) => ValueDefinition::JobDefinition(s.compile(env)?),
|
||||
Node::Closure(s, c) => {
|
||||
let param = s.as_ref().map(|v| {
|
||||
v.iter()
|
||||
.map(|p| p.generate(env))
|
||||
.collect::<CrushResult<Vec<Parameter>>>()
|
||||
});
|
||||
let p = match param {
|
||||
None => None,
|
||||
Some(Ok(p)) => Some(p),
|
||||
Some(Err(e)) => return Err(e),
|
||||
};
|
||||
ValueDefinition::ClosureDefinition(None, p, c.compile(env)?, c.location)
|
||||
}
|
||||
Node::Glob(g) => ValueDefinition::Value(Value::Glob(Glob::new(&g.string)), g.location),
|
||||
Node::File(s, quoted) => ValueDefinition::Value(
|
||||
Value::from(
|
||||
if *quoted { PathBuf::from(&unescape(&s.string)?) } else { PathBuf::from(&s.string) }
|
||||
),
|
||||
s.location,
|
||||
),
|
||||
}))
|
||||
}
|
||||
|
||||
fn compile_standalone_assignment(
|
||||
target: &Box<Node>,
|
||||
op: &String,
|
||||
value: &Node,
|
||||
env: &Scope,
|
||||
) -> CrushResult<Option<CommandInvocation>> {
|
||||
match op.deref() {
|
||||
"=" => match target.as_ref() {
|
||||
Node::Identifier(t) => Node::function_invocation(
|
||||
env.global_static_cmd(vec!["global", "var", "set"])?,
|
||||
t.location,
|
||||
vec![ArgumentDefinition::named(
|
||||
t,
|
||||
propose_name(&t, value.compile_argument(env)?.unnamed_value()?),
|
||||
)],
|
||||
),
|
||||
|
||||
Node::GetItem(container, key) => container.method_invocation(
|
||||
&TrackedString::new("__setitem__", key.location()),
|
||||
vec![
|
||||
ArgumentDefinition::unnamed(key.compile_argument(env)?.unnamed_value()?),
|
||||
ArgumentDefinition::unnamed(value.compile_argument(env)?.unnamed_value()?),
|
||||
],
|
||||
env,
|
||||
true,
|
||||
),
|
||||
|
||||
Node::GetAttr(container, attr) => container.method_invocation(
|
||||
&TrackedString::new("__setattr__", attr.location),
|
||||
vec![
|
||||
ArgumentDefinition::unnamed(ValueDefinition::Value(Value::from(attr),
|
||||
attr.location)),
|
||||
ArgumentDefinition::unnamed(value.compile_argument(env)?.unnamed_value()?),
|
||||
],
|
||||
env,
|
||||
true,
|
||||
),
|
||||
|
||||
_ => error("Invalid left side in assignment"),
|
||||
},
|
||||
":=" => match target.as_ref() {
|
||||
Node::Identifier(t) => Node::function_invocation(
|
||||
env.global_static_cmd(vec!["global", "var", "let"])?,
|
||||
t.location,
|
||||
vec![ArgumentDefinition::named(
|
||||
t,
|
||||
propose_name(&t, value.compile_argument(env)?.unnamed_value()?),
|
||||
)],
|
||||
),
|
||||
_ => error("Invalid left side in declaration"),
|
||||
},
|
||||
_ => error("Unknown assignment operator"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_as_special_command(&self, env: &Scope) -> CrushResult<Option<CommandInvocation>> {
|
||||
match self {
|
||||
Node::Assignment(target, _style, op, value) => {
|
||||
Node::compile_standalone_assignment(target, op, value, env)
|
||||
}
|
||||
|
||||
Node::GetItem(val, key) => {
|
||||
val.method_invocation(&TrackedString::new("__getitem__", key.location()), vec![key.compile_argument(env)?], env, true)
|
||||
}
|
||||
|
||||
Node::Unary(op, _) => match op.string.as_ref() {
|
||||
"@" | "@@" => Ok(None),
|
||||
_ => error("Unknown operator"),
|
||||
},
|
||||
|
||||
Node::Glob(_)
|
||||
| Node::Identifier(_)
|
||||
| Node::Regex(_)
|
||||
| Node::String(_, _)
|
||||
| Node::Integer(_)
|
||||
| Node::Float(_)
|
||||
| Node::GetAttr(_, _)
|
||||
| Node::Substitution(_)
|
||||
| Node::Closure(_, _)
|
||||
| Node::File(_, _) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn function_invocation(
|
||||
function: Command,
|
||||
location: Location,
|
||||
arguments: Vec<ArgumentDefinition>,
|
||||
) -> CrushResult<Option<CommandInvocation>> {
|
||||
Ok(Some(CommandInvocation::new(
|
||||
ValueDefinition::Value(Value::from(function), location),
|
||||
arguments,
|
||||
)))
|
||||
}
|
||||
|
||||
fn method_invocation(
|
||||
&self,
|
||||
name: &TrackedString,
|
||||
arguments: Vec<ArgumentDefinition>,
|
||||
env: &Scope,
|
||||
as_command: bool,
|
||||
) -> CrushResult<Option<CommandInvocation>> {
|
||||
Ok(Some(CommandInvocation::new(
|
||||
ValueDefinition::GetAttr(
|
||||
Box::from(self.compile(env, as_command)?.unnamed_value()?),
|
||||
name.clone(),
|
||||
),
|
||||
arguments,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn identifier(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
let s = is.into();
|
||||
if s.string.starts_with("$") {
|
||||
Box::from(Node::Identifier(s.slice_to_end(1)))
|
||||
} else {
|
||||
Box::from(Node::Identifier(s))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file(is: impl Into<TrackedString>, quoted: bool) -> Box<Node> {
|
||||
Box::from(Node::File(is.into(), quoted))
|
||||
}
|
||||
|
||||
pub fn quoted_string(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
Box::from(Node::String(is.into(), true))
|
||||
}
|
||||
|
||||
pub fn unquoted_string(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
Box::from(Node::String(is.into(), false))
|
||||
}
|
||||
|
||||
pub fn glob(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
Box::from(Node::Glob(is.into()))
|
||||
}
|
||||
|
||||
pub fn integer(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
Box::from(Node::Integer(is.into()))
|
||||
}
|
||||
|
||||
pub fn float(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
Box::from(Node::Float(is.into()))
|
||||
}
|
||||
|
||||
pub fn regex(is: impl Into<TrackedString>) -> Box<Node> {
|
||||
let ts = is.into();
|
||||
let s = ts.string;
|
||||
Box::from(Node::Regex(TrackedString::new(&s[3..s.len() - 1], ts.location)))
|
||||
}
|
||||
}
|
||||
|
@ -14,11 +14,12 @@ pub enum Token<'input> {
|
||||
Star(Location),
|
||||
Slash(Location),
|
||||
QuotedString(&'input str, Location),
|
||||
StringOrGlob(&'input str, Location),
|
||||
Identifier(&'input str, Location),
|
||||
Flag(&'input str, Location),
|
||||
QuotedFile(&'input str, Location),
|
||||
FileOrGlob(&'input str, Location),
|
||||
Glob(&'input str, Location),
|
||||
File(&'input str, Location),
|
||||
String(&'input str, Location),
|
||||
Regex(&'input str, Location),
|
||||
Integer(&'input str, Location),
|
||||
Float(&'input str, Location),
|
||||
@ -45,11 +46,12 @@ impl Token<'_> {
|
||||
Token::UnaryOperator(_, l) |
|
||||
Token::ComparisonOperator(_, l) |
|
||||
Token::QuotedString(_, l) |
|
||||
Token::StringOrGlob(_, l) |
|
||||
Token::String(_, l) |
|
||||
Token::File(_, l) |
|
||||
Token::Glob(_, l) |
|
||||
Token::Identifier(_, l) |
|
||||
Token::Flag(_, l) |
|
||||
Token::QuotedFile(_, l) |
|
||||
Token::FileOrGlob(_, l) |
|
||||
Token::Regex(_, l) |
|
||||
Token::Integer(_, l) |
|
||||
Token::Float(_, l) |
|
||||
@ -81,11 +83,12 @@ impl Token<'_> {
|
||||
Token::UnaryOperator(s, _) |
|
||||
Token::ComparisonOperator(s, _) |
|
||||
Token::QuotedString(s, _) |
|
||||
Token::StringOrGlob(s, _) |
|
||||
Token::String(s, _) |
|
||||
Token::File(s, _) |
|
||||
Token::Glob(s, _) |
|
||||
Token::Identifier(s, _) |
|
||||
Token::Flag(s, _) |
|
||||
Token::QuotedFile(s, _) |
|
||||
Token::FileOrGlob(s, _) |
|
||||
Token::Regex(s, _) |
|
||||
Token::Integer(s, _) |
|
||||
Token::Separator(s, _) |
|
||||
@ -128,11 +131,12 @@ impl<'a> Into<Spanned<'a>> for Token<'a> {
|
||||
Token::LogicalOperator(_, l) |
|
||||
Token::UnaryOperator(_, l) |
|
||||
Token::QuotedString(_, l) |
|
||||
Token::StringOrGlob(_, l) |
|
||||
Token::String(_, l) |
|
||||
Token::File(_, l) |
|
||||
Token::Glob(_, l) |
|
||||
Token::Identifier(_, l) |
|
||||
Token::Flag(_, l) |
|
||||
Token::QuotedFile(_, l) |
|
||||
Token::FileOrGlob(_, l) |
|
||||
Token::Regex(_, l) |
|
||||
Token::Integer(_, l) |
|
||||
Token::ComparisonOperator(_, l) |
|
||||
|
@ -88,11 +88,12 @@ impl From<Token<'_>> for TrackedString {
|
||||
Token::UnaryOperator(_, l) |
|
||||
Token::ComparisonOperator(_, l) |
|
||||
Token::QuotedString(_, l) |
|
||||
Token::StringOrGlob(_, l) |
|
||||
Token::String(_, l) |
|
||||
Token::File(_, l) |
|
||||
Token::Glob(_, l) |
|
||||
Token::Identifier(_, l) |
|
||||
Token::Flag(_, l) |
|
||||
Token::QuotedFile(_, l) |
|
||||
Token::FileOrGlob(_, l) |
|
||||
Token::Regex(_, l) |
|
||||
Token::Integer(_, l) |
|
||||
Token::Float(_, l) |
|
||||
|
@ -355,7 +355,7 @@ pub fn complete(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::lang::value::Value;
|
||||
use crate::util::directory_lister::FakeDirectoryLister;
|
||||
use crate::util::directory_lister::tests::FakeDirectoryLister;
|
||||
use signature::signature;
|
||||
use crate::lang::state::contexts::CommandContext;
|
||||
|
||||
|
@ -218,7 +218,9 @@ fn fetch_value(node: &Node, scope: &Scope, is_command: bool) -> CrushResult<Opti
|
||||
match node {
|
||||
Node::Identifier(l) => scope.get(&l.string),
|
||||
|
||||
Node::Symbol(l) =>
|
||||
Node::String(s, true) => Ok(Some(Value::from(s))),
|
||||
|
||||
Node::String(l, false) =>
|
||||
if is_command {
|
||||
scope.get(&l.string)
|
||||
} else {
|
||||
@ -231,8 +233,6 @@ fn fetch_value(node: &Node, scope: &Scope, is_command: bool) -> CrushResult<Opti
|
||||
None => Ok(None),
|
||||
},
|
||||
|
||||
Node::String(s) => Ok(Some(Value::from(s))),
|
||||
|
||||
Node::Integer(s) => Ok(Some(Value::Integer(to_crush_error(
|
||||
s.string.replace("_", "").parse::<i128>()
|
||||
)?))),
|
||||
@ -264,7 +264,7 @@ fn parse_previous_argument(arg: &Node) -> PreviousArgument {
|
||||
match arg {
|
||||
Node::Assignment(key, _, op, value) => {
|
||||
match (key.as_ref(), op.as_str()) {
|
||||
(Node::Symbol(name), "=") => {
|
||||
(Node::String(name, false), "=") => {
|
||||
let inner = parse_previous_argument(value.as_ref());
|
||||
return PreviousArgument {
|
||||
name: Some(name.string.clone()),
|
||||
@ -307,7 +307,10 @@ pub fn parse(
|
||||
Ok(ParseResult::PartialLabel(
|
||||
label.prefix(cursor).string)),
|
||||
|
||||
Node::Symbol(label) =>
|
||||
Node::String(string, true) =>
|
||||
Ok(ParseResult::PartialQuotedString(string.prefix(cursor).string)),
|
||||
|
||||
Node::String(label, false) =>
|
||||
Ok(ParseResult::PartialField(
|
||||
label.prefix(cursor).string)),
|
||||
|
||||
@ -321,9 +324,6 @@ pub fn parse(
|
||||
if *quoted { unescape(&path.string)? } else { path.string.clone() },
|
||||
*quoted)),
|
||||
|
||||
Node::String(string) =>
|
||||
Ok(ParseResult::PartialQuotedString(string.prefix(cursor).string)),
|
||||
|
||||
Node::GetItem(_, _) => { panic!("AAA"); }
|
||||
|
||||
_ => error(format!("Can't extract command to complete. Unknown node type {}", cmd.type_name())),
|
||||
@ -363,7 +363,7 @@ pub fn parse(
|
||||
|
||||
if argument_complete {
|
||||
match arg.deref() {
|
||||
Node::Symbol(l) => {
|
||||
Node::String(l, false) => {
|
||||
let substring = &l.string[0..min(l.string.len(), cursor - l.location.start)];
|
||||
Ok(ParseResult::PartialArgument(
|
||||
PartialCommandResult {
|
||||
@ -390,7 +390,7 @@ pub fn parse(
|
||||
}
|
||||
)),
|
||||
|
||||
Node::Symbol(l) =>
|
||||
Node::String(l, false) =>
|
||||
Ok(ParseResult::PartialArgument(
|
||||
PartialCommandResult {
|
||||
command: c,
|
||||
@ -423,7 +423,7 @@ pub fn parse(
|
||||
}
|
||||
)),
|
||||
|
||||
Node::String(s) =>
|
||||
Node::String(s, true) =>
|
||||
Ok(ParseResult::PartialArgument(
|
||||
PartialCommandResult {
|
||||
command: c,
|
||||
|
@ -54,9 +54,9 @@ impl RustylineHelper {
|
||||
vec!["global".to_string(), "crush".to_string(), "highlight".to_string()]) {
|
||||
use Token::*;
|
||||
let res = match token_type {
|
||||
Flag(_, _) | StringOrGlob(_, _) | QuotedString(_, _) => highlight.get(&Value::from("string_literal")),
|
||||
Flag(_, _) | String(_, _) | QuotedString(_, _) => highlight.get(&Value::from("string_literal")),
|
||||
Regex(_, _) => highlight.get(&Value::from("string_literal")),
|
||||
FileOrGlob(_, _) | QuotedFile(_, _) => highlight.get(&Value::from("file_literal")),
|
||||
File(_, _) | Glob(_, _) | QuotedFile(_, _) => highlight.get(&Value::from("file_literal")),
|
||||
Float(_, _) | Integer(_, _) => highlight.get(&Value::from("numeric_literal")),
|
||||
Unnamed(_) | Named( _) | Pipe( _) | LogicalOperator(_, _) | UnaryOperator(_, _) |
|
||||
ComparisonOperator(_, _) | Equals( _) | Declare( _) | GetItemEnd( _) | GetItemStart( _) | SubEnd( _) |
|
||||
|
@ -95,7 +95,7 @@ ItemExpr: Box<Node> = {
|
||||
<l: Identifier> => Node::identifier(l),
|
||||
<l: Regex> => Node::regex(l),
|
||||
<l:QuotedFile> => Node::file(l, true),
|
||||
<s:QuotedString> => Node::string(s),
|
||||
<s:QuotedString> => Node::quoted_string(s),
|
||||
<i:Integer> => Node::integer(i),
|
||||
<f:Float> => Node::float(f),
|
||||
<i: ItemExpr> GetItemStart <e: Assignment> GetItemEnd => Box::from(Node::GetItem(i, e)),
|
||||
@ -194,12 +194,13 @@ Default: Option<Node> = {
|
||||
}
|
||||
|
||||
Item: Box<Node> = {
|
||||
<l: StringOrGlob> => Node::parse_symbol_or_glob(l),
|
||||
<l: String> => Node::unquoted_string(l),
|
||||
<l: File> => Node::file(l, false),
|
||||
<l: Glob> => Node::glob(l),
|
||||
<l: Identifier> => Node::identifier(l),
|
||||
<l: Regex> => Node::regex(l),
|
||||
<l: FileOrGlob> => Node::parse_file_or_glob(l),
|
||||
<l:QuotedFile> => Node::file(l, true),
|
||||
<l:QuotedString> => Node::string(l),
|
||||
<l:QuotedString> => Node::quoted_string(l),
|
||||
<l:Integer> => Node::integer(l),
|
||||
<l:Float> => Node::float(l),
|
||||
|
||||
@ -207,19 +208,19 @@ Item: Box<Node> = {
|
||||
let ts = TrackedString::from(l);
|
||||
Box::from(
|
||||
if ts.string.starts_with("--") {
|
||||
Node::Assignment(Box::from(Node::Symbol(ts.slice_to_end(2))),
|
||||
Node::Assignment(Node::unquoted_string(ts.slice_to_end(2)),
|
||||
SwitchStyle::Double,
|
||||
"=".to_string(),
|
||||
Box::from(Node::Identifier(TrackedString::new("true", ts.location()))))
|
||||
} else {
|
||||
Node::Assignment(Box::from(Node::Symbol(ts.slice_to_end(1))),
|
||||
Node::Assignment(Node::unquoted_string(ts.slice_to_end(1)),
|
||||
SwitchStyle::Single,
|
||||
"=".to_string(),
|
||||
Box::from(Node::Identifier(TrackedString::new("true", ts.location()))))
|
||||
})
|
||||
},
|
||||
<i: Item> GetItemStart <e: Assignment> GetItemEnd => Box::from(Node::GetItem(i, e)),
|
||||
<i: Item> MemberOperator <start: @L> <l: StringOrGlob> <end: @R> => Box::from(Node::GetAttr(i, TrackedString::from(l))),
|
||||
<i: Item> MemberOperator <start: @L> <l: String> <end: @R> => Box::from(Node::GetAttr(i, TrackedString::from(l))),
|
||||
JobStart Separators? <s: Signature> <l: JobListWithoutSeparator> JobEnd => Box::from(Node::Closure(s, l)),
|
||||
SubStart <j:Job> SubEnd => Box::from(Node::Substitution(j)),
|
||||
<l: @L>ExprModeStart <e:Expr> SubEnd <r: @R> => Box::from(Node::Substitution(e.expression_to_job())),
|
||||
@ -246,11 +247,12 @@ extern {
|
||||
Plus=> Token::Plus(<Location>),
|
||||
Minus=> Token::Minus(<Location>),
|
||||
QuotedString=> Token::QuotedString(<&'input str>, <Location>),
|
||||
StringOrGlob=> Token::StringOrGlob(<&'input str>, <Location>),
|
||||
String=> Token::String(<&'input str>, <Location>),
|
||||
File=> Token::File(<&'input str>, <Location>),
|
||||
Glob=> Token::Glob(<&'input str>, <Location>),
|
||||
Identifier=> Token::Identifier(<&'input str>, <Location>),
|
||||
Flag=> Token::Flag(<&'input str>, <Location>),
|
||||
QuotedFile=> Token::QuotedFile(<&'input str>, <Location>),
|
||||
FileOrGlob=> Token::FileOrGlob(<&'input str>, <Location>),
|
||||
Regex=> Token::Regex(<&'input str>, <Location>),
|
||||
Separator=> Token::Separator(<&'input str>, <Location>),
|
||||
Integer=> Token::Integer(<&'input str>, <Location>),
|
||||
|
@ -106,11 +106,12 @@ impl Parser {
|
||||
Token::GetItemStart( _) => { stack.push("]"); }
|
||||
Token::SubEnd( _) | Token::JobEnd( _) | Token::GetItemEnd( _) => { stack.pop(); }
|
||||
Token::QuotedString(_, _) => {}
|
||||
Token::StringOrGlob(_, _) => {}
|
||||
Token::String(_, _) => {}
|
||||
Token::File(_, _) => {}
|
||||
Token::Glob(_, _) => {}
|
||||
Token::Identifier(_, _) => {}
|
||||
Token::Flag(_, _) => {}
|
||||
Token::QuotedFile(_, _) => {}
|
||||
Token::FileOrGlob(_, _) => {}
|
||||
Token::Regex(_, _) => {}
|
||||
Token::Integer(_, _) => {}
|
||||
Token::Float(_, _) => {}
|
||||
@ -141,7 +142,7 @@ mod tests {
|
||||
let tok = p().tokenize("{aaa}\n").unwrap();
|
||||
assert_eq!(tok, vec![
|
||||
Token::JobStart(Location::from(0)),
|
||||
Token::StringOrGlob("aaa", Location::new(1,4)),
|
||||
Token::String("aaa", Location::new(1,4)),
|
||||
Token::JobEnd(Location::from(4)),
|
||||
Token::Separator("\n", Location::from(5)),
|
||||
]);
|
||||
|
@ -73,117 +73,117 @@ impl Iterator for RealIter {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub struct FakeDirectoryLister {
|
||||
cwd: PathBuf,
|
||||
map: OrderedMap<PathBuf, Vec<FakeListerEntry>>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl FakeDirectoryLister {
|
||||
pub fn new(cwd: impl Into<PathBuf>) -> FakeDirectoryLister {
|
||||
FakeDirectoryLister {
|
||||
map: OrderedMap::new(),
|
||||
cwd: cwd.into(),
|
||||
}
|
||||
pub mod tests {
|
||||
use std::collections::VecDeque;
|
||||
use ordered_map::{Entry, OrderedMap};
|
||||
use crate::lang::errors::mandate;
|
||||
use super::*;
|
||||
|
||||
pub struct FakeDirectoryLister {
|
||||
cwd: PathBuf,
|
||||
map: OrderedMap<PathBuf, Vec<FakeListerEntry>>,
|
||||
}
|
||||
|
||||
pub fn add(&mut self, path: impl Into<PathBuf>, content: &[&str]) -> &mut FakeDirectoryLister {
|
||||
let g = path.into();
|
||||
let path = if g.is_relative() {
|
||||
self.cwd.join(g)
|
||||
} else {
|
||||
g
|
||||
};
|
||||
|
||||
let mut content = content.iter()
|
||||
.map(|n| FakeListerEntry {
|
||||
name: PathBuf::from(n),
|
||||
is_directory: false,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match self.map.entry(path.clone()) {
|
||||
Entry::Occupied(mut e) => {
|
||||
content.append(&mut e.value().clone());
|
||||
e.insert(content);
|
||||
impl FakeDirectoryLister {
|
||||
pub fn new(cwd: impl Into<PathBuf>) -> FakeDirectoryLister {
|
||||
FakeDirectoryLister {
|
||||
map: OrderedMap::new(),
|
||||
cwd: cwd.into(),
|
||||
}
|
||||
Entry::Vacant(e) => { e.insert(content.to_vec()) }
|
||||
}
|
||||
|
||||
let mut parent = PathBuf::from(path);
|
||||
while let Some(p) = parent.parent() {
|
||||
let mut v = vec![
|
||||
FakeListerEntry {
|
||||
name: PathBuf::from(parent.components().last().unwrap().as_os_str()),
|
||||
is_directory: true,
|
||||
}];
|
||||
pub fn add(&mut self, path: impl Into<PathBuf>, content: &[&str]) -> &mut FakeDirectoryLister {
|
||||
let g = path.into();
|
||||
let path = if g.is_relative() {
|
||||
self.cwd.join(g)
|
||||
} else {
|
||||
g
|
||||
};
|
||||
|
||||
match self.map.entry(p.to_path_buf()) {
|
||||
let mut content = content.iter()
|
||||
.map(|n| FakeListerEntry {
|
||||
name: PathBuf::from(n),
|
||||
is_directory: false,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match self.map.entry(path.clone()) {
|
||||
Entry::Occupied(mut e) => {
|
||||
if !e.value().contains(&v[0]) {
|
||||
let mut tmp = e.value().clone();
|
||||
tmp.append(&mut v);
|
||||
e.insert(tmp);
|
||||
content.append(&mut e.value().clone());
|
||||
e.insert(content);
|
||||
}
|
||||
Entry::Vacant(e) => { e.insert(content.to_vec()) }
|
||||
}
|
||||
|
||||
let mut parent = PathBuf::from(path);
|
||||
while let Some(p) = parent.parent() {
|
||||
let mut v = vec![
|
||||
FakeListerEntry {
|
||||
name: PathBuf::from(parent.components().last().unwrap().as_os_str()),
|
||||
is_directory: true,
|
||||
}];
|
||||
|
||||
match self.map.entry(p.to_path_buf()) {
|
||||
Entry::Occupied(mut e) => {
|
||||
if !e.value().contains(&v[0]) {
|
||||
let mut tmp = e.value().clone();
|
||||
tmp.append(&mut v);
|
||||
e.insert(tmp);
|
||||
}
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(v);
|
||||
}
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(v);
|
||||
}
|
||||
}
|
||||
|
||||
parent = p.to_path_buf();
|
||||
parent = p.to_path_buf();
|
||||
}
|
||||
self
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl DirectoryLister for FakeDirectoryLister {
|
||||
type DirectoryIter = FakeIter;
|
||||
impl DirectoryLister for FakeDirectoryLister {
|
||||
type DirectoryIter = FakeIter;
|
||||
|
||||
fn list(&self, path: impl Into<PathBuf>) -> CrushResult<Self::DirectoryIter> {
|
||||
let g = path.into();
|
||||
let path = if g.is_relative() {
|
||||
self.cwd.join(&g)
|
||||
} else {
|
||||
g.clone()
|
||||
};
|
||||
fn list(&self, path: impl Into<PathBuf>) -> CrushResult<Self::DirectoryIter> {
|
||||
let g = path.into();
|
||||
let path = if g.is_relative() {
|
||||
self.cwd.join(&g)
|
||||
} else {
|
||||
g.clone()
|
||||
};
|
||||
|
||||
Ok(
|
||||
FakeIter {
|
||||
vec: VecDeque::from(
|
||||
mandate(
|
||||
self.map.get(&path)
|
||||
.map(|v|
|
||||
v.iter().map(|f| Directory {
|
||||
name: f.name.clone(),
|
||||
full_path: g.join(&f.name),
|
||||
is_directory: f.is_directory
|
||||
}).collect::<Vec<_>>()),
|
||||
"Unknown directory")?.clone()),
|
||||
}
|
||||
)
|
||||
Ok(
|
||||
FakeIter {
|
||||
vec: VecDeque::from(
|
||||
mandate(
|
||||
self.map.get(&path)
|
||||
.map(|v|
|
||||
v.iter().map(|f| Directory {
|
||||
name: f.name.clone(),
|
||||
full_path: g.join(&f.name),
|
||||
is_directory: f.is_directory
|
||||
}).collect::<Vec<_>>()),
|
||||
format!("Unknown directory {:?}", path))?.clone()),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub struct FakeIter {
|
||||
vec: VecDeque<Directory>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Iterator for FakeIter {
|
||||
type Item = Directory;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.vec.pop_front()
|
||||
pub struct FakeIter {
|
||||
vec: VecDeque<Directory>,
|
||||
}
|
||||
|
||||
impl Iterator for FakeIter {
|
||||
type Item = Directory;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.vec.pop_front()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn as_strs(it: FakeIter) -> Vec<String> {
|
||||
let mut res = it.map(|d| d.name.to_str().unwrap().to_string()).collect::<Vec<_>>();
|
||||
|
406
src/util/glob.rs
406
src/util/glob.rs
@ -1,8 +1,9 @@
|
||||
use crate::lang::errors::{CrushResult, data_error};
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use crate::util::directory_lister::{directory_lister, DirectoryLister};
|
||||
use crate::util::directory_lister::{Directory, directory_lister, DirectoryLister};
|
||||
use crate::util::glob::CompileState::{Regular, WasAny, WasDot, WasSeparator};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
|
||||
pub struct Glob {
|
||||
@ -16,6 +17,7 @@ enum Tile {
|
||||
Single,
|
||||
Any,
|
||||
Recursive,
|
||||
Separator,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
@ -30,37 +32,88 @@ impl Display for Glob {
|
||||
}
|
||||
}
|
||||
|
||||
enum CompileState {
|
||||
WasAny,
|
||||
WasSeparator,
|
||||
WasDot,
|
||||
Regular,
|
||||
}
|
||||
|
||||
fn compile(s: &str) -> Vec<Tile> {
|
||||
let mut res = Vec::new();
|
||||
let mut was_any = false;
|
||||
let mut state = WasSeparator;
|
||||
for c in s.chars() {
|
||||
if was_any {
|
||||
match c {
|
||||
'*' => res.push(Tile::Recursive),
|
||||
'?' => {
|
||||
res.push(Tile::Any);
|
||||
res.push(Tile::Single);
|
||||
}
|
||||
c => {
|
||||
res.push(Tile::Any);
|
||||
res.push(Tile::Char(c));
|
||||
match state {
|
||||
Regular =>
|
||||
match c {
|
||||
'*' => state = WasAny,
|
||||
'/' => {
|
||||
state = WasSeparator;
|
||||
res.push(Tile::Separator)
|
||||
}
|
||||
'?' => res.push(Tile::Single),
|
||||
c => res.push(Tile::Char(c)),
|
||||
},
|
||||
WasAny => {
|
||||
state = Regular;
|
||||
match c {
|
||||
'*' => res.push(Tile::Recursive),
|
||||
'?' => {
|
||||
res.push(Tile::Any);
|
||||
res.push(Tile::Single);
|
||||
}
|
||||
'/' => {
|
||||
state = WasSeparator;
|
||||
res.push(Tile::Any);
|
||||
res.push(Tile::Separator);
|
||||
}
|
||||
c => {
|
||||
res.push(Tile::Any);
|
||||
res.push(Tile::Char(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
was_any = false;
|
||||
} else {
|
||||
match c {
|
||||
'*' => was_any = true,
|
||||
'?' => {
|
||||
res.push(Tile::Single);
|
||||
WasSeparator => {
|
||||
state = Regular;
|
||||
match c {
|
||||
'.' => state = WasDot,
|
||||
'*' => state = WasAny,
|
||||
'/' => {
|
||||
state = WasSeparator;
|
||||
res.push(Tile::Separator)
|
||||
}
|
||||
'?' => res.push(Tile::Single),
|
||||
c => res.push(Tile::Char(c)),
|
||||
}
|
||||
c => {
|
||||
res.push(Tile::Char(c));
|
||||
}
|
||||
WasDot => {
|
||||
state = Regular;
|
||||
match c {
|
||||
'*' => {
|
||||
res.push(Tile::Char('.'));
|
||||
state = WasAny
|
||||
}
|
||||
'/' => {
|
||||
state = WasSeparator;
|
||||
}
|
||||
'?' => {
|
||||
res.push(Tile::Char('.'));
|
||||
res.push(Tile::Single)
|
||||
}
|
||||
c => {
|
||||
res.push(Tile::Char('.'));
|
||||
res.push(Tile::Char(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if was_any {
|
||||
res.push(Tile::Any);
|
||||
|
||||
match state {
|
||||
WasAny => res.push(Tile::Any),
|
||||
WasSeparator => {}
|
||||
Regular => {}
|
||||
WasDot => {}
|
||||
}
|
||||
res
|
||||
}
|
||||
@ -82,44 +135,119 @@ impl Glob {
|
||||
}
|
||||
}
|
||||
|
||||
struct GlobState<'s> {
|
||||
pattern: &'s [Tile],
|
||||
directory: PathBuf,
|
||||
}
|
||||
|
||||
enum GlobMatchResult<'s> {
|
||||
FullMatch,
|
||||
PartialMatch { remaining_pattern: &'s [Tile] },
|
||||
}
|
||||
|
||||
fn glob_file_match<'a>(pattern: &'a [Tile], path: &[char], entry: &Directory, out: &mut HashSet<PathBuf>, queue: &mut VecDeque<GlobState<'a>>) -> CrushResult<()> {
|
||||
match (pattern.first(), path.first()) {
|
||||
(None, None) => {
|
||||
out.insert(entry.full_path.clone());
|
||||
}
|
||||
(Some(Tile::Char(_)), None) | (Some(Tile::Single), None) => {}
|
||||
(Some(Tile::Any), None) => {
|
||||
if pattern.len() == 1 {
|
||||
out.insert(entry.full_path.clone());
|
||||
}
|
||||
if entry.is_directory {
|
||||
if pattern[1..] == [Tile::Separator] {
|
||||
out.insert(entry.full_path.clone());
|
||||
}
|
||||
|
||||
if let Some(Tile::Separator) = pattern.get(1) {
|
||||
queue.push_back(GlobState {
|
||||
pattern: &pattern[2..],
|
||||
directory: entry.full_path.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(Tile::Recursive), None) => {
|
||||
if pattern.len() == 1 {
|
||||
out.insert(entry.full_path.clone());
|
||||
}
|
||||
if entry.is_directory {
|
||||
if pattern[1..] == [Tile::Separator] {
|
||||
out.insert(entry.full_path.clone());
|
||||
}
|
||||
|
||||
if let Some(Tile::Separator) = pattern.get(1) {
|
||||
queue.push_back(GlobState {
|
||||
pattern: &pattern[2..],
|
||||
directory: entry.full_path.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
queue.push_back(GlobState {
|
||||
pattern,
|
||||
directory: entry.full_path.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
(Some(Tile::Separator), None) => {
|
||||
if pattern.len() == 1 {
|
||||
if entry.is_directory {
|
||||
out.insert(entry.full_path.clone());
|
||||
}
|
||||
} else {
|
||||
if entry.is_directory {
|
||||
queue.push_back(GlobState {
|
||||
pattern: &pattern[1..],
|
||||
directory: entry.full_path.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(None, Some(ch)) => {}
|
||||
(Some(Tile::Char(ch1)), Some(ch2)) if ch1 == ch2 => {
|
||||
return glob_file_match(&pattern[1..], &path[1..], entry, out, queue)
|
||||
}
|
||||
(Some(Tile::Char(_)), Some(_)) => {}
|
||||
(Some(Tile::Single), Some(_)) => {
|
||||
return glob_file_match(&pattern[1..], &path[1..], entry, out, queue)
|
||||
}
|
||||
(Some(Tile::Any), Some(_)) | (Some(Tile::Recursive), Some(_)) => {
|
||||
glob_file_match(&pattern[1..], path, entry, out, queue)?;
|
||||
glob_file_match(pattern, &path[1..], entry, out, queue)?;
|
||||
}
|
||||
(Some(Tile::Separator), Some(_)) => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn glob_files(pattern: &[Tile], cwd: &Path, out: &mut Vec<PathBuf>, lister: &impl DirectoryLister) -> CrushResult<()> {
|
||||
if pattern.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut queue = VecDeque::new();
|
||||
let mut dedup = HashSet::new();
|
||||
|
||||
queue.push_back(if matches!(pattern[0], Tile::Char('/')) {
|
||||
("/".to_string(), PathBuf::from("/"))
|
||||
GlobState { pattern: &pattern[1..], directory: PathBuf::from("/") }
|
||||
} else {
|
||||
("".to_string(), cwd.to_path_buf())
|
||||
GlobState { pattern, directory: cwd.to_path_buf() }
|
||||
});
|
||||
|
||||
while !queue.is_empty() {
|
||||
let (s, next_dir) = queue.pop_front().unwrap();
|
||||
for entry in lister.list(&next_dir)? {
|
||||
match entry.name.to_str() {
|
||||
Some(name) => {
|
||||
let mut ss = format!("{}{}", s, name);
|
||||
let res = glob_match(pattern, &ss);
|
||||
if res.matches {
|
||||
out.push(PathBuf::from(&ss))
|
||||
}
|
||||
if res.prefix && entry.is_directory {
|
||||
if !res.matches {
|
||||
let with_trailing_slash = format!("{}/", ss);
|
||||
if glob_match(pattern, &with_trailing_slash).matches {
|
||||
out.push(PathBuf::from(&with_trailing_slash))
|
||||
}
|
||||
}
|
||||
ss.push('/');
|
||||
queue.push_back((ss, entry.full_path));
|
||||
}
|
||||
}
|
||||
None => return data_error("Invalid file name"),
|
||||
while let Some(state) = queue.pop_front() {
|
||||
for entry in lister.list(&state.directory)? {
|
||||
if let Some(path) = entry.name.to_str() {
|
||||
let path_vec: Vec<char> = path.chars().collect();
|
||||
glob_file_match(state.pattern, &path_vec, &entry, &mut dedup, &mut queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for e in dedup.drain() {
|
||||
out.push(e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -183,18 +311,6 @@ fn glob_match(pattern: &[Tile], value: &str) -> GlobResult {
|
||||
},
|
||||
},
|
||||
|
||||
Some(Tile::Char('/')) => match value.chars().next() {
|
||||
Some('/') => glob_match(&pattern[1..], &value[1..]),
|
||||
Some(_) => GlobResult {
|
||||
matches: false,
|
||||
prefix: false,
|
||||
},
|
||||
None => GlobResult {
|
||||
matches: false,
|
||||
prefix: true,
|
||||
},
|
||||
},
|
||||
|
||||
Some(Tile::Char(g)) => match value.chars().next() {
|
||||
Some(v) => {
|
||||
if *g == v {
|
||||
@ -212,95 +328,108 @@ fn glob_match(pattern: &[Tile], value: &str) -> GlobResult {
|
||||
prefix: false,
|
||||
},
|
||||
},
|
||||
|
||||
Some(Tile::Separator) => match value.chars().next() {
|
||||
Some('/') => glob_match(&pattern[1..], &value[1..]),
|
||||
Some(_) => GlobResult {
|
||||
matches: false,
|
||||
prefix: false,
|
||||
},
|
||||
None => GlobResult {
|
||||
matches: false,
|
||||
prefix: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use itertools::Itertools;
|
||||
use super::*;
|
||||
use crate::util::directory_lister::FakeDirectoryLister;
|
||||
use crate::util::directory_lister::tests::FakeDirectoryLister;
|
||||
|
||||
#[test]
|
||||
fn test_glob_match() {
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%"), "a"),
|
||||
glob_match(&compile("**"), "a"),
|
||||
GlobResult {
|
||||
matches: true,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%"), "a/b/c/d"),
|
||||
glob_match(&compile("**"), "a/b/c/d"),
|
||||
GlobResult {
|
||||
matches: true,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%"), "a/b/c/d/"),
|
||||
glob_match(&compile("**"), "a/b/c/d/"),
|
||||
GlobResult {
|
||||
matches: true,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%/"), "a/"),
|
||||
glob_match(&compile("**/"), "a/"),
|
||||
GlobResult {
|
||||
matches: true,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%/"), "a/b/c/d/"),
|
||||
glob_match(&compile("**/"), "a/b/c/d/"),
|
||||
GlobResult {
|
||||
matches: true,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%/"), "a"),
|
||||
glob_match(&compile("**/"), "a"),
|
||||
GlobResult {
|
||||
matches: false,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%/"), "a/b/c/d"),
|
||||
glob_match(&compile("**/"), "a/b/c/d"),
|
||||
GlobResult {
|
||||
matches: false,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%a"), "aaa"),
|
||||
glob_match(&compile("**a"), "aaa"),
|
||||
GlobResult {
|
||||
matches: true,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%a/"), "aaa/"),
|
||||
glob_match(&compile("**a/"), "aaa/"),
|
||||
GlobResult {
|
||||
matches: true,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%a"), "aaa/"),
|
||||
glob_match(&compile("**a"), "aaa/"),
|
||||
GlobResult {
|
||||
matches: false,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("aaa/%"), "aaa"),
|
||||
glob_match(&compile("aaa/*"), "aaa"),
|
||||
GlobResult {
|
||||
matches: false,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("a/%/c"), "a/bbbb"),
|
||||
glob_match(&compile("a/*/c"), "a/bbbb"),
|
||||
GlobResult {
|
||||
matches: false,
|
||||
prefix: true,
|
||||
@ -342,49 +471,49 @@ mod tests {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%a"), "bbb"),
|
||||
glob_match(&compile("**a"), "bbb"),
|
||||
GlobResult {
|
||||
matches: false,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%"), "a/b"),
|
||||
glob_match(&compile("*"), "a/b"),
|
||||
GlobResult {
|
||||
matches: false,
|
||||
prefix: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("%%c"), "a/b"),
|
||||
glob_match(&compile("**c"), "a/b"),
|
||||
GlobResult {
|
||||
matches: false,
|
||||
prefix: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("a/%/c"), "a/b/c"),
|
||||
glob_match(&compile("a/*/c"), "a/b/c"),
|
||||
GlobResult {
|
||||
matches: true,
|
||||
prefix: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("a/b%/c"), "a/b/c"),
|
||||
glob_match(&compile("a/b*/c"), "a/b/c"),
|
||||
GlobResult {
|
||||
matches: true,
|
||||
prefix: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("a/%b/c"), "a/d/c"),
|
||||
glob_match(&compile("a/*b/c"), "a/d/c"),
|
||||
GlobResult {
|
||||
matches: false,
|
||||
prefix: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
glob_match(&compile("a/%/c/"), "a/b/c/"),
|
||||
glob_match(&compile("a/*/c/"), "a/b/c/"),
|
||||
GlobResult {
|
||||
matches: true,
|
||||
prefix: false,
|
||||
@ -394,60 +523,89 @@ mod tests {
|
||||
|
||||
fn lister() -> FakeDirectoryLister {
|
||||
let mut res = FakeDirectoryLister::new("/home/rabbit");
|
||||
res.add("example_data/tree", &vec!["a"])
|
||||
.add("example_data/tree/sub", &vec!["b", "c"]);
|
||||
res.add("tree", &vec!["a"])
|
||||
.add("tree/sub", &vec!["b", "c"]);
|
||||
res
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glob_files() {
|
||||
fn check_file_glob_count(glob: &str, count: usize) {
|
||||
let mut out = Vec::new();
|
||||
let _ = glob_files(
|
||||
&compile("%%"),
|
||||
&PathBuf::from("example_data/tree"),
|
||||
let ggg = glob_files(
|
||||
&compile(glob),
|
||||
&PathBuf::from(""),
|
||||
&mut out,
|
||||
&lister(),
|
||||
);
|
||||
assert_eq!(out.len(), 4);
|
||||
out.clear();
|
||||
let _ = glob_files(
|
||||
&compile("%%/"),
|
||||
&PathBuf::from("example_data/tree"),
|
||||
assert_eq!(out.len(), count);
|
||||
}
|
||||
|
||||
fn check_file_glob(glob: &str, matches: &[&str]) {
|
||||
let mut out = Vec::new();
|
||||
let ggg = glob_files(
|
||||
&compile(glob),
|
||||
&PathBuf::from(""),
|
||||
&mut out,
|
||||
&lister(),
|
||||
);
|
||||
assert_eq!(out.len(), 1);
|
||||
out.clear();
|
||||
let _ = glob_files(
|
||||
&compile("./tree/s%"),
|
||||
&PathBuf::from("example_data"),
|
||||
&mut out,
|
||||
&lister(),
|
||||
);
|
||||
assert_eq!(out.len(), 1);
|
||||
out.clear();
|
||||
let _ = glob_files(
|
||||
&compile("%%/%"),
|
||||
&PathBuf::from("example_data/tree"),
|
||||
&mut out,
|
||||
&lister(),
|
||||
);
|
||||
assert_eq!(out.len(), 3);
|
||||
out.clear();
|
||||
let _ = glob_files(
|
||||
&compile("?%%/?"),
|
||||
&PathBuf::from("example_data/tree"),
|
||||
&mut out,
|
||||
&lister(),
|
||||
);
|
||||
assert_eq!(out.len(), 2);
|
||||
out.clear();
|
||||
let _ = glob_files(
|
||||
&compile("%%b"),
|
||||
&PathBuf::from("example_data/tree"),
|
||||
&mut out,
|
||||
&lister(),
|
||||
);
|
||||
assert_eq!(out.len(), 2);
|
||||
let set: HashSet<String> = out.drain(..).map(|e|{e.to_str().unwrap().to_string()}).collect();
|
||||
assert_eq!(set.len(), matches.len());
|
||||
for el in matches {
|
||||
assert!(
|
||||
set.contains(*el),
|
||||
"The element '{}' wasn't present in glob result. The following items were found: {}",
|
||||
el,
|
||||
set.iter().map({|e| format!("'{}'", e)}).join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glob_files_literal() {
|
||||
check_file_glob("tree", &["tree"]);
|
||||
}
|
||||
#[test]
|
||||
fn test_glob_files_single() {
|
||||
check_file_glob("tre?", &["tree"]);
|
||||
}
|
||||
#[test]
|
||||
fn test_glob_files_any() {
|
||||
check_file_glob("*", &["tree"]);
|
||||
}
|
||||
#[test]
|
||||
fn test_glob_files_recursive() {
|
||||
check_file_glob_count("**", 5);
|
||||
}
|
||||
#[test]
|
||||
fn test_glob_files_recursive_directories() {
|
||||
check_file_glob("**/", &["tree", "tree/sub"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glob_files_with_separator() {
|
||||
check_file_glob("tree/s*", &["tree/sub"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glob_files_leading_dot() {
|
||||
check_file_glob("./tree/s*", &["tree/sub"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glob_files_recursive_any() {
|
||||
check_file_glob_count("**/*", 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glob_files_recursive_any_directory() {
|
||||
check_file_glob("**/*/", &["tree/sub"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glob_files_single_recursive_any() {
|
||||
check_file_glob_count("?**/*", 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glob_files_trailing_letter() {
|
||||
check_file_glob("**b", &["tree/sub/b", "tree/sub"]);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user