Skip to content

Commit

Permalink
Allow custom x escape char
Browse files Browse the repository at this point in the history
  • Loading branch information
jpikl committed Jul 9, 2023
1 parent 3a2c0b4 commit 302e320
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 37 deletions.
40 changes: 22 additions & 18 deletions src/commands/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<OwnArgs: Args>(name: &str) -> clap::Command {
OwnArgs::augment_args(clap::Command::new(name.to_owned()))
}
Expand Down
31 changes: 30 additions & 1 deletion src/commands/x/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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)]
Expand All @@ -48,7 +76,8 @@ impl FromStr for Pattern {
type Err = String;

fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
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()),
}
Expand Down
39 changes: 21 additions & 18 deletions src/commands/x/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -71,14 +70,16 @@ pub struct Parser<'a> {
input: String,
iterator: Peekable<Fuse<Chars<'a>>>,
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,
}
}

Expand Down Expand Up @@ -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));
}
Expand All @@ -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)),
};
}
Expand All @@ -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)),
}
}
Expand All @@ -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
}
}

Expand Down

0 comments on commit 302e320

Please sign in to comment.