diff --git a/src/commands/command.rs b/src/commands/command.rs index 057e11da..3997abbc 100644 --- a/src/commands/command.rs +++ b/src/commands/command.rs @@ -20,24 +20,7 @@ macro_rules! declare_command { impl $crate::commands::command::Command for Command { fn name(&self) -> &str { - let path = std::path::Path::new(file!()); - - let name = path - .file_stem() - .expect("Expected command file to have a stem") - .to_str() - .expect("Expected command name to be UTF-8 string"); - - if name == "mod" { - path.parent() - .expect("Expected command file to have a parent") - .file_name() - .expect("Expected command parent to have a name") - .to_str() - .expect("Expected command name to be UTF-8 string") - } else { - name - } + $crate::commands::command::name(file!()) } fn command(&self) -> clap::Command { @@ -51,6 +34,27 @@ macro_rules! declare_command { }; } +pub fn name(path: &str) -> &str { + let path = std::path::Path::new(path); + + let name = path + .file_stem() + .expect("Expected command file to have a stem") + .to_str() + .expect("Expected command name to be UTF-8 string"); + + if name == "mod" { + path.parent() + .expect("Expected command file to have a parent") + .file_name() + .expect("Expected command parent to have a name") + .to_str() + .expect("Expected command name to be UTF-8 string") + } else { + name + } +} + pub fn create(name: &str) -> clap::Command { OwnArgs::augment_args(clap::Command::new(name.to_owned())) } diff --git a/src/commands/x/mod.rs b/src/commands/x/mod.rs index fa21bb5d..bc65209d 100644 --- a/src/commands/x/mod.rs +++ b/src/commands/x/mod.rs @@ -1,5 +1,6 @@ mod parser; +use crate::commands::command::name; use crate::commands::x::parser::Item; use crate::commands::x::parser::Parser; use crate::components::Buffering; @@ -20,7 +21,10 @@ use crate::init::Init; use anyhow::Result; use bstr::BStr; use bstr::BString; +use clap::command; +use clap::Arg; use clap::Args; +use clap::FromArgMatches; use std::io::BufReader; use std::process::Child; use std::process::ChildStdin; @@ -39,6 +43,30 @@ declare_command! { struct ExecArgs { /// Composition pattern pattern: Pattern, + + #[command(flatten)] + escape: EscapeArgs, +} + +#[derive(Args)] +struct EscapeArgs { + /// Escape character for the pattern + #[arg(short, long, default_value_t = '\\')] + escape: char, +} + +impl EscapeArgs { + fn get() -> Self { + command!() + .subcommand( + EscapeArgs::augment_args(clap::Command::new(name(file!()))) + .arg(Arg::new("pattern")), + ) + .get_matches() + .subcommand() + .map(|(_, matches)| EscapeArgs::from_arg_matches(matches).unwrap()) + .unwrap() + } } #[derive(Debug, Clone)] @@ -48,7 +76,8 @@ impl FromStr for Pattern { type Err = String; fn from_str(value: &str) -> std::result::Result { - match Parser::new(value).parse() { + let escape = EscapeArgs::get().escape; + match Parser::new(value, escape).parse() { Ok(items) => Ok(Self(items)), Err(error) => Err(error.to_string()), } diff --git a/src/commands/x/parser.rs b/src/commands/x/parser.rs index ecad49f6..45bd03b4 100644 --- a/src/commands/x/parser.rs +++ b/src/commands/x/parser.rs @@ -14,7 +14,6 @@ const PIPE: char = '|'; const SINGLE_QUOTE: char = '\''; const DOUBLE_QUOTE: char = '"'; -const DEFAULT_ESCAPE: char = '\\'; const ESCAPED_LF: char = 'n'; const ESCAPED_CR: char = 'c'; const ESCAPED_TAB: char = 't'; @@ -71,14 +70,16 @@ pub struct Parser<'a> { input: String, iterator: Peekable>>, position: usize, + escape: char, } impl Parser<'_> { - pub fn new(input: &str) -> Parser<'_> { + pub fn new(input: &str, escape: char) -> Parser<'_> { Parser { input: input.into(), iterator: input.chars().fuse().peekable(), position: 0, + escape, } } @@ -179,8 +180,9 @@ impl Parser<'_> { if char == quote { self.consume(quote); return Ok(arg); - } else if char == DEFAULT_ESCAPE { - arg.push(self.parse_escape_sequence(|char| char == quote)); + } else if char == self.escape { + let is_escapable = |char| char == quote; + arg.push(self.parse_escape_sequence(is_escapable)); } else { arg.push(self.consume(char)); } @@ -194,19 +196,20 @@ impl Parser<'_> { while let Some(char) = self.peek() { match char { - EXPR_START | PIPE | EXPR_END | ' ' => break, - DEFAULT_ESCAPE => { + EXPR_START | PIPE | EXPR_END => break, + char if char.is_whitespace() => break, + char @ (SINGLE_QUOTE | DOUBLE_QUOTE) => arg.push_str(&self.parse_quote_arg(char)?), + char if char == self.escape => { + let escape = self.escape; let is_escapable = |char| match char { - DEFAULT_ESCAPE | SINGLE_QUOTE | DOUBLE_QUOTE | EXPR_START | PIPE - | EXPR_END => true, + EXPR_START | PIPE | EXPR_END => true, char if char.is_whitespace() => true, + SINGLE_QUOTE | DOUBLE_QUOTE => true, + char if char == escape => true, _ => false, }; arg.push(self.parse_escape_sequence(is_escapable)) } - quote @ (SINGLE_QUOTE | DOUBLE_QUOTE) => { - arg.push_str(&self.parse_quote_arg(quote)?) - } char => arg.push(self.consume(char)), }; } @@ -219,14 +222,14 @@ impl Parser<'_> { while let Some(char) = self.peek() { match char { - DEFAULT_ESCAPE => { - let is_escapable = |char| matches!(char, EXPR_START | EXPR_END); - constant.push(self.parse_escape_sequence(is_escapable)) - } EXPR_START => break, EXPR_END => { return Err(self.error(ErrorKind::MissingExpressionStart, self.position)) } + char if char == self.escape => { + let is_escapable = |char| matches!(char, EXPR_START | EXPR_END); + constant.push(self.parse_escape_sequence(is_escapable)) + } char => constant.push(self.consume(char)), } } @@ -235,15 +238,15 @@ impl Parser<'_> { } fn parse_escape_sequence(&mut self, is_escapable: impl Fn(char) -> bool) -> char { - self.consume(DEFAULT_ESCAPE); + self.consume(self.escape); match self.peek() { - Some(DEFAULT_ESCAPE) => self.consume(DEFAULT_ESCAPE), Some(ESCAPED_LF) => self.consume_as(ESCAPED_LF, '\n'), Some(ESCAPED_CR) => self.consume_as(ESCAPED_CR, '\r'), Some(ESCAPED_TAB) => self.consume_as(ESCAPED_TAB, '\t'), + Some(char) if char == self.escape => self.consume(char), Some(char) if is_escapable(char) => self.consume(char), - _ => DEFAULT_ESCAPE, // If this is not a valid escape sequence, keep the escape character + _ => self.escape, // If this is not a valid escape sequence, keep the escape character } }