use nom::branch::alt;
use nom::bytes::complete::{tag, take_till, take_while1};
use nom::character::complete::{alpha1, line_ending, space0, space1};
use nom::character::is_alphanumeric;
use nom::combinator::eof;
use nom::sequence::tuple;
use nom::IResult;
#[derive(Clone, Debug)]
pub struct PlantUmlLine<'a> {
content: PlantUmlLineKind<'a>,
raw: &'a str,
}
#[derive(Clone, Debug)]
pub enum PlantUmlLineKind<'a> {
Start(StartLine<'a>),
End(EndLine<'a>),
Empty,
Others,
}
#[derive(Clone, Debug)]
pub struct StartLine<'a> {
diagram_kind: &'a str,
id: Option<&'a str>,
}
#[derive(Clone, Debug)]
pub struct EndLine<'a> {
diagram_kind: &'a str,
}
#[derive(Clone, Debug)]
pub struct EmptyLine;
impl<'a> PlantUmlLine<'a> {
pub fn parse(input: &'a str) -> IResult<&'a str, Self> {
let (raws, content) = {
if let Ok((_, (raws, content))) = StartLine::parse(input) {
let content = PlantUmlLineKind::Start(content);
(raws, content)
} else if let Ok((_, (raws, content))) = EndLine::parse(input) {
let content = PlantUmlLineKind::End(content);
(raws, content)
} else if let Ok((_, (raws, _))) = EmptyLine::parse(input) {
let content = PlantUmlLineKind::Empty;
(raws, content)
} else {
let (_, raws) = Self::parse_others(input)?;
let content = PlantUmlLineKind::Others;
(raws, content)
}
};
let len = raws.iter().fold(0, |stack, item| stack + item.len());
let (raw, rest) = input.split_at(len);
let ret = (rest, Self { content, raw });
Ok(ret)
}
pub fn raw(&'a self) -> &'a str {
self.raw
}
pub fn start(&'a self) -> Option<&'a StartLine<'a>> {
if let PlantUmlLineKind::Start(x) = &self.content {
Some(x)
} else {
None
}
}
pub fn end(&'a self) -> Option<&'a EndLine<'a>> {
if let PlantUmlLineKind::End(x) = &self.content {
Some(x)
} else {
None
}
}
pub fn diagram_kind(&'a self) -> Option<&'a str> {
self.start().map(|x| x.diagram_kind)
}
pub fn empty(&self) -> Option<()> {
if let PlantUmlLineKind::Empty = &self.content {
Some(())
} else {
None
}
}
fn parse_others(input: &str) -> IResult<&str, Vec<&str>> {
let (rest, (not_end, end)) = tuple((
take_till(|c| c == '\n' || c == '\r'),
alt((eof, line_ending)),
))(input)?;
Ok((rest, vec![not_end, end]))
}
}
impl<'a> StartLine<'a> {
pub fn parse(input: &'a str) -> IResult<&'a str, (Vec<&'a str>, Self)> {
fn parse_id(input: &str) -> IResult<&str, Vec<&str>> {
fn parse_id(input: &str) -> IResult<&str, &str> {
take_while1(|c: char| is_alphanumeric(c as u8) || '_' == c)(input)
}
fn parse_spaced_id(input: &str) -> IResult<&str, Vec<&str>> {
let (input, parsed) = tuple((space1, parse_id))(input)?;
let parsed = Vec::from(<[&str; 2]>::from(parsed));
Ok((input, parsed))
}
fn parse_parened_id(input: &str) -> IResult<&str, Vec<&str>> {
let (input, parsed) = tuple((tag("(id="), parse_id, tag(")")))(input)?;
let parsed = Vec::from(<[&str; 3]>::from(parsed));
Ok((input, parsed))
}
fn parse_none(input: &str) -> IResult<&str, Vec<&str>> {
Ok((input, vec![]))
}
alt((parse_spaced_id, parse_parened_id, parse_none))(input)
}
let (input, parsed) = tuple((
space0,
tag("@start"),
alpha1,
parse_id,
space0,
alt((eof, line_ending)),
))(input)?;
let pre = [parsed.0, parsed.1, parsed.2];
let post = [parsed.4, parsed.5];
let (ret0, id): (Vec<_>, _) = match parsed.3 {
v if v.len() == 2 || v.len() == 3 => {
let id = *v.get(1).unwrap();
(pre.into_iter().chain(v).chain(post).collect(), Some(id))
}
v => (pre.into_iter().chain(v).chain(post).collect(), None),
};
let diagram_kind = ret0[2];
let ret1 = Self { diagram_kind, id };
Ok((input, (ret0, ret1)))
}
pub fn id(&'a self) -> Option<&'a str> {
self.id
}
}
impl<'a> EndLine<'a> {
fn parse(input: &'a str) -> IResult<&'a str, (Vec<&'a str>, Self)> {
let (input, parsed) =
tuple((space0, tag("@end"), alpha1, space0, alt((eof, line_ending))))(input)?;
let ret0 = Vec::from(<[&str; 5]>::from(parsed));
let diagram_kind = ret0[2];
let ret1 = Self { diagram_kind };
Ok((input, (ret0, ret1)))
}
pub fn eq_diagram_kind(&'a self, diagram_kind: &str) -> bool {
self.diagram_kind == diagram_kind
}
}
impl EmptyLine {
fn parse(input: &str) -> IResult<&str, (Vec<&str>, Self)> {
let (input, parsed) = tuple((space0, alt((eof, line_ending))))(input)?;
let ret0 = Vec::from(<[&str; 2]>::from(parsed));
let ret1 = Self;
Ok((input, (ret0, ret1)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_start_line() -> anyhow::Result<()> {
let testdata = "@startuml\n";
let (rest, (parsed, StartLine { diagram_kind, id })) = StartLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.join(""));
assert_eq!(diagram_kind, "uml");
assert_eq!(id, None);
let (rest, parsed) = PlantUmlLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.raw);
assert!(parsed.start().is_some());
let testdata = " \t@startuml\n";
let (rest, (parsed, StartLine { diagram_kind, id })) = StartLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.join(""));
assert_eq!(diagram_kind, "uml");
assert_eq!(id, None);
let (rest, parsed) = PlantUmlLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.raw);
assert!(parsed.start().is_some());
let testdata = "@startuml \t\n";
let (rest, (parsed, StartLine { diagram_kind, id })) = StartLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.join(""));
assert_eq!(diagram_kind, "uml");
assert_eq!(id, None);
let (rest, parsed) = PlantUmlLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.raw);
assert!(parsed.start().is_some());
let testdata = " \t@startuml \t\n";
StartLine::parse(testdata)?;
let (rest, (parsed, StartLine { diagram_kind, id })) = StartLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.join(""));
assert_eq!(diagram_kind, "uml");
assert_eq!(id, None);
let (rest, parsed) = PlantUmlLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.raw);
assert!(parsed.start().is_some());
let testdata = " \t@startuml id_foo\t\n";
StartLine::parse(testdata)?;
let (rest, (parsed, StartLine { diagram_kind, id })) = StartLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.join(""));
assert_eq!(diagram_kind, "uml");
assert_eq!(id, Some("id_foo"));
let (rest, parsed) = PlantUmlLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.raw);
assert!(parsed.start().is_some());
let testdata = " \t@startuml(id=id_bar)\t\n";
StartLine::parse(testdata)?;
let (rest, (parsed, StartLine { diagram_kind, id })) = StartLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.join(""));
assert_eq!(diagram_kind, "uml");
assert_eq!(id, Some("id_bar"));
let (rest, parsed) = PlantUmlLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.raw);
assert!(parsed.start().is_some());
Ok(())
}
#[test]
fn test_parse_end_line() -> anyhow::Result<()> {
let testdata = "@enduml\n";
let (rest, (parsed, EndLine { diagram_kind })) = EndLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.join(""));
assert_eq!(diagram_kind, "uml");
let (rest, parsed) = PlantUmlLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.raw);
assert!(parsed.end().is_some());
let testdata = " \t@enduml\n";
let (rest, (parsed, EndLine { diagram_kind })) = EndLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.join(""));
assert_eq!(diagram_kind, "uml");
let (rest, parsed) = PlantUmlLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.raw);
assert!(parsed.end().is_some());
let testdata = "@enduml \t\n";
let (rest, (parsed, EndLine { diagram_kind })) = EndLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.join(""));
assert_eq!(diagram_kind, "uml");
let (rest, parsed) = PlantUmlLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.raw);
assert!(parsed.end().is_some());
let testdata = " \t@enduml \t\n";
EndLine::parse(testdata)?;
let (rest, (parsed, EndLine { diagram_kind })) = EndLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.join(""));
assert_eq!(diagram_kind, "uml");
let (rest, parsed) = PlantUmlLine::parse(testdata)?;
assert_eq!(rest, "");
assert_eq!(testdata, parsed.raw);
assert!(parsed.end().is_some());
Ok(())
}
}