use crate::structs::InlineTag;
use nom::{
branch::alt,
bytes::complete::{escaped_transform, tag, take_while_m_n},
character::complete::{char, none_of, satisfy},
combinator::{eof, opt, value},
multi::fold_many_m_n,
sequence::{delimited, tuple},
IResult,
};
pub fn acronym(input: &str) -> IResult<&str, InlineTag> {
let (rest, (caps, opt_title)) = tuple((
capitals,
opt(delimited(char('('), acronym_title, char(')'))),
))(input)?;
alt((eof, take_while_m_n(1, 1, |c: char| !c.is_alphanumeric())))(rest)?;
Ok((
rest,
InlineTag::Acronym {
title: opt_title,
content: caps,
},
))
}
fn acronym_title(input: &str) -> IResult<&str, String> {
let (rest, title) = escaped_transform(
none_of("\\()"),
'\\',
alt((
value("\\", tag("\\")),
value("(", tag("(")),
value(")", tag(")")),
)),
)(input)?;
Ok((rest, String::from(title)))
}
fn capitals(input: &str) -> IResult<&str, String> {
let (rest, caps) = alt((
fold_many_m_n(
2,
usize::MAX,
single_capital,
String::new,
|mut acc, c| {
acc.push(c);
acc
},
),
fold_many_m_n(
2,
usize::MAX,
capital_with_dot,
String::new,
|mut acc, (c, dot)| {
acc.push(c);
acc.push(dot);
acc
},
),
))(input)?;
Ok((rest, String::from(caps)))
}
fn capital_with_dot(input: &str) -> IResult<&str, (char, char)> {
tuple((single_capital, char('.')))(input)
}
fn single_capital(input: &str) -> IResult<&str, char> {
satisfy(|c| c.is_ascii_uppercase())(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn acronym_basic() {
let input = "ACLU(American Civil Liberties Union)";
let result = acronym(input);
assert_eq!(
result,
Ok((
"",
InlineTag::Acronym {
title: Some(String::from("American Civil Liberties Union")),
content: String::from("ACLU")
}
))
);
}
#[test]
fn acronym_without_title() {
let input = "ACLU";
let result = acronym(input);
assert_eq!(
result,
Ok((
"",
InlineTag::Acronym {
title: None,
content: String::from("ACLU")
}
))
);
}
#[test]
fn acronym_with_parentheses() {
let input = "ACLU(American Civil Liberties Union \\(a union\\))";
let result = acronym(input);
assert_eq!(
result,
Ok((
"",
InlineTag::Acronym {
title: Some(String::from(
"American Civil Liberties Union (a union)"
)),
content: String::from("ACLU")
}
))
);
}
#[test]
fn acronym_with_dots() {
let input = "A.C.L.U.(American Civil Liberties Union)";
let result = acronym(input);
assert_eq!(
result,
Ok((
"",
InlineTag::Acronym {
title: Some(String::from("American Civil Liberties Union")),
content: String::from("A.C.L.U.")
}
))
);
}
#[test]
#[should_panic]
fn malformed_acronym_with_dots() {
let input = "A.CL.U.(American CivilLiberties Union)";
acronym(input).unwrap();
}
}