use std::fmt;
use std::str::FromStr;
use nom::{
branch::alt,
bytes::complete::{escaped, is_not},
character::complete::{char, digit1, one_of},
combinator::{all_consuming, map, map_res, opt, peek, verify},
error::{convert_error, ParseError, VerboseError},
multi::many0,
sequence::{delimited, preceded},
Err, IResult,
};
use crate::{elem::Elem, formatter::Formatter, matcher::Matcher, spec::Spec};
#[derive(Debug, Clone)]
pub struct Error {
msg: String,
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.msg)
}
}
pub fn parse<'a>(s: &'a str) -> Result<Vec<Elem>, Error> {
match root::<VerboseError<&'a str>>(s) {
Ok((_, elems)) => Ok(elems),
Err(Err::Error(e)) => Err(Error {
msg: convert_error(s, e),
}),
Err(Err::Failure(e)) => Err(Error {
msg: convert_error(s, e),
}),
Err(Err::Incomplete(_)) => Err(Error {
msg: "incomplete input".to_owned(),
}),
}
}
fn root<'a, E: ParseError<&'a str>>(s: &'a str) -> IResult<&'a str, Vec<Elem>, E> {
all_consuming(many0(elem))(s)
}
fn elem<'a, E: ParseError<&'a str>>(s: &'a str) -> IResult<&'a str, Elem, E> {
alt((elem_lit, elem_spec))(s)
}
fn elem_lit<'a, E: ParseError<&'a str>>(s: &'a str) -> IResult<&'a str, Elem, E> {
map(
verify(escaped(is_not("\\{"), '\\', one_of(r#"{}\"#)), |v: &str| {
!v.is_empty()
}),
|v: &str| Elem::Lit(unescape_lit(v)),
)(s)
}
fn unescape_lit(s: &str) -> String {
s.replace("\\{", "{")
.replace("\\}", "}")
.replace("\\\\", "\\")
}
fn elem_spec<'a, E: ParseError<&'a str>>(s: &'a str) -> IResult<&'a str, Elem, E> {
map(delimited(char('{'), spec, char('}')), Elem::Spec)(s)
}
fn spec<'a, E: ParseError<&'a str>>(s: &'a str) -> IResult<&'a str, Spec, E> {
let (s, matcher) = spec_matcher(s)?;
let (s, index) = opt(map_res(digit1, usize::from_str))(s)?;
let (s, replace) = opt(preceded(char('='), spec_replace))(s)?;
let (s, formatter) = opt(preceded(char(':'), spec_formatter))(s)?;
Ok((
s,
Spec {
matcher,
index,
replace,
formatter,
},
))
}
fn spec_matcher<'a, E: ParseError<&'a str>>(s: &'a str) -> IResult<&'a str, Matcher, E> {
map(opt(is_not("0123456789=:}")), |m: Option<&str>| {
match &m.unwrap_or("").trim()[..] {
"n" => Matcher::Number,
_ => Matcher::Any,
}
})(s)
}
fn spec_replace<'a, E: ParseError<&'a str>>(s: &'a str) -> IResult<&'a str, String, E> {
map(
opt(escaped(is_not("\\:}"), '\\', one_of(r#"{}:\"#))),
|v: Option<&str>| unescape_replace(v.unwrap_or("")),
)(s)
}
fn unescape_replace(s: &str) -> String {
s.replace("\\{", "{")
.replace("\\}", "}")
.replace("\\:", ":")
.replace("\\\\", "\\")
}
fn spec_formatter<'a, E: ParseError<&'a str>>(s: &'a str) -> IResult<&'a str, Formatter, E> {
let (s, fill) = opt(char('0'))(s)?;
let (s, width) = opt(digit1)(s)?;
let (s, _) = peek(char('}'))(s)?;
Ok((
s,
Formatter::with_width(
width.map(|w| w.parse::<usize>().unwrap()).unwrap_or(0),
fill.unwrap_or(' '),
),
))
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! parse_tests {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let (s, expected) = $value;
assert_eq!(parse(s).unwrap_or_else(|e| {
panic!("{}", e);
}), expected);
}
)*
}
}
parse_tests!(
parse_empty: ("", &[]),
parse_literal: ("abc", &[Elem::Lit("abc".to_owned())]),
parse_any: ("{}", &[Elem::Spec(Spec::new(Matcher::Any))]),
parse_number: ("{n}", &[Elem::Spec(Spec::new(Matcher::Number))]),
parse_ignore_ws: ("{ }", &[Elem::Spec(Spec::new(Matcher::Any))]),
parse_ignore_ws_number: ("{ n }", &[Elem::Spec(Spec::new(Matcher::Number))]),
parse_prefix_any: ("abc-{}", &[
Elem::Lit("abc-".to_owned()),
Elem::Spec(Spec::new(Matcher::Any)),
]),
parse_any_number: ("{}{n}", &[
Elem::Spec(Spec::new(Matcher::Any)),
Elem::Spec(Spec::new(Matcher::Number)),
]),
parse_escaped: (r#"\{{}\}"#, &[
Elem::Lit("{".to_owned()),
Elem::Spec(Spec::new(Matcher::Any)),
Elem::Lit("}".to_owned()),
]),
parse_index: (
"{1}",
&[
Elem::Spec(Spec {
matcher: Matcher::Any,
index: Some(1),
replace: None,
formatter: None
}),
],
),
parse_index_ws: (
"{n 1}",
&[
Elem::Spec(Spec {
matcher: Matcher::Number,
index: Some(1),
replace: None,
formatter: None
}),
],
),
parse_replace: (
"{=x}",
&[
Elem::Spec(Spec {
matcher: Matcher::Any,
index: None,
replace: Some("x".to_owned()),
formatter: None
}),
],
),
parse_empty_replace: (
"{=}",
&[
Elem::Spec(Spec {
matcher: Matcher::Any,
index: None,
replace: Some("".to_owned()),
formatter: None
}),
],
),
parse_replace_with_escapes_1: (
r#"{=\:}"#,
&[
Elem::Spec(Spec {
matcher: Matcher::Any,
index: None,
replace: Some(":".to_owned()),
formatter: None
}),
],
),
parse_replace_with_escapes_2: (
r#"{=\:\:}"#,
&[
Elem::Spec(Spec {
matcher: Matcher::Any,
index: None,
replace: Some("::".to_owned()),
formatter: None
}),
],
),
parse_replace_with_escapes_3: (
r#"{=\::}"#,
&[
Elem::Spec(Spec {
matcher: Matcher::Any,
index: None,
replace: Some(":".to_owned()),
formatter: Some(Formatter::new()),
}),
],
),
parse_format: (
"{:4}",
&[
Elem::Spec(Spec {
matcher: Matcher::Any,
index: None,
replace: None,
formatter: Some(Formatter::with_width(4, ' ')),
}),
],
),
parse_format_zero: (
"{n:04}",
&[
Elem::Spec(Spec {
matcher: Matcher::Number,
index: None,
replace: None,
formatter: Some(Formatter::with_width(4, '0')),
}),
],
),
parse_replace_format: (
"{n=1:04}",
&[
Elem::Spec(Spec {
matcher: Matcher::Number,
index: None,
replace: Some("1".to_owned()),
formatter: Some(Formatter::with_width(4, '0')),
}),
],
),
parse_index_replace_format: (
"{n1=1:04}",
&[
Elem::Spec(Spec {
matcher: Matcher::Number,
index: Some(1),
replace: Some("1".to_owned()),
formatter: Some(Formatter::with_width(4, '0')),
}),
],
),
);
#[test]
fn parse_incomplete() {
assert!(parse("{").is_err());
}
}